WebView安全:JavaScript接口漏洞、远程代码执行、文件访问风险、安全配置最佳实践
WebView 是 Android 开发中绕不开的一个组件。说白了,它就是在原生应用里嵌入一个浏览器内核。很多混合开发、H5 页面展示、甚至一些 SDK 的登录页,都离不开它。
但我要说句实话——WebView 也是 Android 安全漏洞的重灾区。我这些年做安全审计,十次里有七八次都能在 WebView 这块找到问题。轻则信息泄露,重则直接被远程代码执行,手机被人拿走控制权。
这一章,我们就来把 WebView 的四个核心安全问题掰开揉碎讲清楚。
1. JavaScript 接口漏洞:addJavascriptInterface 的坑
先看一个最常见的场景。你需要在 H5 页面里调用原生的能力,比如读取本地文件、调用摄像头。Android 提供了 addJavascriptInterface 方法,可以把一个 Java 对象暴露给 JavaScript。
代码写起来很简单:
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsBridge(), "bridge");
然后 H5 里就能直接调:
window.bridge.readFile("/data/data/com.example/db");
问题出在哪?任何加载到该 WebView 的页面都能调用这个接口。如果页面被 XSS 攻击,或者用户访问了恶意网站,攻击者就能通过这个接口执行任意 Java 方法。
我记得有一次做项目复盘,发现某个金融类 App 的 WebView 加载了用户输入的 URL,还暴露了文件读写接口。攻击者只需要诱导用户点击一个链接,就能把手机里的通讯录、短信全部上传到服务器。嗯,这个漏洞当时被评了高危。
安全做法
- 最低 API 版本必须 ≥ 17,并在接口方法上添加
@JavascriptInterface注解。这样只有被注解的方法才会暴露给 JS。 - 接口方法要做严格的参数校验。比如路径不能包含 ".." 跳转,协议必须是白名单内的。
- 不要暴露敏感能力。像文件读写、数据库操作、网络请求这些,能不放就不放。
public class JsBridge {
@JavascriptInterface
public String getSafeData(String key) {
// 只允许读取白名单内的 key
if (!whitelist.contains(key)) return null;
return dataStore.get(key);
}
}
2. 远程代码执行:不止是 addJavascriptInterface
很多人以为远程代码执行只跟 addJavascriptInterface 有关。其实不然。
WebView 的远程代码执行还有两条路径:
- Chrome 内核漏洞:WebView 底层用的是 Chromium,如果系统 WebView 版本过旧,可能存在 V8 引擎的漏洞。攻击者通过精心构造的 JS 代码,可以直接在 App 进程内执行任意代码。
- setWebViewClient 的 shouldOverrideUrlLoading:如果回调里直接执行了
loadUrl(),且 URL 来自外部输入,攻击者可以用javascript:协议注入代码。
我个人的习惯是:所有来自网络的 URL,在传给 WebView 之前,必须经过协议和域名的双重校验。javascript: 协议直接拦截,file: 协议也要谨慎处理。
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 禁止 javascript: 协议
if (url.startsWith("javascript:")) {
return true;
}
// 只允许 https 和 白名单域名
if (!url.startsWith("https://") || !isWhitelistedDomain(url)) {
return true;
}
view.loadUrl(url);
return true;
}
});
3. 文件访问风险:file:// 协议的双刃剑
WebView 默认允许通过 file:// 协议访问本地文件。这意味着,如果页面里有一段 JS 代码:
var xhr = new XMLHttpRequest();
xhr.open("GET", "file:///data/data/com.example/shared_prefs/prefs.xml", false);
xhr.send();
console.log(xhr.responseText);
就能直接读到其他 App 的私有数据。当然,Android 有沙箱机制,但同一个 App 内的文件是可以被读到的。
我曾经遇到过一个案例:某个 App 把用户的登录 token 存在了 SharedPreferences 里,同时 WebView 开启了文件访问。攻击者只需要在 H5 页面里嵌入一段 Ajax 请求,就能把 token 偷走。
避坑指南:如果 WebView 不需要加载本地文件,直接关掉文件访问权限。
webView.getSettings().setAllowFileAccess(false);
webView.getSettings().setAllowFileAccessFromFileURLs(false);
webView.getSettings().setAllowUniversalAccessFromFileURLs(false);
这三个开关,我建议全部设为 false。除非你非常清楚自己在做什么。
4. 安全配置最佳实践:一个 Checklist
说了这么多风险,我们来总结一套安全配置。每次创建 WebView 时,按这个清单走一遍,基本能挡住 90% 的攻击。
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| setJavaScriptEnabled | 按需开启 | 不需要 JS 时务必关闭 |
| setAllowFileAccess | false | 禁止 file:// 协议 |
| setAllowFileAccessFromFileURLs | false | 禁止 file 域访问其他文件 |
| setAllowUniversalAccessFromFileURLs | false | 禁止 file 域跨域访问 |
| setMixedContentMode | NEVER_ALLOW | 禁止 HTTPS 页面加载 HTTP 资源 |
| setWebContentsDebuggingEnabled | false | 发布包必须关闭调试 |
| addJavascriptInterface | 谨慎使用 | 必须加 @JavascriptInterface 注解 |
另外,WebView 的 User-Agent 不要暴露太多信息。默认的 UA 里包含系统版本、设备型号,这些信息可能被用来做指纹追踪。我一般会重写 UA,只保留必要的标识。
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "MyApp/1.0");
webView.loadUrl(url, headers);
知识体系总览
下面这张图,把 WebView 安全的四个维度串起来了。你可以把它当作一个检查清单,每次接入 WebView 时对照着看一遍。
最后说一句,WebView 安全没有银弹。你配置得再严,如果加载的页面本身有 XSS 漏洞,攻击者还是能绕过一些限制。所以,前后端一起做安全,才是正解。
公众号:蓝海资料掘金营,微信deep3321