Skip to content

Commit 29e87b3

Browse files
authored
Merge pull request github#6975 from atorralba/atorralba/android-intent-uri-permission-manipulation
Java: CWE-266 - Query to detect Intent URI Permission Manipulation in Android applications
2 parents b230681 + 3957ebe commit 29e87b3

15 files changed

+437
-1
lines changed

java/ql/lib/semmle/code/java/frameworks/android/Android.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ class AndroidActivity extends ExportableAndroidComponent {
6767
AndroidActivity() { getASuperTypeStar(this).hasQualifiedName("android.app", "Activity") }
6868
}
6969

70+
/** The method `setResult` of the class `android.app.Activity`. */
71+
class ActivitySetResultMethod extends Method {
72+
ActivitySetResultMethod() {
73+
this.getDeclaringType().hasQualifiedName("android.app", "Activity") and
74+
this.hasName("setResult")
75+
}
76+
}
77+
7078
/** An Android service. */
7179
class AndroidService extends ExportableAndroidComponent {
7280
AndroidService() { getASuperTypeStar(this).hasQualifiedName("android.app", "Service") }

java/ql/lib/semmle/code/java/frameworks/android/Intent.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,31 @@ predicate allowIntentExtrasImplicitRead(DataFlow::Node node, DataFlow::Content c
148148
)
149149
}
150150

