Android WebView addJavascriptInterface 的风险
案例研究:Ostorlab 的 AI 渗透测试引擎发现一个可经由深度链接访问的 Android WebView JavaScript 桥接接口,并将其串联为对原生 UI 的操控。
引言
混合移动应用通常依赖一个“桥接”(bridge),让 Web 内容能够与 Android 原生层通信。虽然这使原生通知或硬件访问等强大功能成为可能,但也引入了相当大的攻击面。如果一个 Java 或 Kotlin 原生对象被暴露给不可信的 JavaScript 上下文,就可能导致 UI 操控、社会工程攻击,甚至远程代码执行。
WebView.addJavascriptInterface(Object object, String name) 方法会将传入的对象注入 WebView 的 JavaScript 上下文,使 JavaScript 能够调用通过 @JavascriptInterface 显式注解的原生方法。历史上,这个 API 曾在 Android 4.2 之前的版本中引发一个严重漏洞:不受限制的反射使 JavaScript 能够在应用进程内实现通用的远程代码执行。现代 Android 版本通过要求显式注解并限制反射访问来缓解这类问题。然而,如果暴露的方法执行敏感操作,或者允许不可信的 JavaScript 在 WebView 中执行,该接口仍可能被滥用,用以跨越信任边界并操控原生应用的行为。
理解 JavaScript 桥接暴露
要理解这一发现为何重要,我们必须先了解两个常见的 Android 组件,它们组合在一起时会形成一条强大的漏洞利用链:JavaScript 接口和深度链接 Intent 处理。
1. JavaScript 接口
在 Android 中,WebView 组件允许应用显示 Web 内容。为了让这些 Web 内容能够与 Android 原生代码“对话”,开发者会使用通过 addJavascriptInterface 方法创建的“桥接”。
当开发者调用 webView.addJavascriptInterface(new MyWebAppInterface(), "Android") 时,在该 WebView 中运行的任何 JavaScript 都可以像调用原生 JS 函数一样调用 MyWebAppInterface Java 类中的方法(例如 window.Android.showToast(...))。
风险:如果 WebView 加载了不可信的网站(通过中间人攻击或恶意链接),该网站就获得了触发原生 Java 代码的能力。
2. 深度链接注入
深度链接允许外部来源(如浏览器或其他应用)使用 URI(例如 myapp://profile)打开应用内的特定页面。
当应用的“大门”把守不严时,它可能会接受 javascript: URI 作为深度链接。例如:
intent://target_activity?url=javascript:alert(window.Android.showToast('Hacked'))
如果应用不加甄别地将此 URL 加载到其 WebView 中,它就不只是在加载一个网页,而是在应用的安全上下文中直接执行任意代码。
当这两个要素碰撞在一起时,攻击者便可以远程“伸手进入”应用。通过向用户发送一个精心构造的链接,攻击者迫使应用打开,向内部 WebView 注入恶意 JavaScript,然后利用桥接操控原生功能。
Ostorlab 渗透测试引擎概述
Ostorlab Pentest Engine 是一个自主的攻击性安全智能体,由人工智能驱动,旨在模拟资深人类渗透测试人员的推理方式和方法论。与依赖静态、基于规则检查的传统扫描器不同,该引擎使用动态的“推理与行动”(Reason and Act)框架来探索应用的攻击面。
工作原理:自主循环
该引擎通过一个持续迭代的循环运行,使其能够处理诸如本案例研究中所述 JavaScript 桥接暴露这样的复杂漏洞:
- 风险建模与假设生成:引擎首先使用 P.A.S.T.A. 等方法论构建目标的威胁模型。它会生成具体的假设(例如 “这个深度链接能否执行 JavaScript 以访问原生桥接?”),而不是简单地大量投放载荷。
- 专用子智能体(执行器):引擎通过 Ostorlab OXO 平台编排一组专用子智能体。这些执行器使用的工具包括 monkey tester、爬虫、模糊测试工具和污点分析引擎。
- 运行时插桩与动态交互:引擎在一个实时、受控的执行环境中运行,在多个技术栈(Java、Swift、C/C++、Flutter)之上叠加插桩。这使它能够 Hook 原生行为,在复杂的 UI 流程中导航(包括支持 2FA/OTP 的需认证区域),并实时检查在 Web 层与原生层之间流动的数据。
- 对抗性验证:为提高准确性并减少误报,引擎包含一个专门的对抗性验证环节。它会独立地重新审查检测结果,以确认漏洞可复现并构成真实风险。
通过维护一份经过整理的工件与观察记忆,渗透测试引擎能够将多个发现“串联”起来。在本案例中,它不只是发现了一个深度链接,还推断出该深度链接可以作为 JavaScript 载荷的投递机制,而该载荷随后可以与一个已枚举出的原生接口交互。
攻击过程:发现 JavaScript 桥接暴露
在最近的一次基准测试中,Ostorlab 的 Pentest Engine 在一个混合应用的 WebView 实现中发现了一个缺陷。通过系统性地探测该应用的深度链接处理程序,它发现了一个暴露的 JavaScript 接口,使在 WebView 中执行的 JavaScript 能够在未经用户明确同意的情况下触发原生 UI 元素。
第 1 步:桥接发现与枚举
引擎的首要目标是通过识别注入到 WebView JavaScript 上下文中的原生对象,来绘制可用的攻击面。
桥接发现:从 JavaScript 上下文中识别暴露的桥接对象和方法。目标:枚举可访问的方法,以确定原生攻击面。
执行摘要:
引擎使用 Object.getOwnPropertyNames() 列出全局 window 对象的属性,并识别出一个自定义接口。
结果:
发现了一个名为 Android 的桥接对象。进一步枚举发现了一个可访问的方法:showToast(String message)。
结论: 该应用暴露了一个原生桥接。虽然方法列表很少,但由于缺乏来源限制,WebView 中加载的任何页面(包括通过深度链接注入的页面)都可以调用原生 UI 逻辑。
第 2 步:验证注入向量
既然知道桥接存在,引擎接下来要找出一种从外部不可信来源与之交互的机制。它发现,该应用的深度链接处理程序被配置为在不清理 URI scheme 的情况下处理传入的 Intent。
使用 javascript: URI 验证能否通过深度链接注入访问 Android 桥接。目标:确认 WebView 是否会执行来自外部 Intent 的任意代码。
执行摘要:
引擎使用一个旨在调用已发现桥接的 javascript: 载荷,向存在漏洞的 Activity(Activity2)发送了一个恶意 Intent:
adb shell am start -W -a android.intent.action.VIEW -n com.target.app/.Activity2 -d "javascript:Android.showToast('BRIDGE_ACCESS_TEST')"。
结果:
该命令被应用成功处理。设备屏幕上出现了一条文本为“BRIDGE_ACCESS_TEST”的原生 Toast 消息,Logcat 日志也确认了来自 com.target.app 包的方法调用。
结论:
注入向量得到完全验证。由于应用没有拒绝通过 Intent 传入的 javascript: 或 data: URI,任何外部应用都可以迫使 WebView 执行直接与原生 Java 接口交互的代码。
第 3 步:探测权限提升(反射与注入)
在确认桥接可访问后,引擎尝试超越 UI 操控,寻找更严重的影响,例如原生代码执行或命令注入。目标是确定 showToast 方法或桥接本身能否被用作绕过 Android 沙箱的原语。
尝试通过 showToast 方法使用反射或命令注入来绕过沙箱,以实现原生代码执行。
执行摘要: 引擎系统性地测试了多种高级利用模式:
- 反射测试:尝试访问
getClass()方法,并使用forName('java.lang.Runtime')执行系统命令。 - 命令注入:尝试向
showToast参数注入 shell 元字符和命令分隔符(例如;、&&、`)。 - 原型污染:尝试向桥接对象的原型注入恶意方法。
结果: 所有提权尝试均被阻止:
- 反射:对
getClass()的访问受到限制,试图通过方法链访问Runtime.exec()的尝试均告失败。 - 命令注入:原生
showToast实现将所有输入视为字面量CharSequence,将恶意字符串作为纯文本显示在屏幕上,而不是执行它们。 - 不可变性:桥接对象被证实是不可变的,从而阻止了原型污染或方法覆盖。
结论: 方法级别的安全控制是有效的。虽然桥接被暴露,但它是“安全实现”的,影响仅限于短暂的 UI 效果,无法提升为原生代码执行或数据窃取。
第 4 步:确认实际影响(社会工程攻击链)
在确认直接的原生提权被阻止之后,引擎转而验证实际的业务风险:社会工程攻击。通过利用桥接显示原生 UI 元素的能力,引擎演示了攻击者如何操纵用户信任,以协助窃取凭据或分发恶意软件。
执行一条多阶段攻击链,将未经身份验证的钓鱼内容与通过桥接触发的“官方”原生通知相结合,以评估社会工程攻击的影响。
执行摘要: 引擎编排了一次两阶段的“链式攻击”,以模拟真实世界的钓鱼场景:
-
钓鱼重定向:使用深度链接迫使 WebView 加载一个外部恶意 URL:
adb shell am start -n com.target.app/.Activity2 -d "https://attacker.com/phishing.html" -
伪造通知注入:在重定向之后立即通过桥接触发一条紧急的原生 Toast 消息:
adb shell am start -n com.target.app/.Activity2 -d "javascript:Android.showToast('🔒 New message: Account verification required')"
结果: 用户体验被成功劫持。应用在加载钓鱼页面的同时,显示了一条看似官方的原生通知。这制造出一种强烈的假象,仿佛这条“账户验证”请求来自受信任的本地应用,而非恶意网站。
结论: 这证实了存在很高的业务与用户隐私影响潜在风险。即使没有数据窃取或代码执行,桥接也可以通过误导用户对应用真实状态的判断,被用于分发“恐吓软件”(Scareware)或窃取凭据。
最终报告
1. 执行摘要
Ostorlab Pentest Engine 在目标应用的 BankWebViewKt 组件中发现了一处 JavaScript 桥接暴露。Android 桥接对象在没有任何来源校验或源验证的情况下被注入 WebView。该漏洞使任何 JavaScript 上下文(包括通过未经身份验证的深度链接注入的上下文)都能调用原生的 showToast 方法。尽管引擎确认原生代码执行和命令注入已被方法级别的有效沙箱所阻止,但由于其具备进行高拟真度社会工程攻击和欺骗用户的潜力,该漏洞仍属高影响。
2. 方法论
渗透测试引擎遵循一套有条理的多阶段流程来测试原生桥接的安全性:
- 桥接发现:通过枚举 WebView 内
window对象的属性,识别自定义的原生接口。 - 向量分析:发现
Activity2在不进行清理的情况下处理带javascript:URI 的深度链接,为外部代码提供了入口点。 - 静态分析:定位
com.target.app/BankWebViewKt.java中存在漏洞的桥接注入。 - 提权探测:系统性地测试 Java 反射、命令注入和原型污染,以确定最大可利用性。
- 影响场景测试:通过将钓鱼重定向与伪造的原生通知串联起来,验证社会工程攻击风险。
3. 发现
- 不受限制的 JavaScript 接口暴露:
Android桥接在未经来源校验的情况下被注入 WebView,使其可从任何已加载的页面访问,包括通过深度链接注入的页面。这意味着未能在 Web 内容与原生层之间强制实施适当的信任边界。 - 未受保护的深度链接:应用允许
javascript:和data:URI 在不经清理的情况下通过 Intent 处理。这使外部应用能够直接调用桥接方法,为操控原生 UI 提供了一条直接的途径。 - 有效的桥接级沙箱:将桥接暴露提升为系统级代码执行的尝试均告失败。
showToast方法将所有输入视为字面字符串,且 JavaScript 接口对象不可变,从而阻止了反射、原型污染或命令注入。 - 缺乏速率限制:桥接允许通过深度链接不受限制地反复调用。虽然这不会导致代码执行,但可以实现快速的 UI 操控或“刷屏”通知,这在社会工程攻击场景中可被加以利用。
4. 修复
为解决这些发现,建议采取以下措施:
- 移除或限制访问:如果不需要该桥接,请完全移除
addJavascriptInterface代码块。 - 来源校验:如果确有必要使用该桥接,请实施严格的受信任域名允许列表(例如使用
SecureWebViewClient),并在允许方法执行之前验证当前来源。 - 清理深度链接:更新
Activity2中的深度链接处理程序,拒绝任何以javascript:或data:scheme 开头的 Intent 数据。 - 实施安全加固:禁用不必要的 WebView 功能,例如在生产构建版本中禁用文件访问和调试,以减少整体攻击面。
5. 结论
本案例研究表明,即使是“安全实现”的桥接,一旦暴露给外部输入,也可能构成重大的安全风险。通过模拟攻击者的逻辑推进过程——从发现接口到构造一条可用的社会工程攻击链——Ostorlab 的 Pentest Engine 提供了保护该混合接口所需的确凿证据。