Merge branch '10-create-plugins' into 'master'
Resolve "Create plugins" Closes #10 See merge request bartlomiej.pluta/esa-tool!10
This commit is contained in:
@@ -23,7 +23,8 @@ dependencies {
|
||||
compile "com.github.javaparser:javaparser-core:${javaParserVersion}"
|
||||
compile "commons-io:commons-io:${commonsIoVersion}"
|
||||
compile "org.fusesource.jansi:jansi:${jansiVersion}"
|
||||
|
||||
compile "org.apache.commons:commons-lang3:${commonsLangVersion}"
|
||||
compile "org.apache.commons:commons-text:${commonsTextVersion}"
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
@@ -6,4 +6,6 @@ ext {
|
||||
javaParserVersion = '3.13.4'
|
||||
commonsIoVersion = '2.6'
|
||||
jansiVersion = '1.17.1'
|
||||
commonsLangVersion = '3.8.1'
|
||||
commonsTextVersion = '1.6'
|
||||
}
|
||||
3
esa
Executable file
3
esa
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
java -jar build/libs/esa-1.0-SNAPSHOT.jar $@
|
||||
@@ -9,6 +9,7 @@ import com.bartek.esa.file.provider.FileProvider;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -76,7 +77,7 @@ public abstract class Analyser {
|
||||
outputPlugins = plugins.stream()
|
||||
.filter(plugin -> pluginCodes
|
||||
.stream()
|
||||
.anyMatch(pluginCode -> plugin.getClass().getCanonicalName().equals(pluginCode))
|
||||
.anyMatch(doesNameMatchPlugin(plugin))
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
@@ -85,11 +86,16 @@ public abstract class Analyser {
|
||||
outputPlugins = outputPlugins.stream()
|
||||
.filter(plugin -> excludeCodes
|
||||
.stream()
|
||||
.noneMatch(pluginCode -> plugin.getClass().getCanonicalName().equals(pluginCode))
|
||||
.noneMatch(doesNameMatchPlugin(plugin))
|
||||
)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
return outputPlugins;
|
||||
}
|
||||
|
||||
private Predicate<String> doesNameMatchPlugin(Plugin plugin) {
|
||||
return pluginCode -> plugin.getClass().getCanonicalName().equals(pluginCode)
|
||||
|| plugin.getClass().getSimpleName().equals(pluginCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.bartek.esa.core.model.object.Issue;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BasePlugin implements Plugin {
|
||||
@@ -32,11 +34,21 @@ public abstract class BasePlugin implements Plugin {
|
||||
addIssue(severity, "", lineNumber, line);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, Map<String, String> descriptionModel, Integer lineNumber, String line) {
|
||||
addIssue(severity, "", descriptionModel, lineNumber, line);
|
||||
}
|
||||
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, Integer lineNumber, String line) {
|
||||
addIssue(severity, descriptionCode, new HashMap<>(), lineNumber, line);
|
||||
}
|
||||
|
||||
protected void addIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, Integer lineNumber, String line) {
|
||||
Issue issue = Issue.builder()
|
||||
.severity(severity)
|
||||
.issuer(this.getClass())
|
||||
.descriptionCode(descriptionCode)
|
||||
.descriptionModel(descriptionModel)
|
||||
.file(file)
|
||||
.lineNumber(lineNumber)
|
||||
.line(line)
|
||||
|
||||
@@ -3,16 +3,21 @@ package com.bartek.esa.core.archetype;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.Problem;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.TokenRange;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import io.vavr.control.Try;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public abstract class JavaPlugin extends BasePlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
@@ -34,8 +39,27 @@ public abstract class JavaPlugin extends BasePlugin {
|
||||
return;
|
||||
}
|
||||
|
||||
CompilationUnit compilationUnit = Try.of(() -> StaticJavaParser.parse(file)).getOrElseThrow(EsaException::new);
|
||||
try {
|
||||
CompilationUnit compilationUnit = StaticJavaParser.parse(file);
|
||||
run(compilationUnit);
|
||||
} catch (ParseProblemException e) {
|
||||
printParsingErrorToStderr(e, file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void printParsingErrorToStderr(ParseProblemException e, File file) {
|
||||
e.getProblems().stream()
|
||||
.map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getRange(p), p.getMessage()))
|
||||
.forEach(System.err::println);
|
||||
}
|
||||
|
||||
private String getRange(Problem problem) {
|
||||
return problem.getLocation()
|
||||
.flatMap(TokenRange::toRange)
|
||||
.map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private boolean isApplicationPackageFile(File file) {
|
||||
@@ -47,6 +71,7 @@ public abstract class JavaPlugin extends BasePlugin {
|
||||
Issue issue = Issue.builder()
|
||||
.issuer(JavaPlugin.class)
|
||||
.descriptionCode(".NO_PACKAGE")
|
||||
.descriptionModel(new HashMap<>())
|
||||
.severity(Severity.ERROR)
|
||||
.build();
|
||||
|
||||
@@ -56,7 +81,11 @@ public abstract class JavaPlugin extends BasePlugin {
|
||||
}
|
||||
|
||||
String path = packageValue.getNodeValue().replaceAll("\\.", "/");
|
||||
return globMatcher.fileMatchesGlobPattern(file, String.format("**/%s/**", path));
|
||||
return globMatcher.fileMatchesGlobPattern(file, format("**/%s/**", path));
|
||||
}
|
||||
|
||||
protected Integer getLineNumberFromExpression(Expression expression) {
|
||||
return expression.getRange().map(r -> r.begin.line).orElse(null);
|
||||
}
|
||||
|
||||
public abstract void run(CompilationUnit compilationUnit);
|
||||
|
||||
@@ -3,9 +3,16 @@ package com.bartek.esa.core.archetype;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public abstract class XmlPlugin extends BasePlugin {
|
||||
private final GlobMatcher globMatcher;
|
||||
@@ -32,4 +39,21 @@ public abstract class XmlPlugin extends BasePlugin {
|
||||
protected Object xPath(Document xml, String expression, QName returnType) {
|
||||
return xmlHelper.xPath(xml, expression, returnType);
|
||||
}
|
||||
|
||||
protected Stream<Node> stream(NodeList nodeList) {
|
||||
return xmlHelper.stream(nodeList);
|
||||
}
|
||||
|
||||
protected String tagString(Node node) {
|
||||
Node[] attributes = new Node[node.getAttributes().getLength()];
|
||||
for(int i=0; i<attributes.length; ++i) {
|
||||
attributes[i] = node.getAttributes().item(i);
|
||||
}
|
||||
|
||||
String attributesString = Arrays.stream(attributes)
|
||||
.map(n -> format("%s=\"%s\"", n.getNodeName(), n.getNodeValue()))
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
return format("<%s %s ...", node.getNodeName(), attributesString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bartek.esa.core.desc.provider;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import io.vavr.control.Try;
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
@@ -20,7 +21,7 @@ public class DescriptionProvider {
|
||||
|
||||
public String getDescriptionForIssue(Issue issue) {
|
||||
String code = issue.getIssuer().getCanonicalName() + issue.getDescriptionCode();
|
||||
String description = descriptions.getProperty(code);
|
||||
String description = StringSubstitutor.replace(descriptions.getProperty(code), issue.getDescriptionModel());
|
||||
return description != null && !description.isEmpty() ? description : "No description provided.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.bartek.esa.core.di;
|
||||
|
||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
||||
import com.bartek.esa.core.executor.PluginExecutor;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import dagger.Module;
|
||||
@@ -16,17 +18,27 @@ public class CoreModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
public JavaSyntaxRegexProvider javaSyntaxRegexProvider() {
|
||||
return new JavaSyntaxRegexProvider();
|
||||
public DescriptionProvider descriptionProvider() {
|
||||
return new DescriptionProvider();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public DescriptionProvider descriptionProvider() {
|
||||
return new DescriptionProvider();
|
||||
public JavaSyntaxRegexProvider javaSyntaxRegexProvider() {
|
||||
return new JavaSyntaxRegexProvider();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public XmlHelper xmlHelper() {
|
||||
return new XmlHelper();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public StaticScopeHelper staticScopeHelper() {
|
||||
return new StaticScopeHelper();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public ParentNodeFinder parentNodeFinder() {
|
||||
return new ParentNodeFinder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package com.bartek.esa.core.di;
|
||||
|
||||
import com.bartek.esa.core.archetype.Plugin;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
import com.bartek.esa.core.plugin.*;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.ElementsIntoSet;
|
||||
import dagger.multibindings.IntoSet;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -16,4 +23,130 @@ public class PluginModule {
|
||||
public Set<Plugin> plugins() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin loggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new LoggingPlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin debuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new DebuggablePlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin allowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new AllowBackupPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin permissionRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new PermissionsRaceConditionPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin secureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SecureRandomPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin implicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
return new ImplicitIntentsPlugin(globMatcher, xmlHelper, javaSyntaxRegexProvider);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin sharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SharedUidPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin usesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new UsesSdkPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin cipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new CipherInstancePlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin strictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
return new StrictModePlugin(globMatcher, xmlHelper, staticScopeHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin externalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
|
||||
return new ExternalStoragePlugin(globMatcher, xmlHelper, parentNodeFinder);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new ExportedComponentsPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin dangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new DangerousPermissionPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin textInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new TextInputValidationPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin intentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new IntentFilterPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin sqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new SqlInjectionPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin worldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new WorldAccessPermissionsPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin orderedAndStickyBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new OrderedBroadcastPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin webViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new WebViewPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin telephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
return new TelephonyManagerPlugin(globMatcher, xmlHelper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PluginExecutor {
|
||||
|
||||
public Set<Issue> executeForFiles(File manifest, Set<File> files, Set<Plugin> plugins, boolean debug) {
|
||||
return files.stream()
|
||||
.peek(file -> { if(debug) System.out.printf("File: %s", file.getAbsolutePath()); })
|
||||
.peek(file -> { if(debug) System.out.printf("File: %s\n", file.getAbsolutePath()); })
|
||||
.map(file -> executeForFile(manifest, file, plugins, debug))
|
||||
.flatMap(Set::stream)
|
||||
.collect(toSet());
|
||||
@@ -29,8 +29,8 @@ public class PluginExecutor {
|
||||
|
||||
private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins, boolean debug) {
|
||||
Document xmlManifest = xmlHelper.parseXml(manifest);
|
||||
return plugins.parallelStream()
|
||||
.peek(plugin -> { if(debug) System.out.printf(" Plugin: %s", plugin.getClass().getCanonicalName()); })
|
||||
return plugins.stream()
|
||||
.peek(plugin -> { if(debug) System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName()); })
|
||||
.peek(plugin -> plugin.update(file, xmlManifest))
|
||||
.filter(plugin -> plugin.supports(file))
|
||||
.map(Plugin::runForIssues)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bartek.esa.core.helper;
|
||||
|
||||
|
||||
import com.github.javaparser.ast.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ParentNodeFinder {
|
||||
|
||||
@Inject
|
||||
public ParentNodeFinder() {
|
||||
|
||||
}
|
||||
|
||||
public <T extends Node> Optional<T> findParentNode(Node child, Class<T> nodeType) {
|
||||
Node parent = child.getParentNode().orElse(null);
|
||||
|
||||
while(parent != null && !parent.getClass().equals(nodeType)) {
|
||||
parent = parent.getParentNode().orElse(null);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(parent).map(nodeType::cast);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bartek.esa.core.helper;
|
||||
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.ImportDeclaration;
|
||||
import com.github.javaparser.ast.Node;
|
||||
import com.github.javaparser.ast.expr.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class StaticScopeHelper {
|
||||
|
||||
@Inject
|
||||
public StaticScopeHelper() {
|
||||
|
||||
}
|
||||
|
||||
public Predicate<MethodCallExpr> isFromScope(CompilationUnit compilationUnit, String methodName, String scope, String importScope) {
|
||||
return expr -> {
|
||||
boolean isFromScope = expr.getScope()
|
||||
.filter(Expression::isNameExpr)
|
||||
.map(Expression::asNameExpr)
|
||||
.map(NameExpr::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.map(s -> s.equals(scope))
|
||||
.orElse(false);
|
||||
|
||||
if(!isFromScope) {
|
||||
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
|
||||
.filter(ImportDeclaration::isStatic)
|
||||
.filter(e -> e.getName().getIdentifier().matches(methodName))
|
||||
.map(ImportDeclaration::getName)
|
||||
.map(Name::getQualifier)
|
||||
.flatMap(Optional::stream)
|
||||
.map(Node::toString)
|
||||
.anyMatch(q -> q.equals(format("%s.%s", importScope, scope)));
|
||||
}
|
||||
|
||||
if(!isFromScope) {
|
||||
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
|
||||
.filter(ImportDeclaration::isStatic)
|
||||
.filter(ImportDeclaration::isAsterisk)
|
||||
.map(ImportDeclaration::getName)
|
||||
.map(Name::getQualifier)
|
||||
.flatMap(Optional::stream)
|
||||
.map(Node::toString)
|
||||
.anyMatch(q -> q.equals(importScope));
|
||||
}
|
||||
|
||||
return isFromScope;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.bartek.esa.core.java;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import javax.inject.Inject;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class JavaSyntaxRegexProvider {
|
||||
|
||||
@@ -11,7 +12,20 @@ public class JavaSyntaxRegexProvider {
|
||||
|
||||
}
|
||||
|
||||
public String methodInvocation(String methodName) {
|
||||
return format("^%s\\s*\\($", methodName);
|
||||
public Pattern constant() {
|
||||
return Pattern.compile("^[A-Z0-9_$]*$");
|
||||
}
|
||||
|
||||
public boolean isConstant(Expression expression) {
|
||||
String value = expression.toString();
|
||||
if(expression.isNameExpr() && constant().matcher(value).matches()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(expression.isFieldAccessExpr()) {
|
||||
return constant().matcher(expression.asFieldAccessExpr().getName().getIdentifier()).matches();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@@ -12,6 +14,7 @@ public class Issue implements Comparable {
|
||||
private final Class<?> issuer;
|
||||
private final Severity severity;
|
||||
private final String descriptionCode;
|
||||
private final Map<String, String> descriptionModel;
|
||||
private final File file;
|
||||
private final Integer lineNumber;
|
||||
private final String line;
|
||||
@@ -25,6 +28,6 @@ public class Issue implements Comparable {
|
||||
return compByFile;
|
||||
}
|
||||
|
||||
return lineNumber - another.lineNumber;
|
||||
return Optional.ofNullable(lineNumber).orElse(0) - Optional.ofNullable(another.lineNumber).orElse(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AllowBackupPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public AllowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
|
||||
Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:allowBackup")).ifPresentOrElse(n -> {
|
||||
if (!n.getNodeValue().equals("false")) {
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString());
|
||||
}
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", null, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class CipherInstancePlugin extends JavaPlugin {
|
||||
private static final Pattern ALGORITHM_QUALIFIER = Pattern.compile("^\"\\w+/\\w+/\\w+\"$");
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public CipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("getInstance"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "getInstance", "Cipher", "javax.crypto"))
|
||||
.filter(expr -> expr.getArguments().isNonEmpty())
|
||||
.filter(expr -> !isFullCipherQualifier(expr.getArguments().get(0).toString()))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private boolean isFullCipherQualifier(String qualifier) {
|
||||
return ALGORITHM_QUALIFIER.matcher(qualifier).matches();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DangerousPermissionPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public DangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList customPermissions = (NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET);
|
||||
stream(customPermissions)
|
||||
.filter(this::isDangerousPermission)
|
||||
.filter(this::doesNotHaveDescription)
|
||||
.forEach(permission -> addIssue(Severity.WARNING, null, tagString(permission)));
|
||||
}
|
||||
|
||||
private boolean isDangerousPermission(Node permission) {
|
||||
return Optional.ofNullable(permission.getAttributes().getNamedItem("android:protectionLevel"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(v -> v.equals("dangerous"))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private boolean doesNotHaveDescription(Node permission) {
|
||||
Boolean doesHaveDescription = Optional.ofNullable(permission.getAttributes().getNamedItem("android:description"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(v -> !v.isEmpty())
|
||||
.orElse(false);
|
||||
|
||||
return !doesHaveDescription;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DebuggablePlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public DebuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
|
||||
Optional.ofNullable(applicationNode.getAttributes().getNamedItem("android:debuggable")).ifPresentOrElse(n -> {
|
||||
if(!n.getNodeValue().equals("false")) {
|
||||
addIssue(Severity.WARNING, ".NO_FALSE", null, n.toString());
|
||||
}
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_ATTR",null, null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class ExportedComponentsPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
findExportedComponents(xml, "activity");
|
||||
findExportedComponents(xml, "service");
|
||||
findExportedComponents(xml, "receiver");
|
||||
findExportedProviders(xml);
|
||||
}
|
||||
|
||||
private void findExportedComponents(Document xml, String component) {
|
||||
NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET);
|
||||
stream(exportedActivities)
|
||||
.filter(this::isExported)
|
||||
.filter(node -> doesNotHavePermission(node, "android:permission"))
|
||||
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null));
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(Node node) {
|
||||
return Map.of(
|
||||
"componentName", node.getAttributes().getNamedItem("android:name").getNodeValue(),
|
||||
"componentType", node.getNodeName()
|
||||
);
|
||||
}
|
||||
|
||||
private void findExportedProviders(Document xml) {
|
||||
NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET);
|
||||
stream(exportedProviders)
|
||||
.filter(this::isExported)
|
||||
.filter(node -> doesNotHavePermission(node, "android:writePermission")
|
||||
|| doesNotHavePermission(node, "android:readPermission"))
|
||||
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null));
|
||||
}
|
||||
|
||||
private boolean doesNotHavePermission(Node node, String permissionAttribute) {
|
||||
Boolean doesHavePermission = Optional.ofNullable(node.getAttributes().getNamedItem(permissionAttribute))
|
||||
.map(Node::getNodeValue)
|
||||
.map(s -> !s.isEmpty())
|
||||
.orElse(false);
|
||||
return !doesHavePermission;
|
||||
}
|
||||
|
||||
private boolean isExported(Node node) {
|
||||
return Optional.ofNullable(node.getAttributes().getNamedItem("android:exported"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(v -> v.equals("true"))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private String nodeToString(Node node) {
|
||||
String nodeName = Optional.ofNullable(node.getAttributes().getNamedItem("android:name"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(name -> format(" android:name=\"%s\"", name))
|
||||
.orElse("");
|
||||
|
||||
return format("<%s%s ...", node.getNodeName(), nodeName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.ParentNodeFinder;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class ExternalStoragePlugin extends JavaPlugin {
|
||||
private final ParentNodeFinder parentNodeFinder;
|
||||
|
||||
@Inject
|
||||
public ExternalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.parentNodeFinder = parentNodeFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getExternalStorageDirectory|getExternalStoragePublicDirectory"))
|
||||
.forEach(this::findCheckingStorageStateForAccessingExternalStorage);
|
||||
}
|
||||
|
||||
private void findCheckingStorageStateForAccessingExternalStorage(MethodCallExpr accessingToExternalStorage) {
|
||||
parentNodeFinder.findParentNode(accessingToExternalStorage, MethodDeclaration.class).ifPresent(methodDeclaration ->
|
||||
findCheckingStorageStateInMethodDeclaration(accessingToExternalStorage, methodDeclaration)
|
||||
);
|
||||
}
|
||||
|
||||
private void findCheckingStorageStateInMethodDeclaration(MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) {
|
||||
boolean isStateBeingChecked = methodDeclaration.findAll(MethodCallExpr.class).stream()
|
||||
.anyMatch(e -> e.getName().getIdentifier().equals("getExternalStorageState"));
|
||||
|
||||
if (!isStateBeingChecked) {
|
||||
addIssue(Severity.WARNING, getLineNumberFromExpression(accessingToExternalStorage), accessingToExternalStorage.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.Node;
|
||||
import com.github.javaparser.ast.NodeList;
|
||||
import com.github.javaparser.ast.body.VariableDeclarator;
|
||||
import com.github.javaparser.ast.expr.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImplicitIntentsPlugin extends JavaPlugin {
|
||||
private final JavaSyntaxRegexProvider java;
|
||||
|
||||
@Inject
|
||||
public ImplicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.java = javaSyntaxRegexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
checkCreatingImplicitIntents(compilationUnit);
|
||||
checkCreatingPendingIntentsWithoutIntentVariable(compilationUnit);
|
||||
checkCreatingPendingIntentsWithIntentVariables(compilationUnit);
|
||||
checkCreatingPendingIntentsWithIntentsArraysVariables(compilationUnit);
|
||||
}
|
||||
|
||||
// Works for:
|
||||
// Intent[] myIntents = { new Intent(...), ... }
|
||||
// getActivities(this, 0, myIntents, 0);
|
||||
private void checkCreatingPendingIntentsWithIntentsArraysVariables(CompilationUnit compilationUnit) {
|
||||
List<String> implicitIntentsArraysVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.map(Node::getParentNode)
|
||||
.flatMap(Optional::stream)
|
||||
.map(Node::getParentNode)
|
||||
.flatMap(Optional::stream)
|
||||
.filter(node -> node instanceof VariableDeclarator)
|
||||
.map(node -> (VariableDeclarator) node)
|
||||
.map(VariableDeclarator::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivities"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isNameExpr())
|
||||
.filter(expr -> implicitIntentsArraysVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private void checkCreatingImplicitIntents(CompilationUnit compilationUnit) {
|
||||
List<String> intentVariables = getIntentVariables(compilationUnit);
|
||||
checkAllSetActionMethodInvocations(compilationUnit, intentVariables);
|
||||
checkCreatingImplicitIntentsUsingConstructor(compilationUnit);
|
||||
}
|
||||
|
||||
// Works for: new Intent(Intent.ABC), new Intent(ABC)
|
||||
private void checkCreatingImplicitIntentsUsingConstructor(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.forEach(objectCreation -> addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(objectCreation), objectCreation.toString()));
|
||||
}
|
||||
|
||||
// Returns: i for: Intent i = new Intent(...)
|
||||
private List<String> getIntentVariables(CompilationUnit compilationUnit) {
|
||||
return compilationUnit.findAll(VariableDeclarationExpr.class).stream()
|
||||
.filter(expr -> expr.getElementType().toString().equals("Intent"))
|
||||
.map(VariableDeclarationExpr::getVariables)
|
||||
.flatMap(NodeList::stream)
|
||||
.map(VariableDeclarator::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Checks if: new Intent(Intent.ABC), new Intent(ABC)
|
||||
private boolean isCreatingImplicitIntent(ObjectCreationExpr objectCreation) {
|
||||
NodeList<Expression> arguments = objectCreation.getArguments();
|
||||
boolean isImplicit = false;
|
||||
|
||||
// Works for: new Intent(CONSTANT, ...)
|
||||
if (arguments.size() == 1) {
|
||||
Expression argument = arguments.get(0);
|
||||
isImplicit = java.isConstant(argument);
|
||||
}
|
||||
|
||||
// Not works for: new Intent(this, ...)
|
||||
if(arguments.size() == 2) {
|
||||
Expression firstArg = arguments.get(0);
|
||||
Expression secondArg = arguments.get(1);
|
||||
boolean isThisExpr = firstArg.isThisExpr();
|
||||
boolean isTryingToGetClass = secondArg.isClassExpr();
|
||||
boolean isExplicit = isThisExpr || isTryingToGetClass;
|
||||
isImplicit = !isExplicit;
|
||||
}
|
||||
|
||||
return isImplicit;
|
||||
}
|
||||
|
||||
// Works for: i.setAction(...)
|
||||
private void checkAllSetActionMethodInvocations(CompilationUnit compilationUnit, List<String> intentVariables) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).forEach(methodCall -> {
|
||||
boolean isCalledOnIntentObject = methodCall.getScope()
|
||||
.map(Expression::toString)
|
||||
.filter(intentVariables::contains)
|
||||
.isPresent();
|
||||
if(isCalledOnIntentObject && methodCall.getName().getIdentifier().equals("setAction")) {
|
||||
addIssue(Severity.INFO, ".IMPLICIT_INTENT", getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Works for:
|
||||
// Intent myIntent = new Intent(...)
|
||||
// getActivity(this, 0, myIntent, 0);
|
||||
private void checkCreatingPendingIntentsWithIntentVariables(CompilationUnit compilationUnit) {
|
||||
List<String> implicitIntentsVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
|
||||
.filter(this::isCreatingImplicitIntent)
|
||||
.map(Node::getParentNode)
|
||||
.flatMap(Optional::stream)
|
||||
.filter(node -> node instanceof VariableDeclarator)
|
||||
.map(node -> (VariableDeclarator) node)
|
||||
.map(VariableDeclarator::getName)
|
||||
.map(SimpleName::getIdentifier)
|
||||
.collect(Collectors.toList());
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isNameExpr())
|
||||
.filter(expr -> implicitIntentsVariables.contains(expr.getArguments().get(2).asNameExpr().getName().getIdentifier()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private void checkCreatingPendingIntentsWithoutIntentVariable(CompilationUnit compilationUnit) {
|
||||
// Works for: getActivity(this, 0, new Intent(...), 0)
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivity|getBroadcast|getService"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isObjectCreationExpr())
|
||||
.filter(expr -> isCreatingImplicitIntent(expr.getArguments().get(2).asObjectCreationExpr()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
|
||||
// Works for: getActivities(this, 0, new Intent[] { new Intent(...), ...}, 0)
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("getActivities"))
|
||||
.filter(expr -> expr.getArguments().size() >= 4)
|
||||
.filter(expr -> expr.getArguments().get(2).isArrayCreationExpr())
|
||||
.filter(expr -> isCreatingImplicitIntentsArray(expr.getArguments().get(2).asArrayCreationExpr()))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, ".PENDING_INTENT", getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private boolean isCreatingImplicitIntentsArray(ArrayCreationExpr arrayCreationExpr) {
|
||||
return arrayCreationExpr.getInitializer()
|
||||
.map(this::isCreatingImplicitIntentsArray)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private boolean isCreatingImplicitIntentsArray(ArrayInitializerExpr arrayInitializerExpr) {
|
||||
return arrayInitializerExpr.getValues().stream()
|
||||
.filter(Expression::isObjectCreationExpr)
|
||||
.map(Expression::asObjectCreationExpr)
|
||||
.anyMatch(this::isCreatingImplicitIntent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
public class IntentFilterPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public IntentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList filters = xml.getElementsByTagName("intent-filter");
|
||||
stream(filters)
|
||||
.filter(this::isNotMainActivity)
|
||||
.map(Node::getParentNode)
|
||||
.forEach(n -> addIssue(Severity.INFO, getModel(n), null, null));
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(Node node) {
|
||||
return Map.of(
|
||||
"componentType", node.getNodeName(),
|
||||
"componentName", node.getAttributes().getNamedItem("android:name").getNodeValue()
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isNotMainActivity(Node filter) {
|
||||
long mainActivityIntentFilters = stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
.map(n -> n.getAttributes().getNamedItem("android:name"))
|
||||
.map(Node::getNodeValue)
|
||||
.filter(v -> v.equals("android.intent.action.MAIN") || v.equals("android.intent.category.LAUNCHER"))
|
||||
.count();
|
||||
|
||||
long currentIntentFilters = stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
.count();
|
||||
|
||||
return mainActivityIntentFilters != currentIntentFilters;
|
||||
}
|
||||
}
|
||||
29
src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java
Normal file
29
src/main/java/com/bartek/esa/core/plugin/LoggingPlugin.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class LoggingPlugin extends JavaPlugin {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public LoggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("v|d|i|w|e|wtf"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "v|d|i|w|e|wtf", "Log", "android.util"))
|
||||
.forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class OrderedBroadcastPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public OrderedBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("sendOrderedBroadcast|sendOrderedBroadcastAsUser|sendStickyOrderedBroadcast|sendStickyOrderedBroadcastAsUser"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.Integer.parseInt;
|
||||
|
||||
public class PermissionsRaceConditionPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public PermissionsRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
boolean isAnyPermissionDefined = ((NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET)).getLength() > 0;
|
||||
if(isAnyPermissionDefined) {
|
||||
Node usesSdkNode = (Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE);
|
||||
Node minSdkVersionNode = usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion");
|
||||
int minSdkVersion = parseInt(minSdkVersionNode.getNodeValue());
|
||||
if(minSdkVersion < 21) {
|
||||
addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), null, minSdkVersionNode.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(int minSdkVersion) {
|
||||
return Map.of("minSdkVersion", Integer.toString(minSdkVersion));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.ObjectCreationExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SecureRandomPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public SecureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(ObjectCreationExpr.class).stream()
|
||||
.filter(expr -> expr.getType().getName().getIdentifier().equals("SecureRandom"))
|
||||
.filter(expr -> expr.getArguments().isNonEmpty())
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SharedUidPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public SharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Node manifestNode = (Node) xPath(xml, "/manifest", XPathConstants.NODE);
|
||||
Optional.ofNullable(manifestNode.getAttributes().getNamedItem("android:sharedUserId")).ifPresent(node -> {
|
||||
addIssue(Severity.VULNERABILITY, null, node.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SqlInjectionPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public SqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("rawQuery"))
|
||||
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.helper.StaticScopeHelper;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class StrictModePlugin extends JavaPlugin {
|
||||
private final StaticScopeHelper staticScopeHelper;
|
||||
|
||||
@Inject
|
||||
public StrictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
this.staticScopeHelper = staticScopeHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("setThreadPolicy"))
|
||||
.filter(staticScopeHelper.isFromScope(compilationUnit, "setThreadPolicy", "StrictMode", "android.os"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.AnnotationExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class SuppressWarningsPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public SuppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(AnnotationExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().equals("SuppressWarnings"))
|
||||
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.CastExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class TelephonyManagerPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public TelephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(CastExpr.class).stream()
|
||||
.filter(expr -> expr.getType().isClassOrInterfaceType())
|
||||
.filter(expr -> expr.getType().asClassOrInterfaceType().getName().getIdentifier().equals("TelephonyManager"))
|
||||
.forEach(expr -> addIssue(Severity.INFO, getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.ResourceLayoutPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class TextInputValidationPlugin extends ResourceLayoutPlugin {
|
||||
|
||||
@Inject
|
||||
public TextInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
NodeList editTextNodes = xml.getElementsByTagName("EditText");
|
||||
stream(editTextNodes)
|
||||
.filter(this::doesNotHaveInputType)
|
||||
.forEach(n -> addIssue(Severity.WARNING, null, tagString(n)));
|
||||
}
|
||||
|
||||
private boolean doesNotHaveInputType(Node editText) {
|
||||
Boolean doesHaveInputType = Optional.ofNullable(editText.getAttributes().getNamedItem("android:inputType"))
|
||||
.map(Node::getNodeValue)
|
||||
.map(v -> !v.isEmpty())
|
||||
.orElse(false);
|
||||
return !doesHaveInputType;
|
||||
}
|
||||
}
|
||||
34
src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java
Normal file
34
src/main/java/com/bartek/esa/core/plugin/UsesSdkPlugin.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class UsesSdkPlugin extends AndroidManifestPlugin {
|
||||
|
||||
@Inject
|
||||
public UsesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Document xml) {
|
||||
Optional.ofNullable((Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> {
|
||||
if(usesSdkNode.getAttributes().getNamedItem("android:minSdkVersion") == null) {
|
||||
addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", null, null);
|
||||
}
|
||||
|
||||
Optional.ofNullable(usesSdkNode.getAttributes().getNamedItem("android:maxSdkVersion")).ifPresent(maxSdkVersion ->
|
||||
addIssue(Severity.ERROR, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()),null, maxSdkVersion.toString())
|
||||
);
|
||||
}, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", null, null));
|
||||
}
|
||||
}
|
||||
51
src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java
Normal file
51
src/main/java/com/bartek/esa/core/plugin/WebViewPlugin.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.Expression;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class WebViewPlugin extends JavaPlugin {
|
||||
private static final String SETTINGS_METHODS = "addJavascriptInterface|setJavaScriptEnabled|setWebContentsDebuggingEnabled|setAllowFileAccess|setDomStorageEnabled";
|
||||
|
||||
@Inject
|
||||
public WebViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(MethodCallExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches(SETTINGS_METHODS))
|
||||
.forEach(this::issueMethod);
|
||||
}
|
||||
|
||||
private void issueMethod(MethodCallExpr methodCall) {
|
||||
switch (methodCall.getName().getIdentifier()) {
|
||||
case "addJavascriptInterface":
|
||||
addIssue(Severity.VULNERABILITY, ".JS_INTERFACE", getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
break;
|
||||
case "setJavaScriptEnabled":
|
||||
issueSettingsMethod(methodCall, ".JS_ENABLED");
|
||||
break;
|
||||
case "setWebContentsDebuggingEnabled":
|
||||
issueSettingsMethod(methodCall, ".DEBUGGING_ENABLED");
|
||||
break;
|
||||
case "setAllowFileAccess":
|
||||
issueSettingsMethod(methodCall, ".ALLOW_FILE_ACCESS");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void issueSettingsMethod(MethodCallExpr methodCall, String descriptionCode) {
|
||||
Expression firstArg = methodCall.getArguments().get(0);
|
||||
if (firstArg.isBooleanLiteralExpr() && firstArg.asBooleanLiteralExpr().getValue()) {
|
||||
addIssue(Severity.WARNING, descriptionCode, getLineNumberFromExpression(methodCall), methodCall.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.core.archetype.JavaPlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.ast.expr.FieldAccessExpr;
|
||||
import com.github.javaparser.ast.expr.NameExpr;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Map;
|
||||
|
||||
public class WorldAccessPermissionsPlugin extends JavaPlugin {
|
||||
|
||||
@Inject
|
||||
public WorldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
|
||||
super(globMatcher, xmlHelper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(CompilationUnit compilationUnit) {
|
||||
compilationUnit.findAll(NameExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)"))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString()));
|
||||
|
||||
compilationUnit.findAll(FieldAccessExpr.class).stream()
|
||||
.filter(expr -> expr.getName().getIdentifier().matches("MODE_WORLD_(READABLE|WRITEABLE)"))
|
||||
.forEach(expr -> addIssue(Severity.ERROR, getModel(expr), getLineNumberFromExpression(expr), expr.toString()));
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(NameExpr expression) {
|
||||
return Map.of("exprName", expression.getName().getIdentifier());
|
||||
}
|
||||
|
||||
private Map<String, String> getModel(FieldAccessExpr expression) {
|
||||
return Map.of("exprName", expression.getName().getIdentifier());
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.bartek.esa.core.xml;
|
||||
import com.bartek.esa.error.EsaException;
|
||||
import io.vavr.control.Try;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.namespace.QName;
|
||||
@@ -10,6 +12,8 @@ import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class XmlHelper {
|
||||
|
||||
@@ -28,4 +32,13 @@ public class XmlHelper {
|
||||
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
|
||||
.getOrElseThrow(EsaException::new);
|
||||
}
|
||||
|
||||
public Stream<Node> stream(NodeList nodeList) {
|
||||
Node[] nodes = new Node[nodeList.getLength()];
|
||||
for (int i=0; i<nodeList.getLength(); ++i) {
|
||||
nodes[i] = nodeList.item(i);
|
||||
}
|
||||
|
||||
return Arrays.stream(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.bartek.esa.formatter.formatter;
|
||||
|
||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.formatter.archetype.Formatter;
|
||||
import org.fusesource.jansi.Ansi;
|
||||
@@ -16,6 +17,10 @@ import static org.fusesource.jansi.Ansi.Color.*;
|
||||
import static org.fusesource.jansi.Ansi.ansi;
|
||||
|
||||
public class ColorFormatter implements Formatter {
|
||||
private static final Ansi.Color INFO_COLOR = GREEN;
|
||||
private static final Ansi.Color WARNING_COLOR = YELLOW;
|
||||
private static final Ansi.Color ERROR_COLOR = MAGENTA;
|
||||
private static final Ansi.Color VULNERABILITY_COLOR = RED;
|
||||
|
||||
private final DescriptionProvider descriptionProvider;
|
||||
|
||||
@@ -39,6 +44,7 @@ public class ColorFormatter implements Formatter {
|
||||
.collect(Collectors.joining());
|
||||
|
||||
System.out.println(format.substring(0, format.length() - 2));
|
||||
System.out.println(printSummary(issues));
|
||||
AnsiConsole.systemUninstall();
|
||||
}
|
||||
|
||||
@@ -73,8 +79,9 @@ public class ColorFormatter implements Formatter {
|
||||
.map(file -> ansi
|
||||
.fg(BLUE)
|
||||
.a("File: ")
|
||||
.reset()
|
||||
.fg(CYAN)
|
||||
.a(file.getAbsolutePath())
|
||||
.reset()
|
||||
.a("\n"))
|
||||
.orElse(ansi);
|
||||
}
|
||||
@@ -86,16 +93,40 @@ public class ColorFormatter implements Formatter {
|
||||
.fg(CYAN)
|
||||
.a("Line");
|
||||
Optional.ofNullable(issue.getLineNumber()).ifPresentOrElse(
|
||||
number -> ansi.a(" ").a(number).a(": "),
|
||||
number -> ansi.a(" ").fg(MAGENTA).a(number).fg(CYAN).a(": "),
|
||||
() -> ansi.a(": ")
|
||||
);
|
||||
ansi.reset().a(line).a("\n");
|
||||
ansi.fg(BLUE).a(line).reset().a("\n");
|
||||
});
|
||||
return ansi;
|
||||
}
|
||||
|
||||
private String printSummary(Set<Issue> issues) {
|
||||
Ansi ansi = ansi();
|
||||
ansi.a("\n--- Total:\n");
|
||||
Arrays.stream(Severity.values())
|
||||
.forEach(severity -> ansi.fg(getColorForSeverity(severity))
|
||||
.a(severity.name())
|
||||
.a(": ")
|
||||
.reset()
|
||||
.a(countIssuesBySeverity(issues, severity))
|
||||
.a("\n"));
|
||||
return ansi.toString();
|
||||
}
|
||||
|
||||
private long countIssuesBySeverity(Set<Issue> issues, Severity severity) {
|
||||
return issues.stream()
|
||||
.map(Issue::getSeverity)
|
||||
.filter(severity::equals)
|
||||
.count();
|
||||
}
|
||||
|
||||
private Ansi.Color getColorForSeverity(Issue issue) {
|
||||
switch (issue.getSeverity()) {
|
||||
return getColorForSeverity(issue.getSeverity());
|
||||
}
|
||||
|
||||
private Ansi.Color getColorForSeverity(Severity severity) {
|
||||
switch (severity) {
|
||||
case INFO:
|
||||
return GREEN;
|
||||
case WARNING:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.bartek.esa.formatter.formatter;
|
||||
|
||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.model.object.Issue;
|
||||
import com.bartek.esa.formatter.archetype.Formatter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -30,6 +32,7 @@ public class SimpleFormatter implements Formatter {
|
||||
.collect(Collectors.joining());
|
||||
|
||||
System.out.println(format.substring(0, format.length() - 2));
|
||||
System.out.println(printSummary(issues));
|
||||
}
|
||||
|
||||
private String format(Issue issue) {
|
||||
@@ -71,4 +74,23 @@ public class SimpleFormatter implements Formatter {
|
||||
format.append(line).append("\n");
|
||||
});
|
||||
}
|
||||
|
||||
private String printSummary(Set<Issue> issues) {
|
||||
StringBuilder format = new StringBuilder();
|
||||
format.append("\n--- Total:\n");
|
||||
Arrays.stream(Severity.values())
|
||||
.forEach(severity -> format
|
||||
.append(severity.name())
|
||||
.append(": ")
|
||||
.append(countIssuesBySeverity(issues, severity))
|
||||
.append("\n"));
|
||||
return format.toString();
|
||||
}
|
||||
|
||||
private long countIssuesBySeverity(Set<Issue> issues, Severity severity) {
|
||||
return issues.stream()
|
||||
.map(Issue::getSeverity)
|
||||
.filter(severity::equals)
|
||||
.count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,141 @@ com.bartek.esa.core.archetype.JavaPlugin.NO_PACKAGE=There is no package defined
|
||||
Package should be defined as attribute of <manifest> tag.\n\
|
||||
For example: <manifest package="com.bartek.esa.test">\n\
|
||||
Please fix it to use this tool.
|
||||
|
||||
com.bartek.esa.core.plugin.LoggingPlugin=Potential data leakage in logs. \n\
|
||||
Logging method was detected. Please check if no sensitive data is logged there.
|
||||
|
||||
com.bartek.esa.core.plugin.DebuggablePlugin.NO_ATTR=There is no android:debuggable option. Potential data leakage. \n\
|
||||
The android:debuggable option was not found in the AndroidManifest.xml file. \n\
|
||||
To avoid any potential data leakage in the future, please explicitly set this flag to false. \n\
|
||||
The attribute should be placed in <application> tag.\n\
|
||||
For example: <application android:debuggable="false">
|
||||
|
||||
com.bartek.esa.core.plugin.DebuggablePlugin.NO_FALSE=The android:debuggable is set to 'true'. Potential data leakage. \n\
|
||||
The android:debuggable option in AndroidManifest.xml is set to 'true'. \n\
|
||||
This will cause application to be debuggable and can result in \
|
||||
security issues and data leakage on the production environment. \n\
|
||||
Consider setting it to 'false'.
|
||||
|
||||
com.bartek.esa.core.plugin.AllowBackupPlugin.NO_ATTR=There is no android:allowBackup option. Potential data leakage. \n\
|
||||
The android:allowBackup option was not found in the AndroidManifest.xml file. \n\
|
||||
To avoid any potential data theft in the future, please explicitly set this flag to false. \n\
|
||||
The attribute should be placed in <application> tag.\n\
|
||||
For example: <application android:allowBackup="false">
|
||||
|
||||
com.bartek.esa.core.plugin.AllowBackupPlugin.NO_FALSE=The android:allowBackup is set to 'true'. Potential data leakage. \n\
|
||||
The android:allowBackup option in AndroidManifest.xml is set to 'true'. \n\
|
||||
This will allow accessing the backups via adb if device has USB debugging enabled.\n\
|
||||
Consider setting it to 'false'.
|
||||
|
||||
com.bartek.esa.core.plugin.PermissionsRaceConditionPlugin=Potential permissions race condition vulnerability. \n\
|
||||
There are declared custom permissions in AndroidManifest.xml and the minimal API version is set to ${minSdkVersion} that is less than 21.\n\
|
||||
It means that declared permissions can be obtained by malicious application installed before and without need of having proper signature.\n\
|
||||
Consider setting minimal API version to 21 at least.
|
||||
|
||||
com.bartek.esa.core.plugin.SecureRandomPlugin=Initializing SecureRandom object with custom seed. \n\
|
||||
Specifying custom seed for SecureRandom can produce predictable sequence of numbers. \n\
|
||||
Please create SecureRandom object without any arguments instead.
|
||||
|
||||
com.bartek.esa.core.plugin.ImplicitIntentsPlugin.IMPLICIT_INTENT=Creating implicit intent. Potential data leakage. \n\
|
||||
Implicit intents can be abused in man-in-the-middle attack. Malicious application can hijack intent and start its\n\
|
||||
activity/send service etc. to steal sent data. \n\
|
||||
Also make sure that no sensitive information is passing to this intent.
|
||||
|
||||
com.bartek.esa.core.plugin.ImplicitIntentsPlugin.PENDING_INTENT=Creating pending intent from implicit intent. Potential permission escalation vulnerability\n\
|
||||
As far as pending intents contains UID of issuing application and its permissions, they should be fed only\n\
|
||||
with explicit intents to avoid permission escalation vulnerability.
|
||||
|
||||
com.bartek.esa.core.plugin.SharedUidPlugin=Making use of shared UserID.\n\
|
||||
Shared UserID violates a sandbox nature of Android system. All applications working with the same UID work also \n\
|
||||
within the same process and share granted permissions, resources and so on.\n\
|
||||
Remember, that if you really want to use this feature, after publishing your app, you won't be able to change it anymore.
|
||||
|
||||
com.bartek.esa.core.plugin.UsesSdkPlugin.NO_USES_SDK=There is no <uses-sdk> defined in AndroidManifest.xml file.\n\
|
||||
In order to use this tool, <uses-sdk> should be defined in AndroidManifest.xml with android:minSdkVersion attribute at least.\n\
|
||||
This element should be placed below the root (<manifest>) level.\n\
|
||||
For example:\n\
|
||||
<manifest>\n\
|
||||
\t<uses-sdk android:minSdkVersion="23">\n\
|
||||
\t...\n\
|
||||
</manifest>
|
||||
|
||||
com.bartek.esa.core.plugin.UsesSdkPlugin.USES_SDK.NO_MIN_SDK_VERSION=There is no minSdkVersion defined in AndroidManifest.xml file.\n\
|
||||
In order to use this tool, minimal SDK version should be provided as the attribute of <uses-sdk> element.\n\
|
||||
For example: <uses-sdk android:minSdkVersion="23">
|
||||
|
||||
com.bartek.esa.core.plugin.UsesSdkPlugin.USES_SDK.MAX_SDK_VERSION=Application defines an upper limit for API version.\n\
|
||||
The android:maxSdkVersion is set to ${maxSdkVersion} in AndroidManifest.xml.\n\
|
||||
There is no need to limit available platforms for application.\n\
|
||||
Furthermore it can cause unexpected application uninstall\n\
|
||||
on upgrading Android version (along with API which can exceed defined maximal API version).
|
||||
|
||||
com.bartek.esa.core.plugin.CipherInstancePlugin=Not fully-qualified algorithm name provided in Cipher.getInstance() method.\n\
|
||||
Passing a shortcut instead of fully-qualified algorithm name in Cipher.getInstance() method is not portable across providers\n\
|
||||
and can impact the system low secure than intended to be.\n\
|
||||
Fully-qualified name matches the pattern: algorithm/mode/pattern\n\
|
||||
For example: AES/CBC/PKCS5Padding
|
||||
|
||||
com.bartek.esa.core.plugin.StrictModePlugin=Strict mode is turned on.\n\
|
||||
Strict mode was found in the file. Remember to delete it before publishing.
|
||||
|
||||
com.bartek.esa.core.plugin.ExternalStoragePlugin=External storage state is not checked.\n\
|
||||
There is attempt to access to external storage without checking its state.\n\
|
||||
External storage state should be checked through 'Environment.getExternalStorageState()' method.
|
||||
|
||||
com.bartek.esa.core.plugin.SuppressWarningsPlugin=@SuppressWarnings annotation was found.\n\
|
||||
The @SuppressWarnings annotation might be hiding useful warnings.\n\
|
||||
Consider removing it.
|
||||
|
||||
com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION=Exported ${componentType}.\n\
|
||||
The ${componentType} with name '${componentName}' is exported but not protected by any permission. \n\
|
||||
It means any malicious application could make use of the component. \n\
|
||||
Consider using 'android:permission' tag and adding custom permission to protect it.
|
||||
|
||||
com.bartek.esa.core.plugin.DangerousPermissionPlugin=Custom permission without description.\n\
|
||||
Custom permission with 'dangerous' protection level was found and it doesn't have any description.\n\
|
||||
As long as the permission requires user attention, he should have provided a meaningful description about\n\
|
||||
permission.
|
||||
|
||||
com.bartek.esa.core.plugin.TextInputValidationPlugin=Input type is no selected.\n\
|
||||
The EditText view doesn't have a input type selected.\n\
|
||||
Consider associating a input type with this view.\n\
|
||||
For example: <EditText android:inputType="number" ...
|
||||
|
||||
com.bartek.esa.core.plugin.IntentFilterPlugin=Implemented intent filter.\n\
|
||||
The ${componentType} with name '${componentName}' does have a intent filter declared. \n\
|
||||
It means, that the component is implicitly exposed to public.\n\
|
||||
Consider removing intent filter.\n\
|
||||
Also be aware, that intent filter is not a security tool. It can be easily omitted.
|
||||
|
||||
com.bartek.esa.core.plugin.SqlInjectionPlugin='rawQuery' method detected. Potential SQL injection attack.\n\
|
||||
'rawQuery' method should be avoided because of possibility to inject SQL code.
|
||||
|
||||
com.bartek.esa.core.plugin.WorldAccessPermissionsPlugin=World access permissions detected. Potential data leakage.\n\
|
||||
The deprecated '${exprName}' constant has been found and it can be risky to use.\n\
|
||||
It grants world access permission to selected resource.\n\
|
||||
Consider using less permissive mode.
|
||||
|
||||
com.bartek.esa.core.plugin.OrderedBroadcastPlugin=Sending ordered broadcast. Potential broadcast theft.\n\
|
||||
Malicious applications can intercept ordered broadcasts, stop their propagation and resend with malicious data.
|
||||
|
||||
com.bartek.esa.core.plugin.WebViewPlugin.JS_INTERFACE=WebView with JavaScript interface. Potential malicious code injection.\n\
|
||||
The WebView uses 'addJavascriptInterface' method which exposes public methods to JavaScript code. Loading JavaScript code \n\
|
||||
from untrusted sources is a major security violation and should never be used.
|
||||
|
||||
com.bartek.esa.core.plugin.WebViewPlugin.JS_ENABLED=JavaScript enabled in WebView.\n\
|
||||
The WebView has enabled JavaScript code execution. This can effect in XSS attack.\n\
|
||||
Consider disabling JavaScript in WebView.
|
||||
|
||||
com.bartek.esa.core.plugin.WebViewPlugin.DEBUGGING_ENABLED=JavaScript debugging enabled in WebView.\n\
|
||||
The WebView has enabled JavaScript code debugging. This can effect in data leakage from WebView component.\n\
|
||||
Consider disabling JavaScript debugging in WebView.
|
||||
|
||||
com.bartek.esa.core.plugin.WebViewPlugin.ALLOW_FILE_ACCESS=Access to file system from WebView.\n\
|
||||
The WebView has granted access to private files. Loading content from untrusted source may effect with \n\
|
||||
accessing private files by malicious site/application.\n\
|
||||
Consider disabling this option.
|
||||
|
||||
com.bartek.esa.core.plugin.TelephonyManagerPlugin=Usage of TelephonyManager.\n\
|
||||
The TelephonyManager service is detected to be used.\n\
|
||||
Make sure that no sensitive data (like IMEI, phone number etc.) exits the application.
|
||||
|
||||
Reference in New Issue
Block a user