151+
/**
152+
* The fields to grant URI permissions of the class `android.content.Intent`:
153+
*
154+
* - `Intent.FLAG_GRANT_READ_URI_PERMISSION`
155+
* - `Intent.FLAG_GRANT_WRITE_URI_PERMISSION`
156+
* - `Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION`
157+
* - `Intent.FLAG_GRANT_PREFIX_URI_PERMISSION`
158+
*/
159+
class GrantUriPermissionFlag extends Field {
160+
GrantUriPermissionFlag() {
161+
this.getDeclaringType() instanceof TypeIntent and
162+
this.getName().matches("FLAG_GRANT_%_URI_PERMISSION")
163+
}
164+
}
165+
166+
/** The field `Intent.FLAG_GRANT_READ_URI_PERMISSION`. */
167+
class GrantReadUriPermissionFlag extends GrantUriPermissionFlag {
168+
GrantReadUriPermissionFlag() { this.hasName("FLAG_GRANT_READ_URI_PERMISSION") }
169+
}
170+
171+
/** The field `Intent.FLAG_GRANT_WRITE_URI_PERMISSION`. */
172+
class GrantWriteUriPermissionFlag extends GrantUriPermissionFlag {
173+
GrantWriteUriPermissionFlag() { this.hasName("FLAG_GRANT_WRITE_URI_PERMISSION") }
174+
}
175+
151176
private class IntentBundleFlowSteps extends SummaryModelCsv {
152177
override predicate row(string row) {
153178
row =
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Provides classes and predicates to reason about Intent URI permission manipulation
3+
* vulnerabilities on Android.
4+
*/
5+
6+
import java
7+
private import semmle.code.java.controlflow.Guards
8+
private import semmle.code.java.dataflow.DataFlow
9+
private import semmle.code.java.dataflow.TaintTracking
10+
private import semmle.code.java.frameworks.android.Android
11+
private import semmle.code.java.frameworks.android.Intent
12+
13+
/**
14+
* A sink for Intent URI permission manipulation vulnerabilities in Android,
15+
* that is, method calls that return an Intent as the result of an Activity.
16+
*/
17+
abstract class IntentUriPermissionManipulationSink extends DataFlow::Node { }
18+
19+
/**
20+
* A sanitizer that makes sure that an Intent is safe to be returned to another Activity.
21+
*
22+
* Usually, this is done by setting the Intent's data URI and/or its flags to safe values.
23+
*/
24+
abstract class IntentUriPermissionManipulationSanitizer extends DataFlow::Node { }
25+
26+
/**
27+
* A guard that makes sure that an Intent is safe to be returned to another Activity.
28+
*
29+
* Usually, this is done by checking that the Intent's data URI and/or its flags contain
30+
* expected values.
31+
*/
32+
abstract class IntentUriPermissionManipulationGuard extends DataFlow::BarrierGuard { }
33+
34+
/**
35+
* An additional taint step for flows related to Intent URI permission manipulation
36+
* vulnerabilities.
37+
*/
38+
class IntentUriPermissionManipulationAdditionalTaintStep extends Unit {
39+
/**
40+
* Holds if the step from `node1` to `node2` should be considered a taint
41+
* step for flows related to Intent URI permission manipulation vulnerabilities.
42+
*/
43+
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
44+
}
45+
46+
private class DefaultIntentUriPermissionManipulationSink extends IntentUriPermissionManipulationSink {
47+
DefaultIntentUriPermissionManipulationSink() {
48+
exists(MethodAccess ma | ma.getMethod() instanceof ActivitySetResultMethod |
49+
ma.getArgument(1) = this.asExpr()
50+
)
51+
}
52+
}
53+
54+
/**
55+
* Sanitizer that prevents access to arbitrary content providers by modifying the Intent in one of
56+
* the following ways:
57+
* * Removing the flags `FLAG_GRANT_READ_URI_PERMISSION` and `FLAG_GRANT_WRITE_URI_PERMISSION`.
58+
* * Setting the flags to a combination that doesn't include `FLAG_GRANT_READ_URI_PERMISSION` or
59+
* `FLAG_GRANT_WRITE_URI_PERMISSION`.
60+
* * Replacing the data URI.
61+
*/
62+
private class IntentFlagsOrDataChangedSanitizer extends IntentUriPermissionManipulationSanitizer {
63+
IntentFlagsOrDataChangedSanitizer() {
64+
exists(MethodAccess ma, Method m |
65+
ma.getMethod() = m and
66+
m.getDeclaringType() instanceof TypeIntent and
67+
this.asExpr() = ma.getQualifier()
68+
|
69+
m.hasName("removeFlags") and
70+
bitwiseLocalTaintStep*(DataFlow::exprNode(any(GrantReadUriPermissionFlag f).getAnAccess()),
71+
DataFlow::exprNode(ma.getArgument(0))) and
72+
bitwiseLocalTaintStep*(DataFlow::exprNode(any(GrantWriteUriPermissionFlag f).getAnAccess()),
73+
DataFlow::exprNode(ma.getArgument(0)))
74+
or
75+
m.hasName("setFlags") and
76+
not bitwiseLocalTaintStep*(DataFlow::exprNode(any(GrantUriPermissionFlag f).getAnAccess()),
77+
DataFlow::exprNode(ma.getArgument(0)))
78+
or
79+
m.hasName("setData")
80+
)
81+
}
82+
}
83+
84+
/**
85+
* A guard that checks an Intent's flags or data URI to make sure they are trusted.
86+
* It matches the following patterns:
87+
*
88+
* ```java
89+
* if (intent.getData().equals("trustedValue")) {}
90+
*
91+
* if (intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION == 0 &&
92+
* intent.getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION == 0) {}
93+
*
94+
* if (intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION != 0 ||
95+
* intent.getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION != 0) {}
96+
* ```
97+
*/
98+
private class IntentFlagsOrDataCheckedGuard extends IntentUriPermissionManipulationGuard {
99+
Expr condition;
100+
101+
IntentFlagsOrDataCheckedGuard() { intentFlagsOrDataChecked(this, _, _) }
102+
103+
override predicate checks(Expr e, boolean branch) { intentFlagsOrDataChecked(this, e, branch) }
104+
}
105+
106+
/**
107+
* Holds if `g` checks `intent` when the result of an `intent.getFlags` or `intent.getData` call
108+
* is equality-tested.
109+
*/
110+
private predicate intentFlagsOrDataChecked(Guard g, Expr intent, boolean branch) {
111+
exists(MethodAccess ma, Method m, Expr checkedValue |
112+
ma.getQualifier() = intent and
113+
ma.getMethod() = m and
114+
m.getDeclaringType() instanceof TypeIntent and
115+
m.hasName(["getFlags", "getData"]) and
116+
bitwiseLocalTaintStep*(DataFlow::exprNode(ma), DataFlow::exprNode(checkedValue))
117+
|
118+
bitwiseCheck(g, branch) and
119+
checkedValue = g.(EqualityTest).getAnOperand().(AndBitwiseExpr)
120+
or
121+
g.(MethodAccess).getMethod() instanceof EqualsMethod and
122+
branch = true and
123+
checkedValue = [g.(MethodAccess).getArgument(0), g.(MethodAccess).getQualifier()]
124+
)
125+
}
126+
127+
/**
128+
* Holds if `g` is a bitwise check. `branch` is `true` when the expected value is `0`
129+
* and `false` otherwise.
130+
*/
131+
private predicate bitwiseCheck(Guard g, boolean branch) {
132+
exists(CompileTimeConstantExpr operand | operand = g.(EqualityTest).getAnOperand() |
133+
if operand.getIntValue() = 0
134+
then g.(EqualityTest).polarity() = branch
135+
else g.(EqualityTest).polarity().booleanNot() = branch
136+
)
137+
}
138+
139+
/**
140+
* Holds if taint can flow from `source` to `sink` in one local step,
141+
* including bitwise operations.
142+
*/
143+
private predicate bitwiseLocalTaintStep(DataFlow::Node source, DataFlow::Node sink) {
144+
TaintTracking::localTaintStep(source, sink) or
145+
source.asExpr() = sink.asExpr().(BitwiseExpr).(BinaryExpr).getAnOperand()
146+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Provides taint tracking configurations to be used in queries related to Intent URI permission
3+
* manipulation in Android.
4+
*/
5+
6+
import java
7+
private import semmle.code.java.dataflow.FlowSources
8+
private import semmle.code.java.dataflow.DataFlow
9+
private import IntentUriPermissionManipulation
10+
11+
/**
12+
* A taint tracking configuration for user-provided Intents being returned to third party apps.
13+
*/
14+
class IntentUriPermissionManipulationConf extends TaintTracking::Configuration {
15+
IntentUriPermissionManipulationConf() { this = "UriPermissionManipulationConf" }
16+
17+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
18+
19+
override predicate isSink(DataFlow::Node sink) {
20+
sink instanceof IntentUriPermissionManipulationSink
21+
}
22+
23+
override predicate isSanitizer(DataFlow::Node barrier) {
24+
barrier instanceof IntentUriPermissionManipulationSanitizer
25+
}
26+
27+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
28+
guard instanceof IntentUriPermissionManipulationGuard
29+
}
30+
31+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
32+
any(IntentUriPermissionManipulationAdditionalTaintStep c).step(node1, node2)
33+
}
34+
}

java/ql/lib/semmle/code/xml/AndroidManifest.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ class AndroidReceiverXmlElement extends AndroidComponentXmlElement {
7474
AndroidReceiverXmlElement() { this.getName() = "receiver" }
7575
}
7676

77+
/**
78+
* An XML attribute with the `android:` prefix.
79+
*/
80+
class AndroidXmlAttribute extends XMLAttribute {
81+
AndroidXmlAttribute() { this.getNamespace().getPrefix() = "android" }
82+
}
83+
7784
/**
7885
* A `<provider>` element in an Android manifest file.
7986
*/
@@ -91,6 +98,14 @@ class AndroidProviderXmlElement extends AndroidComponentXmlElement {
9198
this.getAnAttribute().(AndroidPermissionXmlAttribute).isWrite() and
9299
this.getAnAttribute().(AndroidPermissionXmlAttribute).isRead()
93100
}
101+
102+
predicate grantsUriPermissions() {
103+
exists(AndroidXmlAttribute attr |
104+
this.getAnAttribute() = attr and
105+
attr.getName() = "grantUriPermissions" and
106+
attr.getValue() = "true"
107+
)
108+
}
94109
}
95110

