package com.android.tools.klint.checks;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.JavaContext;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.Scope;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Detector that looks for leaked credentials in strings.
*/
public class StringAuthLeakDetector extends Detector implements Detector.UastScanner {
/** Looks for hidden code */
public static final Issue AUTH_LEAK = Issue.create(
"AuthLeak", "Code might contain an auth leak",
"Strings in java apps can be discovered by decompiling apps, this lint check looks " +
"for code which looks like it may contain an url with a username and password",
Category.SECURITY, 6, Severity.WARNING,
new Implementation(StringAuthLeakDetector.class, Scope.JAVA_FILE_SCOPE));
@Nullable
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(ULiteralExpression.class);
}
@Nullable
@Override
public UastVisitor createUastVisitor(@NonNull JavaContext context) {
return new AuthLeakChecker(context);
}
private static class AuthLeakChecker extends AbstractUastVisitor {
private final static String LEGAL_CHARS = "([\\w_.!~*\'()%;&=+$,-]+)"; // From RFC 2396
private final static Pattern AUTH_REGEXP =
Pattern.compile("([\\w+.-]+)://" + LEGAL_CHARS + ':' + LEGAL_CHARS + '@' +
LEGAL_CHARS);
private final JavaContext mContext;
private AuthLeakChecker(JavaContext context) {
mContext = context;
}
@Override
public boolean visitLiteralExpression(ULiteralExpression node) {
if (node.getValue() instanceof String) {
Matcher matcher = AUTH_REGEXP.matcher((String)node.getValue());
if (matcher.find()) {
String password = matcher.group(3);
if (password == null || (password.startsWith("%") && password.endsWith("s"))) {
return super.visitLiteralExpression(node);
}
Location location = mContext.getUastLocation(node);
mContext.report(AUTH_LEAK, node, location, "Possible credential leak");
}
}
return super.visitLiteralExpression(node);
}
}
}