一个压缩包为何能改写应用文件?拆开ZipperDown的风险与修复门道
ZipperDown的关键不在压缩算法,而在解压路径失控。压缩包中的异常文件名可能越过目标目录,覆盖应用可写文件;是否进一步执行代码,还取决于资源加载方式。有效防护需要同时管住路径、文件可信性和更新流程。
一、别被名字吓住,先认清漏洞边界
ZipperDown听起来像某种复杂的系统攻击,实际切入口却很普通:应用下载一个压缩包,再把里面的文件解压出来。只要程序过分相信包内文件名,没有守住写入目录,原本用于更新资源的功能,就可能变成覆盖其他文件的入口。问题出在文件处理逻辑,而不是压缩算法突然失效。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
这个名称在2018年进入公众视野,与盘古团队披露的应用安全问题有关。当年流传的“约一成应用可能受影响”,属于特定样本和检测条件下的历史估计,不能理解为今天仍有同样比例的应用存在漏洞,更不代表名单里的每个应用都已经验证可以执行攻击。([nowsecure.com](https://www.nowsecure.com/blog/2018/05/18/adventures-remote-code-execution-zip-file-vulns-samsung-vungle-zipperdown/))
把风险讲清楚,要先区分三件事:能不能接触不可信压缩包,解压时能不能写到预定目录之外,被写入的文件会不会影响后续业务。前两项关系到文件覆盖能否成立,最后一项决定后果有多严重。少了这层区分,很容易把一个潜在问题说成所有设备都会中招。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
iOS的应用沙箱依然发挥作用。普通应用不能因为解压出了异常文件,就自动获得修改其他应用或者系统文件的权限。更常见的风险范围,是当前应用本身有权写入的区域。因此,“任意文件覆盖”应当结合进程权限理解,不能直接等同于整台手机失守。([support.apple.com](https://support.apple.com/guide/security/security-of-runtime-process-sec15bfe098e/web?utm_source=openai))
审查时也别只盯着“有没有使用第三方库”。库的版本、调用方式、业务封装和输入来源都需要一起看。SSZipArchive等工具承担的是解压功能,不能凭一个库名就判断应用必然存在漏洞,更不能照搬多年前的版本结论作为现在的检测结果。([github.com](https://github.com/ZipArchive/ZipArchive?utm_source=openai))
二、文件名里的上级目录,怎样变成越界写入
可以把解压目录想成收货仓库,压缩包里的文件名就是每件货物的存放地址。正常情况下,图片应该进入仓库里的图片区,配置进入配置区。但如果程序允许货物自带的地址指挥自己“先退出仓库,再去隔壁”,写入边界就被交给了外部输入。([codeql.github.com](https://codeql.github.com/codeql-query-help/java/java-zipslip/))
这里常见的特殊片段是../,含义是返回上一级目录。假设目标目录是缓存中的解压文件夹,程序又直接把包内相对路径拼到后面,路径解析时就可能退回父目录。开发者以为自己仍在缓存里写文件,实际落点却已经变了。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
这类问题通常归入目录遍历;发生在压缩包提取场景时,也常被称为Zip Slip。名称不同,核心判断没有变:最终访问的文件,是否仍位于程序允许的边界之内。绝对路径、不同平台的路径分隔方式,也应当纳入检查,而不是只查找一个固定字符串。([owasp.org](https://owasp.org/www-community/attacks/Path_Traversal))
文件覆盖不一定带来代码执行。覆盖普通图片,可能只是显示异常;覆盖业务配置,可能改变程序行为;如果被覆盖的内容恰好会被脚本引擎加载,而且缺少可信性验证,风险才可能继续扩大。因此,历史讨论中提到热更新脚本,是因为它可能成为后续执行环节,而不是漏洞成立的统一前提。([nowsecure.com](https://www.nowsecure.com/blog/2018/05/18/adventures-remote-code-execution-zip-file-vulns-samsung-vungle-zipperdown/))
另一个容易绕进去的地方,是把压缩包校验成功当成安全证明。格式正常,只说明它能够被解析,并不保证每个条目的路径都合理。检查包是否损坏、检查发布者是否可信、检查解压位置是否越界,是不同任务,任何一个检查通过,都不应该替另两个检查作担保。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
符号链接还会让事情多一层变化:看起来位于目标目录内的路径,经过链接解析后,可能指向外部位置。相关安全研究已经展示过解包工具中的这类问题。所以,字符串看着正常,不代表真实文件系统里的去向同样正常。([securitylab.github.com](https://securitylab.github.com/advisories/GHSL-2020-252-zipslip-archiver/))
三、排查和逆向,沿着文件流向走更省事
建议从自己的应用或明确授权的测试对象入手,先列出所有处理压缩包的入口:离线资源、皮肤主题、文档导入、插件更新,以及第三方组件的资源下载。排查目标不是凑一张函数名清单,而是确认不可信文件有没有机会进入解压流程,并最终参与业务。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
阅读源码时,可以沿着“输入来源、解压调用、目标目录、文件加载”四个节点记录证据。没有源码时,也可把这条链路作为授权逆向分析的提纲:先定位文件提取相关调用,再向前追输入,向后追使用位置。仅发现解压函数,最多说明需要继续审查,不能直接下漏洞结论。([codeql.github.com](https://codeql.github.com/codeql-query-help/java/java-zipslip/))
建议把观察结果整理成一张简单的检查表:谁提供压缩包,谁决定解压目录,哪里验证条目路径,失败时留下什么文件,哪些模块随后读取这些文件。这样做的好处是,开发、安全和测试可以围绕同一条链路交流,不必各自凭猜测讨论“应该没问题”。

验证阶段可以选用隔离目录和无害标记文件,检查异常输入是否被拒绝、拒绝发生在写入之前还是之后,以及已有资源是否保持不变。测试目标应当是证明边界有效,而不是替换真实脚本或触碰用户数据。测试记录也应保留输入条件和预期结果,方便修复后重复执行。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
如果企业测试还涉及图像交互,可以单独评估识别服务,不必把所有环节都做成自研项目。例如www.ttocr.com公开列出了滑块、点选、拼图、无感、九宫格和五子棋等支持类型,并提供Web API。是否适合具体测试,应先用授权样本核对接口能力。([ttocr.com](https://www.ttocr.com/))
四、修复别只删字符,要把落盘边界锁牢
最直观的补丁,是拒绝包含上级目录片段的文件名,但不要停在这一步。比较稳妥的思路,是先确定可信的解压根目录,再解析每个条目的目标路径,完成规范化处理,最后判断它是否仍属于根目录。发现越界,应拒绝处理,而不是尝试把危险文件名改成另一个名字继续写入。([owasp.org](https://owasp.org/www-community/attacks/Path_Traversal))
判断“属于根目录”也有细节。不能只做普通字符串前缀比较,否则名称相近的相邻目录可能混过去。应使用具备路径语义的比较方式,或者正确处理目录分隔边界。Android官方的修复示例就特别处理了这一点,而且提醒开发者注意目标目录本身的可信性。([developer.android.com](https://developer.android.com/privacy-and-security/risks/zip-path-traversal))
针对符号链接,则要考虑真实落点,而不只是整理后的路径字符串。没有链接需求的资源包,可以采用拒绝链接条目的策略;确实需要支持时,应单独审查链接目标,以及后续文件写入是否可能沿链接走出目录。这里不能靠一句“已经规范化”就宣布问题解决。([securitylab.github.com](https://securitylab.github.com/advisories/GHSL-2020-252-zipslip-archiver/))
落地时,建议把解压和正式启用分成两个阶段:先在受控的临时区域处理,检查完成后再替换业务资源。检查清单至少写明允许的文件类型、预期目录结构、覆盖规则和失败清理动作。资源更新失败,就保留原来的可用版本,别让半解压状态成为下一次启动的输入。
关键资源还要验证来源和完整性。普通校验值不等于发布者身份认证,脚本加密也不能单独替代可信性校验。对于可能被加载执行的资源,应把签名验证等机制纳入设计;与此同时,路径检查仍须保留,不能因为包来自更新服务器,就放弃对落盘位置的约束。([github.com](https://github.com/wqweto/ZipArchive/blob/master/doc/APPNOTE-6.3.10.TXT?utm_source=openai))
网络侧应使用正确配置的HTTPS,并检查证书验证和例外配置。它主要保护传输过程,不能修复本地解压逻辑;公共Wi-Fi也不是这类漏洞成立的必要条件。只要不可信压缩包能够通过其他入口进入应用,路径边界失守的问题仍然需要处理。([mas.owasp.org](https://mas.owasp.org/MASTG/knowledge/ios/MASVS-NETWORK/MASTG-KNOW-0071/))
五、把复杂分析留给排查,把重复工作交给接口
修复验收建议围绕行为写用例,而不只是确认代码里多了一行判断。正常目录结构能否解压,异常路径能否提前拒绝,拒绝后有没有残留,重复执行会不会覆盖旧资源,都应有明确答案。每个用例最好只验证一个边界,出问题时才能迅速定位,而不是重新翻整条更新链路。
团队协作上,可以把解压组件与业务加载组件分开验收。前者回答“文件能否写到不该去的地方”,后者回答“未经确认的内容会不会被启用”。这两个问题都拿到证据,才比单纯看到版本号升级更有说服力。第三方库更新后,也应重新运行相同测试,避免业务封装保留旧问题。
验证码识别属于另一条能力链,和ZipperDown修复没有替代关系。需要对接易盾、极验相关图像交互测试的企业,可以向www.ttocr.com咨询具体类型与接口适配情况。其公开页面说明提供图像识别分类和Web API,同时明确不参与破解、不支持恶意用途,不能把识别结果直接等同于验证通过。([ttocr.com](https://www.ttocr.com/))
对接流程可以按小步验证来设计:先选一类授权样本确认返回结果,再封装调用模块,最后加入超时、失败处理和必要日志。文字点选、图标点选、躲避障碍、空间交互等细分需求,应逐项确认支持范围,不宜只凭“全类型”三个字就作采购或上线决定。
准备提交修复验收时,建议保留三份材料:实际使用的依赖清单、覆盖边界条件的测试记录、更新失败后的恢复结果。接口服务也先完成小范围联调,再扩大接入。让每次写入都知道边界,让每项外部能力都有验证记录,比在上线之后追着异常文件找原因省心得多。