0x00 分析环境
apktool 版本:2.2.2 https://connortumbleson.com/2017/01/23/apktool-v2-2-2-released/
参考链接:https://security.tencent.com/index.php/blog/msg/122
0x01 XXE漏洞利用分析
原理:Apktool在解析AndroidManifest.xml文件时,没有禁用外部实体,导致了外部实体注入攻击漏洞
利用过程:
首先创建一个简单的APK,利用apktool反编译:
1 | mask@mask-virtual-machine:~/AndroidXXE$ java -jar apktool_2.2.2.jar d app-release.apk -o xxe |
反编译后,进入xxe文件夹修改AndroidManifest.xml:
原文件:
1 | <?xml version="1.0" encoding="utf-8" standalone="no"?> |
修改成:
1 | <?xml version="1.0" encoding="utf-8" standalone="no"?> |
然后保存,再使用apktool进行重打包:
1 | mask@mask-virtual-machine:~/AndroidXXE$ java -jar apktool_2.2.2.jar b xxe/ -o xxe.apk |
捕捉到了流量,那么确实是访问了
源码分析:
首先分析Main.java
这里接受传入的参数,d
单表反编译,b
代表重打包,因为这个漏洞是在重打包时触发的,那么我们就跟进cmdBuild()
这个函数
1 | private static void cmdBuild(final CommandLine cli) throws BrutException { |
前面一大堆都不重要,就是一些参数选项,最后一句开始build apk,那么我们跟进这个build
函数
1 | public void build(File appDir, File outFile) throws BrutException { |
继续跟进:
1 | public void build(final ExtFile appDir, File outFile) throws BrutException { |
前面一大堆不用看,直接看操作AndroidManifest.xml的地方,
1 | ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest); |
继续跟进:
1 | public static void fixingPublicAttrsInProviderAttributes(final File file) throws AndrolibException { |
这里对文件进行了处理:
1 | final Document doc = loadDocument(file) |
再跟进:
1 | private static Document loadDocument(final File file) throws IOException, SAXException, ParserConfigurationException { |
可以发现此处就是直接对AndroidManifest.xml文件进行解析,并没有禁用外部实体,那么来看看作者是怎样修复的
加入了禁用外部实体然后再解析:
1 | /** |
0x02 路径穿越漏洞
原理:Apktool在解析apktool.yml时,因为没有对“../“字符串过滤,导致漏洞发生
利用过程:
还是用刚才那个apk,依旧使用apktool进行反编译:
1 | mask@mask-virtual-machine:~/AndroidTraveral$ java -jar apktool_2.2.2.jar d app-release.apk -o demo |
编辑apktool.yml
原文件:
1 | !!brut.androlib.meta.MetaInfo |
对unknownFiles的值进行修改:
1 | unknownFiles: |
(Shell路径自行修改)
因为apk太简单,反编译后没有生成unknown文件夹,那么我们手动创建一个,并且添加一个Demo文件(unknown文件夹不能为空,否则后面再次反编译的时候,不会释放Shell文件)
- 在反编译文件夹下新建一个unknown文件夹,并且创建一个Demo文件(空白即可)
- 按照刚才Shell路径,在此路径下添加一个Shell文件
然后进行重打包:
1 | mask@mask-virtual-machine:~/AndroidTraveral$ java -jar apktool_2.2.2.jar b demo/ -o demo.apk |
然后我们将之前创建的Shell删除,再次使用apktool反编译:
1 | mask@mask-virtual-machine:~/AndroidTraveral$ java -jar apktool_2.2.2.jar d demo.apk -o poc |
那么可以看到,文件确实释放了
源码分析:
反编译时
1 | this.mAndrolib.decodeUnknownFiles(this.mApkFile, outDir, this.mResTable); |
跟进查看:
1 | public void decodeUnknownFiles(final ExtFile apkFile, final File outDir, final ResTable resTable) throws AndrolibException { |
再来看下重打包的时候:
1 | // we must go after the Apk is built, and copy the files in via Zip |
跟进:
1 | public void buildUnknownFiles(File appDir, File outFile, MetaInfo meta) |
这一句
1 | copyUnknownFiles(appDir, actualOutput, files); |
然后跟进:
1 | private void copyUnknownFiles(final File appDir, final ZipOutputStream outputFile, final Map<String, String> files) throws IOException { |
可以看到这里貌似并没有作啥路径的检测,那么就会导致可能导致存在非法路径,重打包后再解析就会触发漏洞
来看看作者更新后,左边为更新后,右边为原文件
从函数名来看是进行检测,跟进这个函数
1 | public static String sanitizeUnknownFile(final File directory, final String entry) throws IOException, BrutException { |
那么可以看到这里的报错提示就是绝对路径下的文件不允许,路径穿越不允许:)