首页 / 资讯中心 / 文章详情

Appshark实战:定位Android路径遍历漏洞的静态分析

Appshark实战:定位Android路径遍历漏洞的静态分析 ★ FEATURED ARTICLE
做移动端安全测试这些年我经手过的Android应用少说也有上百个如果要列一个“最容易出现却又最容易被忽略”的漏洞类型路径遍历绝对排得上号。很多团队直到被第三方报告打回重测才意识到自己的文件处理逻辑早就被“../”穿了筛子。而定位这类问题靠人工翻代码效率太低靠动态fuzz又覆盖不全这时候就需要一个称手的静态分析工具。Appshark就是我在这个场景下用得最多的一个。Appshark是一款针对Android应用的静态分析工具核心能力是把APK里的数据流梳理清楚帮你找出从外部输入比如Intent参数、网络返回一路流到危险操作比如文件读写、命令执行的完整链条。路径遍历正是它最擅长抓的漏洞类型之一只要配置好source和sink规则它就能自动标记出可疑链路我们只需要在报告基础上做人工确认和PoC验证。这篇文章不聊虚的直接从原理、规则配置、实战扫描到误报排查完整走一遍用Appshark定位Android路径遍历漏洞的流程适合正在做App安全测试的同行也适合刚接触静态分析、想找一款能快速上手的工具的同学。1. 路径遍历漏洞在Android应用中的真实形态1.1 漏洞原理为什么“../”能穿透看似严密的文件逻辑路径遍历Path Traversal本质上是一个“边界绕过”问题。很多应用在拼接文件路径时天真地以为只要我限制了基目录用户传什么参数都逃不出去。但实际上文件系统对“..”的解析是发生在操作系统层面的file.getCanonicalPath()、new File()这些API都会老老实实地把/data/user/0/某某包名/files/../../..逐级向上解析最终指向一个完全不在预期范围内的目录。举个例子某应用从服务端下载附件文件名直接从接口字段拼接String fileName response.body().getFileName(); File target new File(getExternalFilesDir(downloads), fileName); FileOutputStream fos new FileOutputStream(target);如果fileName被恶意控制为../../shared_prefs/xxx.xml那target的实际路径就会跳出downloads目录落到应用私有目录甚至更上层。常规的字符串过滤比如只替换一次../根本防不住因为攻击者可以编码、双重编码、混合大小写绕过静态分析的价值就在于不依赖这些变体的枚举而是从数据流源头把问题揪出来。1.2 Android场景中常见的路径遍历高危入口路径遍历不是Web应用独有的Android里高危入口比很多人想象的要多Zip解压场景Zip Slip从网络或本地获取zip压缩包后遍历ZipEntry逐个解压但entry.getName()里带了../直接拼接进输出路径。这是我在实际项目里遇到频率最高的一类几乎每隔几个应用就能翻到一个。Intent传递文件路径应用通过Intent接收其他应用传过来的URI或String路径比如intent.getStringExtra(file_path)随后直接交给文件操作API。由于Intent可以跨应用发送恶意应用完全能构造一个带../../的路径来触发。ContentProvider的openFile逻辑自定义ContentProvider覆写openFile()时如果直接使用Uri.getPath()去定位本地文件攻击者构造的Uri路径就能越权读取其他目录文件。服务端返回的文件名/相对路径App作为客户端把服务端下发的文件名字段直接用于本地落盘这类问题介于App漏洞和服务端漏洞之间但利用点完全在客户端。每个入口对应的source类型不一样排查方式也有差异这是后面配置Appshark规则时需要特别注意的点。1.3 为什么要用静态分析来排查路径遍历很多人会问直接跑一遍功能、抓包改参数或者用frida hook文件操作函数不也能测出来吗动态测试当然有效但它有两个硬伤一是覆盖率受限于你点的业务功能很多路径遍历隐藏在不常触发的逻辑分支里比如特定错误处理、后台同步、崩溃恢复二是触发链路过长动态测试很难快速定位到真正的污染点在哪。静态分析则是对整个APK做全量扫描从入口到出口把所有可达路径都过一遍虽然会带来误报但它能保证“覆盖”这件事是完整的。Appshark在这件事上的工程化程度做得不错扫描完直接给出调用链人工确认成本大大降低。2. Appshark工具解析与选型理由2.1 Appshark的核心工作方式Appshark的底层思路不复杂先把APK解析成中间表示再基于数据流分析技术在所有被标记为source的语句处注入“污染”沿着数据依赖和控制依赖向后传播一旦发现污染到达了被标记为sink的语句就把整条路径记录下来。它的一个亮点是内置了一个高性能的模式匹配引擎规则用JSON编写不需要编译插件改完配置立即生效。这对安全测试来说很重要——我在实际测试中经常需要针对某个特定SDK自定义规则Appshark这种“扫描器 规则文件”的结构让我可以像写用例一样快速迭代规则而不是被困在工具本身的二次开发里。2.2 与其他静态分析工具的对比市面上面向Android的静态分析工具不少我也都折腾过简单说下对比感受工具优势痛点Appshark规则灵活、扫描性能好、调用链清晰自定义规则需要理解source/sink模型其他通用Java静态分析工具跨语言支持广Android上下文缺失误报极高在线扫描平台开箱即用源码隐私风险、规则不透明选Appshark还有一重考虑它是面向新技术栈设计的对目前主流Android应用里常见的读写大文件、异步任务、协程调用链都有比较完善的建模。早年我用老牌工具扫描的时候经常因为调用关系解析不全导致漏报Appshark在分析完整度上的表现明显好一截特别是针对路径遍历这类需要追踪长链路的漏洞。2.3 环境要求与安装配置Appshark的运行环境很轻量一台8核16G内存的普通机器就能跑比较复杂的APK我自己常用的环境是CentOS 7 JDK17官方建议的JDK版本也是17。安装方式直接拉源码编译即可git clone https://github.com/Appshark/Appshark.git cd Appshark ./gradlew installDist编译完成后核心脚本在build/install/Appshark/bin/Appshark目录下。如果你是JDK8环境有概率在编译阶段遇到依赖问题建议直接上JDK17省去折腾。另外扫描前建议保证磁盘剩余空间至少在20GB以上——大型APK的分析中间文件量不小这是我第一次跑大型应用时踩过的坑磁盘写满导致扫描中断白等了一个多小时。3. 实战准备构建一条完整的路径遍历检测规则3.1 理解source-sink数据流模型在使用Appshark之前必须先把它的核心抽象搞清楚一切漏洞分析都被建模成从source到sink的污点传播。source外部可控数据的入口可以理解为攻击者的数据是从哪里进入应用的。路径遍历场景下常见的source包括Intent.getStringExtra()、Intent.getDataString()、Uri.getPath()、网络响应体里的字段读取等。sink危险操作发生的位置也就是污染数据最终被使用的地方。路径遍历的sink主要是文件读写类API比如new File(String)、FileOutputStream构造器、FileWriter、ZipFile、Files.move()等。规则的作用就是告诉Appshark“请帮我找出所有从这些source出发经过任意函数调用最终到达这些sink的路径。”中间怎么走、走了多远工具会帮你自动分析你不用手动描述每条调用关系。3.2 路径遍历场景的规则编写一个最简路径遍历规则长这样[ { id: path_traversal_basic, name: Path Traversal Detection, type: Taint, sources: [ { type: MethodReturn, clazz: android.content.Intent, method: getStringExtra, index: 0 }, { type: MethodReturn, clazz: android.content.Intent, method: getDataString, index: 0 } ], sinks: [ { type: MethodArgument, clazz: java.io.File, method: init, index: 0 }, { type: MethodArgument, clazz: java.io.FileOutputStream, method: init, index: 0 } ], extras: { maxCallDepth: 30, maxPaths: 20 } } ]拿这个规则跑一遍Appshark会找出所有“Intent传参→File构造/FileOutputStream构造”的链路。maxCallDepth控制分析的最大调用深度防中间层函数过多导致超时maxPaths控制单个source-sink对最多保留多少条路径避免报告爆炸。3.3 规则参数详解与调优写规则时最常调的几个参数我逐个说下实际体会index指定参数位置sink里的index指的是方法参数下标new File(String)的构造器里路径字符串是第0个参数。如果你要检测的sink是Files.move(Path, Path, CopyOption...)污染路径可能是第0个也可能是第1个参数那就需要配置两条sink条目或者把index分别设为0和1。maxCallDepth深度取舍深度太小容易漏报因为真实应用里source到sink之间往往夹着三四层工具类封装深度太大又容易爆时间和内存。我一般先设20扫完看结果再决定要不要加大。如果发现漏报集中在某个特定业务链路上优先单独降低范围去追而不是全局加深度。maxPaths数量控制默认值如果偏大报告文件会变得非常臃肿。对于路径遍历这种场景单条source-sink路径只要能确认一条可利用漏洞就是存在的所以maxPaths不需要很高我一般设20到50之间既能覆盖变体又不至于刷屏。另外提醒一句Appshark规则是数组结构一个文件里可以同时定义多条规则。我通常会额外写一条Zip Slip规则把sink指向ZipEntry.getName()的返回值然后再拼接进FileOutputStream的链路。因为zip解压场景的source不是Intent而是网络流读取单独一个source定义覆盖不到。4. 全流程实测从APK到漏洞报告4.1 第一步扫描任务启动与参数选择规则文件就绪后把它放到Appshark的运行目录下然后启动扫描。以我常用的方式为例./Appshark -a target.apk -r rules.json -o output_dir --pwd pwd参数说明-a待扫描的APK路径-r规则文件路径可以传多个规则文件-o结果输出目录--pwd输出目录的访问口令生成一个简单的口令文件即可扫描过程中Appshark会在控制台打印进度比如“Analyzing classes...”“Running taint analysis...”大型APK比如50MB以上、方法数超过10万的跑完一轮通常在30分钟到2小时之间。这个阶段我一般会做两件事一是观察日志里有没有明显的异常结束二是趁扫描期间先把APK的关键业务模块梳理一遍为后面人工验证做准备。4.2 第二步分析报告与关键字段解读扫描完成后结果会输出到指定目录。Appshark的报告是JSON格式每条漏洞对应一个JSON文件里面包含的关键字段如下{ vulnName: Path Traversal, source: intent.getStringExtra(0), sink: new File(String), taintPaths: [ { trace: [ android.app.Activity.onCreate, com.example.MainActivity.onNewIntent, com.example.util.FileHelper.saveFile, java.io.File.init ], confidence: high } ] }实际读报告时不要只看trace尾部需要重点确认三件事source类型是否真的是外部可控的。Intent.getStringExtra()确实是外部输入但如果它的调用者只在应用内部、没有exported组件暴露实际可利用性就大打折扣。中间函数是否有净化操作。如果链路中间存在对路径的过滤或规范化处理比如用getCanonicalPath()后重新校验前缀那这条链路就不成立。Appshark默认不做净化语义分析因为没有通用标准能覆盖所有业务这部分判断必须由人来做。sink之后的代码是否真的执行了危险操作。有些链路虽然污染到了new File()但如果后面只是把File对象用于打印、判断是否存在而没有读写操作那危害等级也相应降低。4.3 第三步人工验证与PoC构造拿到疑似链路后最后一步就是人工验证。我常用的验证思路是这样的写一个小的Android测试工程或者在目标APK上用frida hook关键方法直接构造恶意的Intent去触发。比如下面的PoC意图就是传入一个带../../的文件名如果目标Activity是exported的可以直接用adb起一个隐式Intent来验证如果不是exported的就只能在应用内部想办法或者通过ContentProvider等跨进程入口做验证。在实际项目中我用这套流程在一款IoT设备的配套App里定位到了一个真实的路径遍历问题App负责从云端下载设备升级包文件名是云端下发的fileName字段App拿到后拼接到getExternalFilesDir(ota)后面。正常情况下这没什么问题但云端的文件名如果被劫持或篡改App就会把文件写到公共目录之外。虽然当时云端安全做得还行但客户端这种“无条件信任外部文件名”的做法始终是个隐患最后推动研发团队把文件名做了白名单规范化后才关闭这条风险。这类案例在SRC测试中更常见。某次众测项目里目标App的附件下载模块就是典型的Zip Slip它先把远程zip包下载到缓存目录然后遍历解压输出路径直接拼了entry.getName()攻击者只要在zip里构造一个../../shared_prefs/下的条目就能覆盖应用自己的配置。用Appshark扫出来以后我构造了一个恶意zip包做验证不到半小时就确认并提报了这条高危漏洞。5. 常见问题与排查技巧实录5.1 扫描超时与内存不足这是用Appshark最容易遇到的第一道坎。症状通常有两种一是日志卡在某个阶段长时间不动二是直接报OutOfMemoryError退出。我的排查顺序先降maxCallDepth很多超时是分析深度太大引起的再降sources数量把明显不可能的source比如只在系统组件里出现的从规则里摘掉最后检查APK本身如果方法数超过20万建议用--trim参数开启精简模式去掉部分反射和动态加载调用后重扫。还有个小技巧对于超大APK考虑先做分包再扫。把主dex和三方SDK分开处理——大部分二次开发的App自研代码就3到5个合理包名前缀SDK代码往往才是分析负担的大头。5.2 误报率高的典型模式路径遍历规则写得太宽误报是必然的。我见过的高频误报模式主要有三种模式一source本身的污染性不强。比如getIntent().getData()在deep link场景下虽然外部可控但很多应用在入口处做过合法性校验比如只接受特定scheme或host。这类链路Appshark识别不到校验逻辑报告出来是“疑似”人肉确认后大概率是误报。模式二中间层有黑名单过滤。很多开发者知道路径遍历的威胁会写一个replace(.., )之类的过滤器。字符串过滤虽然不完善绕过变体多但Appshark不会自动识别这种净化操作仍然会把链路标记出来。确认这类报告时我会重点看中间层的函数名称——如果出现sanitize、filter、checkPath之类的命名就值得多看两眼。模式三sink本身不构成危害。举个例子new File(path).exists()这种判断型代码即使path可控也不会造成文件被读取或写入。如果业务逻辑里只是拿File判断存在与否而后面的操作又和这个路径无关报告可以直接归档为误报。5.3 漏报场景与规则补强误报还有迹可循漏报才更隐蔽。我踩过的漏报场景有几个典型source不在Intent上而在本地数据库或SP里。比如某个路径字段最早是从网络下发的存在SharedPreferences里之后某个功能从SP读出来直接拼文件路径。Appshark如果只配了Intent类source这条链路就会被漏掉。补强做法是把SharedPreferences.getString()也加进source。sink藏在自定义工具类里。如果研发把所有文件操作封装在了FileUtil.writeText()里而规则的sink只覆盖了FileOutputStream构造器中间隔了一层工具类调用虽然Appshark理论上能穿透但如果修改过的函数签名复杂、多态分支多分析中确实可能被丢弃。我的做法是额外写一条规则把自定义的FileUtil类方法也配置为sink直接观察其参数是否来自外部。补强规则其实是在“精准”和“噪音”之间找平衡。我一般会开两轮扫描第一轮用宽规则多source、多sink摸全量风险面第二轮用窄规则针对具体可疑业务链做收敛两轮报告合起来覆盖和可读性就都有了。6. 漏洞修复与加固建议6.1 路径规范化与白名单校验修复路径遍历最推荐的做法是先规范化再校验而不是做字符串过滤。“先替换后拼接”的方案本质上是在和绕过变体做军备竞赛永远追不完。工程上可靠的做法是String basePath context.getExternalFilesDir(downloads).getCanonicalPath(); File target new File(basePath, fileName); String targetPath target.getCanonicalPath(); if (!targetPath.startsWith(basePath File.separator)) { throw new IllegalArgumentException(illegal path); }这段代码的核心逻辑是先让系统把..解析掉再拿解析后的完整路径和预期的基目录做前缀比对。为什么必须用getCanonicalPath()而不是getAbsolutePath()因为前者会解析符号链接和..后者只是简单拼接不解析二者是不一样的。还有一个细节前缀判断一定要带File.separator否则攻击者构造一个downloads_evil之类的目录名也能绕过startsWith(basePath)的检查这种边界情况我在代码审计里见过不止一次。6.2 加固的常见误区说完正确做法再列几个我见过的伪修复只拦截..不拦截绝对路径。有开发者做了过滤“..”却忘了绝对路径也能穿目录比如直接传/data/data/某某/shared_prefs/xxx.xml一样可以跳出预期目录。白名单前缀校验对绝对路径同样有效因为它会把绝对路径解析后和基目录比对。在服务端信任App传回来的路径。有些应用客户端保存的是路径字符串上传到服务端后再原样返回使用等于把验证责任推给了一个不可信的来源。正确的思路是客户端只保存文件ID所有路径拼接统一在服务端做好后再下发。用正则黑名单过滤文件名。正则的绕过空间太大了——编码、大小写、Unicode归一化都能绕过这种方式只适合做第一层粗筛不能作为唯一防线。另外如果是Zip解压场景不仅要校验解压路径还要注意zip条目本身可能是目录穿越的变体Windows盘符比如C:开头和绝对路径。校验逻辑应该是“每一条entry.getName()在解压前都单独做一次规范化前缀检查”而不是只在循环开始时检查一次。修复之后的复测也有讲究改完代码后重新打包APK用同一份规则再跑一轮Appshark确认同类链路不再出现。同时建议把PoC脚本保留在测试资产库里以后每次发版都能跑一遍回归防止研发后续重构时把漏洞改回来。用了几年的个人感受回过头看用Appshark定位路径遍历漏洞这件事最值钱的经验倒不是工具本身而是“规则思维”。工具只是执行者真正的判断力在于你有多清楚目标应用的数据从哪进来、到哪去、中间经过哪些不可信的变换。多花半小时梳理业务入口和文件操作点比盲目放宽规则扫三轮的效率高得多。我自己在做新项目的时候第一件事永远是先拉一遍AndroidManifest看看哪些组件是exported的、哪些ContentProvider是暴露的再去配source规则——这样出来的报告含金量完全不一样。如果你刚开始上手Appshark不用急着一口气配全所有漏洞类型先拿路径遍历这一个场景练熟规则编写和报告解读后面再扩展到命令注入、SQL注入就顺理成章了。
阅读完成 · 觉得有帮助?
咨询建站