Android Zip解压缩目录穿越导致文件覆盖漏洞

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Zip解压缩目录穿越导致文件覆盖漏洞相关的知识,希望对你有一定的参考价值。

文章目录

漏洞简述

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

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

漏洞原理

在 Linux/Unix 系统中 ../ 代表的是向上级目录跳转,有些程序在当前工作目录中处理到诸如用“../../../../../../../../../../../etc/hosts”表示的文件,会跳转出当前工作目录,跳转到到其他目录中。

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

漏洞实例

来通过一个实例代码演示上述漏洞,编写 APP,主活动类的代码如下:

public class MainActivity extends AppCompatActivity 
    public static String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button_Activity1=(Button) findViewById(R.id.button);
        button_Activity1.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                String zipPtath = getApplicationContext().getCacheDir()+"/test.zip";//压缩包路径
                String unzipPath = getApplicationContext().getCacheDir().toString();//解压路径
                try 
                    unzipFile(zipPtath,unzipPath);
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        );
    


    public void unzipFile(String zipPtath, String outputDirectory)throws IOException 
        /**
         * 解压assets的zip压缩文件到指定目录
         * @param context上下文对象
         * @param assetName压缩文件名
         * @param outputDirectory输出目录
         * @param isReWrite是否覆盖
         * @throws IOException
         */
        Log.i(TAG,"开始解压的文件: "  + zipPtath + "\\n" + "解压的目标路径:" + outputDirectory );
        // 创建解压目标目录
        File file = new File(outputDirectory);
        // 如果目标目录不存在,则创建
        if (!file.exists()) 
            file.mkdirs();
        
        // 打开压缩文件
        InputStream inputStream = new FileInputStream(zipPtath); ;
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        // 读取一个进入点
        ZipEntry zipEntry = zipInputStream.getNextEntry();
        // 使用1Mbuffer
        byte[] buffer = new byte[1024 * 1024];
        // 解压时字节计数
        int count = 0;
        // 如果进入点为空说明已经遍历完所有压缩包中文件和目录
        while (zipEntry != null) 
            Log.i(TAG,"解压文件 入口 1: " +zipEntry );
            if (!zipEntry.isDirectory())   //如果是一个文件
                // 如果是文件
                String fileName = zipEntry.getName();
                //Log.i(TAG,"解压文件 原来 文件的位置: " + fileName);
                //fileName = fileName.substring(fileName.lastIndexOf("/") + 1);  //截取文件的名字 去掉原文件夹名字
                Log.i(TAG,"解压文件 的名字: " + fileName);
                file = new File(outputDirectory + File.separator + fileName);  //放到新的解压的文件路径
                file.createNewFile();
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                while ((count = zipInputStream.read(buffer)) > 0) 
                    fileOutputStream.write(buffer, 0, count);
                
                fileOutputStream.close();
            
            // 定位到下一个文件入口
            zipEntry = zipInputStream.getNextEntry();
            Log.i(TAG,"解压文件 入口 2: " + zipEntry );
        
        zipInputStream.close();
        Log.i(TAG,"解压完成");
    

正常压缩包

本地压缩一个正常的 zip 文件,包含 test.txt 文件:

将其 push 到 /data/user/0/com.bwshen.test/cache 目录下:

运行 APP 程序对其进行解压缩,log 如下:

查看文件解压缩结果,程序可正常完成解压缩任务:

Ok,至此一切正常。下面来看看如果 ZIP 压缩包里面包含异常文件名的文件的话会是什么结果。

恶意压缩包

使用如下 Python 代码生成一个包含 …/test.txt 文件的压缩文件 test.zip:

import zipfile

def zip():
    try:
        with open("test.txt", "r") as f:
            binary = f.read()
            zipFile = zipfile.ZipFile("test.zip", "a", zipfile.ZIP_DEFLATED)
            info = zipfile.ZipInfo("test.zip")
            //zipFile.writestr("../../../../../data/data/com.test.demo/files/test.txt", binary)
            zipFile.writestr("../test.txt", binary)
            zipFile.close()
    except IOError as e:
        raise e

if __name__ == '__main__':
    zip()

生成的 test.zip 里的文件如下:

将其 push 到 /data/user/0/com.bwshen.test/cache 目录下:

运行 APP 程序对其进行解压缩,log 如下:

查看文件最终解压缩后的路径,发现成功解压缩并穿越到上级目录:

此处只是演示了穿越到上级目录,攻击者完全可以将文件穿越、解压缩到应用程序的其他私有路径下。

历史漏洞

以海豚浏览器远程代码执行漏洞为例。

任意命令执行

海豚浏览器的主题设置中允许用户通过网络下载新的主题进行更换,主题文件其实是一个 ZIP 压缩文件。通过中间人攻击的方法可以替换掉这个 ZIP 文件。替换后的 ZIP 文件中有重新编译过的 libdolphin.so。此 so 文件重写了 JNI_OnLoad() 函数:

此 so 文件以“../../../../../../../../../../data/data/mobi.mgeek.TunnyBrowser/files/libdolphin.so”的形式存在恶意 ZIP 文件中。海豚浏览器解压恶意 ZIP 文件后,重新的 libdolphin.so 就会覆盖掉原有的 so 文件,重新运行海豚浏览器会弹出 Toast 提示框:

能弹出Toast说明也就可以执行其他代码。

问题根因分析

这里分析下此漏洞产生的原因是:

1、主题文件其实是一个 ZIP 压缩包,从服务器下载后进行解压,但是解压时没有过滤 getName() 返回的字符串中是否有“../”:

2、动态链接库文件 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 目录的权限:

漏洞防御

针对上述 ZIP 文件解压缩的目录穿越导致文件覆盖漏洞,修复建议如下:

  1. 对重要的 ZIP 压缩包文件进行数字签名校验,校验通过才进行解压。
  2. 检查 Zip 压缩包中使用 ZipEntry.getName() 获取的文件名中是否包含 ../ 或者 ..,检查 ../ 的时候不必进行 URI Decode(以防通过URI编码 ..%2F 来进行绕过),测试发现 ZipEntry.getName() 对于 Zip 包中有 ..%2F 的文件路径不会进行处理。

具体做法可以在使用 java.util.zip 包中 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…

以上是关于Android Zip解压缩目录穿越导致文件覆盖漏洞的主要内容,如果未能解决你的问题,请参考以下文章

Android Zip解压缩目录穿越导致文件覆盖漏洞

iOS解压缩_路径穿越_解析

linux下压缩,解压缩的方法

Linux文件解压缩详解-转

Linux系统-解压缩命令集合

linux压缩命令之zip总结