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漏洞

我记得有个经典漏洞:通过onJsPromptonJsConfirm这些回调,攻击者可以构造特殊的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安全知识体系

WebView安全知识体系 WebView安全 JS接口漏洞 addJavascriptInterface 远程代码执行 setWebChromeClient 文件访问风险 file://协议 安全配置不当 混合内容等 防护措施 最小化暴露 @JavascriptInterface 禁用危险功能 文件访问、数据库 HTTPS强制 混合内容禁止 白名单机制 只加载可信域名 核心原则:最小权限 + 白名单 + 输入校验

进阶配置: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,都按上面的清单过一遍。养成习惯,比事后补漏洞省心多了。

公众号:蓝海资料掘金营,微信deep3321