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