也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大

少走了弯路,也就错过了风景,无论如何,感谢经历


转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球

感谢大家一直以来对我CSDN博客的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步


Android安全付费专栏长期更新,本篇最新内容请前往:

  • [车联网安全自学篇] Android安全之ZIP文件目录遍历漏洞

0x01 前言

zip 类型的压缩包文件中允许存在 …/ 类型的字符串,用于表示上一层级的目录。攻击者可以利用这一特性,通过精心构造 zip 文件,利用多个 ../从而改变 zip 包中某个文件的存放位置,达到替换掉原有文件的目的

那么,如果被替换掉的文件是是 .so、.dex.odex类型文件,那么攻击者就可以轻易更改原有的代码逻辑,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全。比如寄生兽漏洞、海豚浏览器远程命令执行漏洞和三星默认输入法远程代码执行等著名的安全事件

  • 漏洞原理

在linux系统中,../代表切换到上一级目录,有些程序在当前工作目录中处理到诸如../../../../../../../../../../../etc/hosts表示的文件,会跳转出当前工作目录,跳转到到其他目录中对应的hosts目录

Java代码在解压zip文件时,会使用ZipEntry类的getName()方法,获取文件的名称,如果 zip 文件中包含../字符串,该方法的返回值里面会原样返回,如果没有过滤掉 getName() 返回值中的../字符串,继续解压缩操作,就会在其他目录中创建解压的文件

因为ZipEntry在进行压缩文件的时候,名称没有做任何限制,而在Android系统中…/这种特殊符号代表的是回到上层目录,又因为这个解压工作在本应用中,可以借助app的自生权限,把恶意文件名改成:…/…/…/data/data/…即可在解压的时候把文件解压到了应用的沙盒中

一些示例的zip包样本:

  • https://github.com/snyk/zip-slip-vulnerability
  • https://www.apkhere.com/app/com.dolphin.browser.express.web

0x02 vuls APP ZIP文件目录遍历【漏洞复现】

:Android6.0 已经打了补丁,在进行解压的时候对../这种情况进行了过滤,这样就导致不能进行成功的穿越,所以我们用低版本Android系统复现学习

vuls样本下载:https://github.com/AndroidAppSec/vuls/releases/tag/v4.3

ZipEntry关键 API

  • java.util.zip.ZipEntry#getName()
/*** Returns the name of the entry. * @return the name of the entry */public String getName() { return name; }
  • java.util.zip.ZipInputStream#getNextEntry()
/*** Reads the next ZIP file entry and positions the stream at the* beginning of the entry data.* @return the next ZIP file entry, or null if there are no more entries*/public ZipEntry getNextEntry()

