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 "com.github.javaparser:javaparser-core:${javaParserVersion}"
|
||||||
compile "commons-io:commons-io:${commonsIoVersion}"
|
compile "commons-io:commons-io:${commonsIoVersion}"
|
||||||
compile "org.fusesource.jansi:jansi:${jansiVersion}"
|
compile "org.fusesource.jansi:jansi:${jansiVersion}"
|
||||||
|
compile "org.apache.commons:commons-lang3:${commonsLangVersion}"
|
||||||
|
compile "org.apache.commons:commons-text:${commonsTextVersion}"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ ext {
|
|||||||
javaParserVersion = '3.13.4'
|
javaParserVersion = '3.13.4'
|
||||||
commonsIoVersion = '2.6'
|
commonsIoVersion = '2.6'
|
||||||
jansiVersion = '1.17.1'
|
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.io.File;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ public abstract class Analyser {
|
|||||||
outputPlugins = plugins.stream()
|
outputPlugins = plugins.stream()
|
||||||
.filter(plugin -> pluginCodes
|
.filter(plugin -> pluginCodes
|
||||||
.stream()
|
.stream()
|
||||||
.anyMatch(pluginCode -> plugin.getClass().getCanonicalName().equals(pluginCode))
|
.anyMatch(doesNameMatchPlugin(plugin))
|
||||||
)
|
)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
@@ -85,11 +86,16 @@ public abstract class Analyser {
|
|||||||
outputPlugins = outputPlugins.stream()
|
outputPlugins = outputPlugins.stream()
|
||||||
.filter(plugin -> excludeCodes
|
.filter(plugin -> excludeCodes
|
||||||
.stream()
|
.stream()
|
||||||
.noneMatch(pluginCode -> plugin.getClass().getCanonicalName().equals(pluginCode))
|
.noneMatch(doesNameMatchPlugin(plugin))
|
||||||
)
|
)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
return outputPlugins;
|
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 org.w3c.dom.Document;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public abstract class BasePlugin implements Plugin {
|
public abstract class BasePlugin implements Plugin {
|
||||||
@@ -32,11 +34,21 @@ public abstract class BasePlugin implements Plugin {
|
|||||||
addIssue(severity, "", lineNumber, line);
|
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) {
|
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()
|
Issue issue = Issue.builder()
|
||||||
.severity(severity)
|
.severity(severity)
|
||||||
.issuer(this.getClass())
|
.issuer(this.getClass())
|
||||||
.descriptionCode(descriptionCode)
|
.descriptionCode(descriptionCode)
|
||||||
|
.descriptionModel(descriptionModel)
|
||||||
.file(file)
|
.file(file)
|
||||||
.lineNumber(lineNumber)
|
.lineNumber(lineNumber)
|
||||||
.line(line)
|
.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.enumeration.Severity;
|
||||||
import com.bartek.esa.core.model.object.Issue;
|
import com.bartek.esa.core.model.object.Issue;
|
||||||
import com.bartek.esa.core.xml.XmlHelper;
|
import com.bartek.esa.core.xml.XmlHelper;
|
||||||
import com.bartek.esa.error.EsaException;
|
|
||||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
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.StaticJavaParser;
|
||||||
|
import com.github.javaparser.TokenRange;
|
||||||
import com.github.javaparser.ast.CompilationUnit;
|
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.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathConstants;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
public abstract class JavaPlugin extends BasePlugin {
|
public abstract class JavaPlugin extends BasePlugin {
|
||||||
private final GlobMatcher globMatcher;
|
private final GlobMatcher globMatcher;
|
||||||
@@ -34,8 +39,27 @@ public abstract class JavaPlugin extends BasePlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CompilationUnit compilationUnit = Try.of(() -> StaticJavaParser.parse(file)).getOrElseThrow(EsaException::new);
|
try {
|
||||||
run(compilationUnit);
|
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) {
|
private boolean isApplicationPackageFile(File file) {
|
||||||
@@ -47,6 +71,7 @@ public abstract class JavaPlugin extends BasePlugin {
|
|||||||
Issue issue = Issue.builder()
|
Issue issue = Issue.builder()
|
||||||
.issuer(JavaPlugin.class)
|
.issuer(JavaPlugin.class)
|
||||||
.descriptionCode(".NO_PACKAGE")
|
.descriptionCode(".NO_PACKAGE")
|
||||||
|
.descriptionModel(new HashMap<>())
|
||||||
.severity(Severity.ERROR)
|
.severity(Severity.ERROR)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -56,7 +81,11 @@ public abstract class JavaPlugin extends BasePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String path = packageValue.getNodeValue().replaceAll("\\.", "/");
|
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);
|
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.core.xml.XmlHelper;
|
||||||
import com.bartek.esa.file.matcher.GlobMatcher;
|
import com.bartek.esa.file.matcher.GlobMatcher;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
import java.io.File;
|
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 {
|
public abstract class XmlPlugin extends BasePlugin {
|
||||||
private final GlobMatcher globMatcher;
|
private final GlobMatcher globMatcher;
|
||||||
@@ -32,4 +39,21 @@ public abstract class XmlPlugin extends BasePlugin {
|
|||||||
protected Object xPath(Document xml, String expression, QName returnType) {
|
protected Object xPath(Document xml, String expression, QName returnType) {
|
||||||
return xmlHelper.xPath(xml, expression, 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.core.model.object.Issue;
|
||||||
import com.bartek.esa.error.EsaException;
|
import com.bartek.esa.error.EsaException;
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
|
import org.apache.commons.text.StringSubstitutor;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -20,7 +21,7 @@ public class DescriptionProvider {
|
|||||||
|
|
||||||
public String getDescriptionForIssue(Issue issue) {
|
public String getDescriptionForIssue(Issue issue) {
|
||||||
String code = issue.getIssuer().getCanonicalName() + issue.getDescriptionCode();
|
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.";
|
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.desc.provider.DescriptionProvider;
|
||||||
import com.bartek.esa.core.executor.PluginExecutor;
|
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.java.JavaSyntaxRegexProvider;
|
||||||
import com.bartek.esa.core.xml.XmlHelper;
|
import com.bartek.esa.core.xml.XmlHelper;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -16,17 +18,27 @@ public class CoreModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public JavaSyntaxRegexProvider javaSyntaxRegexProvider() {
|
public DescriptionProvider descriptionProvider() {
|
||||||
return new JavaSyntaxRegexProvider();
|
return new DescriptionProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public DescriptionProvider descriptionProvider() {
|
public JavaSyntaxRegexProvider javaSyntaxRegexProvider() {
|
||||||
return new DescriptionProvider();
|
return new JavaSyntaxRegexProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public XmlHelper xmlHelper() {
|
public XmlHelper xmlHelper() {
|
||||||
return new 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;
|
package com.bartek.esa.core.di;
|
||||||
|
|
||||||
import com.bartek.esa.core.archetype.Plugin;
|
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.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.ElementsIntoSet;
|
import dagger.multibindings.ElementsIntoSet;
|
||||||
|
import dagger.multibindings.IntoSet;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -16,4 +23,130 @@ public class PluginModule {
|
|||||||
public Set<Plugin> plugins() {
|
public Set<Plugin> plugins() {
|
||||||
return new HashSet<>();
|
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) {
|
public Set<Issue> executeForFiles(File manifest, Set<File> files, Set<Plugin> plugins, boolean debug) {
|
||||||
return files.stream()
|
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))
|
.map(file -> executeForFile(manifest, file, plugins, debug))
|
||||||
.flatMap(Set::stream)
|
.flatMap(Set::stream)
|
||||||
.collect(toSet());
|
.collect(toSet());
|
||||||
@@ -29,8 +29,8 @@ public class PluginExecutor {
|
|||||||
|
|
||||||
private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins, boolean debug) {
|
private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins, boolean debug) {
|
||||||
Document xmlManifest = xmlHelper.parseXml(manifest);
|
Document xmlManifest = xmlHelper.parseXml(manifest);
|
||||||
return plugins.parallelStream()
|
return plugins.stream()
|
||||||
.peek(plugin -> { if(debug) System.out.printf(" Plugin: %s", plugin.getClass().getCanonicalName()); })
|
.peek(plugin -> { if(debug) System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName()); })
|
||||||
.peek(plugin -> plugin.update(file, xmlManifest))
|
.peek(plugin -> plugin.update(file, xmlManifest))
|
||||||
.filter(plugin -> plugin.supports(file))
|
.filter(plugin -> plugin.supports(file))
|
||||||
.map(Plugin::runForIssues)
|
.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;
|
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 {
|
public class JavaSyntaxRegexProvider {
|
||||||
|
|
||||||
@@ -11,7 +12,20 @@ public class JavaSyntaxRegexProvider {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String methodInvocation(String methodName) {
|
public Pattern constant() {
|
||||||
return format("^%s\\s*\\($", methodName);
|
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 lombok.Data;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
@@ -12,6 +14,7 @@ public class Issue implements Comparable {
|
|||||||
private final Class<?> issuer;
|
private final Class<?> issuer;
|
||||||
private final Severity severity;
|
private final Severity severity;
|
||||||
private final String descriptionCode;
|
private final String descriptionCode;
|
||||||
|
private final Map<String, String> descriptionModel;
|
||||||
private final File file;
|
private final File file;
|
||||||
private final Integer lineNumber;
|
private final Integer lineNumber;
|
||||||
private final String line;
|
private final String line;
|
||||||
@@ -25,6 +28,6 @@ public class Issue implements Comparable {
|
|||||||
return compByFile;
|
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 com.bartek.esa.error.EsaException;
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
@@ -10,6 +12,8 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class XmlHelper {
|
public class XmlHelper {
|
||||||
|
|
||||||
@@ -28,4 +32,13 @@ public class XmlHelper {
|
|||||||
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
|
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
|
||||||
.getOrElseThrow(EsaException::new);
|
.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;
|
package com.bartek.esa.formatter.formatter;
|
||||||
|
|
||||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
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.core.model.object.Issue;
|
||||||
import com.bartek.esa.formatter.archetype.Formatter;
|
import com.bartek.esa.formatter.archetype.Formatter;
|
||||||
import org.fusesource.jansi.Ansi;
|
import org.fusesource.jansi.Ansi;
|
||||||
@@ -16,6 +17,10 @@ import static org.fusesource.jansi.Ansi.Color.*;
|
|||||||
import static org.fusesource.jansi.Ansi.ansi;
|
import static org.fusesource.jansi.Ansi.ansi;
|
||||||
|
|
||||||
public class ColorFormatter implements Formatter {
|
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;
|
private final DescriptionProvider descriptionProvider;
|
||||||
|
|
||||||
@@ -39,6 +44,7 @@ public class ColorFormatter implements Formatter {
|
|||||||
.collect(Collectors.joining());
|
.collect(Collectors.joining());
|
||||||
|
|
||||||
System.out.println(format.substring(0, format.length() - 2));
|
System.out.println(format.substring(0, format.length() - 2));
|
||||||
|
System.out.println(printSummary(issues));
|
||||||
AnsiConsole.systemUninstall();
|
AnsiConsole.systemUninstall();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +79,9 @@ public class ColorFormatter implements Formatter {
|
|||||||
.map(file -> ansi
|
.map(file -> ansi
|
||||||
.fg(BLUE)
|
.fg(BLUE)
|
||||||
.a("File: ")
|
.a("File: ")
|
||||||
.reset()
|
.fg(CYAN)
|
||||||
.a(file.getAbsolutePath())
|
.a(file.getAbsolutePath())
|
||||||
|
.reset()
|
||||||
.a("\n"))
|
.a("\n"))
|
||||||
.orElse(ansi);
|
.orElse(ansi);
|
||||||
}
|
}
|
||||||
@@ -86,16 +93,40 @@ public class ColorFormatter implements Formatter {
|
|||||||
.fg(CYAN)
|
.fg(CYAN)
|
||||||
.a("Line");
|
.a("Line");
|
||||||
Optional.ofNullable(issue.getLineNumber()).ifPresentOrElse(
|
Optional.ofNullable(issue.getLineNumber()).ifPresentOrElse(
|
||||||
number -> ansi.a(" ").a(number).a(": "),
|
number -> ansi.a(" ").fg(MAGENTA).a(number).fg(CYAN).a(": "),
|
||||||
() -> ansi.a(": ")
|
() -> ansi.a(": ")
|
||||||
);
|
);
|
||||||
ansi.reset().a(line).a("\n");
|
ansi.fg(BLUE).a(line).reset().a("\n");
|
||||||
});
|
});
|
||||||
return ansi;
|
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) {
|
private Ansi.Color getColorForSeverity(Issue issue) {
|
||||||
switch (issue.getSeverity()) {
|
return getColorForSeverity(issue.getSeverity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ansi.Color getColorForSeverity(Severity severity) {
|
||||||
|
switch (severity) {
|
||||||
case INFO:
|
case INFO:
|
||||||
return GREEN;
|
return GREEN;
|
||||||
case WARNING:
|
case WARNING:
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.bartek.esa.formatter.formatter;
|
package com.bartek.esa.formatter.formatter;
|
||||||
|
|
||||||
import com.bartek.esa.core.desc.provider.DescriptionProvider;
|
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.core.model.object.Issue;
|
||||||
import com.bartek.esa.formatter.archetype.Formatter;
|
import com.bartek.esa.formatter.archetype.Formatter;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -30,6 +32,7 @@ public class SimpleFormatter implements Formatter {
|
|||||||
.collect(Collectors.joining());
|
.collect(Collectors.joining());
|
||||||
|
|
||||||
System.out.println(format.substring(0, format.length() - 2));
|
System.out.println(format.substring(0, format.length() - 2));
|
||||||
|
System.out.println(printSummary(issues));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String format(Issue issue) {
|
private String format(Issue issue) {
|
||||||
@@ -71,4 +74,23 @@ public class SimpleFormatter implements Formatter {
|
|||||||
format.append(line).append("\n");
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,142 @@
|
|||||||
com.bartek.esa.core.archetype.JavaPlugin.NO_PACKAGE=There is no package defined in AndroidManifest.xml file. \n\
|
com.bartek.esa.core.archetype.JavaPlugin.NO_PACKAGE=There is no package defined in AndroidManifest.xml file. \n\
|
||||||
Package should be defined as attribute of <manifest> tag.\n\
|
Package should be defined as attribute of <manifest> tag.\n\
|
||||||
For example: <manifest package="com.bartek.esa.test">\n\
|
For example: <manifest package="com.bartek.esa.test">\n\
|
||||||
Please fix it to use this tool.
|
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