96111
/**
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
public class IntentUriPermissionManipulation extends Activity {
2+
3+
// BAD: the user-provided Intent is returned as-is
4+
public void dangerous() {
5+
Intent intent = getIntent();
6+
intent.putExtra("result", "resultData");
7+
setResult(intent);
8+
}
9+
10+
// GOOD: a new Intent is created and returned
11+
public void safe() {
12+
Intent intent = new Intent();
13+
intent.putExtra("result", "resultData");
14+
setResult(intent);
15+
}
16+
17+
// GOOD: the user-provided Intent is sanitized before being returned
18+
public void sanitized() {
19+
Intent intent = getIntent();
20+
intent.putExtra("result", "resultData");
21+
intent.removeFlags(
22+
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
23+
setResult(intent);
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>When an Android component expects a result from an Activity, <code>startActivityForResult</code> can be used.
7+
The started Activity can then use <code>setResult</code> to return the appropriate data to the calling component.</p>
8+
<p>If an Activity obtains the incoming, user-provided Intent and directly returns it via <code>setResult</code>
9+
without any checks, the application may be unintentionally giving arbitrary access to its content providers, even
10+
if they are not exported, as long as they are configured with the attribute <code>android:grantUriPermissions="true"</code>.
11+
This happens because the attacker adds the appropriate URI permission flags to the provided Intent, which take effect
12+
once the Intent is reflected back.</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>Avoid returning user-provided or untrusted Intents via <code>setResult</code>. Use a new Intent instead.</p>
17+
<p>If it is required to use the received Intent, make sure that it does not contain URI permission flags, either
18+
by checking them with <code>Intent.getFlags</code> or removing them with <code>Intent.removeFlags</code>.</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>The following sample contains three examples. In the first example, a user-provided Intent is obtained and
23+
directly returned back with <code>setResult</code>, which is dangerous. In the second example, a new Intent
24+
is created to safely return the desired data. The third example shows how the obtained Intent can be sanitized
25+
by removing dangerous flags before using it to return data to the calling component.
26+
</p>
27+
28+
<sample src="IntentUriPermissionManipulation.java" />
29+
</example>
30+
31+
<references>
32+
<li>Google Help: <a href="https://support.google.com/faqs/answer/9267555?hl=en">Remediation for Intent Redirection Vulnerability</a>.</li>
33+
</references>
34+
</qhelp>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Intent URI permission manipulation
3+
* @description Returning an externally provided Intent via 'setResult' may allow a malicious
4+
* application to access arbitrary content providers of the vulnerable application.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 7.8
8+
* @precision high
9+
* @id java/android/intent-uri-permission-manipulation
10+
* @tags security
11+
* external/cwe/cwe-266
12+
* external/cwe/cwe-926
13+
*/
14+
15+
import java
16+
import semmle.code.java.security.IntentUriPermissionManipulationQuery
17+
import semmle.code.java.dataflow.DataFlow
18+
import DataFlow::PathGraph
19+
20+
from DataFlow::PathNode source, DataFlow::PathNode sink
21+
where any(IntentUriPermissionManipulationConf c).hasFlowPath(source, sink)
22+
select sink.getNode(), source, sink,
23+
"This Intent can be set with arbitrary flags from $@, " +
24+
"and used to give access to internal content providers.", source.getNode(), "this user input"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
category: newQuery
3+
---
4+
* A new query "Intent URI permission manipulation" (`java/android/intent-uri-permission-manipulation`) has been added.
5+
This query finds Android components that return unmodified, received Intents to the calling applications, which
6+
can provide unintended access to internal content providers of the victim application.

java/ql/test/library-tests/frameworks/android/content-provider/Safe.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import android.os.ParcelFileDescriptor;
1212
import android.os.RemoteException;
1313

14-
// This Content Provider isn't exported, so there shouldn't be any flow
14+
// This content provider isn't exported, so there shouldn't be any flow
1515
public class Safe extends ContentProvider {
1616

1717
void sink(Object o) {}

0 commit comments

Comments
 (0)