Merge branch '17-reinvent-architecture'

This commit is contained in:
2020-05-30 17:21:39 +02:00
59 changed files with 941 additions and 595 deletions

View File

@@ -6,7 +6,7 @@ plugins {
group 'com.bartek'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.12
sourceCompatibility = 1.11
repositories {
mavenCentral()
@@ -25,6 +25,7 @@ dependencies {
compile "org.fusesource.jansi:jansi:${jansiVersion}"
compile "org.apache.commons:commons-lang3:${commonsLangVersion}"
compile "org.apache.commons:commons-text:${commonsTextVersion}"
compile "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
}
jar {

View File

@@ -8,4 +8,5 @@ ext {
jansiVersion = '1.17.1'
commonsLangVersion = '3.8.1'
commonsTextVersion = '1.6'
jacksonVersion = '2.9.8'
}

View File

@@ -2,16 +2,24 @@ package com.bartek.esa;
import com.bartek.esa.analyser.apk.ApkAnalyser;
import com.bartek.esa.analyser.source.SourceAnalyser;
import com.bartek.esa.cli.model.CliArgsOptions;
import com.bartek.esa.cli.model.object.CliArgsOptions;
import com.bartek.esa.cli.parser.CliArgsParser;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.di.DaggerDependencyInjector;
import com.bartek.esa.dispatcher.dispatcher.MethodDispatcher;
import com.bartek.esa.dispatcher.model.DispatcherActions;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.formatter.archetype.Formatter;
import com.bartek.esa.formatter.provider.FormatterProvider;
import io.vavr.control.Try;
import javax.inject.Inject;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class EsaMain {
@@ -39,9 +47,15 @@ public class EsaMain {
CliArgsOptions options = cliArgsParser.parse(args);
Set<Issue> issues = methodDispatcher.dispatchMethod(options, dispatcherActions);
Set<Issue> filteredIssues = filterIssuesBySeverity(options, issues);
formatterProvider.provide(options).format(filteredIssues);
Formatter formatter = formatterProvider.provide(options);
formatter.beforeFormat();
String output = formatter.format(filteredIssues);
displayOutputOrSaveToFile(options, output);
formatter.afterFormat();
exitWithErrorIfAnyIssueIsAnError(filteredIssues);
if(options.isStrictMode()) {
exitWithErrorIfAnyIssueIsAnError(filteredIssues);
}
}
private Set<Issue> filterIssuesBySeverity(CliArgsOptions options, Set<Issue> issues) {
@@ -50,6 +64,24 @@ public class EsaMain {
.collect(Collectors.toSet());
}
private void displayOutputOrSaveToFile(CliArgsOptions options, String output) {
Optional.ofNullable(options.getOut())
.map(this::getWriter)
.ifPresentOrElse(writeString(output), () -> System.out.println(output));
}
private BufferedWriter getWriter(File file) {
return Try.of(() -> new BufferedWriter(new FileWriter(file)))
.getOrElseThrow(EsaException::new);
}
private Consumer<BufferedWriter> writeString(String string) {
return writer -> Try.run(() -> {
writer.write(string);
writer.close();
}).getOrElseThrow(EsaException::new);
}
private void exitWithErrorIfAnyIssueIsAnError(Set<Issue> issues) {
if(issues.stream().anyMatch(i -> i.getSeverity().isExitWithError())) {
System.exit(1);

View File

@@ -1,6 +1,7 @@
package com.bartek.esa.analyser.apk;
import com.bartek.esa.analyser.core.Analyser;
import com.bartek.esa.context.constructor.ContextConstructor;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.decompiler.archetype.Decompiler;
@@ -19,8 +20,8 @@ public class ApkAnalyser extends Analyser {
private final FileCleaner fileCleaner;
private final GlobMatcher globMatcher;
public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
super(pluginExecutor, plugins, fileProvider);
public ApkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) {
super(pluginExecutor, plugins, fileProvider, contextConstructor);
this.decompiler = decompiler;
this.fileCleaner = fileCleaner;
this.globMatcher = globMatcher;

View File

@@ -1,5 +1,7 @@
package com.bartek.esa.analyser.core;
import com.bartek.esa.context.constructor.ContextConstructor;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.core.model.object.Issue;
@@ -7,31 +9,34 @@ import com.bartek.esa.error.EsaException;
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;
public abstract class Analyser {
private final PluginExecutor pluginExecutor;
private final Set<Plugin> plugins;
private final FileProvider fileProvider;
private final ContextConstructor contextConstructor;
public Analyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
public Analyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
this.pluginExecutor = pluginExecutor;
this.plugins = plugins;
this.fileProvider = fileProvider;
this.contextConstructor = contextConstructor;
}
public Set<Issue> analyse(String source, Set<String> pluginCodes, Set<String> excludeCodes, boolean debug) {
String newSource = prepareSources(source, debug);
File manifest = getManifest(newSource);
Set<File> files = getFiles(newSource);
Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes);
Set<File> javaSources = getJavaSources(newSource);
Set<File> layoutFiles = getLayoutFiles(newSource);
Context context = contextConstructor.construct(manifest, javaSources, layoutFiles);
Set<Plugin> selectedPlugins = getPlugins(pluginCodes, excludeCodes);
Set<Issue> issues = pluginExecutor.executeForContext(context, selectedPlugins, debug);
Set<Issue> issues = pluginExecutor.executeForFiles(manifest, files, selectedPlugins, debug);
performCleaning(newSource, debug);
return issues;
}
@@ -60,14 +65,12 @@ public abstract class Analyser {
return (File) (manifests.toArray())[0];
}
private Set<File> getFiles(String source) {
Set<File> javaFiles = fileProvider.getGlobMatchedFiles(source, getJavaGlob());
Set<File> layoutFiles = fileProvider.getGlobMatchedFiles(source, getLayoutGlob());
Set<File> androidManifest = Collections.singleton(getManifest(source));
private Set<File> getJavaSources(String source) {
return fileProvider.getGlobMatchedFiles(source, getJavaGlob());
}
return Stream.of(javaFiles, androidManifest, layoutFiles)
.flatMap(Set::stream)
.collect(Collectors.toSet());
private Set<File> getLayoutFiles(String source) {
return fileProvider.getGlobMatchedFiles(source, getLayoutGlob());
}
private Set<Plugin> getPlugins(Set<String> pluginCodes, Set<String> excludeCodes) {

View File

@@ -2,6 +2,7 @@ package com.bartek.esa.analyser.di;
import com.bartek.esa.analyser.apk.ApkAnalyser;
import com.bartek.esa.analyser.source.SourceAnalyser;
import com.bartek.esa.context.constructor.ContextConstructor;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.decompiler.archetype.Decompiler;
@@ -17,12 +18,12 @@ import java.util.Set;
public class AnalyserModule {
@Provides
public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
return new SourceAnalyser(pluginExecutor, plugins, fileProvider);
public SourceAnalyser sourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
return new SourceAnalyser(pluginExecutor, plugins, fileProvider, contextConstructor);
}
@Provides
public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher) {
return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher);
public ApkAnalyser apkAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, Decompiler decompiler, FileCleaner fileCleaner, GlobMatcher globMatcher, ContextConstructor contextConstructor) {
return new ApkAnalyser(pluginExecutor, plugins, fileProvider, decompiler, fileCleaner, globMatcher, contextConstructor);
}
}

View File

@@ -1,6 +1,7 @@
package com.bartek.esa.analyser.source;
import com.bartek.esa.analyser.core.Analyser;
import com.bartek.esa.context.constructor.ContextConstructor;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.error.EsaException;
@@ -14,8 +15,8 @@ public class SourceAnalyser extends Analyser {
private static final String JAVA_GLOB = "**/*.java";
private static final String LAYOUT_GLOB = "**/res/layout*/*.xml";
public SourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider) {
super(pluginExecutor, plugins, fileProvider);
public SourceAnalyser(PluginExecutor pluginExecutor, Set<Plugin> plugins, FileProvider fileProvider, ContextConstructor contextConstructor) {
super(pluginExecutor, plugins, fileProvider, contextConstructor);
}
@Override

View File

@@ -0,0 +1,7 @@
package com.bartek.esa.cli.model.enumeration;
public enum OutputType {
DEFAULT,
COLOR,
JSON;
}

View File

@@ -1,8 +1,10 @@
package com.bartek.esa.cli.model;
package com.bartek.esa.cli.model.object;
import com.bartek.esa.cli.model.enumeration.OutputType;
import lombok.Builder;
import lombok.Data;
import java.io.File;
import java.util.Set;
import static java.util.Collections.emptySet;
@@ -14,9 +16,11 @@ public class CliArgsOptions {
private String apkAuditFile;
private Set<String> excludes;
private Set<String> plugins;
private boolean color;
private OutputType outputType;
private Set<String> severities;
private boolean debug;
private File out;
private boolean strictMode;
public boolean isSourceAnalysis() {
return sourceAnalysisDirectory != null;
@@ -30,6 +34,8 @@ public class CliArgsOptions {
return CliArgsOptions.builder()
.excludes(emptySet())
.plugins(emptySet())
.severities(emptySet())
.outputType(OutputType.DEFAULT)
.build();
}
}

View File

@@ -1,11 +1,14 @@
package com.bartek.esa.cli.parser;
import com.bartek.esa.cli.model.CliArgsOptions;
import com.bartek.esa.cli.model.enumeration.OutputType;
import com.bartek.esa.cli.model.object.CliArgsOptions;
import com.bartek.esa.cli.printer.PluginPrinter;
import com.bartek.esa.core.model.enumeration.Severity;
import io.vavr.control.Try;
import org.apache.commons.cli.*;
import javax.inject.Inject;
import java.io.File;
import java.util.HashSet;
import java.util.stream.Collectors;
@@ -19,10 +22,12 @@ public class CliArgsParser {
private static final String EXCLUDE_OPT = "exclude";
private static final String HELP_OPT = "help";
private static final String PLUGINS_OPT = "plugins";
private static final String COLOR_OPT = "color";
private static final String FORMAT_OPT = "format";
private static final String OUT_OPT = "out";
private static final String SEVERITIES_OPT = "severities";
private static final String DEBUG_OPT = "debug";
private static final String LIST_PLUGINS_OPT = "list-plugins";
private static final String STRICT_OPT = "strict";
private final PluginPrinter pluginPrinter;
@@ -66,12 +71,23 @@ public class CliArgsParser {
.apkAuditFile(command.hasOption(APK_OPT) ? command.getOptionValue(APK_OPT) : null)
.plugins(command.hasOption(PLUGINS_OPT) ? new HashSet<>(asList(command.getOptionValues(PLUGINS_OPT))) : emptySet())
.excludes(command.hasOption(EXCLUDE_OPT) ? new HashSet<>(asList(command.getOptionValues(EXCLUDE_OPT))) : emptySet())
.color(command.hasOption(COLOR_OPT))
.outputType(getOutputTypeForOptions(command))
.severities(command.hasOption(SEVERITIES_OPT) ? new HashSet<>(asList((command.getOptionValues(SEVERITIES_OPT)))) : stream(Severity.values()).map(Severity::name).collect(Collectors.toSet()))
.debug(command.hasOption(DEBUG_OPT))
.out(command.hasOption(OUT_OPT) ? new File(command.getOptionValue(OUT_OPT)) : null)
.strictMode(command.hasOption(STRICT_OPT))
.build();
}
private OutputType getOutputTypeForOptions(CommandLine command) {
if(command.hasOption(FORMAT_OPT)) {
return Try.of(() -> OutputType.valueOf(command.getOptionValue(FORMAT_OPT).toUpperCase()))
.getOrElse(OutputType.DEFAULT);
}
return OutputType.DEFAULT;
}
private void printHelp() {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("esa", prepareOptions(), true);
@@ -84,10 +100,12 @@ public class CliArgsParser {
options.addOption(exclude());
options.addOption(plugins());
options.addOption(help());
options.addOption(color());
options.addOption(format());
options.addOption(severities());
options.addOption(debug());
options.addOption(listPlugins());
options.addOption(out());
options.addOption(strict());
return options;
}
@@ -134,10 +152,12 @@ public class CliArgsParser {
.build();
}
private Option color() {
private Option format() {
return Option.builder()
.longOpt(COLOR_OPT)
.desc("enable colored output")
.longOpt(FORMAT_OPT)
.argName("OUTPUT_TYPE")
.numberOfArgs(1)
.desc("select format format (available: default, color, json)")
.build();
}
@@ -147,7 +167,7 @@ public class CliArgsParser {
.longOpt(SEVERITIES_OPT)
.argName("SEVERITY")
.numberOfArgs(Option.UNLIMITED_VALUES)
.desc("filter output to selected severities(available: " + severities + ")")
.desc("filter format to selected severities(available: " + severities + ")")
.build();
}
@@ -164,4 +184,20 @@ public class CliArgsParser {
.desc("list available plugins")
.build();
}
private Option out() {
return Option.builder()
.longOpt(OUT_OPT)
.argName("PATH")
.numberOfArgs(1)
.desc("optional output of analysis - recommended to use with non-text output format")
.build();
}
private Option strict() {
return Option.builder()
.longOpt(STRICT_OPT)
.desc("enable strict mode - return code depends on analysis result (recommended to use in batch mode)")
.build();
}
}

View File

@@ -0,0 +1,108 @@
package com.bartek.esa.context.constructor;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.context.model.Source;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.matcher.PackageNameMatcher;
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 org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.inject.Inject;
import javax.xml.xpath.XPathConstants;
import java.io.File;
import java.util.Optional;
import java.util.Set;
import static java.lang.String.format;
import static java.util.stream.Collectors.toSet;
public class ContextConstructor {
private final XmlHelper xmlHelper;
private final PackageNameMatcher packageNameMatcher;
@Inject
public ContextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
this.xmlHelper = xmlHelper;
this.packageNameMatcher = packageNameMatcher;
}
public Context construct(File androidManifestFile, Set<File> javaFiles, Set<File> layoutFiles) {
Document manifest = xmlHelper.parseXml(androidManifestFile);
String packageName = getPackageName(manifest);
return Context.builder()
.packageName(packageName)
.minSdkVersion(getUsesSdkVersion(manifest, "android:minSdkVersion"))
.targetSdkVersion(getUsesSdkVersion(manifest, "android:targetSdkVersion"))
.maxSdkVersion(getUsesSdkVersion(manifest, "android:maxSdkVersion"))
.manifest(new Source<>(androidManifestFile, manifest))
.javaSources(parseJavaFiles(javaFiles, packageName))
.layouts(parseLayoutFiles(layoutFiles))
.build();
}
private String getPackageName(Document androidManifest) {
return Optional.ofNullable(xmlHelper.xPath(androidManifest, "/manifest", XPathConstants.NODE))
.map(n -> (Node) n)
.map(Node::getAttributes)
.map(attr -> attr.getNamedItem("package"))
.map(Node::getNodeValue)
.orElseThrow(() -> new EsaException("No 'package' attribute found in manifest file. Interrupting..."));
}
private Integer getUsesSdkVersion(Document manifest, String attribute) {
return Optional.ofNullable(xmlHelper.xPath(manifest, "/manifest/uses-sdk", XPathConstants.NODE))
.map(n -> (Node) n)
.map(Node::getAttributes)
.map(attr -> attr.getNamedItem(attribute))
.map(Node::getNodeValue)
.map(Integer::parseInt)
.orElse(null);
}
private Set<Source<Document>> parseLayoutFiles(Set<File> layoutFiles) {
return layoutFiles.stream()
.map(file -> new Source<>(file, xmlHelper.parseXml(file)))
.collect(toSet());
}
private Set<Source<CompilationUnit>> parseJavaFiles(Set<File> javaFiles, String packageName) {
return javaFiles.stream()
.filter(file -> packageNameMatcher.doesFileMatchPackageName(file, packageName))
.map(file -> new Source<>(file, parseJava(file)))
.filter(s -> s.getModel() != null)
.collect(toSet());
}
private CompilationUnit parseJava(File javaFile) {
try {
return StaticJavaParser.parse(javaFile);
} catch (ParseProblemException e) {
printParsingErrorToStderr(e, javaFile);
} catch (Exception e) {
throw new EsaException(e);
}
return null;
}
private void printParsingErrorToStderr(ParseProblemException e, File file) {
e.getProblems().stream()
.map(p -> format("%s%s:\n%s\nIgnoring file...\n", file.getAbsolutePath(), getProblemRange(p), p.getMessage()))
.forEach(System.err::println);
}
private String getProblemRange(Problem problem) {
return problem.getLocation()
.flatMap(TokenRange::toRange)
.map(range -> format(" (line %d, col %d)", range.begin.line, range.begin.column))
.orElse("");
}
}

View File

@@ -0,0 +1,15 @@
package com.bartek.esa.context.di;
import com.bartek.esa.context.constructor.ContextConstructor;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.PackageNameMatcher;
import dagger.Module;
@Module
public class ContextModule {
public ContextConstructor contextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
return new ContextConstructor(xmlHelper, packageNameMatcher);
}
}

View File

@@ -0,0 +1,25 @@
package com.bartek.esa.context.model;
import com.github.javaparser.ast.CompilationUnit;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.w3c.dom.Document;
import java.util.Set;
@Getter
@Builder
@ToString
@EqualsAndHashCode
public class Context {
private String packageName;
private Integer minSdkVersion;
private Integer targetSdkVersion;
private Integer maxSdkVersion;
private Source<Document> manifest;
private Set<Source<CompilationUnit>> javaSources;
private Set<Source<Document>> layouts;
}

View File

@@ -0,0 +1,17 @@
package com.bartek.esa.context.model;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.io.File;
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class Source <M> {
private final File file;
private final M model;
}

View File

@@ -1,20 +1,15 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.context.model.Source;
import org.w3c.dom.Document;
import java.io.File;
public abstract class AndroidManifestPlugin extends XmlPlugin {
private final GlobMatcher globMatcher;
public AndroidManifestPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
this.globMatcher = globMatcher;
}
public abstract class AndroidManifestPlugin extends BasePlugin {
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/AndroidManifest.xml");
protected void run(Context context) {
run(context.getManifest());
}
protected abstract void run(Source<Document> manifest);
}

View File

@@ -1,49 +1,92 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.model.enumeration.Severity;
import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document;
import com.github.javaparser.ast.expr.Expression;
import org.w3c.dom.Node;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import static java.lang.String.format;
public abstract class BasePlugin implements Plugin {
private Set<Issue> issues = new HashSet<>();
private Document manifest;
private File file;
private Set<Issue> issues;
@Override
public void update(File file, Document manifest) {
this.file = file;
this.manifest = manifest;
this.issues.clear();
}
@Override
public Set<Issue> runForIssues() {
run(file);
public Set<Issue> runForIssues(Context context) {
issues = new HashSet<>();
run(context);
return issues;
}
protected abstract void run(File file);
protected abstract void run(Context context);
protected void addIssue(Severity severity, Integer lineNumber, String line) {
addIssue(severity, "", lineNumber, line);
protected void addJavaIssue(Severity severity, File file, Expression expression) {
addIssue(severity, file, getLineNumberFromExpression(expression), expression.toString());
}
protected void addIssue(Severity severity, Map<String, String> descriptionModel, Integer lineNumber, String line) {
addIssue(severity, "", descriptionModel, lineNumber, line);
private Integer getLineNumberFromExpression(Expression expression) {
return expression.getRange().map(r -> r.begin.line).orElse(null);
}
protected void addJavaIssue(Severity severity, Map<String, String> descriptionModel, File file, Expression expression) {
addIssue(severity, "", descriptionModel, file, getLineNumberFromExpression(expression), expression.toString());
}
protected void addIssue(Severity severity, String descriptionCode, Integer lineNumber, String line) {
addIssue(severity, descriptionCode, new HashMap<>(), lineNumber, line);
protected void addJavaIssue(Severity severity, String descriptionCode, File file, Expression expression) {
addIssue(severity, descriptionCode, new HashMap<>(), file, getLineNumberFromExpression(expression), expression.toString());
}
protected void addIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, Integer lineNumber, String line) {
protected void addJavaIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Expression expression) {
addIssue(severity, descriptionCode, descriptionModel, file, getLineNumberFromExpression(expression), expression.toString());
}
protected void addXmlIssue(Severity severity, File file, Node node) {
addIssue(severity, file, null, tagString(node));
}
protected void addXmlIssue(Severity severity, Map<String, String> descriptionModel, File file, Node node) {
addIssue(severity, descriptionModel, file, null, tagString(node));
}
protected void addXmlIssue(Severity severity, String descriptionCode, File file, Node node) {
addIssue(severity, descriptionCode, file, null, tagString(node));
}
protected void addXmlIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Node node) {
addIssue(severity, descriptionCode, descriptionModel, file, null, tagString(node));
}
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);
}
protected void addIssue(Severity severity, File file, Integer lineNumber, String line) {
addIssue(severity, "", file, lineNumber, line);
}
protected void addIssue(Severity severity, Map<String, String> descriptionModel, File file, Integer lineNumber, String line) {
addIssue(severity, "", descriptionModel, file, lineNumber, line);
}
protected void addIssue(Severity severity, String descriptionCode, File file, Integer lineNumber, String line) {
addIssue(severity, descriptionCode, new HashMap<>(), file, lineNumber, line);
}
protected void addIssue(Severity severity, String descriptionCode, Map<String, String> descriptionModel, File file, Integer lineNumber, String line) {
Issue issue = Issue.builder()
.severity(severity)
.issuer(this.getClass())
@@ -54,18 +97,10 @@ public abstract class BasePlugin implements Plugin {
.line(line)
.build();
issues.add(issue);
addIssue(issue);
}
protected void addIssue(Issue issue) {
issues.add(issue);
}
protected File getOriginalFile() {
return file;
}
protected Document getManifest() {
return manifest;
}
}

View File

@@ -1,92 +1,15 @@
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.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.bartek.esa.context.model.Context;
import com.bartek.esa.context.model.Source;
import com.github.javaparser.ast.CompilationUnit;
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;
private final XmlHelper xmlHelper;
public JavaPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
this.globMatcher = globMatcher;
this.xmlHelper = xmlHelper;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/*.java");
protected void run(Context context) {
context.getJavaSources().forEach(this::run);
}
@Override
protected void run(File file) {
if(!isApplicationPackageFile(file)) {
return;
}
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) {
Document manifest = getManifest();
Node root = (Node) xmlHelper.xPath(manifest, "/manifest", XPathConstants.NODE);
Node packageValue = root.getAttributes().getNamedItem("package");
if(packageValue == null) {
Issue issue = Issue.builder()
.issuer(JavaPlugin.class)
.descriptionCode(".NO_PACKAGE")
.descriptionModel(new HashMap<>())
.severity(Severity.ERROR)
.build();
addIssue(issue);
return false;
}
String path = packageValue.getNodeValue().replaceAll("\\.", "/");
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);
protected abstract void run(Source<CompilationUnit> compilationUnit);
}

View File

@@ -1,13 +1,10 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document;
import java.io.File;
import java.util.Set;
public interface Plugin {
boolean supports(File file);
void update(File file, Document manifest);
Set<Issue> runForIssues();
Set<Issue> runForIssues(Context context);
}

View File

@@ -1,20 +1,15 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.context.model.Source;
import org.w3c.dom.Document;
import java.io.File;
public abstract class ResourceLayoutPlugin extends XmlPlugin {
private final GlobMatcher globMatcher;
public ResourceLayoutPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
this.globMatcher = globMatcher;
}
public abstract class ResourceLayoutPlugin extends BasePlugin {
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/res/layout*/*.xml");
protected void run(Context context) {
context.getLayouts().forEach(this::run);
}
protected abstract void run(Source<Document> layout);
}

View File

@@ -1,59 +0,0 @@
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;
private final XmlHelper xmlHelper;
public XmlPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
this.globMatcher = globMatcher;
this.xmlHelper = xmlHelper;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/*.xml");
}
@Override
protected void run(File file) {
Document xml = xmlHelper.parseXml(file);
run(xml);
}
protected abstract void run(Document xml);
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);
}
}

View File

@@ -4,6 +4,7 @@ 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.helper.StringConcatenationChecker;
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
import com.bartek.esa.core.xml.XmlHelper;
import dagger.Module;
@@ -13,8 +14,8 @@ import dagger.Provides;
public class CoreModule {
@Provides
public PluginExecutor pluginExecutor(XmlHelper xmlHelper) {
return new PluginExecutor(xmlHelper);
public PluginExecutor pluginExecutor() {
return new PluginExecutor();
}
@Provides
@@ -37,6 +38,11 @@ public class CoreModule {
return new StaticScopeHelper();
}
@Provides
public StringConcatenationChecker stringConcatenationChecker(StaticScopeHelper staticScopeHelper) {
return new StringConcatenationChecker(staticScopeHelper);
}
@Provides
public ParentNodeFinder parentNodeFinder() {
return new ParentNodeFinder();

View File

@@ -3,10 +3,11 @@ 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.helper.StringConcatenationChecker;
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 com.bartek.esa.file.matcher.PackageNameMatcher;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
@@ -26,127 +27,127 @@ public class PluginModule {
@Provides
@IntoSet
public Plugin loggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
return new LoggingPlugin(globMatcher, xmlHelper, staticScopeHelper);
public Plugin loggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) {
return new LoggingPlugin(staticScopeHelper, stringConcatenationChecker);
}
@Provides
@IntoSet
public Plugin debuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new DebuggablePlugin(globMatcher, xmlHelper);
public Plugin debuggablePlugin(XmlHelper xmlHelper) {
return new DebuggablePlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin allowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new AllowBackupPlugin(globMatcher, xmlHelper);
public Plugin allowBackupPlugin(XmlHelper xmlHelper) {
return new AllowBackupPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin permissionRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new PermissionsRaceConditionPlugin(globMatcher, xmlHelper);
public Plugin permissionRaceConditionPlugin(XmlHelper xmlHelper) {
return new PermissionsRaceConditionPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin secureRandomPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new SecureRandomPlugin(globMatcher, xmlHelper);
public Plugin secureRandomPlugin() {
return new SecureRandomPlugin();
}
@Provides
@IntoSet
public Plugin implicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
return new ImplicitIntentsPlugin(globMatcher, xmlHelper, javaSyntaxRegexProvider);
public Plugin implicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
return new ImplicitIntentsPlugin(javaSyntaxRegexProvider);
}
@Provides
@IntoSet
public Plugin sharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new SharedUidPlugin(globMatcher, xmlHelper);
public Plugin sharedUidPlugin(XmlHelper xmlHelper) {
return new SharedUidPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin usesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new UsesSdkPlugin(globMatcher, xmlHelper);
public Plugin usesSdkPlugin(XmlHelper xmlHelper) {
return new UsesSdkPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin cipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
return new CipherInstancePlugin(globMatcher, xmlHelper, staticScopeHelper);
public Plugin cipherInstancePlugin(StaticScopeHelper staticScopeHelper) {
return new CipherInstancePlugin(staticScopeHelper);
}
@Provides
@IntoSet
public Plugin strictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
return new StrictModePlugin(globMatcher, xmlHelper, staticScopeHelper);
public Plugin strictModePlugin(StaticScopeHelper staticScopeHelper) {
return new StrictModePlugin(staticScopeHelper);
}
@Provides
@IntoSet
public Plugin externalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
return new ExternalStoragePlugin(globMatcher, xmlHelper, parentNodeFinder);
public Plugin externalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) {
return new ExternalStoragePlugin(parentNodeFinder, staticScopeHelper);
}
@Provides
@IntoSet
public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new SuppressWarningsPlugin(globMatcher, xmlHelper);
public Plugin suppressWarningsPlugin() {
return new SuppressWarningsPlugin();
}
@Provides
@IntoSet
public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new ExportedComponentsPlugin(globMatcher, xmlHelper);
public Plugin exportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
return new ExportedComponentsPlugin(xmlHelper, packageNameMatcher);
}
@Provides
@IntoSet
public Plugin dangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new DangerousPermissionPlugin(globMatcher, xmlHelper);
public Plugin dangerousPermissionPlugin(XmlHelper xmlHelper) {
return new DangerousPermissionPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin textInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new TextInputValidationPlugin(globMatcher, xmlHelper);
public Plugin textInputValidationPlugin(XmlHelper xmlHelper) {
return new TextInputValidationPlugin(xmlHelper);
}
@Provides
@IntoSet
public Plugin intentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new IntentFilterPlugin(globMatcher, xmlHelper);
public Plugin intentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
return new IntentFilterPlugin(xmlHelper, packageNameMatcher);
}
@Provides
@IntoSet
public Plugin sqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new SqlInjectionPlugin(globMatcher, xmlHelper);
public Plugin sqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) {
return new SqlInjectionPlugin( stringConcatenationChecker);
}
@Provides
@IntoSet
public Plugin worldAccessPermissionsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new WorldAccessPermissionsPlugin(globMatcher, xmlHelper);
public Plugin worldAccessPermissionsPlugin() {
return new WorldAccessPermissionsPlugin();
}
@Provides
@IntoSet
public Plugin orderedAndStickyBroadcastPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new OrderedBroadcastPlugin(globMatcher, xmlHelper);
public Plugin orderedAndStickyBroadcastPlugin() {
return new OrderedBroadcastPlugin();
}
@Provides
@IntoSet
public Plugin webViewPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new WebViewPlugin(globMatcher, xmlHelper);
public Plugin webViewPlugin() {
return new WebViewPlugin();
}
@Provides
@IntoSet
public Plugin telephonyManagerPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
return new TelephonyManagerPlugin(globMatcher, xmlHelper);
public Plugin telephonyManagerPlugin() {
return new TelephonyManagerPlugin();
}
}

View File

@@ -1,40 +1,30 @@
package com.bartek.esa.core.executor;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.core.xml.XmlHelper;
import org.w3c.dom.Document;
import javax.inject.Inject;
import java.io.File;
import java.util.Set;
import java.util.function.Consumer;
import static java.util.stream.Collectors.toSet;
public class PluginExecutor {
private final XmlHelper xmlHelper;
@Inject
public PluginExecutor(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
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\n", file.getAbsolutePath()); })
.map(file -> executeForFile(manifest, file, plugins, debug))
.flatMap(Set::stream)
.collect(toSet());
}
private Set<Issue> executeForFile(File manifest, File file, Set<Plugin> plugins, boolean debug) {
Document xmlManifest = xmlHelper.parseXml(manifest);
public Set<Issue> executeForContext(Context context, Set<Plugin> plugins, boolean debug) {
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)
.peek(logPlugin(debug))
.map(plugin -> plugin.runForIssues(context))
.flatMap(Set::stream)
.collect(toSet());
}
private Consumer<Plugin> logPlugin(boolean debug) {
return plugin -> {
if(debug) {
System.out.printf(" Plugin: %s\n", plugin.getClass().getCanonicalName());
}
};
}
}

View File

@@ -0,0 +1,39 @@
package com.bartek.esa.core.helper;
import com.github.javaparser.Position;
import com.github.javaparser.ast.Node;
import static java.lang.String.format;
public class NodeUtil {
private Node first;
private NodeUtil(Node node) {
this.first = node;
}
public static NodeUtil is(Node node) {
return new NodeUtil(node);
}
public boolean after(Node second) {
Position firstPosition = getPosition(first);
Position secondPosition = getPosition(second);
return firstPosition.isAfter(secondPosition);
}
private Position getPosition(Node node) {
return node.getRange()
.map(r -> r.begin)
.orElseGet(() -> {
System.err.println(format("Cannot determine position of:\n%s\nProduced results might not be reliable."));
return new Position(0, 0);
});
}
public boolean before(Node second) {
Position firstPosition = getPosition(first);
Position secondPosition = getPosition(second);
return firstPosition.isBefore(secondPosition);
}
}

View File

@@ -20,6 +20,12 @@ public class StaticScopeHelper {
public Predicate<MethodCallExpr> isFromScope(CompilationUnit compilationUnit, String methodName, String scope, String importScope) {
return expr -> {
if(!expr.getName().getIdentifier().matches(methodName)) {
return false;
}
// is called with scope
// Class.method()
boolean isFromScope = expr.getScope()
.filter(Expression::isNameExpr)
.map(Expression::asNameExpr)
@@ -28,6 +34,9 @@ public class StaticScopeHelper {
.map(s -> s.equals(scope))
.orElse(false);
// is from static import
// import static a.b.Class.method
// method()
if(!isFromScope) {
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
.filter(ImportDeclaration::isStatic)
@@ -39,6 +48,9 @@ public class StaticScopeHelper {
.anyMatch(q -> q.equals(format("%s.%s", importScope, scope)));
}
// is from import
// import static a.b.Class.*
// method()
if(!isFromScope) {
isFromScope = compilationUnit.findAll(ImportDeclaration.class).stream()
.filter(ImportDeclaration::isStatic)

View File

@@ -0,0 +1,45 @@
package com.bartek.esa.core.helper;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import javax.inject.Inject;
import java.util.function.Predicate;
public class StringConcatenationChecker {
private final StaticScopeHelper staticScopeHelper;
@Inject
public StringConcatenationChecker(StaticScopeHelper staticScopeHelper) {
this.staticScopeHelper = staticScopeHelper;
}
public boolean isStringConcatenation(CompilationUnit unit, Expression expr) {
Predicate<MethodCallExpr> isStringFormatMethod = staticScopeHelper.isFromScope(unit, "format", "String", "java.lang");
if(expr.isMethodCallExpr() && isStringFormatMethod.test(expr.asMethodCallExpr())) {
return true;
}
return isStringConcatenation(expr);
}
private boolean isStringConcatenation(Expression expr) {
if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) {
return isLiteralStringOrConcatenation(expr);
}
return false;
}
private boolean isLiteralStringOrConcatenation(Expression expr) {
if(expr.isBinaryExpr() && expr.asBinaryExpr().getOperator().asString().equals("+")) {
boolean isLeftArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getLeft());
boolean isRightArgumentString = isLiteralStringOrConcatenation(expr.asBinaryExpr().getRight());
return isLeftArgumentString || isRightArgumentString;
}
return expr.isStringLiteralExpr();
}
}

View File

@@ -23,7 +23,6 @@ public class Issue implements Comparable {
public int compareTo(Object o) {
Issue another = (Issue) o;
int compByFile = file.compareTo(another.file);
if(compByFile != 0) {
return compByFile;
}

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants;
import java.util.Optional;
public class AllowBackupPlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public AllowBackupPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public AllowBackupPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
protected void run(Source<Document> manifest) {
Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/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.WARNING, ".NO_FALSE", manifest.getFile(), null, n.toString());
}
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", null, null));
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null));
}
}

View File

@@ -1,10 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -16,19 +15,18 @@ public class CipherInstancePlugin extends JavaPlugin {
private final StaticScopeHelper staticScopeHelper;
@Inject
public CipherInstancePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
super(globMatcher, xmlHelper);
public CipherInstancePlugin(StaticScopeHelper staticScopeHelper) {
this.staticScopeHelper = staticScopeHelper;
}
@Override
public void run(CompilationUnit compilationUnit) {
compilationUnit.findAll(MethodCallExpr.class).stream()
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(MethodCallExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().equals("getInstance"))
.filter(staticScopeHelper.isFromScope(compilationUnit, "getInstance", "Cipher", "javax.crypto"))
.filter(staticScopeHelper.isFromScope(java.getModel(), "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()));
.forEach(expr -> addJavaIssue(Severity.ERROR, java.getFile(), expr));
}
private boolean isFullCipherQualifier(String qualifier) {

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -13,19 +13,20 @@ import javax.xml.xpath.XPathConstants;
import java.util.Optional;
public class DangerousPermissionPlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public DangerousPermissionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public DangerousPermissionPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
NodeList customPermissions = (NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET);
stream(customPermissions)
protected void run(Source<Document> manifest) {
NodeList customPermissions = (NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET);
xmlHelper.stream(customPermissions)
.filter(this::isDangerousPermission)
.filter(this::doesNotHaveDescription)
.forEach(permission -> addIssue(Severity.WARNING, null, tagString(permission)));
.forEach(permission -> addXmlIssue(Severity.WARNING, manifest.getFile(), permission));
}
private boolean isDangerousPermission(Node permission) {

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -12,19 +12,20 @@ import javax.xml.xpath.XPathConstants;
import java.util.Optional;
public class DebuggablePlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public DebuggablePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public DebuggablePlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
Node applicationNode = (Node) xPath(xml, "/manifest/application", XPathConstants.NODE);
protected void run(Source<Document> manifest) {
Node applicationNode = (Node) xmlHelper.xPath(manifest.getModel(), "/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.WARNING, ".NO_FALSE", manifest.getFile(),null, n.toString());
}
}, () -> addIssue(Severity.ERROR, ".NO_ATTR",null, null));
}, () -> addIssue(Severity.ERROR, ".NO_ATTR", manifest.getFile(), null, null));
}
}

View File

@@ -1,10 +1,11 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.archetype.BasePlugin;
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 com.bartek.esa.file.matcher.PackageNameMatcher;
import com.github.javaparser.ast.expr.MethodCallExpr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@@ -15,27 +16,46 @@ import java.util.Optional;
import static java.lang.String.format;
public class ExportedComponentsPlugin extends AndroidManifestPlugin {
public class ExportedComponentsPlugin extends BasePlugin {
private final XmlHelper xmlHelper;
private final PackageNameMatcher packageNameMatcher;
@Inject
public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public ExportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
this.xmlHelper = xmlHelper;
this.packageNameMatcher = packageNameMatcher;
}
@Override
protected void run(Document xml) {
findExportedComponents(xml, "activity");
findExportedComponents(xml, "service");
findExportedComponents(xml, "receiver");
findExportedProviders(xml);
protected void run(Context context) {
findExportedComponents(context, "activity");
findExportedComponents(context, "service");
findExportedComponents(context, "receiver");
findExportedProviders(context);
}
private void findExportedComponents(Document xml, String component) {
NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET);
stream(exportedActivities)
private void findExportedComponents(Context context, String component) {
NodeList exportedActivities = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), format("/manifest/application/%s", component), XPathConstants.NODESET);
xmlHelper.stream(exportedActivities)
.filter(this::isExported)
.filter(node -> doesNotHavePermission(node, "android:permission"))
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), null, null));
.forEach(node -> {
String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue();
String canonicalName = context.getPackageName() + componentName;
if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) {
addIssue(Severity.WARNING, ".NO_PERMISSION.DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null);
} else {
addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null);
}
});
}
private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) {
return context.getJavaSources().stream()
.filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName))
.flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream())
.filter(expr -> expr.getName().getIdentifier().equals("getIntent"))
.anyMatch(expr -> expr.getArguments().isEmpty());
}
private Map<String, String> getModel(Node node) {
@@ -45,13 +65,13 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin {
);
}
private void findExportedProviders(Document xml) {
NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET);
stream(exportedProviders)
private void findExportedProviders(Context context) {
NodeList exportedProviders = (NodeList) xmlHelper.xPath(context.getManifest().getModel(), "/manifest/application/provider", XPathConstants.NODESET);
xmlHelper.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));
.forEach(node -> addIssue(Severity.WARNING, ".NO_PERMISSION", getModel(node), context.getManifest().getFile(), null, null));
}
private boolean doesNotHavePermission(Node node, String permissionAttribute) {
@@ -68,13 +88,4 @@ public class ExportedComponentsPlugin extends AndroidManifestPlugin {
.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);
}
}

View File

@@ -1,44 +1,51 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
import com.bartek.esa.core.archetype.JavaPlugin;
import com.bartek.esa.core.helper.ParentNodeFinder;
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.body.MethodDeclaration;
import com.github.javaparser.ast.expr.MethodCallExpr;
import javax.inject.Inject;
import java.util.function.Consumer;
import static com.bartek.esa.core.helper.NodeUtil.is;
public class ExternalStoragePlugin extends JavaPlugin {
private final ParentNodeFinder parentNodeFinder;
private final StaticScopeHelper staticScopeHelper;
@Inject
public ExternalStoragePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, ParentNodeFinder parentNodeFinder) {
super(globMatcher, xmlHelper);
public ExternalStoragePlugin(ParentNodeFinder parentNodeFinder, StaticScopeHelper staticScopeHelper) {
this.parentNodeFinder = parentNodeFinder;
this.staticScopeHelper = staticScopeHelper;
}
@Override
public void run(CompilationUnit compilationUnit) {
compilationUnit.findAll(MethodCallExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().matches("getExternalStorageDirectory|getExternalStoragePublicDirectory"))
.forEach(this::findCheckingStorageStateForAccessingExternalStorage);
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(MethodCallExpr.class).stream()
.filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageDirectory|getExternalStoragePublicDirectory", "Environment", "android.os"))
.forEach(findCheckingStorageStateForAccessingExternalStorage(java));
}
private void findCheckingStorageStateForAccessingExternalStorage(MethodCallExpr accessingToExternalStorage) {
parentNodeFinder.findParentNode(accessingToExternalStorage, MethodDeclaration.class).ifPresent(methodDeclaration ->
findCheckingStorageStateInMethodDeclaration(accessingToExternalStorage, methodDeclaration)
);
private Consumer<MethodCallExpr> findCheckingStorageStateForAccessingExternalStorage(Source<CompilationUnit> java) {
return accessingToExternalStorage -> parentNodeFinder
.findParentNode(accessingToExternalStorage, MethodDeclaration.class)
.ifPresent(methodDeclaration ->
findCheckingStorageStateInMethodDeclaration(java, accessingToExternalStorage, methodDeclaration)
);
}
private void findCheckingStorageStateInMethodDeclaration(MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) {
private void findCheckingStorageStateInMethodDeclaration(Source<CompilationUnit> java, MethodCallExpr accessingToExternalStorage, MethodDeclaration methodDeclaration) {
boolean isStateBeingChecked = methodDeclaration.findAll(MethodCallExpr.class).stream()
.anyMatch(e -> e.getName().getIdentifier().equals("getExternalStorageState"));
.filter(staticScopeHelper.isFromScope(java.getModel(), "getExternalStorageState", "Environment", "android.os"))
.anyMatch(checkingMethod -> is(accessingToExternalStorage).after(checkingMethod));
if (!isStateBeingChecked) {
addIssue(Severity.WARNING, getLineNumberFromExpression(accessingToExternalStorage), accessingToExternalStorage.toString());
addJavaIssue(Severity.WARNING, java.getFile(), accessingToExternalStorage);
}
}
}

View File

@@ -1,10 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -17,27 +16,26 @@ import java.util.Optional;
import java.util.stream.Collectors;
public class ImplicitIntentsPlugin extends JavaPlugin {
private final JavaSyntaxRegexProvider java;
private final JavaSyntaxRegexProvider javaSyntax;
@Inject
public ImplicitIntentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
super(globMatcher, xmlHelper);
this.java = javaSyntaxRegexProvider;
public ImplicitIntentsPlugin(JavaSyntaxRegexProvider javaSyntaxRegexProvider) {
this.javaSyntax = javaSyntaxRegexProvider;
}
@Override
public void run(CompilationUnit compilationUnit) {
checkCreatingImplicitIntents(compilationUnit);
checkCreatingPendingIntentsWithoutIntentVariable(compilationUnit);
checkCreatingPendingIntentsWithIntentVariables(compilationUnit);
checkCreatingPendingIntentsWithIntentsArraysVariables(compilationUnit);
public void run(Source<CompilationUnit> java) {
checkCreatingImplicitIntents(java);
checkCreatingPendingIntentsWithoutIntentVariable(java);
checkCreatingPendingIntentsWithIntentVariables(java);
checkCreatingPendingIntentsWithIntentsArraysVariables(java);
}
// Works for:
// Intent[] myIntents = { new Intent(...), ... }
// getActivities(this, 0, myIntents, 0);
private void checkCreatingPendingIntentsWithIntentsArraysVariables(CompilationUnit compilationUnit) {
List<String> implicitIntentsArraysVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
private void checkCreatingPendingIntentsWithIntentsArraysVariables(Source<CompilationUnit> java) {
List<String> implicitIntentsArraysVariables = java.getModel().findAll(ObjectCreationExpr.class).stream()
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
.filter(this::isCreatingImplicitIntent)
.map(Node::getParentNode)
@@ -49,31 +47,31 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
.map(VariableDeclarator::getName)
.map(SimpleName::getIdentifier)
.collect(Collectors.toList());
compilationUnit.findAll(MethodCallExpr.class).stream()
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
}
private void checkCreatingImplicitIntents(CompilationUnit compilationUnit) {
List<String> intentVariables = getIntentVariables(compilationUnit);
checkAllSetActionMethodInvocations(compilationUnit, intentVariables);
checkCreatingImplicitIntentsUsingConstructor(compilationUnit);
private void checkCreatingImplicitIntents(Source<CompilationUnit> java) {
List<String> intentVariables = getIntentVariables(java);
checkAllSetActionMethodInvocations(java, intentVariables);
checkCreatingImplicitIntentsUsingConstructor(java);
}
// Works for: new Intent(Intent.ABC), new Intent(ABC)
private void checkCreatingImplicitIntentsUsingConstructor(CompilationUnit compilationUnit) {
compilationUnit.findAll(ObjectCreationExpr.class).stream()
private void checkCreatingImplicitIntentsUsingConstructor(Source<CompilationUnit> java) {
java.getModel().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()));
.forEach(objectCreation -> addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), objectCreation));
}
// Returns: i for: Intent i = new Intent(...)
private List<String> getIntentVariables(CompilationUnit compilationUnit) {
return compilationUnit.findAll(VariableDeclarationExpr.class).stream()
private List<String> getIntentVariables(Source<CompilationUnit> java) {
return java.getModel().findAll(VariableDeclarationExpr.class).stream()
.filter(expr -> expr.getElementType().toString().equals("Intent"))
.map(VariableDeclarationExpr::getVariables)
.flatMap(NodeList::stream)
@@ -90,7 +88,7 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
// Works for: new Intent(CONSTANT, ...)
if (arguments.size() == 1) {
Expression argument = arguments.get(0);
isImplicit = java.isConstant(argument);
isImplicit = javaSyntax.isConstant(argument);
}
// Not works for: new Intent(this, ...)
@@ -107,14 +105,14 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
}
// Works for: i.setAction(...)
private void checkAllSetActionMethodInvocations(CompilationUnit compilationUnit, List<String> intentVariables) {
compilationUnit.findAll(MethodCallExpr.class).forEach(methodCall -> {
private void checkAllSetActionMethodInvocations(Source<CompilationUnit> java, List<String> intentVariables) {
java.getModel().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());
addJavaIssue(Severity.INFO, ".IMPLICIT_INTENT", java.getFile(), methodCall);
}
});
}
@@ -122,8 +120,8 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
// Works for:
// Intent myIntent = new Intent(...)
// getActivity(this, 0, myIntent, 0);
private void checkCreatingPendingIntentsWithIntentVariables(CompilationUnit compilationUnit) {
List<String> implicitIntentsVariables = compilationUnit.findAll(ObjectCreationExpr.class).stream()
private void checkCreatingPendingIntentsWithIntentVariables(Source<CompilationUnit> java) {
List<String> implicitIntentsVariables = java.getModel().findAll(ObjectCreationExpr.class).stream()
.filter(expr -> expr.getType().getName().getIdentifier().equals("Intent"))
.filter(this::isCreatingImplicitIntent)
.map(Node::getParentNode)
@@ -133,30 +131,30 @@ public class ImplicitIntentsPlugin extends JavaPlugin {
.map(VariableDeclarator::getName)
.map(SimpleName::getIdentifier)
.collect(Collectors.toList());
compilationUnit.findAll(MethodCallExpr.class).stream()
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
}
private void checkCreatingPendingIntentsWithoutIntentVariable(CompilationUnit compilationUnit) {
private void checkCreatingPendingIntentsWithoutIntentVariable(Source<CompilationUnit> java) {
// Works for: getActivity(this, 0, new Intent(...), 0)
compilationUnit.findAll(MethodCallExpr.class).stream()
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
// Works for: getActivities(this, 0, new Intent[] { new Intent(...), ...}, 0)
compilationUnit.findAll(MethodCallExpr.class).stream()
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, ".PENDING_INTENT", java.getFile(), expr));
}
private boolean isCreatingImplicitIntentsArray(ArrayCreationExpr arrayCreationExpr) {

View File

@@ -1,30 +1,51 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.core.archetype.AndroidManifestPlugin;
import com.bartek.esa.context.model.Context;
import com.bartek.esa.core.archetype.BasePlugin;
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 com.bartek.esa.file.matcher.PackageNameMatcher;
import com.github.javaparser.ast.expr.MethodCallExpr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.inject.Inject;
import java.util.Map;
import java.util.Optional;
public class IntentFilterPlugin extends AndroidManifestPlugin {
public class IntentFilterPlugin extends BasePlugin {
private final XmlHelper xmlHelper;
private final PackageNameMatcher packageNameMatcher;
@Inject
public IntentFilterPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public IntentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
this.xmlHelper = xmlHelper;
this.packageNameMatcher = packageNameMatcher;
}
@Override
protected void run(Document xml) {
NodeList filters = xml.getElementsByTagName("intent-filter");
stream(filters)
protected void run(Context context) {
NodeList filters = context.getManifest().getModel().getElementsByTagName("intent-filter");
xmlHelper.stream(filters)
.filter(this::isNotMainActivity)
.filter(this::isNotExported)
.map(Node::getParentNode)
.forEach(n -> addIssue(Severity.INFO, getModel(n), null, null));
.forEach(node -> {
String componentName = node.getAttributes().getNamedItem("android:name").getNodeValue();
String canonicalName = context.getPackageName() + componentName;
if (isIntentDataBeingUsedInsideComponent(context, canonicalName)) {
addIssue(Severity.WARNING, ".DATA_USAGE", getModel(node), context.getManifest().getFile(), null, null);
} else {
addIssue(Severity.WARNING, getModel(node), context.getManifest().getFile(), null, null);
}
});
}
private boolean isNotExported(Node component) {
return !Optional.ofNullable(component.getAttributes().getNamedItem("android:exported"))
.map(Node::getNodeValue)
.map(Boolean::parseBoolean)
.orElse(false);
}
private Map<String, String> getModel(Node node) {
@@ -35,17 +56,25 @@ public class IntentFilterPlugin extends AndroidManifestPlugin {
}
private boolean isNotMainActivity(Node filter) {
long mainActivityIntentFilters = stream(filter.getChildNodes())
.filter(n -> n.getNodeName().matches("action|category"))
long mainActivityIntentFilters = xmlHelper.stream(filter.getChildNodes())
.filter(n -> n.getNodeName().matches("action|category|data"))
.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"))
long currentIntentFilters = xmlHelper.stream(filter.getChildNodes())
.filter(n -> n.getNodeName().matches("action|category|data"))
.count();
return mainActivityIntentFilters != currentIntentFilters;
}
private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) {
return context.getJavaSources().stream()
.filter(java -> packageNameMatcher.doesFileMatchPackageName(java.getFile(), componentCanonicalName))
.flatMap(java -> java.getModel().findAll(MethodCallExpr.class).stream())
.filter(expr -> expr.getName().getIdentifier().equals("getIntent"))
.anyMatch(expr -> expr.getArguments().isEmpty());
}
}

View File

@@ -1,10 +1,10 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
import com.bartek.esa.core.archetype.JavaPlugin;
import com.bartek.esa.core.helper.StaticScopeHelper;
import com.bartek.esa.core.helper.StringConcatenationChecker;
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;
@@ -12,18 +12,21 @@ import javax.inject.Inject;
public class LoggingPlugin extends JavaPlugin {
private final StaticScopeHelper staticScopeHelper;
private final StringConcatenationChecker stringConcatenationChecker;
@Inject
public LoggingPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
super(globMatcher, xmlHelper);
public LoggingPlugin(StaticScopeHelper staticScopeHelper, StringConcatenationChecker stringConcatenationChecker) {
this.staticScopeHelper = staticScopeHelper;
this.stringConcatenationChecker = stringConcatenationChecker;
}
@Override
public void run(CompilationUnit compilationUnit) {
compilationUnit.findAll(MethodCallExpr.class).stream()
public void run(Source<CompilationUnit> java) {
java.getModel().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()));
.filter(staticScopeHelper.isFromScope(java.getModel(), "v|d|i|w|e|wtf", "Log", "android.util"))
.filter(expr -> expr.getArguments().size() >= 2)
.filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(1)))
.forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr));
}
}

View File

@@ -1,25 +1,17 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(MethodCallExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().matches("sendOrderedBroadcast|sendOrderedBroadcastAsUser|sendStickyOrderedBroadcast|sendStickyOrderedBroadcastAsUser"))
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
}
}

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -15,21 +15,22 @@ import java.util.Map;
import static java.lang.Integer.parseInt;
public class PermissionsRaceConditionPlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public PermissionsRaceConditionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public PermissionsRaceConditionPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
boolean isAnyPermissionDefined = ((NodeList) xPath(xml, "/manifest/permission", XPathConstants.NODESET)).getLength() > 0;
protected void run(Source<Document> manifest) {
boolean isAnyPermissionDefined = ((NodeList) xmlHelper.xPath(manifest.getModel(), "/manifest/permission", XPathConstants.NODESET)).getLength() > 0;
if(isAnyPermissionDefined) {
Node usesSdkNode = (Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE);
Node usesSdkNode = (Node) xmlHelper.xPath(manifest.getModel(), "/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());
addIssue(Severity.VULNERABILITY, getModel(minSdkVersion), manifest.getFile(), null, minSdkVersionNode.toString());
}
}
}

View File

@@ -1,26 +1,18 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr));
}
}

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -12,17 +12,18 @@ import javax.xml.xpath.XPathConstants;
import java.util.Optional;
public class SharedUidPlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public SharedUidPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public SharedUidPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
Node manifestNode = (Node) xPath(xml, "/manifest", XPathConstants.NODE);
protected void run(Source<Document> manifest) {
Node manifestNode = (Node) xmlHelper.xPath(manifest.getModel(), "/manifest", XPathConstants.NODE);
Optional.ofNullable(manifestNode.getAttributes().getNamedItem("android:sharedUserId")).ifPresent(node -> {
addIssue(Severity.VULNERABILITY, null, node.toString());
addIssue(Severity.VULNERABILITY, manifest.getFile(), null, node.toString());
});
}
}

View File

@@ -1,25 +1,28 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
import com.bartek.esa.core.archetype.JavaPlugin;
import com.bartek.esa.core.helper.StringConcatenationChecker;
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 {
private final StringConcatenationChecker stringConcatenationChecker;
@Inject
public SqlInjectionPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public SqlInjectionPlugin(StringConcatenationChecker stringConcatenationChecker) {
this.stringConcatenationChecker = stringConcatenationChecker;
}
@Override
public void run(CompilationUnit compilationUnit) {
compilationUnit.findAll(MethodCallExpr.class).stream()
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(MethodCallExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().equals("rawQuery"))
.forEach(expr -> addIssue(Severity.VULNERABILITY, getLineNumberFromExpression(expr), expr.toString()));
.filter(expr -> expr.getArguments().size() >= 2)
.filter(expr -> stringConcatenationChecker.isStringConcatenation(java.getModel(), expr.getArguments().get(0)))
.forEach(expr -> addJavaIssue(Severity.VULNERABILITY, java.getFile(), expr));
}
}

View File

@@ -1,10 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -14,16 +13,15 @@ public class StrictModePlugin extends JavaPlugin {
private final StaticScopeHelper staticScopeHelper;
@Inject
public StrictModePlugin(GlobMatcher globMatcher, XmlHelper xmlHelper, StaticScopeHelper staticScopeHelper) {
super(globMatcher, xmlHelper);
public StrictModePlugin(StaticScopeHelper staticScopeHelper) {
this.staticScopeHelper = staticScopeHelper;
}
@Override
public void run(CompilationUnit compilationUnit) {
compilationUnit.findAll(MethodCallExpr.class).stream()
public void run(Source<CompilationUnit> java) {
java.getModel().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()));
.filter(staticScopeHelper.isFromScope(java.getModel(), "setThreadPolicy", "StrictMode", "android.os"))
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
}
}

View File

@@ -1,25 +1,17 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(AnnotationExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().equals("SuppressWarnings"))
.forEach(expr -> addIssue(Severity.WARNING, getLineNumberFromExpression(expr), expr.toString()));
.forEach(expr -> addJavaIssue(Severity.WARNING, java.getFile(), expr));
}
}

View File

@@ -1,26 +1,18 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.INFO, java.getFile(), expr));
}
}

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -12,18 +12,19 @@ import javax.inject.Inject;
import java.util.Optional;
public class TextInputValidationPlugin extends ResourceLayoutPlugin {
private final XmlHelper xmlHelper;
@Inject
public TextInputValidationPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public TextInputValidationPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
NodeList editTextNodes = xml.getElementsByTagName("EditText");
stream(editTextNodes)
protected void run(Source<Document> layout) {
NodeList editTextNodes = layout.getModel().getElementsByTagName("EditText");
xmlHelper.stream(editTextNodes)
.filter(this::doesNotHaveInputType)
.forEach(n -> addIssue(Severity.WARNING, null, tagString(n)));
.forEach(n -> addXmlIssue(Severity.WARNING, layout.getFile(), n));
}
private boolean doesNotHaveInputType(Node editText) {

View File

@@ -1,9 +1,9 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
@@ -13,22 +13,23 @@ import java.util.Map;
import java.util.Optional;
public class UsesSdkPlugin extends AndroidManifestPlugin {
private final XmlHelper xmlHelper;
@Inject
public UsesSdkPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
public UsesSdkPlugin(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
@Override
protected void run(Document xml) {
Optional.ofNullable((Node) xPath(xml, "/manifest/uses-sdk", XPathConstants.NODE)).ifPresentOrElse(usesSdkNode -> {
protected void run(Source<Document> manifest) {
Optional.ofNullable((Node) xmlHelper.xPath(manifest.getModel(), "/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);
addIssue(Severity.ERROR, ".USES_SDK.NO_MIN_SDK_VERSION", manifest.getFile(), 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, ".USES_SDK.MAX_SDK_VERSION", Map.of("maxSdkVersion", maxSdkVersion.getNodeValue()), manifest.getFile(), null, maxSdkVersion.toString())
);
}, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", null, null));
}, () -> addIssue(Severity.ERROR, ".NO_USES_SDK", manifest.getFile(), null, null));
}
}

View File

@@ -1,51 +1,47 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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;
import java.util.function.Consumer;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().findAll(MethodCallExpr.class).stream()
.filter(expr -> expr.getName().getIdentifier().matches(SETTINGS_METHODS))
.forEach(this::issueMethod);
.forEach(issueMethod(java));
}
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 Consumer<MethodCallExpr> issueMethod(Source<CompilationUnit> java) {
return methodCall -> {
switch (methodCall.getName().getIdentifier()) {
case "addJavascriptInterface":
addJavaIssue(Severity.VULNERABILITY, ".JS_INTERFACE", java.getFile(), methodCall);
break;
case "setJavaScriptEnabled":
issueSettingsMethod(java, methodCall, ".JS_ENABLED");
break;
case "setWebContentsDebuggingEnabled":
issueSettingsMethod(java, methodCall, ".DEBUGGING_ENABLED");
break;
case "setAllowFileAccess":
issueSettingsMethod(java, methodCall, ".ALLOW_FILE_ACCESS");
break;
}
};
}
private void issueSettingsMethod(MethodCallExpr methodCall, String descriptionCode) {
private void issueSettingsMethod(Source<CompilationUnit> java, MethodCallExpr methodCall, String descriptionCode) {
Expression firstArg = methodCall.getArguments().get(0);
if (firstArg.isBooleanLiteralExpr() && firstArg.asBooleanLiteralExpr().getValue()) {
addIssue(Severity.WARNING, descriptionCode, getLineNumberFromExpression(methodCall), methodCall.toString());
addJavaIssue(Severity.WARNING, descriptionCode, java.getFile(), methodCall);
}
}
}

View File

@@ -1,32 +1,25 @@
package com.bartek.esa.core.plugin;
import com.bartek.esa.context.model.Source;
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()
public void run(Source<CompilationUnit> java) {
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr));
compilationUnit.findAll(FieldAccessExpr.class).stream()
java.getModel().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()));
.forEach(expr -> addJavaIssue(Severity.ERROR, getModel(expr), java.getFile(), expr));
}
private Map<String, String> getModel(NameExpr expression) {

View File

@@ -3,6 +3,7 @@ package com.bartek.esa.di;
import com.bartek.esa.EsaMain;
import com.bartek.esa.analyser.di.AnalyserModule;
import com.bartek.esa.cli.di.CliModule;
import com.bartek.esa.context.di.ContextModule;
import com.bartek.esa.core.di.CoreModule;
import com.bartek.esa.core.di.PluginModule;
import com.bartek.esa.decompiler.di.DecompilerModule;
@@ -19,7 +20,8 @@ import dagger.Component;
CoreModule.class,
PluginModule.class,
AnalyserModule.class,
FormatterModule.class
FormatterModule.class,
ContextModule.class
})
public interface DependencyInjector {
EsaMain esa();

View File

@@ -1,6 +1,6 @@
package com.bartek.esa.dispatcher.dispatcher;
import com.bartek.esa.cli.model.CliArgsOptions;
import com.bartek.esa.cli.model.object.CliArgsOptions;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.dispatcher.model.DispatcherActions;

View File

@@ -2,6 +2,7 @@ package com.bartek.esa.file.di;
import com.bartek.esa.file.cleaner.FileCleaner;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.file.matcher.PackageNameMatcher;
import com.bartek.esa.file.provider.FileContentProvider;
import com.bartek.esa.file.provider.FileProvider;
import com.bartek.esa.file.zip.ZipTool;
@@ -35,4 +36,9 @@ public class FileModule {
public FileCleaner fileCleaner() {
return new FileCleaner();
}
@Provides
public PackageNameMatcher packageNameMatcher() {
return new PackageNameMatcher();
}
}

View File

@@ -0,0 +1,16 @@
package com.bartek.esa.file.matcher;
import javax.inject.Inject;
import java.io.File;
public class PackageNameMatcher {
@Inject
public PackageNameMatcher() {
}
public boolean doesFileMatchPackageName(File file, String packageName) {
return file.getAbsolutePath().replaceAll(File.separator, ".").contains(packageName);
}
}

View File

@@ -5,5 +5,7 @@ import com.bartek.esa.core.model.object.Issue;
import java.util.Set;
public interface Formatter {
void format(Set<Issue> issues);
void beforeFormat();
String format(Set<Issue> issues);
void afterFormat();
}

View File

@@ -2,6 +2,7 @@ package com.bartek.esa.formatter.di;
import com.bartek.esa.core.desc.provider.DescriptionProvider;
import com.bartek.esa.formatter.formatter.ColorFormatter;
import com.bartek.esa.formatter.formatter.JsonFormatter;
import com.bartek.esa.formatter.formatter.SimpleFormatter;
import com.bartek.esa.formatter.provider.FormatterProvider;
import dagger.Module;
@@ -21,7 +22,12 @@ public class FormatterModule {
}
@Provides
public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) {
return new FormatterProvider(simpleFormatter, colorFormatter);
public JsonFormatter jsonFormatter() {
return new JsonFormatter();
}
@Provides
public FormatterProvider formatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) {
return new FormatterProvider(simpleFormatter, colorFormatter, jsonFormatter);
}
}

View File

@@ -30,12 +30,15 @@ public class ColorFormatter implements Formatter {
}
@Override
public void format(Set<Issue> issues) {
public void beforeFormat() {
AnsiConsole.systemInstall();
}
@Override
public String format(Set<Issue> issues) {
if (issues.isEmpty()) {
Ansi noIssuesFound = ansi().fg(GREEN).a("No issues found.").reset();
System.out.println(noIssuesFound);
return;
return noIssuesFound.toString();
}
String format = issues.stream()
@@ -43,8 +46,11 @@ public class ColorFormatter implements Formatter {
.map(this::format)
.collect(Collectors.joining());
System.out.println(format.substring(0, format.length() - 2));
System.out.println(printSummary(issues));
return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues));
}
@Override
public void afterFormat() {
AnsiConsole.systemUninstall();
}

View File

@@ -0,0 +1,29 @@
package com.bartek.esa.formatter.formatter;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.formatter.archetype.Formatter;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vavr.control.Try;
import java.util.Set;
public class JsonFormatter implements Formatter {
@Override
public void beforeFormat() {
// nothing to do
}
@Override
public String format(Set<Issue> issues) {
ObjectMapper objectMapper = new ObjectMapper();
return Try.of(() -> objectMapper.writeValueAsString(issues))
.getOrElseThrow(EsaException::new);
}
@Override
public void afterFormat() {
// nothing to do
}
}

View File

@@ -20,10 +20,14 @@ public class SimpleFormatter implements Formatter {
}
@Override
public void format(Set<Issue> issues) {
public void beforeFormat() {
// nothing to do
}
@Override
public String format(Set<Issue> issues) {
if (issues.isEmpty()) {
System.out.println("No issues found.");
return;
return "No issues found.";
}
String format = issues.stream()
@@ -31,8 +35,12 @@ public class SimpleFormatter implements Formatter {
.map(this::format)
.collect(Collectors.joining());
System.out.println(format.substring(0, format.length() - 2));
System.out.println(printSummary(issues));
return String.format("%s\n%s", format.substring(0, format.length() - 2), printSummary(issues));
}
@Override
public void afterFormat() {
// nothing to do
}
private String format(Issue issue) {

View File

@@ -1,8 +1,9 @@
package com.bartek.esa.formatter.provider;
import com.bartek.esa.cli.model.CliArgsOptions;
import com.bartek.esa.cli.model.object.CliArgsOptions;
import com.bartek.esa.formatter.archetype.Formatter;
import com.bartek.esa.formatter.formatter.ColorFormatter;
import com.bartek.esa.formatter.formatter.JsonFormatter;
import com.bartek.esa.formatter.formatter.SimpleFormatter;
import javax.inject.Inject;
@@ -10,14 +11,24 @@ import javax.inject.Inject;
public class FormatterProvider {
private final SimpleFormatter simpleFormatter;
private final ColorFormatter colorFormatter;
private final JsonFormatter jsonFormatter;
@Inject
public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter) {
public FormatterProvider(SimpleFormatter simpleFormatter, ColorFormatter colorFormatter, JsonFormatter jsonFormatter) {
this.simpleFormatter = simpleFormatter;
this.colorFormatter = colorFormatter;
this.jsonFormatter = jsonFormatter;
}
public Formatter provide(CliArgsOptions options) {
return options.isColor() ? colorFormatter : simpleFormatter;
switch (options.getOutputType()) {
case COLOR:
return colorFormatter;
case JSON:
return jsonFormatter;
case DEFAULT:
default:
return simpleFormatter;
}
}
}

View File

@@ -93,6 +93,13 @@ com.bartek.esa.core.plugin.ExportedComponentsPlugin.NO_PERMISSION=Exported ${com
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.ExportedComponentsPlugin.NO_PERMISSION.DATA_USAGE=Exported ${componentType} makes use of data of incoming intents.\n\
The ${componentType} with name '${componentName}' is exported but not protected by any permission. \n\
It probably does also process a data from incoming intent. \n\
It means any malicious application could make use of the component or inject a fake (potentially malicious) data to it. \n\
Consider using 'android:permission' tag and adding custom permission to protect it. \n\
Also make sure, that intent is correctly validated before processing 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\
@@ -103,12 +110,22 @@ com.bartek.esa.core.plugin.TextInputValidationPlugin=Input type is no selected.\
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\
com.bartek.esa.core.plugin.IntentFilterPlugin=Implemented intent filter inside private component.\n\
The non-exported ${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\
Consider removing intent filter or set it explicitely to be exported using following attribute:\n\
android:exported="true". \n\
Also be aware, that intent filter is not a security tool. It can be easily omitted.
com.bartek.esa.core.plugin.IntentFilterPlugin.DATA_USAGE=Implemented intent filter inside private component making use of incoming data.\n\
The non-exported ${componentType} with name '${componentName}' does have a intent filter declared \n\
and also does make use of incoming intent data. \n\
It means, that the component is implicitly exposed to public and can be spoofed with fake data.\n\
Consider removing intent filter or set it explicitely to be exported using following attribute:\n\
android:exported="true". \n\
Be aware, that intent filter is not a security tool. It can be easily omitted. \n\
Also make sure, that data is correctly validated before taking advantage of it.
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.