☰
OWASP MASTG 演示实战:使用 WebStorage API 清理 Android WebView 敏感数据(MASTG-DEMO-0082 完整复现与原理剖析)
2026/10/6 7:32:22 网站建设 项目流程
  • 文档
  • 教程
  • 网络安全

【免费下载链接】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 MASTG(Mobile Application Security Testing Guide)仓库中的动态分析演示 MASTG-DEMO-0082 展开:它展示了一个会向 WebView 的 localStorage 写入令牌与驾照编号等敏感数据的 Android 应用,以及应用如何在生命周期回调中调用WebStorage.deleteAllData()完成清理,并配套给出基于 Frida 的运行时验证方案。读完本文,你将掌握"WebView 存储区域有哪些、如何用 Frida 挂钩清理 API、如何用 adb 在app_webview目录中验证敏感数据残留"这一整套可复用的动态测试方法,并能独立判断目标应用是否满足 MASTG-TEST-0320 的判定标准。

一、背景:WebView 敏感数据残留为什么值得测试

Android 的 WebView 基于 Chromium 内核(自 Android 4.4 起),每个应用拥有自己独立的 WebView 存储区,不与其他应用共享。绝大多数 Web 相关数据都落在 Chromium profile 目录中:

/data/data/<app_package>/app_webview/

按照 MASTG-KNOW-0018(WebViews) 的归纳,WebView 会按 origin 持久化以下几类数据:

  • 缓存网络资源:服务器返回带Cache-Control/Expires等缓存头时产生,存在内存与磁盘上的 Chromium cache 中;
  • DOM 存储:localStorage与sessionStorage;
  • WebSQL:在现代 WebView 中已移除;
  • IndexedDB与Origin Private File System(OPFS):由 Chromium 内部管理,不以普通文件形式出现在应用沙箱里;
  • Cookies:包括会话 Cookie 与持久 Cookie。

如果应用在 WebView 中处理了敏感数据(令牌、证件号、交易信息等)却不清理,这些数据就会在设备上留存超出必要的时间,构成MASWE-0001(敏感数据泄漏/过度留存)类弱点。这正是 MASTG-TEST-0320(WebViews Not Cleaning Up Sensitive Data) 要验证的核心问题:应用启用哪些存储区域、是否在不再需要 WebView 时调用了对应的清理 API。

测试用例明确列举了需要关注的"启用 API ↔ 清理 API"配对关系:

启用的存储能力对应的清理要求
WebSettings.setAppCacheEnabled()或setCacheMode()非LOAD_NO_CACHE调用WebView.clearCache(includeDiskFiles = true)
WebSettings.setDomStorageEnabled(true)(DOM 存储)调用WebStorage.deleteAllData()
WebSettings.setDatabaseEnabled(true)(数据库)调用WebStorage.deleteAllData()
CookieManager.setAcceptCookie()未显式设为false(默认开启)调用CookieManager.removeAllCookies(...)

注意:无论应用自身是否显式调用这些 API,WebView 在渲染页面(例如页面里的 JavaScript 使用localStorage)时都可能在内部用到它们,因此测试既要追踪相关 API 调用,也要检查文件系统层面的实际残留。

二、演示样本:应用如何制造"脏数据"

本演示的应用源码位于 demos/android/MASVS-PLATFORM/MASTG-DEMO-0082,包含三个核心文件:

  • MainActivityWebView.kt:Activity,在onStop()生命周期回调中执行清理;
  • MastgTestWebView.kt:WebView 配置与内联 HTML,负责写入敏感数据;
  • AndroidManifest.xml:声明INTERNET权限与入口 Activity。

2.1 在生命周期回调中执行清理

清理动作发生在 MainActivityWebView.kt 第 40-43 行:

override fun onStop() { WebStorage.getInstance().deleteAllData() super.onStop() }

选择onStop()作为清理时机是经过考量的:当用户离开当前 Activity(例如按下返回、跳转其他页面)时,系统会调用onStop(),此时 WebView 已不再需要展示,正是清除其存储数据的合理窗口。这也是 MASTG-BEST-0028 建议的"在 WebView 不再需要时清理"实践的一种落地方式。

2.2 WebView 配置与敏感数据写入

MastgTestWebView.kt 负责配置 WebView 并注入页面:

@SuppressLint("SetJavaScriptEnabled") fun mastgTest(webView: WebView) { webView.apply { settings.apply { javaScriptEnabled = true // 启用 JS domStorageEnabled = true // 启用 DOM 存储(localStorage 的前提) } addJavascriptInterface(AndroidBridge(), "Android") // 暴露给页面的 JS 桥 ... loadDataWithBaseURL("https://mas.owasp.org/", html, "text/html", "utf-8", null) } }

关键细节:

  • domStorageEnabled = true与javaScriptEnabled = true是页面脚本能够使用localStorage的前提,同时也就此"启用"了 DOM 存储这一存储区域——按 MASTG-TEST-0320 的判定逻辑,启用后就必须配套清理;
  • loadDataWithBaseURL("https://mas.owasp.org/", ...)指定了 base URL,为内联 HTML 定义了 localStorage 的 origin,数据才会真正写入磁盘上的 LevelDB;
  • addJavascriptInterface(AndroidBridge(), "Android")注入一个 JS 桥,页面上倒计时结束后调用Android.closeApp(),桥接方法内部通过runOnUiThread { activity.finish() }关闭 Activity,从而触发onStop()清理路径。

2.3 页面写入的敏感数据

内联 HTML 通过页面脚本写入两条敏感数据(MastgTestWebView.kt 第 60-63 行):

localStorage.setItem('sensitive_token', 'SECRET_TOKEN_123456'); localStorage.setItem('driving_license_id', 'DL-987654321');
  • 'sensitive_token'→'SECRET_TOKEN_123456'(令牌类敏感数据)
  • 'driving_license_id'→'DL-987654321'(个人身份类敏感数据)

页面随后显示 10 秒倒计时,结束后调用Android.closeApp()关闭应用。这两条字符串将贯穿全文:Frida 挂钩用于观察清理 API 的调用,adb 搜索则用于验证数据是否真的从磁盘消失。

三、测试步骤:完整复现 WebView 存储清理验证

按 MASTG-DEMO-0082.md 给出的步骤,整套动态测试流程如下:

  1. 安装应用到测试设备(见 MASTG-TECH-0005(Installing Apps));
  2. 准备动态分析环境:在测试机安装 Frida(即演示中引用的 @MASTG-TOOL-0001),并在设备上运行frida-server;
  3. 运行run.sh,用 Frida 以 spawn 方式启动应用并加载挂钩脚本;
  4. 在应用界面点击Start按钮打开 WebView;
  5. 等待页面倒计时结束、Activity 关闭,期间 Frida 脚本捕获 WebView 清理相关调用;
  6. 退出 Frida CLI 停止脚本;
  7. 取证验证:按 MASTG-TECH-0002(Host-Device Data Transfer) 的方法,在/data/data/org.owasp.mastestapp/app_webview/目录中搜索之前写入的敏感数据。

3.1 启动脚本 run.sh

run.sh 内容极简,一条命令完成 spawn、注入与日志落盘:

#!/bin/bash frida -U -n MASTestApp -l script.js -o output.json

参数含义:

  • -U:连接 USB 连接的设备;
  • -n MASTestApp:按进程名(应用包名对应的进程)附加目标;
  • -l script.js:加载挂钩脚本;
  • -o output.json:将 Frida 控制台输出重定向到output.json,便于后续分析。

3.2 取证搜索命令

成功场景下用于验证残留的 adb 命令如下(即output_adb_deletion_succeeded.txt的内容):

adb shell 'grep -nri -E "SECRET_TOKEN_123456|DL-987654321" /data/data/org.owasp.mastestapp/app_webview'

这条命令递归(-r)、忽略大小写(-i)、显示行号(-n)地在 WebView 存储目录中搜索两条敏感数据字符串。localStorage 在 Chromium 中落盘为 LevelDB 格式(位于app_webview/Default/Local Storage/leveldb/),因此即使数据存在,grep通常也是以"匹配二进制文件"的形式报出,而非输出明文行。

四、Frida 挂钩脚本原理:追踪清理 API 的调用链

script.js 是本演示的动态分析核心。它同时挂钩两个方法:deleteAllData(清理动作)与setDomStorageEnabled(启用动作),从而完整还原"先启用 DOM 存储、后清理全部数据"的证据链。

4.1 定位 Chromium 内部实现类

WebView 的公开 API(android.webkit.WebStorage、android.webkit.WebSettings)在运行时实际由 Chromium 实现类承载,类名模式为com.android.webview.chromium.*。脚本首先用Java.enumerateMethods在该命名空间下枚举目标方法:

function enumerateDeleteAllDataMethod() { const res = Java.enumerateMethods('com.android.webview.chromium.*!deleteAllData'); return res && res[0]; }

Java.enumerateMethods('pattern!methodName')返回所有匹配类与方法的描述符。取第一个结果即可拿到真实实现类及其 class loader——这是后续 hook 的关键,因为 Chromium 的实现类位于 WebView 的独立 class loader 中,直接Java.use可能失败。

4.2 确保目标类被加载

一个常见的坑:如果应用尚未真正使用过 WebStorage,其内部实现类可能还没有被加载到内存,enumerateMethods会返回undefined。脚本的处理是主动触发加载:

if (enumerateDeleteAllDataMethod() === undefined) { console.log('Bring WebStorage to memory so we can hook its deleteAllData method.'); Java.use('android.webkit.WebStorage').getInstance(); }

调用WebStorage.getInstance()会迫使 Chromium 初始化 WebStorage 的实现类,从而让后续枚举能够命中。同理,对WebView与WebSettings也通过ensureClassLoaded主动Java.use一次,确保其内部类进入内存。

4.3 挂钩 deleteAllData 并打印调用栈

找到方法描述符后,切换到正确的 class loader 并替换实现:

const deleteAllDataMethod = enumerateDeleteAllDataMethod(); if (deleteAllDataMethod !== undefined) { Java.classFactory.loader = deleteAllDataMethod.loader; // 切换到 Chromium 类加载器 const WebStorageAdapter = Java.use(deleteAllDataMethod.classes[0].name); WebStorageAdapter.deleteAllData.implementation = function () { console.log('WebStorage.deleteAllData called.'); printBacktrace(); // 打印 Java 调用栈 return this.deleteAllData(); // 调用原实现,不改变行为 }; }

Java.classFactory.loader = ...将后续Java.use的解析上下文切换到该类的真实 loader,避免类加载器不匹配导致的 hook 失败。printBacktrace()用java.lang.Exception的堆栈快照还原调用来源,让测试人员能确认清理动作确实由应用自己的代码触发。

4.4 挂钩 setDomStorageEnabled

同样的思路用于捕获存储区域启用动作,且遍历所有重载逐一挂钩:

ContentSettingsAdapter.setDomStorageEnabled.overloads.forEach(function (ov) { ov.implementation = function () { const enabled = arguments.length > 0 ? arguments[0] : undefined; console.log('ContentSettingsAdapter.setDomStorageEnabled called, enabled is ' + enabled + '.'); printBacktrace(); return ov.apply(this, arguments); }; });

这条 hook 的作用是证明"应用确实启用了 DOM 存储",与"随后调用了deleteAllData"构成一对完整的证据,正好对应 MASTG-TEST-0320 中"启用 API 列表 + 清理 API 列表"的观察要求。

五、运行结果解读:从日志到磁盘取证

5.1 Frida 输出(output.json)

output.json 记录了完整调用序列,按时间顺序解读:

Enumerating chromium. WebStorage.deleteAllData hooked. ContentSettingsAdapter.setDomStorageEnabled hooked. ContentSettingsAdapter.setDomStorageEnabled called, enabled is true.

第一条setDomStorageEnabled called, enabled is true的调用栈直指应用代码:

com.android.webview.chromium.ContentSettingsAdapter.setDomStorageEnabled(Native Method) org.owasp.mastestapp.MastgTestWebView.mastgTest(MastgTestWebView.kt:20) ← 启用 DOM 存储 org.owasp.mastestapp.MainActivityWebViewKt$WebViewScreen$3.invoke$lambda$1(MainActivityWebView.kt:81)

这与源码完全对应:MastgTestWebView.kt 第 20 行 的domStorageEnabled = true,经由 Compose 的AndroidViewfactory(MainActivityWebView.kt 第 81 行 调用mastgTest(this))执行。

随后捕获到清理动作:

WebStorage.deleteAllData called. Backtrace: com.android.webview.chromium.e.deleteAllData(Native Method) org.owasp.mastestapp.MainActivityWebView.onStop(MainActivityWebView.kt:41) ← 生命周期回调中的清理 android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1623) android.app.Activity.performStop(Activity.java:8838) ... android.app.servertransaction.TransactionExecutor.performLifecycleSequence(...)

这条调用栈完美验证了设计意图:页面倒计时结束 → JS 桥Android.closeApp()→activity.finish()→ Activity 进入onStop→ MainActivityWebView.kt 第 41 行 的WebStorage.getInstance().deleteAllData()被执行。

5.2 磁盘取证:成功场景(数据已清理)

output_adb_deletion_succeeded.txt中,同样的 adb 搜索命令没有任何匹配输出:

adb shell 'grep -nri -E "SECRET_TOKEN_123456|DL-987654321" /data/data/org.owasp.mastestapp/app_webview'

(命令执行后无任何匹配行返回)这说明deleteAllData()生效后,原先写入 localStorage 的两条敏感字符串在app_webview目录中已不存在,数据没有残留在磁盘上。

5.3 磁盘取证:失败场景(数据残留)

output_adb_deletion_failed.txt展示了清理缺失时的对照结果——这正是演示文档 "Evaluation" 一节所描述的失败用例:

adb shell 'grep -nri -E "SECRET_TOKEN_123456|DL-987654321" /data/data/org.owasp.mastestapp/app_webview' Binary file /data/data/org.owasp.mastestapp/app_webview/Default/Local Storage/leveldb/000003.log matches Binary file /data/data/org.owasp.mastestapp/app_webview/Default/Local Storage/leveldb/000003.log matches

grep在 LevelDB 日志文件leveldb/000003.log中两次命中敏感数据。这里需要说明:localStorage 在 Chromium 中以 LevelDB 落盘,其键值对是二进制编码存储的,所以grep报告"Binary file ... matches"而非输出明文。两条敏感字符串都命中,说明 localStorage 内容完整残留。这演示了失败场景——应用关闭后敏感数据仍留在 WebView 存储目录中。

六、测试判定:通过/失败的标准

依据 MASTG-TEST-0320 的 Evaluation 规则:

  • 测试失败:应用关闭后,/data/data/<app_package>/app_webview/目录中仍存在敏感数据——通常是应用启用了相应存储区域(DOM 存储、数据库、缓存、Cookie)却没有调用配套的清理 API;
  • 测试通过:应用正确调用了与所启用存储区域对应的清理 API,关闭后目录中不再有敏感数据残留。

本演示默认场景通过:Frida 日志证明WebStorage.deleteAllData()在onStop()中被调用,adb 取证证明敏感数据已从app_webview目录消失。

演示文档还给出了构造失败用例的简单方法:注释掉 MainActivityWebView.kt 第 41 行 的WebStorage.getInstance().deleteAllData()并重新运行。此时 Frida 不再捕获到deleteAllData调用,adb 搜索则会在leveldb/000003.log中命中两条敏感数据,测试判定为失败。

七、原理纵深:deleteAllData 到底清理了什么、清不掉什么

WebStorage.deleteAllData()是对 WebView 存储进行清理时最常用也最容易误用的 API,理解它的能力边界至关重要。据 MASTG-KNOW-0018 的说明:

  • ✅可以清理:DOM 存储(localStorage/sessionStorage)与遗留的 WebSQL 数据库;
  • ❌不能清理:IndexedDB 与 Origin Private File System(OPFS)——它们由 Chromium 内部管理,不属于WebStorageAPI 的管辖范围;
  • ❌不能清理:Cookie——需要单独调用CookieManager.removeAllCookies(ValueCallback);
  • ❌不能清理:HTTP 磁盘缓存——需要WebView.clearCache(includeDiskFiles = true)。

此外,Android没有提供删除整个 Chromium profile(即app_webview目录)的专用 API,应用也不应直接删除该目录;唯一的系统级方式是清除应用数据(系统设置或ActivityManager.clearApplicationUserData()),但这种方式会一并丢弃应用的其他用户数据,通常不符合"只清理 WebView 残留"的需求。

一个值得借鉴的完整清理参考实现来自开源项目 Firefox Focus(见 MASTG-KNOW-0018 第 259-283 行),其cleanup()依次执行:

clearFormData() clearHistory() clearMatches() clearSslPreferences() clearCache(true) // 清理 HTTP 缓存 CookieManager.getInstance().removeAllCookies(null) // 清理 Cookie WebStorage.getInstance().deleteAllData() // 清理 DOM 存储 / WebSQL

可以看出,一个严谨的 WebView 清理流程需要针对每一个已启用的存储区域分别调用对应 API。这与 MASTG-TEST-0320 开篇列出的"启用 ↔ 清理"配对表完全一致。

八、最佳实践与测试中的现实挑战

MASTG-BEST-0028(WebViews Cache Cleanup) 对开发与测试双方都提出了明确建议:

  1. 优先从源头禁止缓存:对包含敏感数据的 API 响应使用Cache-Control: no-cache等头,让 WebView 不缓存,这是最省力的控制;
  2. 客户端显式兜底:若无法控制服务器,可设置WebSettings.setCacheMode(WebSettings.LOAD_NO_CACHE),或在 WebView 使用结束后(如 Activity 的onDestroy)调用WebView.clearCache(includeDiskFiles = true)。

同时,这类清理方案存在两个已知劣势,测试时需留意:

  • clearCache(true)会无差别清除全部缓存数据,包括图片等本可受益于缓存的大文件;
  • 清理方法不保证一定被调用——例如应用进程被系统强制杀死时,onStop()/onDestroy()可能根本不会执行。因此评估时需要同时考察"上次运行是否清理过 + 本次是否执行了清理"两方面的证据(例如在下次启动时补做评估)。

而站在测试人员视角,MASTG-KNOW-0018 的 "Challenges of Testing WebView Cache Cleanup" 一节 总结了 WebView 清理测试的四大难点:

  1. 需要识别应用中 WebView 实例的数量、各自的WebSettings以及实例间的关联,确保测试结论只针对被测的那个 WebView;
  2. 对每个实例,需要确认各存储区域如何配置、数据基于 HTTP 缓存头与存储配置如何实际落盘;
  3. 需要确定每条敏感数据项的生命周期与预期保留时长;
  4. 需要应对清理方法可能不被调用的场景(进程被突杀等),并检查是否存在应对这些场景的缓解措施。

这也是 MASTG-TEST-0320 建议"动态分析挂钩相关 API + 检查文件系统残留"双管齐下的原因:单独看 API 调用日志(本演示的 Frida 输出)或单独看磁盘残留(adb grep)都可能产生误判,两者互为印证才是可靠结论。若需要在运行期做更细粒度的文件系统追踪,可按 MASTG-TECH-0143 对 WebView 存储目录中的open、openat、unlinkat等文件操作进行额外追踪。

九、小结

MASTG-DEMO-0082 用一整套可复现的工程化样本,把"WebView 敏感数据清理"这一主题从现象到原理完整呈现:

  • 样本层面:一个localStorage写入敏感数据的应用 +onStop()中调用WebStorage.deleteAllData()的清理实现(MainActivityWebView.kt);
  • 测试层面:Frida 挂钩deleteAllData/setDomStorageEnabled还原调用链(script.js),adb 在app_webview目录中取证,并通过注释清理代码构造失败对照组;
  • 理论层面:deleteAllData()的能力边界(能清 DOM 存储/WebSQL,清不掉 IndexedDB/OPFS/Cookie/缓存),以及 MASTG-TEST-0320、MASTG-KNOW-0018、MASTG-BEST-0028 构成的完整知识闭环。

这套方法可以直接迁移到真实应用的评估中:定位其 WebView 实例与存储配置 → 挂钩启用/清理 API → 注入敏感数据并关闭应用 → 在app_webview目录取证,即可对"WebView 是否妥善清理敏感数据"给出有据可依的结论。

  • 文档
  • 教程
  • 网络安全

【免费下载链接】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
点击查看免费下载
上一篇:Flink CDC 集成 ClickHouse:3 条路径搭通实时数据同步链路
下一篇:品牌资产组织实战指南:用 ui-ux-pro-max-skill 构建可搜索、可校验、可追溯的营销资产体系

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询