private void zipperDown(){String url = "http://www.example.com/test.zip";OkHttpClient okHttpClient = new OkHttpClient();final Request request = new Request.Builder().url(url).get().build();Call call = okHttpClient.newCall(request);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {Log.e("zipperDown", "zipperDown fail");}@Overridepublic void onResponse(Call call, Response response) throws IOException {Log.e("zipperDown", response.body().bytes().length + "");String dstPath = getCacheDir().toString();ZipEntry zipEntry = null;ZipInputStream zipInputStream = new ZipInputStream(response.body().byteStream());while ((zipEntry = zipInputStream.getNextEntry()) != null){//调用zipEntry的getName方法没有检查是否包含"../"String entryName = zipEntry.getName();if (zipEntry.isDirectory()){// 创建文件夹entryName = entryName.substring(0, entryName.length() -1);File folder = new File(dstPath + File.separator + entryName);folder.mkdirs();}else {// 尝试解压zip entry到指定路径String fileName = dstPath + File.separator + entryName;Log.e("zipperDown", fileName);File file = new File(fileName);file.createNewFile();FileOutputStream fileOutputStream = new FileOutputStream(file);byte[] buffer = new byte[1024];int n = 0;while ((n = zipInputStream.read(buffer, 0 , 1024)) != -1){fileOutputStream.write(buffer, 0 , n);}fileOutputStream.flush();fileOutputStream.close();}}zipInputStream.close();}});}

在上图红色标记的代码中,直接将获取的压缩文件中的文件名进行拼接,没有对路径中是否包含 ../或者 ..字符就做过滤判断,即:文件名称不能包含"…/"这种特殊字符做处理。正常情况下,zip 文件中文件名为 og_test_zip.txt,会被下载到 /data/data/ddns.android.vuls/cache/og_test_zip.txt【如下图】。如果攻击者构造一个 ../files/og_test_zip.txt的文件名,则拼接后的文件名为 /data/data/ddns.android.vuls/cache/../files/og_test_zip.txt,其会下载到vuls APP应用本地的/files/目录下,当然如果是想做覆盖本地文件的话,只要取相同名字即可覆盖

构建一个zip poc文件,步骤如下:

  • 创建一个og_test_zip.txt,内容为Orangey test zip poc!
  • 将 og_test_zip.txt 压缩为 og_test_zip.zip
  • 切记,别用winrar打开来修改文件名,反正用winrar打开来修改文件名不行,这里,我们使用 7-Zip 打开文件并修改文件名
    • 为什么要借用压缩包根据来修改,大家看下图:


  • 攻击效果如下

  • 默认抓包host域名或GET 请求地址为:p://www.example.com/test.zip,两种修改方法

第一种:直接通过抓包工具,进行修改请求的host和路径地址为我们自己的,其实抓包工具就是做了一个中间人攻击的角色

Burp配置一下默认的host和端口,当然也可以自行抓到包后自行修改



第二种:反编译APK为smali,然后修改对应的smali文件中请求的地址

# 反编译APK
apktool.jar d -f vuls_v4.4.apk -o vuls_v4.4

# 修改好后回编译APK并签名以及丢到模拟器中运行
java -jar apktool.jar b vuls_v4.4 -o vuls_v4.4-key.apkjava -jar  signapk.jar testkey.x509.pem testkey.pk8 vuls_v4.4-key.apk vuls_v4.4-key_debug.apk

如果应用动态加载代码之前未做签名校验,利用目录穿越漏洞进行覆盖,可实现稳定的任意代码执行。此外由于在文件系统中写入了可执行文件,还可以实现持久化攻击的效果

在Android中,System.loadLibrary()是从应用的lib目录中加载 .so文件,而System.load()是用某个.so文件的绝对路径加载,这个.so文件可以不在应用的lib目录中,可以在SD卡中,或者在应用的files目录中,只要应用有读的权限目录中即可

在files目录中,应用具有写入权限,利用ZIP文件目录遍历漏洞可以替换掉原先的so文件,达到远程命令执行的目的。而应用的lib目录是软链接到了/data/app-lib/应用目录,属于system用户,第三方应用在执行时没有写入/data/app-lib目录的权限

0x03 海豚浏览器ZIP文件目录遍历【漏洞复现】

样本下载地址:https://www.apkhere.com/down/com.dolphin.browser.express.web_11.4.18_free

现在知道了一个应用的沙盒数据的详细信息,比如一些隐私数据存放在SharedPreferences.xml中,那么这时候我们可以利用这个漏洞,把恶意文件命名成 ../../../../data/data/xxx.xxx.xxx/shared_pref/info.xml,这样在使用ZipEntry进行解压文件的时候,因为直接使用了ZipEntry.getName方法或者文件名,然后直接释放解压到本地了,所以这时候就相当于替换了本应用的沙盒数据了,这个也是利用了app本身的权限来写入沙盒数据

海豚浏览器海豚浏览器的主题设置中允许用户通过网络下载新的主题进行更换,如下:

dwp 文件是海豚浏览器自己定义的主题文件包,本质上是一个 zip 包,里面有如下三个资源文件:

GET http://opsen-static.dolphin-browser.com/resources/atheme/110010060sea_1498465941.dwp HTTP/1.1

tar -tf 110010060sea_1498465941.dwp

或使用7z压缩工具的命令

7z l 110010060sea_1498465941.dwp

那么,如何实现zip目录穿越了,是不是可以尝试去构建一个这样的zip包,去替换浏览器的下载包,并重命令去文件名,使得替换浏览器中的关键文件

由于海豚浏览器并未对解压的文件进行验证,且安卓系统zip库的默认行为是允许解压文件到所在目录之外,因此通过中间人攻击,修改HTTP请求返回主题文件zip包中的内容,可以实现在海豚浏览器拥有权限的目录写文件

使用如下 Python 代码生成一个可以触发该漏洞的 zip 包:

# -*- coding: utf-8 -*-import zipfile
import sysif __name__ == "__main__":  try:with open("og_test_zip.txt", 'rb') as f:binary = f.read()zipFile = zipfile.ZipFile("og_test_zip.zip", "a", zipfile.ZIP_DEFLATED)info = zipfile.ZipInfo("og_test_zip.zip")zipFile.writestr("../../../../../data/data/com.dolphin.browser.express.web/files/og_test_zip.txt", binary)zipFile.close()except IOError as e:raise e


从上我们已知,海豚浏览器的主题设置中允许用户通过网络下载新的主题进行更换,主题文件其实是一个ZIP压缩文件。通过中间人攻击的方法可以替换掉这个ZIP文件。替换后的ZIP文件中有重新编译过的libdolphin.so。此so文件重写了JNI_OnLoad()函数,此so文件以“../../../../../data/data/com.dolphin.browser.express.web/files/libdolphin.so”的形式存在恶意ZIP文件中。海豚浏览器解压恶意ZIP文件后,重新的libdolphin.so就会覆盖掉原有的so文件

动态链接库文件libdolphin.so,并没有放在应用数据的lib目录下,而是放在了files目录中,导致存在被覆盖的风险。

加载使用的地方是com.dolphin.browser.search.redirect包中的SearchRedirector:

使用的是System.load()来加载libdolphin.so而非System.loadLibrary(),在Android中,System.loadLibrary()是从应用的lib目录中加载.so文件,而System.load()是用某个.so文件的绝对路径加载,这个.so文件可以不在应用的lib目录中,可以在SD卡中,或者在应用的files目录中,只要应用有读的权限目录中即可。

在files目录中,应用具有写入权限,通过网络中间人攻击,同时利用ZIP文件目录遍历漏洞,替换掉文件libdolphin.so,达到远程命令执行的目的

应用的lib目录是软链接到了/data/app-lib/应用目录,如果libdolphin.so文件在lib目录下就不会被覆盖了,第三方应用在执行时没有写入/data/app-lib目录的权限:

0x04 修复建议

  • 使用 https 并且对证书进行正确校验,防止中间人攻击,可以在一定程度上增加攻击难度

  • 对重要的ZIP压缩包文件进行数字签名校验,校验通过才进行解压

  • 当App中使用zipInputStream类对zip压缩包进行解压操作时,在zipEntry.getName()获取文件名后,必须添加过滤代码对文件名中可能包含的“…/”进行,要注意ZipEntry.getName()对于Zip包中有“…%2F”的文件路径不会进行处理

  • 检查 zip 压缩包中在调用getName()方法之后,判断路径中是否包含 ../或者 ..字符。如果有包含 ../或者 ..字符就做过滤判断,即:文件名称不能包含"…/"这种特殊字符,检查”…/”的时候不必进行URI Decode(以防通过URI编码”…%2F”来进行绕过)

while(( zipEntry = zipInputStream.getNextEntry()) != null ){String entryName = zipEntry.getName();if(entryName.contains("../")){continue;// 或者// throw new Exception("发现不安全的zip文件解压路径!")}...
}
  • 在调用ZipEntry.getName()方法之后,调用File的getCanonicalPath()方法,获取绝对路径,要判断该路径是否在要解压目录的子目录
  • 对重要的 zip 压缩包文件进行数字签名校验,校验通过才进行解压
  • 更换 zip 解压方式,不使用 ZipEntry.getName()的方式,使用 ZipInputStream 替代
  • Google 建议的修复方案:
InputStream is = new InputStream(untrustedFileName);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));
while((ZipEntry ze = zis.getNextEntry()) != null) {File f = new File(DIR, ze.getName());String canonicalPath = f.getCanonicalPath();if (!canonicalPath.startsWith(DIR)) {// SecurityException}// Finish unzipping…
}

参考链接

https://blog.csdn.net/u010889616/article/details/80955250

https://mp.weixin.qq.com/s/2sQcsKVIXlz2v8u-AQ5Uqw

https://www.jianshu.com/p/f4c05a436785

https://segmentfault.com/a/1190000005785252

http://blog.neargle.com/SecNewsBak/drops/%E6%B5%B7%E8%B1%9A%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8E%E6%B0%B4%E6%98%9F%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E8%AF%A6%E8%A7%A3%20.html


你以为你有很多路可以选择,其实你只有一条路可以走


[免费专栏] Android安全之ZIP文件目录遍历漏洞相关推荐

  1. Android安全开发之ZIP文件目录遍历

    1.ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在"../"的字符串,攻击者可以利用多个"../"在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原 ...

  2. [免费专栏] Android安全之动态调试APP的一些技巧「Android Studio调试」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  3. [免费专栏] Android安全之静态逆向APK应用浅析「手动注入smali」+「IDA Pro静态分析so文件」+「IDA Pro基础使用讲解」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  4. [免费专栏] Android安全之Android Xposed插件开发,小白都能看得懂的教程

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  5. [免费专栏] Android安全之数据存储与数据安全「详解」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  6. [免费专栏] Android安全之检测APK中调试代码是否暴露敏感信息

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  7. [免费专栏] Android安全之ADB命令总结「收藏版」

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  8. [免费专栏] Android安全之APK逆向入门介绍

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  9. [免费专栏] Android安全之Android APP应用程序的汉化功能 (修改so中的字符串内容)

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

最新文章

  1. Robosense 32线lidar ——SLAM
  2. Win2008 server backup系统备份组件安装
  3. linux内核 漏洞扫描,Linux kernel中存在15年的漏洞
  4. spring里头各种获取ApplicationContext的方法
  5. CentOS --kickstart服务器搭建(一)
  6. 编译protobuf-3.11.4 错误: aclocal-1.15: command not found的解决办法
  7. Xshell 基本使用方式 (1) -- 使用Xshell 连接 VMware下的linux系统
  8. Linux环境变量详解
  9. web前端黑客技术揭秘 读书笔记
  10. 更换S60第三版程序图标
  11. springboot+vue+websocket 消息推送
  12. css3做各种角度三角形
  13. 银联无卡涉及关键词整理
  14. 宋宝华:为了不忘却的纪念,评Linux 5.13内核
  15. Matlab中freqz函数使用
  16. Betwin实现电脑一分为二
  17. 2014年总结——回眸过去
  18. Arduino Uno 与 触摸模块 ttp223 实验详录
  19. 从宗教信仰看东西方文化对人的影响
  20. 关于程序的编译和解释!

热门文章

  1. github文件夹有白色箭头且无法打开的解决方法
  2. Golang——解析GBK编码XML文件
  3. android之UI美化
  4. 贴自己喜欢的几首英文歌曲
  5. 基于movable-view的微信小程序拖拽排序(含源码)
  6. 破解华为电脑耳机弹窗+超级终端
  7. sw无法打开--解决办法
  8. python中如何实现复制粘贴_引子,Python中PyQt5实现复制粘贴,程序界面如何访问系统剪贴板...
  9. 关于集合removeAll方法的性能测试
  10. android 各种Span使用得当可以用Edittext做一个文本编辑器