Skip to content

Commit 67f8bcc

Browse files
authored
Merge pull request #14752 from masterofnow/LoadClassNoSignatureCheck
Java: Insecure Loading of Class in Android App without Package Signature Checking
2 parents e43fafc + 0fd0975 commit 67f8bcc

File tree

9 files changed

+258
-1
lines changed

9 files changed

+258
-1
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package poc.sample.classloader;
2+
3+
import android.app.Application;
4+
import android.content.pm.PackageInfo;
5+
import android.content.Context;
6+
import android.util.Log;
7+
8+
public class BadClassLoader extends Application {
9+
@Override
10+
public void onCreate() {
11+
super.onCreate();
12+
for (PackageInfo p : getPackageManager().getInstalledPackages(0)) {
13+
try {
14+
if (p.packageName.startsWith("some.package.")) {
15+
Context appContext = createPackageContext(p.packageName,
16+
CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);
17+
ClassLoader classLoader = appContext.getClassLoader();
18+
Object result = classLoader.loadClass("some.package.SomeClass")
19+
.getMethod("someMethod")
20+
.invoke(null);
21+
}
22+
} catch (Exception e) {
23+
Log.e("Class loading failed", e.toString());
24+
}
25+
}
26+
}
27+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package poc.sample.classloader;
2+
3+
import android.app.Application;
4+
import android.content.pm.PackageInfo;
5+
import android.content.Context;
6+
import android.content.pm.PackageManager;
7+
import android.util.Log;
8+
9+
public class GoodClassLoader extends Application {
10+
@Override
11+
public void onCreate() {
12+
super.onCreate();
13+
PackageManager pm = getPackageManager();
14+
for (PackageInfo p : pm.getInstalledPackages(0)) {
15+
try {
16+
if (p.packageName.startsWith("some.package.") &&
17+
(pm.checkSignatures(p.packageName, getApplicationContext().getPackageName()) == PackageManager.SIGNATURE_MATCH)
18+
) {
19+
Context appContext = createPackageContext(p.packageName,
20+
CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);
21+
ClassLoader classLoader = appContext.getClassLoader();
22+
Object result = classLoader.loadClass("some.package.SomeClass")
23+
.getMethod("someMethod")
24+
.invoke(null);
25+
}
26+
} catch (Exception e) {
27+
Log.e("Class loading failed", e.toString());
28+
}
29+
}
30+
}
31+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
If an application loads classes or code from another app based solely on its package name without
7+
first checking its package signature, this could allow a malicious app with the same package name
8+
to be loaded through "package namespace squatting".
9+
If the victim user install such malicious app in the same device as the vulnerable app, the vulnerable app would load
10+
classes or code from the malicious app, potentially leading to arbitrary code execution.
11+
</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>
16+
Verify the package signature in addition to the package name before loading any classes or code from another application.
17+
</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>
22+
The <code>BadClassLoader</code> class illustrates class loading with the <code>android.content.pm.PackageInfo.packageName.startsWith()</code> method without any check on the package signature.
23+
</p>
24+
<sample src="BadClassLoader.java" />
25+
<p>
26+
The <code>GoodClassLoader</code> class illustrates class loading with correct package signature check using the <code>android.content.pm.PackageManager.checkSignatures()</code> method.
27+
</p>
28+
<sample src="GoodClassLoader.java" />
29+
</example>
30+
31+
32+
<references>
33+
<li>
34+
<a href="https://blog.oversecured.com/Android-arbitrary-code-execution-via-third-party-package-contexts/">
35+
Oversecured (Android: arbitrary code execution via third-party package contexts)
36+
</a>
37+
</li>
38+
</references>
39+
40+
</qhelp>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* @name Load 3rd party classes or code ('unsafe reflection') without signature check
3+
* @description Loading classes or code from third-party packages without checking the
4+
* package signature could make the application
5+
* susceptible to package namespace squatting attacks,
6+
* potentially leading to arbitrary code execution.
7+
* @problem.severity error
8+
* @precision high
9+
* @kind path-problem
10+
* @id java/android/unsafe-reflection
11+
* @tags security
12+
* experimental
13+
* external/cwe/cwe-470
14+
*/
15+
16+
import java
17+
import semmle.code.java.dataflow.TaintTracking
18+
import semmle.code.java.controlflow.Guards
19+
import semmle.code.java.dataflow.SSA
20+
import semmle.code.java.frameworks.android.Intent
21+
22+
class CheckSignaturesGuard extends Guard instanceof EqualityTest {
23+
MethodCall checkSignatures;
24+
25+
CheckSignaturesGuard() {
26+
this.getAnOperand() = checkSignatures and
27+
checkSignatures
28+
.getMethod()
29+
.hasQualifiedName("android.content.pm", "PackageManager", "checkSignatures") and
30+
exists(Expr signatureCheckResult |
31+
this.getAnOperand() = signatureCheckResult and signatureCheckResult != checkSignatures
32+
|
33+
signatureCheckResult.(CompileTimeConstantExpr).getIntValue() = 0 or
34+
signatureCheckResult
35+
.(FieldRead)
36+
.getField()
37+
.hasQualifiedName("android.content.pm", "PackageManager", "SIGNATURE_MATCH")
38+
)
39+
}
40+
41+
Expr getCheckedExpr() { result = checkSignatures.getArgument(0) }
42+
}
43+
44+
predicate signatureChecked(Expr safe) {
45+
exists(CheckSignaturesGuard g, SsaVariable v |
46+
v.getAUse() = g.getCheckedExpr() and
47+
safe = v.getAUse() and
48+
g.controls(safe.getBasicBlock(), g.(EqualityTest).polarity())
49+
)
50+
}
51+
52+
module InsecureLoadingConfig implements DataFlow::ConfigSig {
53+
predicate isSource(DataFlow::Node src) {
54+
exists(Method m | m = src.asExpr().(MethodCall).getMethod() |
55+
m.getDeclaringType().getASourceSupertype*() instanceof TypeContext and
56+
m.hasName("createPackageContext") and
57+
not signatureChecked(src.asExpr().(MethodCall).getArgument(0))
58+
)
59+
}
60+
61+
predicate isSink(DataFlow::Node sink) {
62+
exists(MethodCall ma |
63+
ma.getMethod().hasQualifiedName("java.lang", "ClassLoader", "loadClass")
64+
|
65+
sink.asExpr() = ma.getQualifier()
66+
)
67+
}
68+
69+
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
70+
exists(MethodCall ma, Method m |
71+
ma.getMethod() = m and
72+
m.getDeclaringType().getASourceSupertype*() instanceof TypeContext and
73+
m.hasName("getClassLoader")
74+
|
75+
node1.asExpr() = ma.getQualifier() and
76+
node2.asExpr() = ma
77+
)
78+
}
79+
}
80+
81+
module InsecureLoadFlow = TaintTracking::Global<InsecureLoadingConfig>;
82+
83+
import InsecureLoadFlow::PathGraph
84+
85+
from InsecureLoadFlow::PathNode source, InsecureLoadFlow::PathNode sink
86+
where InsecureLoadFlow::flowPath(source, sink)
87+
select sink.getNode(), source, sink, "Class loaded from a $@ without signature check",
88+
source.getNode(), "third party library"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package poc.sample.classloader;
2+
3+
import android.app.Application;
4+
import android.content.pm.PackageInfo;
5+
import android.content.Context;
6+
import android.util.Log;
7+
8+
public class BadClassLoader extends Application {
9+
@Override
10+
public void onCreate() {
11+
super.onCreate();
12+
for (PackageInfo p : getPackageManager().getInstalledPackages(0)) {
13+
try {
14+
if (p.packageName.startsWith("some.package.")) {
15+
Context appContext = createPackageContext(p.packageName,
16+
CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);
17+
ClassLoader classLoader = appContext.getClassLoader();
18+
Object result = classLoader.loadClass("some.package.SomeClass")
19+
.getMethod("someMethod")
20+
.invoke(null);
21+
}
22+
} catch (Exception e) {
23+
Log.e("Class loading failed", e.toString());
24+
}
25+
}
26+
}
27+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package poc.sample.classloader;
2+
3+
import android.app.Application;
4+
import android.content.pm.PackageInfo;
5+
import android.content.Context;
6+
import android.content.pm.PackageManager;
7+
import android.util.Log;
8+
9+
public class GoodClassLoader extends Application {
10+
@Override
11+
public void onCreate() {
12+
super.onCreate();
13+
PackageManager pm = getPackageManager();
14+
for (PackageInfo p : pm.getInstalledPackages(0)) {
15+
try {
16+
if (p.packageName.startsWith("some.package.") &&
17+
(pm.checkSignatures(p.packageName, getApplicationContext().getPackageName()) == PackageManager.SIGNATURE_MATCH)
18+
) {
19+
Context appContext = createPackageContext(p.packageName,
20+
CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);
21+
ClassLoader classLoader = appContext.getClassLoader();
22+
Object result = classLoader.loadClass("some.package.SomeClass")
23+
.getMethod("someMethod")
24+
.invoke(null);
25+
}
26+
} catch (Exception e) {
27+
Log.e("Class loading failed", e.toString());
28+
}
29+
}
30+
}
31+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
edges
2+
| BadClassLoader.java:15:42:16:75 | createPackageContext(...) : Context | BadClassLoader.java:17:47:17:56 | appContext : Context |
3+
| BadClassLoader.java:17:47:17:56 | appContext : Context | BadClassLoader.java:17:47:17:73 | getClassLoader(...) : ClassLoader |
4+
| BadClassLoader.java:17:47:17:73 | getClassLoader(...) : ClassLoader | BadClassLoader.java:18:37:18:47 | classLoader |
5+
nodes
6+
| BadClassLoader.java:15:42:16:75 | createPackageContext(...) : Context | semmle.label | createPackageContext(...) : Context |
7+
| BadClassLoader.java:17:47:17:56 | appContext : Context | semmle.label | appContext : Context |
8+
| BadClassLoader.java:17:47:17:73 | getClassLoader(...) : ClassLoader | semmle.label | getClassLoader(...) : ClassLoader |
9+
| BadClassLoader.java:18:37:18:47 | classLoader | semmle.label | classLoader |
10+
subpaths
11+
#select
12+
| BadClassLoader.java:18:37:18:47 | classLoader | BadClassLoader.java:15:42:16:75 | createPackageContext(...) : Context | BadClassLoader.java:18:37:18:47 | classLoader | Class loaded from a $@ without signature check | BadClassLoader.java:15:42:16:75 | createPackageContext(...) | third party library |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-470/LoadClassNoSignatureCheck.ql
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8/
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.3.8/:${testdir}/../../../../stubs/google-android-9.0.0

0 commit comments

Comments
 (0)