Skip to content

Commit bd0c577

Browse files
luchua-bcsmowton
authored andcommitted
Unsafe resource loading in Android webview
1 parent b05cc2e commit bd0c577

File tree

21 files changed

+9850
-0
lines changed

21 files changed

+9850
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
public class UnsafeAndroidAccess extends Activity {
2+
public void onCreate(Bundle savedInstanceState) {
3+
super.onCreate(savedInstanceState);
4+
setContentView(R.layout.webview);
5+
6+
// BAD: Have both JavaScript and universal resource access enabled in webview while
7+
// taking remote user inputs
8+
{
9+
WebView wv = (WebView) findViewById(R.id.my_webview);
10+
WebSettings webSettings = wv.getSettings();
11+
12+
webSettings.setJavaScriptEnabled(true);
13+
webSettings.setAllowUniversalAccessFromFileURLs(true);
14+
15+
wv.setWebViewClient(new WebViewClient() {
16+
@Override
17+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
18+
view.loadUrl(url);
19+
return true;
20+
}
21+
});
22+
23+
String thisUrl = getIntent().getExtras().getString("url"); // dangerous remote input
24+
//String thisUrl = getIntent().getStringExtra("url"); //An alternative way to load dangerous remote input
25+
wv.loadUrl(thisUrl);
26+
}
27+
28+
// GOOD: Have JavaScript and universal resource access disabled while taking
29+
// remote user inputs
30+
{
31+
WebView wv = (WebView) findViewById(-1);
32+
WebSettings webSettings = wv.getSettings();
33+
34+
webSettings.setJavaScriptEnabled(false);
35+
36+
wv.setWebViewClient(new WebViewClient() {
37+
@Override
38+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
39+
view.loadUrl(url);
40+
return true;
41+
}
42+
});
43+
44+
String thisUrl = getIntent().getExtras().getString("url"); // remote input
45+
wv.loadUrl(thisUrl);
46+
}
47+
48+
// GOOD: Have JavaScript enabled in webview but remote user input is not allowed
49+
{
50+
WebView wv = (WebView) findViewById(-1);
51+
WebSettings webSettings = wv.getSettings();
52+
53+
webSettings.setJavaScriptEnabled(true);
54+
55+
wv.setWebViewClient(new WebViewClient() {
56+
@Override
57+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
58+
view.loadUrl(url);
59+
return true;
60+
}
61+
});
62+
63+
wv.loadUrl("https://www.mycorp.com");
64+
}
65+
}
66+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>Android WebViews that allow loading urls controlled by external inputs and whose JavaScript interface is enabled are potentially vulnerable to cross-site scripting and sensitive resource disclosure attacks.</p>
6+
<p>WebSettings of WebViews with setAllowFileAccessFromFileURLs or setAllowUniversalAccessFromFileURLs enabled must not load any untrusted web content.</p>
7+
<p>Enabling this setting allows malicious scripts loaded in a file:// context to launch cross-site scripting attacks, either accessing arbitrary local files including WebView cookies, session tokens, private app data or even credentials used on arbitrary web sites.</p>
8+
<p>This query detects the following two scenarios:</p>
9+
<ol>
10+
<li>Vulnerability introduced by WebViews with JavaScript enabled and remote inputs allowed.</li>
11+
<li>High precision vulnerability when allowing universal resource access is also enabled. The setting was just deprecated in API level 30 (Android 11) thus most devices are still affected given that Android phones don't get timely version updates like iPhones.</li>
12+
</ol>
13+
</overview>
14+
15+
<recommendation>
16+
<p>Only allow trusted web contents to be displayed in WebViews when JavaScript is enabled. And disallow universal resource access in WebSetting to reduce the attack surface .</p>
17+
</recommendation>
18+
19+
<example>
20+
<p>The following example shows both 'BAD' and 'GOOD' configurations. In the 'BAD' configuration, setting is enabled and JavaScript is enabled while urls are loaded from externally controlled inputs. In the 'GOOD' configuration, JavaScript is disabled or only trusted web contents are allowed to be loaded.</p>
21+
<sample src="UnsafeAndroidAccess.java" />
22+
</example>
23+
24+
<references>
25+
<li>
26+
<a href="https://cwe.mitre.org/data/definitions/749.html">CWE-749</a>
27+
<a href="https://support.google.com/faqs/answer/7668153?hl=en">Fixing a File-based XSS Vulnerability</a>
28+
<a href="https://github.com/OWASP/owasp-mstg/blob/master/Document/0x05h-Testing-Platform-Interaction.md">OWASP - Testing WebView Protocol Handlers (MSTG-PLATFORM-5 and MSTG-PLATFORM-6)</a>
29+
</li>
30+
</references>
31+
</qhelp>
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* @name Unsafe resource loading in Android webview
3+
* @description JavaScript rendered inside WebViews can access any protected application file and web resource from any origin
4+
* @kind path-problem
5+
* @tags security
6+
* external/cwe/cwe-749
7+
* external/cwe/cwe-079
8+
*/
9+
10+
import java
11+
import semmle.code.java.frameworks.android.Intent
12+
import semmle.code.java.frameworks.android.WebView
13+
import semmle.code.java.dataflow.FlowSources
14+
15+
/**
16+
* Allow universal access methods in the WebSettings class
17+
*/
18+
class AllowUniversalAccessMethod extends Method {
19+
AllowUniversalAccessMethod() {
20+
this.getDeclaringType() instanceof TypeWebSettings and
21+
(
22+
this.hasName("setAllowUniversalAccessFromFileURLs") or
23+
this.hasName("setAllowFileAccessFromFileURLs")
24+
)
25+
}
26+
}
27+
28+
/**
29+
* `setJavaScriptEnabled` method for the webview
30+
*/
31+
class AllowJavaScriptMethod extends Method {
32+
AllowJavaScriptMethod() {
33+
this.getDeclaringType() instanceof TypeWebSettings and
34+
this.hasName("setJavaScriptEnabled")
35+
}
36+
}
37+
38+
/**
39+
* Check whether JavaScript is enabled in the webview with universal resource access
40+
*/
41+
predicate isJSEnabled(MethodAccess ma) {
42+
exists(VarAccess va, MethodAccess jsa |
43+
ma.getQualifier() = va and
44+
jsa.getQualifier() = va.getVariable().getAnAccess() and
45+
jsa.getMethod() instanceof AllowJavaScriptMethod and
46+
jsa.getArgument(0).(BooleanLiteral).getBooleanValue() = true
47+
)
48+
}
49+
50+
/**
51+
* Load URL method call on the `android.webkit.WebView` object
52+
*/
53+
class LoadResourceMethodAccess extends MethodAccess {
54+
LoadResourceMethodAccess() {
55+
this.getMethod().getDeclaringType() instanceof TypeWebView and
56+
(
57+
this.getMethod().hasName("loadUrl") or
58+
this.getMethod().hasName("postUrl")
59+
)
60+
}
61+
}
62+
63+
/**
64+
* Method access to external inputs of `android.content.Intent` object
65+
*/
66+
class IntentGetExtraMethodAccess extends MethodAccess {
67+
IntentGetExtraMethodAccess() {
68+
this.getMethod().getName().regexpMatch("get\\w+Extra") and
69+
this.getMethod().getDeclaringType() instanceof TypeIntent
70+
or
71+
this.getMethod().getName().regexpMatch("get\\w+") and
72+
this.getQualifier().(MethodAccess).getMethod().hasName("getExtras") and
73+
this.getQualifier().(MethodAccess).getMethod().getDeclaringType() instanceof TypeIntent
74+
}
75+
}
76+
77+
/**
78+
* Source of loading urls
79+
*/
80+
class UntrustedResourceSource extends RemoteFlowSource {
81+
UntrustedResourceSource() {
82+
exists(MethodAccess ma |
83+
ma instanceof IntentGetExtraMethodAccess and
84+
this.asExpr().(VarAccess).getVariable().getAnAssignedValue() = ma
85+
)
86+
}
87+
88+
override string getSourceType() {
89+
result = "user input vulnerable to XSS and sensitive resource disclosure attacks" and
90+
exists(MethodAccess ma |
91+
ma.getMethod() instanceof AllowUniversalAccessMethod and //High precision match of unsafe resource loading
92+
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
93+
)
94+
or
95+
result = "user input potentially vulnerable to XSS and sensitive resource disclosure attacks" and
96+
not exists(MethodAccess ma |
97+
ma.getMethod() instanceof AllowUniversalAccessMethod and //High precision match of unsafe resource loading
98+
ma.getArgument(0).(BooleanLiteral).getBooleanValue() = true
99+
)
100+
}
101+
}
102+
103+
/**
104+
* load externally controlled data from loadUntrustedResource
105+
*/
106+
predicate loadUntrustedResource(MethodAccess ma, Expr sink) {
107+
ma instanceof LoadResourceMethodAccess and
108+
sink = ma.getArgument(0)
109+
}
110+
111+
/**
112+
* Sink of loading urls
113+
*/
114+
class UntrustedResourceSink extends DataFlow::ExprNode {
115+
UntrustedResourceSink() { loadUntrustedResource(_, this.getExpr()) }
116+
117+
MethodAccess getMethodAccess() { loadUntrustedResource(result, this.getExpr()) }
118+
}
119+
120+
class LoadUntrustedResourceConfiguration extends TaintTracking::Configuration {
121+
LoadUntrustedResourceConfiguration() { this = "LoadUntrustedResourceConfiguration" }
122+
123+
override predicate isSource(DataFlow::Node source) { source instanceof UntrustedResourceSource }
124+
125+
override predicate isSink(DataFlow::Node sink) { sink instanceof UntrustedResourceSink }
126+
}
127+
128+
from DataFlow::PathNode source, DataFlow::PathNode sink, LoadUntrustedResourceConfiguration conf
129+
where
130+
exists(VarAccess webviewVa, MethodAccess getSettingsMa, MethodAccess ma |
131+
conf.hasFlowPath(source, sink) and
132+
sink.getNode().(UntrustedResourceSink).getMethodAccess().getQualifier() = webviewVa and
133+
webviewVa.getVariable().getAnAccess() = getSettingsMa.getQualifier() and
134+
ma.getQualifier().(VarAccess).getVariable().getAnAssignedValue() = getSettingsMa and
135+
isJSEnabled(ma)
136+
)
137+
select sink.getNode().(UntrustedResourceSink).getMethodAccess(), source, sink,
138+
"Unsafe resource loading in Android webview due to $@.", source.getNode(),
139+
source.getNode().(UntrustedResourceSource).getSourceType()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
| UnsafeAndroidAccess.java:29:3:29:21 | loadUrl(...) | UnsafeAndroidAccess.java:29:14:29:20 | thisUrl | UnsafeAndroidAccess.java:29:14:29:20 | thisUrl | Unsafe resource loading in Android webview due to $@. | UnsafeAndroidAccess.java:29:14:29:20 | thisUrl | user input vulnerable to XSS and sensitive resource disclosure attacks |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import android.app.Activity;
2+
3+
import android.os.Bundle;
4+
5+
import android.webkit.WebSettings;
6+
import android.webkit.WebView;
7+
import android.webkit.WebViewClient;
8+
9+
public class UnsafeAndroidAccess extends Activity {
10+
public void onCreate(Bundle savedInstanceState) {
11+
super.onCreate(savedInstanceState);
12+
setContentView(-1);
13+
14+
WebView wv = (WebView) findViewById(-1);
15+
WebSettings webSettings = wv.getSettings();
16+
17+
webSettings.setJavaScriptEnabled(true);
18+
webSettings.setAllowFileAccessFromFileURLs(true);
19+
20+
wv.setWebViewClient(new WebViewClient() {
21+
@Override
22+
public boolean shouldOverrideUrlLoading(WebView view, String url) {
23+
view.loadUrl(url);
24+
return true;
25+
}
26+
});
27+
28+
String thisUrl = getIntent().getExtras().getString("url");
29+
wv.loadUrl(thisUrl);
30+
}
31+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-749/UnsafeAndroidAccess.ql
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0

0 commit comments

Comments
 (0)