ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OWASP MASTG 进程内存安全指南:Android 敏感数据的内存暴露、清理与检测实践

2026/10/7 9:22:58 拓冰建站 浏览量
OWASP MASTG 进程内存安全指南:Android 敏感数据的内存暴露、清理与检测实践 文档教程网络安全【免费下载链接】mastgThe 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 知识库文章《Process Memory》MASTG-KNOW-0051为骨架结合 MASTG 测试库、技术手册与工具文档系统讲解 Android 应用中敏感数据密钥、口令、令牌等在进程内存中的暴露路径、安全处置原则与检测手段。读完本文你将掌握识别内存中敏感数据的多个副本来源、在byte[]与String之间做出正确选型、编写真正能清零内存的代码以及借助 r2frida、objection、Android Studio 与 MAT 对进程内存进行转储和实时分析。为什么进程内存值得关注许多应用都会处理由用户、操作系统或后端提供的敏感信息。只要应用正在运行这些数据就不可避免地以某种形式存在于进程内存中——因为它们正是应用实现功能所必需的。问题不在于敏感数据是否进入了内存而在于它进入内存后能存活多久、存在多少份副本。从攻击面看敏感数据在内存中通常不止一份。以后端下发的数据为例数据最终会落在应用的模型对象中但在到达终点之前它很可能同时存在于HTTP 客户端的响应缓冲XML/JSON 解析器的中间缓冲区网络库的字符串拼接产物日志构建过程中隐式创建的StringBuilder每多一份副本就多一个被读取的入口。而检测或攻击这些内存副本的途径主要有两类详见 进程探索技术 MASTG-TECH-0044 与 调试技术 MASTG-TECH-0031内存转储memory dump把进程的整个地址空间导出来离线分析实时内存分析通过调试器JDWP或二进制插桩框架Frida/r2frida在应用运行期间直接查看内存。第一步先做减法再谈清理在动手写清零代码之前MASTG 建议先回答一个更根本的问题这份数据真的需要在内存中以明文形式出现吗理解应用架构及其在系统中的角色有助于识别那些根本不该暴露在内存中的敏感信息。最典型的例子是纯转发pass-through场景应用从服务器 A 收到数据不做任何处理就直接传给服务器 B。这种数据完全可以以加密形式接收与转发从头到尾都不需要在内存中暴露明文——加密格式本身就能防止内存层面的暴露。如果敏感数据确实必须在内存中明文处理则应遵循三条设计原则最小副本敏感数据的处理要集中化参与的组件越少越好避免数据在多个组件间流转时被反复复制最短时长数据不再被主动使用后应立即处置dispose而不是等垃圾回收器自然回收可变基础结构存储敏感数据的载体应选用原始primitive、可原地修改mutable的数据结构以便开发者能直接访问并覆盖这些内存。数据类型选型可变基础类型 vs 不可变类型MASTG 对数据类型的选择给出了非常明确的建议优先选用byte[]、char[]等原始数组。它们让开发者拥有直接的内存访问权可以在使用完毕后用假数据通常是零值原地覆盖。避免使用String、BigInteger等不可变类型。一旦试图修改这类对象实际发生的是创建并修改一份新副本旧副本的原内存内容依然残留直到被垃圾回收器回收。StringBuffer / StringBuilder 的两面性使用StringBuffer、StringBuilder这类非原始的可变类型可能可以接受但 MASTG 明确指出这是有风险信号indicative的写法需要格外小心。原因有三这类类型的设计初衷是修改内容这正是清零操作想要的但取值时通常要走toString()方法而toString()会产生一份不可变副本这份副本无法被安全清理虽然存在绕过toString()直接操作内部缓冲的若干技巧但它们比直接使用原始数组要费力得多更隐蔽的问题是自动扩容当修改内容导致数据量超出当前缓冲容量时缓冲会自动增长底层内容可能被复制到内存中的另一个位置而旧位置的内容由于失去了引用你将永远无法再覆盖它——它只能等待垃圾回收。因此安全内存管理是这类类型的一把双刃剑它给了你管理入口也埋下了你管不到的分身。原地销毁的残酷现实JVM 与 Android 堆内秘密的固有限制即便开发者写下了正确的清零代码真·原地销毁true in-place destruction在 Java/Android 生态中也非常罕见。更关键的是即使实现了原地销毁也无法清除堆内存中可能存在的其他副本。这是 Java 与 Android 内存管理对堆内秘密in-heap secrets的固有局限。MASTG 给出了四个典型证据场景问题本质secretKey.getEncoded()返回的数组getEncoded()返回的是副本对这份副本清零对密钥对象内部持有的字节毫无影响该问题在 JDK 中以 JDK-6263419 跟踪SecretKeySpec.destroy()SecretKeySpec并未提供可用的destroy()实现对由它支撑的SecretKey调用destroy()不能保证密钥材料被擦除对应 JDK 缺陷 JDK-8160206Android Keystore 之外的 RSA 密钥RSA 密钥底层由不可变的BigInteger对象承载一旦创建便无法清除数据只能残留在内存中等待垃圾回收用户输入口令、社保号、信用卡号通过EditText输入的数据经Editable接口传递若应用不提供自定义Editable.Factory数据大概率会在内存中滞留过久。默认实现SpannableStringBuilder与 Java 的StringBuilder/StringBuffer存在同样的滞留问题敏感内容直到垃圾回收前都留在内存里这四条证据说明选择了正确的容器不等于数据被安全地清理了——还必须对所使用的每一个 API 是否真正支持就地销毁保持怀疑并在设计阶段就评估密钥材料在堆中的留存风险。静态分析清单与安全编码实践进程内存敏感数据问题的根源在设计与编码阶段静态分析因此是性价比最高的第一道工序。原 已废弃测试 MASTG-TEST-0011 中保留了一份可操作的分析清单值得完整继承识别应用组件绘制敏感数据的使用路径确保敏感数据只由尽可能少的组件处理确保对象不再需要时其引用被正确移除引用移除后主动请求垃圾回收例如加密、解析含敏感信息的服务端响应等关键操作之后以缩短副本在内存中的存活时间确保敏感数据在不再需要时立即被覆盖不要用不可变类型String、BigInteger承载避免非原始类型如StringBuilder覆盖引用应在finalize方法之外完成关注第三方组件库与框架公开 API 如何处理敏感数据是很好的观察指标。基础清零模式及其隐患最朴素也最流行的做法是用零值填充数组MASTG 给出的 Java/Kotlin 双版本如下byte[] secret null; try{ // 获取或生成 secret使用它确保过程中不产生本地副本 } finally { if (null ! secret) { Arrays.fill(secret, (byte) 0); } }val secret: ByteArray? null try { // 获取或生成 secret使用它确保过程中不产生本地副本 } finally { if (null ! secret) { Arrays.fill(secret, 0.toByte()) } }但这个模式存在三重隐患编译器优化可能令覆盖失效为了优化字节码编译器会分析出数据此后不再使用从而判定覆盖操作不必要并将其剔除。即便代码已进入 DEXJIT即时编译或 AOT预先编译阶段仍可能执行同样的优化。因此运行时是否真的发生覆盖无法得到保证Arrays.fill是显眼的 Hook 目标该方法调用模式过于典型很容易被插桩框架定位和篡改参见 MASTG-TECH-0043只写零值太容易被识别安全扫描器可以依据全零填充这一特征构建识别规则。改进版用非敏感数据覆盖 强制副作用既然编译器的优化来源于数据不再被使用的判定那么让覆盖后的数据逃出编译器的分析范围即可强制覆盖真实发生。MASTG 给出的改进方案是用公开的、非敏感的数据循环覆盖秘密字节然后把结果写入/dev/null以制造数据仍被使用的假象byte[] nonSecret somePublicString.getBytes(ISO-8859-1); byte[] secret null; try{ // 获取或生成 secret使用它确保过程中不产生本地副本 } finally { if (null ! secret) { for (int i 0; i secret.length; i) { secret[i] nonSecret[i % nonSecret.length]; } FileOutputStream out new FileOutputStream(/dev/null); out.write(secret); out.flush(); out.close(); } }val nonSecret: ByteArray somePublicString.getBytes(ISO-8859-1) val secret: ByteArray? null try { // 获取或生成 secret使用它确保过程中不产生本地副本 } finally { if (null ! secret) { for (i in secret.indices) { secret[i] nonSecret[i % nonSecret.size] } val out FileOutputStream(/dev/null) out.write(secret) out.flush() out.close() } }需要说明的是这个问题没有银弹。用随机数据或非关键数据覆盖、配合把数据写出作用域如写入临时文件等手段各有代价——前者无法预知编译器优化分析到什么程度后者则必然影响性能与可维护性。实操时应根据数据的敏感等级做取舍。SecureSecretKey可销毁密钥的安全实现范式针对SecretKey系列 API 无法真正销毁密钥的问题MASTG 提供了一个SecureSecretKey参考实现它同时实现javax.crypto.SecretKey与Destroyable核心思路是构造时克隆输入字节调用方负责清理自己传入的数组类内只保留一份可控副本getEncoded()返回克隆副本并明确要求调用方在使用完毕后自行清零destroy()用非敏感字节逐位覆盖内部数组再写入/dev/null强制覆盖生效最后置空引用并请求垃圾回收isDestroyed()通过key null判断状态。Java 版本核心片段public class SecureSecretKey implements javax.crypto.SecretKey, Destroyable { private byte[] key; private final String algorithm; public SecureSecretKey(final byte[] key, final String algorithm) { this.key key.clone(); this.algorithm algorithm; } public byte[] getEncoded() { if(null key){ throw new NullPointerException(); } return key.clone(); } public void destroy() { if (isDestroyed()) { return; } byte[] nonSecret new String(RuntimeException).getBytes(ISO-8859-1); for (int i 0; i key.length; i) { key[i] nonSecret[i % nonSecret.length]; } FileOutputStream out new FileOutputStream(/dev/null); out.write(key); out.flush(); out.close(); this.key null; System.gc(); } public boolean isDestroyed() { return key null; } }该设计解决了两个核心关切不在不同上下文之间流转敏感数据每份副本都可在其创建作用域内被清除且本地副本严格按前述建议清理。完整实现含 Kotlin 版本见 MASTG-TEST-0011。与之形成对比的是仓库 MASTG-DEMO-0017 中的反面样例硬编码字节直接喂给SecretKeySpec密钥材料以不可变对象形式常驻内存只能依赖垃圾回收。用户输入自定义 Editable.Factory对于通过EditText输入的用户敏感数据口令、身份证号、卡号等Android 允许通过自定义Editable.Factory实现部分擦除EditText editText ...; // 指向你的 EditText 实例 editText.setEditableFactory(new Editable.Factory() { public Editable newEditable(CharSequence source) { ... // 返回一个安全的 Editable 实现实例 } });要点与边界提供自定义工厂后editText.getText()产生的所有副本都在你的掌控之内可按SecureSecretKey的思路实现可清零的Editable也可通过editText.setText()尝试覆盖内部缓冲但无法保证缓冲尚未被复制过若依赖默认输入法与EditText你对软键盘等组件毫无控制力因此该方案只适用于半机密semi-confidential信息。此外无论采用哪种方案都应在用户登出时清除内存中的敏感数据对高度敏感的信息应在 Activity/Fragment 触发onPause的瞬间完成清除——尽管这可能意味着应用每次恢复时用户都要重新认证。动态分析转储与实时检测静态分析能定位可能的问题但无法回答数据到底在内存里暴露了多久也无法覆盖闭源依赖。动态分析正是为此存在它包含两条主线操作细节参见 MASTG-TECH-0044。内存转储与离线分析无论设备是否已 root都可以对应用进程进行内存转储root 设备直接安装并运行 frida-server非 root 设备则需要用 Frida Gadget 重新打包并重签名应用过程详见 MASTG-TECH-0026。以 objectionMASTG-TOOL-0038为例一条命令即可导出全部内存$ objection --name sg.vantagepoint.helloworldjni start sg.vantagepoint.helloworldjni on (google: 8.1.0) [usb] # memory dump all /Users/foo/memory_Android/memory Will dump 719 rw- images, totalling 1.6 GiB Dumping 1002.8 MiB from base: 0x14140000 [------------------------------------] 0% 00:11:03(session detach message) process-terminated Dumping 8.0 MiB from base: 0x7fc753e000 [####################################] 100% Memory dumped to file: /Users/foo/memory_Android/memory转储过程中常见的内存访问违例错误通常可以忽略转储工具会无视读写权限尝试导出所有已映射内存读取不可读区域时自然会报错。拿到转储文件后按数据类型选择分析工具只找字符串strings或 radare2 的rabin2即可。# 使用 strings $ strings memory strings.txt # 使用 rabin2 $ rabin2 -ZZ memory strings.txt其他二进制数据用 radare2 的搜索能力/?可查看全部选项常用子命令包括/c[ar]搜索加密材料、/w foo搜索宽字符字符串、/x ff0033搜索十六进制串、/z min max按长度搜索字符串。另一种被广泛使用的转储工具是 Fridumppython3 fridump.py -U 包名 -s加-s后会把转储出的字符串汇总到dump/strings.txt随后即可在转储目录中直接 grep 输入的关键字例如登录口令来验证敏感数据是否在操作完成后仍残留于内存——这也是检验应用是否及时清零的最直接方法。实时内存分析r2frida与其把内存导出来慢慢翻不如在进程运行时直接搜索。r2fridaMASTG-TOOL-0036将 radare2 的逆向能力与 Frida 的运行时插桩合并一条命令即可挂到目标应用r2 frida://usb//sg.vantagepoint.helloworldjni会话内所有 Frida 相关命令以:开头核心操作链包括查看内存映射:dm列出全部映射输出可达 1500~2000 行用:dm~包名过滤出属于应用的区域il可只看已加载模块:dm.可随时确认当前偏移所在区域内存内搜索:/ Hello搜索字符串:/w Hello搜索宽字符串变体有时宽字符串是唯一能找到目标的方式:/x搜索十六进制可配合:e search.quiettrue隐藏搜索进度用:dm. hit0_*批量定位命中地址所属的内存区域判定归属与存活命中地址落在libnative-lib.so、base.apk还是dalvik-main space一查便知。由此可以验证登录后搜索用户口令若操作完成后仍能命中说明数据未被及时清零。Java 堆专项分析Android Studio MAT如果目标限定在 Java 堆内对象Android Studio 提供了更低门槛的路径。在Android Monitor选项卡选中设备和应用点击Dump Java Heap即可在项目 captures 目录生成.hprof文件用Package Tree View可以按包导航转储中保存的类实例更深入的分析则交给 Eclipse Memory Analyzer ToolMAT。先用 Android SDK 自带的hprof-conv转换格式再用 MAT 打开./hprof-conv memory.hprof memory-mat.hprofMAT 的杀手锏是Object Query LanguageOQL一种类 SQL 的查询语言。若干实用查询-- 列出所有 String 对象 SELECT * FROM java.lang.String -- 只看每个字符串的值 SELECT toString(object) FROM java.lang.String object -- 访问所有 char 数组的内容 SELECT toString(arr) FROM char[] arr -- 找出含 RSA 密钥 ASN.1 OID 的字节数组有概率性但值得追踪 SELECT * FROM byte[] b WHERE toString(b).matches(.*1\.2\.840\.113549\.1\.1\.1.*) -- 查找所有带 password 字段的对象 SELECT password FROM .* WHERE (null ! password)分析时重点搜索语义化字段名password、pass、pin、secret、private等、典型模式如 RSA 指纹、以及已知秘密值自己输入的信用卡号、后端下发的认证令牌。重复多次转储并对比同一内存段的变化既能统计数据暴露时长也可能发现其他方式难以识别的敏感数据。为什么 OWASP MASTG 不再提供进程内存测试MASTG 曾经有对应的安全测试Android 侧为 已废弃的 MASTG-TEST-0011对应 iOS 侧为 MASTG-TEST-0060两者均标注 deprecated 并由知识库文章承接。移除的原因值得每一位测试者理解因为它直接影响测试策略的制定实用价值有限内存检查通常需要特权访问root 或越狱而这已经超出了大多数生产环境的威胁模型——具备该级别访问权限的攻击者本身就已攻陷了设备整体安全单独的内存层面发现意义有限执行可靠性差内存转储结果高度可变受时机、垃圾回收、系统级优化影响极易出现误报或漏报不适合作为标准化、可重复的安全控制回归设计预防OWASP 转而强调通过安全设计与开发实践来预防问题。软件保障成熟度模型OWASP SAMM与 NIST SP 800-218《安全软件开发框架SSDF》均指导开发者在内存中安全处理敏感数据、最小化暴露窗口、使用安全的密码学与存储 APIMASVS 的数据存储MASVS-STORAGE、网络通信MASVS-NETWORK、认证与会话管理MASVS-AUTH三大类别已覆盖运行时防意外暴露所需的保护措施。关于内存中秘密处理的具体建议可参考 OWASP Secrets Management Cheat Sheet 的 Handling Secrets in Memory 一节。也就是说进程内存检查从测试项降级为设计约束测试者应当把精力放在审查应用的清零逻辑与数据流设计上而不是把内存转储当作常规回归手段。小结Android 进程内存中的敏感数据治理可以归纳为四句话能不进内存就不进内存——架构上支持纯转发时让数据保持加密形态必须进内存就少留副本——集中处理、原始可变数组承载byte[]/char[]远离String/BigInteger谨慎使用StringBuffer/StringBuilder用完必须真的擦掉——警惕编译器优化、getEncoded()副本、SecretKeySpec.destroy()空实现、SpannableStringBuilder滞留等 Java/Android 固有陷阱必要时实现SecureSecretKey这类可销毁容器并配合自定义Editable.Factory管控用户输入测试者要用对工具验证——r2frida 实时搜索、objection/Fridump 全量转储、MAT OQL 堆内深挖但需明白此类检查已不再是 MASTG 的标准测试项其定位是设计审查的辅助而非合规的充分条件。相关阅读Android 侧知识库原文 MASTG-KNOW-0051、iOS 侧对应文章 MASTG-KNOW-0103、完整测试细节 MASTG-TEST-0011、工具链参考 MASTG-TOOL-0036r2frida 与 MASTG-TOOL-0038objection。赞分享文档教程网络安全【免费下载链接】mastgThe 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 实战Android 内部存储明文敏感数据检测MASTG-DEMO-0010 / MASTG-TEST-0207OWASP MASTG 实战Android 内部存储明文敏感数据检测MASTG DEMO 0010 / MASTG TEST 0207 导读 本文以 OW文档教程网络安全OWASP MASTG iOS IPC 安全实践最小化敏感数据经进程间通信通道的暴露OWASP MASTG iOS IPC 安全实践最小化敏感数据经进程间通信通道的暴露 iOS 应用之间并没有一套通用的直接通信机制几乎所有数据交换都发生在平文档教程网络安全OWASP MASTG 实战Android WebView 允许内容访问AllowContentAccess导致敏感数据泄露的 Frida 动态检测OWASP MASTG 实战Android WebView 允许内容访问AllowContentAccess导致敏感数据泄露的 Frida 动态检测 导读文档教程网络安全上一篇React-Cropper深度解析从基础配置到高级用法下一篇【亲测免费】 SOLIDER 项目使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考