WebView安全:JavaScript接口漏洞、远程代码执行、文件访问风险、安全配置最佳实践
WebView,说白了就是App里内嵌的一个浏览器。它让原生应用能加载网页,实现Hybrid开发。但便利背后,藏着不少坑。我这些年做安全审计,十有八九的漏洞都跟WebView配置不当有关。今天咱们就把它掰开揉碎了讲清楚。
核心观点:WebView的安全问题,本质上是「信任边界」的模糊。你让一个不受控的网页,拥有了原生代码的权限——不出事才怪。
1. JavaScript接口漏洞:addJavascriptInterface的噩梦
Android提供了addJavascriptInterface方法,让网页里的JS能直接调用Java对象的方法。听起来很酷,对吧?但这就是个潘多拉魔盒。
我记得2012年那会儿,这个漏洞几乎让所有Android手机沦陷。攻击者只要诱导用户打开一个恶意网页,就能通过JS接口执行任意Java代码。说白了,你的手机就是人家的了。
漏洞原理
当你在WebView中注册了一个JS接口:
webView.addJavascriptInterface(new JsBridge(), "bridge");
网页里的JS就可以这样调用:
window.bridge.executeCommand("rm -rf /");
嗯,你没看错。如果你的JsBridge类里有个executeCommand方法,并且它执行了shell命令——那后果不堪设想。
警告:在Android 4.2(API 17)以下,所有通过addJavascriptInterface暴露的方法都是可被调用的。4.2以上版本需要添加@JavascriptInterface注解才能被调用,但这只是降低了风险,并没有根除。
安全实践
- 最小化暴露原则:只暴露必要的方法,不要整个对象都丢出去
- 使用@JavascriptInterface注解:这是Android官方推荐的唯一安全方式
- 参数校验:所有从JS传来的参数,都要做严格的类型和范围检查
- 避免反射调用:不要在JS接口里使用反射,这等于给攻击者开了后门
我的习惯:我会单独创建一个「Bridge」类,里面只放那些确实需要被JS调用的方法。每个方法都加上@JavascriptInterface,并且对参数做白名单校验。曾经有个项目,就因为没校验参数,被注入了恶意URL——从那以后,我再也不敢偷懒了。
2. 远程代码执行:不止是JS接口
很多人以为远程代码执行只跟addJavascriptInterface有关。其实不然。WebView的远程代码执行漏洞,还有好几个变种。
WebView的setWebChromeClient漏洞
我记得有个经典漏洞:通过onJsPrompt、onJsConfirm这些回调,攻击者可以构造特殊的JS代码,实现任意代码执行。为什么会这样?因为这些回调里,开发者经常不加区分地执行了传入的字符串。
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
// 危险!直接执行了message
evalJs(message);
return true;
}
});
你想想看,如果message里藏着恶意代码,那不就中招了吗?
安全实践
- 禁用JavaScript:如果不需要,直接关掉
webView.getSettings().setJavaScriptEnabled(false); - 使用安全的白名单:只允许加载可信域名的网页
- 避免使用eval:无论是Java端还是JS端,eval都是危险的
- 升级WebView:使用系统WebView或Chromium WebView,及时打补丁
避坑指南:我曾经接手过一个项目,里面用了大量的loadUrl("javascript:...")来执行动态JS。这其实跟eval一样危险。我建议改用evaluateJavascript方法,并且只执行预定义的、经过审核的JS代码。
3. 文件访问风险:你的本地文件在裸奔
WebView默认可以访问本地文件。这意味着,如果攻击者能控制你加载的网页,他就能读取你的本地文件。比如:
// 恶意网页中的JS
var xhr = new XMLHttpRequest();
xhr.open("GET", "file:///data/data/com.example.app/databases/secret.db", true);
xhr.send();
嗯,你的数据库就这么被偷走了。
关键配置
| 配置项 | 说明 | 建议值 |
|---|---|---|
| setAllowFileAccess | 是否允许WebView访问文件系统 | false(除非绝对必要) |
| setAllowFileAccessFromFileURLs | 是否允许file://协议下的页面访问其他文件 | false |
| setAllowUniversalAccessFromFileURLs | 是否允许file://协议下的页面访问任意资源 | false |
| setJavaScriptEnabled | 是否启用JavaScript | 按需开启,不用的场景关掉 |
注意:即使你关闭了setAllowFileAccess,如果WebView加载的是file://协议的页面,它仍然可以通过XMLHttpRequest访问同目录下的文件。所以,千万别加载用户提供的本地文件!
4. 安全配置最佳实践:一套完整的防护方案
说了这么多问题,那到底该怎么配?我总结了一套「安全配置清单」,每次新建WebView我都会过一遍。
基础配置模板
WebView webView = findViewById(R.id.webview);
WebSettings settings = webView.getSettings();
// 1. 按需启用JavaScript
settings.setJavaScriptEnabled(true); // 如果不需要,设为false
// 2. 关闭文件访问
settings.setAllowFileAccess(false);
settings.setAllowFileAccessFromFileURLs(false);
settings.setAllowUniversalAccessFromFileURLs(false);
// 3. 关闭数据库和存储
settings.setDatabaseEnabled(false);
settings.setDomStorageEnabled(false); // 按需开启
// 4. 混合内容处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
}
// 5. 安全地注册JS接口
webView.addJavascriptInterface(new SafeBridge(), "bridge");
// 6. 使用HTTPS
webView.loadUrl("https://trusted-site.com");
SVG:WebView安全知识体系
进阶配置:WebViewClient安全增强
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// 只允许HTTPS和特定域名
if (url.startsWith("https://trusted.com")) {
return false; // 让WebView加载
}
// 其他URL尝试打开外部浏览器
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
startActivity(intent);
return true;
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// 不要调用handler.proceed()!应该拒绝
handler.cancel();
// 记录日志或提示用户
}
});
我的经验:在shouldOverrideUrlLoading里做URL校验,是防止钓鱼攻击的第一道防线。我曾经见过一个金融App,因为没做这个校验,被恶意网页重定向到了钓鱼页面——用户输入了账号密码,结果可想而知。
总结
WebView安全,说白了就是管好「入口」和「权限」。入口是URL,只让可信的进来;权限是功能,只给必要的能力。做到这两点,大部分漏洞就跟你没关系了。
嗯,最后说一句:别偷懒。每次新建WebView,都按上面的清单过一遍。养成习惯,比事后补漏洞省心多了。