diff --git a/java/ql/src/Security/CWE/CWE-094/ArbitraryAPKInstallation.ql b/java/ql/src/Security/CWE/CWE-094/ArbitraryAPKInstallation.ql new file mode 100644 index 00000000000..6ef44cd0869 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-094/ArbitraryAPKInstallation.ql @@ -0,0 +1,105 @@ +/** + * @name Android APK installation + * @description Installing an APK from an untrusted source. + * @kind path-problem + * @tags security + */ + +import java +import semmle.code.java.frameworks.android.Intent +import semmle.code.java.dataflow.DataFlow +import semmle.code.java.dataflow.TaintTracking +private import semmle.code.java.dataflow.ExternalFlow + +class PackageArchiveMimeTypeLiteral extends StringLiteral { + PackageArchiveMimeTypeLiteral() { this.getValue() = "application/vnd.android.package-archive" } +} + +class SetTypeMethod extends Method { + SetTypeMethod() { + this.hasName(["setType", "setTypeAndNormalize"]) and + this.getDeclaringType().getASupertype*() instanceof TypeIntent + } +} + +class SetDataAndTypeMethod extends Method { + SetDataAndTypeMethod() { + this.hasName(["setDataAndType", "setDataAndTypeAndNormalize"]) and + this.getDeclaringType().getASupertype*() instanceof TypeIntent + } +} + +class SetDataMethod extends Method { + SetDataMethod() { + this.hasName(["setData", "setDataAndNormalize", "setDataAndType", "setDataAndTypeAndNormalize"]) and + this.getDeclaringType().getASupertype*() instanceof TypeIntent + } +} + +class SetDataSink extends DataFlow::ExprNode { + SetDataSink() { this.getExpr().(MethodAccess).getMethod() instanceof SetDataMethod } + + DataFlow::ExprNode getUri() { result.asExpr() = this.getExpr().(MethodAccess).getArgument(0) } +} + +class UriParseMethod extends Method { + UriParseMethod() { + this.hasName(["parse", "fromFile"]) and + this.getDeclaringType().hasQualifiedName("android.net", "Uri") + } +} + +class ExternalSource extends DataFlow::Node { + ExternalSource() { + sourceNode(this, "android-external-storage-dir") or + this.asExpr().(MethodAccess).getMethod() instanceof UriParseMethod or + this.asExpr().(StringLiteral).getValue().matches(["file://%", "http://%", "https://%"]) + } +} + +class ExternalSourceConfiguration extends DataFlow2::Configuration { + ExternalSourceConfiguration() { this = "ExternalSourceConfiguration" } + + override predicate isSource(DataFlow::Node node) { node instanceof ExternalSource } + + override predicate isSink(DataFlow::Node node) { + // any(PackageArchiveMimeTypeConfiguration c).hasFlow(_, node) + node instanceof SetDataSink + } +} + +class PackageArchiveMimeTypeConfiguration extends TaintTracking::Configuration { + PackageArchiveMimeTypeConfiguration() { this = "PackageArchiveMimeTypeConfiguration" } + + override predicate isSource(DataFlow::Node node) { + node.asExpr() instanceof PackageArchiveMimeTypeLiteral + } + + override predicate isAdditionalTaintStep( + DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2, + DataFlow::FlowState state2 + ) { + state1 instanceof DataFlow::FlowStateEmpty and + state2 = "typeSet" and + exists(MethodAccess ma | + ma.getQualifier() = node2.asExpr() and + ( + ma.getMethod() instanceof SetTypeMethod and + ma.getArgument(0) = node1.asExpr() + or + ma.getMethod() instanceof SetDataAndTypeMethod and + ma.getArgument(1) = node1.asExpr() + ) + ) + } + + override predicate isSink(DataFlow::Node node, DataFlow::FlowState state) { + state = "typeSet" and + node instanceof SetDataSink + // and any(ExternalSourceConfiguration c).hasFlow(_, node.(SetDataSink).getUri()) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, PackageArchiveMimeTypeConfiguration config +where config.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Android APK installation" diff --git a/java/ql/test/query-tests/security/CWE-094/APKInstallation.java b/java/ql/test/query-tests/security/CWE-094/APKInstallation.java new file mode 100644 index 00000000000..4e16985fd0c --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-094/APKInstallation.java @@ -0,0 +1,38 @@ +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; + +import java.io.File; + +public class APKInstallation extends Activity { + static final String APK_MIMETYPE = "application/vnd.android.package-archive"; + + public void installAPK(String path) { + // BAD: the path is not checked + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive"); + startActivity(intent); + } + + public void downloadAPK(String url) { + // BAD: the url is not checked + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(url), "application/vnd.android.package-archive"); + startActivity(intent); + } + + public void installAPK2() { + String path = "file:///sdcard/Download/MyApp.apk"; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType("application/vnd.android.package-archive"); + intent.setData(Uri.parse(path)); + startActivity(intent); + } + + public void installAPK3(String path) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setType(APK_MIMETYPE); + intent.setData(Uri.fromFile(new File(path))); + startActivity(intent); + } +} diff --git a/java/ql/test/query-tests/security/CWE-094/APKInstallation.qlref b/java/ql/test/query-tests/security/CWE-094/APKInstallation.qlref new file mode 100644 index 00000000000..e2a41dbb284 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-094/APKInstallation.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-094/ArbitraryAPKInstallation.ql \ No newline at end of file diff --git a/java/ql/test/query-tests/security/CWE-094/options b/java/ql/test/query-tests/security/CWE-094/options index 72dc22e6bd3..469e3df8ac0 100644 --- a/java/ql/test/query-tests/security/CWE-094/options +++ b/java/ql/test/query-tests/security/CWE-094/options @@ -1 +1 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/validation-api-2.0.1.Final:${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../stubs/apache-commons-logging-1.2:${testdir}/../../../stubs/mvel2-2.4.7:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/scriptengine:${testdir}/../../../stubs/jsr223-api:${testdir}/../../../stubs/apache-freemarker-2.3.31:${testdir}/../../../stubs/jinjava-2.6.0:${testdir}/../../../stubs/pebble-3.1.5:${testdir}/../../../stubs/thymeleaf-3.0.14:${testdir}/../../../stubs/apache-velocity-2.3 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/validation-api-2.0.1.Final:${testdir}/../../../stubs/springframework-5.3.8:${testdir}/../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../stubs/apache-commons-logging-1.2:${testdir}/../../../stubs/mvel2-2.4.7:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/scriptengine:${testdir}/../../../stubs/jsr223-api:${testdir}/../../../stubs/apache-freemarker-2.3.31:${testdir}/../../../stubs/jinjava-2.6.0:${testdir}/../../../stubs/pebble-3.1.5:${testdir}/../../../stubs/thymeleaf-3.0.14:${testdir}/../../../stubs/apache-velocity-2.3:${testdir}/../../..//stubs/google-android-9.0.0