17: Improve IntentFilterPlugin to work on context
This commit is contained in:
@@ -4,7 +4,7 @@ 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.GlobMatcher;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.Problem;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
@@ -18,19 +18,18 @@ import javax.xml.xpath.XPathConstants;
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class ContextConstructor {
|
||||
private final XmlHelper xmlHelper;
|
||||
private final GlobMatcher globMatcher;
|
||||
private final PackageNameMatcher packageNameMatcher;
|
||||
|
||||
@Inject
|
||||
public ContextConstructor(XmlHelper xmlHelper, GlobMatcher globMatcher) {
|
||||
public ContextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.globMatcher = globMatcher;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
public Context construct(File androidManifestFile, Set<File> javaFiles, Set<File> layoutFiles) {
|
||||
@@ -76,19 +75,12 @@ public class ContextConstructor {
|
||||
|
||||
private Set<Source<CompilationUnit>> parseJavaFiles(Set<File> javaFiles, String packageName) {
|
||||
return javaFiles.stream()
|
||||
.filter(isApplicationPackageFile(packageName))
|
||||
.filter(file -> packageNameMatcher.doesFileMatchPackageName(file, packageName))
|
||||
.map(file -> new Source<>(file, parseJava(file)))
|
||||
.filter(s -> s.getModel() != null)
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
private Predicate<File> isApplicationPackageFile(String packageName) {
|
||||
return file -> {
|
||||
String path = packageName.replaceAll("\\.", "/");
|
||||
return globMatcher.fileMatchesGlobPattern(file, format("**/%s/**", path));
|
||||
};
|
||||
}
|
||||
|
||||
private CompilationUnit parseJava(File javaFile) {
|
||||
try {
|
||||
return StaticJavaParser.parse(javaFile);
|
||||
|
||||
@@ -2,14 +2,14 @@ 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.GlobMatcher;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import dagger.Module;
|
||||
|
||||
@Module
|
||||
public class ContextModule {
|
||||
|
||||
public ContextConstructor contextConstructor(XmlHelper xmlHelper, GlobMatcher globMatcher) {
|
||||
return new ContextConstructor(xmlHelper, globMatcher);
|
||||
public ContextConstructor contextConstructor(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new ContextConstructor(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.PackageNameMatcher;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.ElementsIntoSet;
|
||||
@@ -98,8 +99,8 @@ public class PluginModule {
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin exportedComponentsPlugin(XmlHelper xmlHelper) {
|
||||
return new ExportedComponentsPlugin(xmlHelper);
|
||||
public Plugin exportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new ExportedComponentsPlugin(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -116,8 +117,8 @@ public class PluginModule {
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
public Plugin intentFilterPlugin(XmlHelper xmlHelper) {
|
||||
return new IntentFilterPlugin(xmlHelper);
|
||||
public Plugin intentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
return new IntentFilterPlugin(xmlHelper, packageNameMatcher);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Context;
|
||||
import com.bartek.esa.context.model.Source;
|
||||
import com.bartek.esa.core.archetype.BasePlugin;
|
||||
import com.bartek.esa.core.model.enumeration.Severity;
|
||||
import com.bartek.esa.core.xml.XmlHelper;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.bartek.esa.file.matcher.PackageNameMatcher;
|
||||
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
@@ -14,16 +13,17 @@ import javax.inject.Inject;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class ExportedComponentsPlugin extends BasePlugin {
|
||||
private final XmlHelper xmlHelper;
|
||||
private final PackageNameMatcher packageNameMatcher;
|
||||
|
||||
@Inject
|
||||
public ExportedComponentsPlugin(XmlHelper xmlHelper) {
|
||||
public ExportedComponentsPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,7 +52,7 @@ public class ExportedComponentsPlugin extends BasePlugin {
|
||||
|
||||
private boolean isIntentDataBeingUsedInsideComponent(Context context, String componentCanonicalName) {
|
||||
return context.getJavaSources().stream()
|
||||
.filter(doesMatchCanonicalName(componentCanonicalName))
|
||||
.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());
|
||||
@@ -88,8 +88,4 @@ public class ExportedComponentsPlugin extends BasePlugin {
|
||||
.map(v -> v.equals("true"))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private Predicate<Source<CompilationUnit>> doesMatchCanonicalName(String canonicalName) {
|
||||
return source -> source.getFile().getAbsolutePath().replaceAll("/", ".").contains(canonicalName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,51 @@
|
||||
package com.bartek.esa.core.plugin;
|
||||
|
||||
import com.bartek.esa.context.model.Source;
|
||||
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 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(XmlHelper xmlHelper) {
|
||||
public IntentFilterPlugin(XmlHelper xmlHelper, PackageNameMatcher packageNameMatcher) {
|
||||
this.xmlHelper = xmlHelper;
|
||||
this.packageNameMatcher = packageNameMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Source<Document> manifest) {
|
||||
NodeList filters = manifest.getModel().getElementsByTagName("intent-filter");
|
||||
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), manifest.getFile(), 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) {
|
||||
@@ -37,16 +57,24 @@ public class IntentFilterPlugin extends AndroidManifestPlugin {
|
||||
|
||||
private boolean isNotMainActivity(Node filter) {
|
||||
long mainActivityIntentFilters = xmlHelper.stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
.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 = xmlHelper.stream(filter.getChildNodes())
|
||||
.filter(n -> n.getNodeName().matches("action|category"))
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -110,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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user