一个被研究人员命名为“寄生兽”的安全漏洞影响市面上数以千万的APP,包括互联网巨头BAT等厂商的众多流行移动产品。利用该漏洞,攻击者可以直接在用户手机中植入木马,盗取用户的短信照片等个人隐私,盗取银行、支付宝等账号密码等。
发现该漏洞的360手机安全研究团队VulpeckerTeam向安全牛表示,寄生兽属于APK缓存代码劫持漏洞,他们已经向补天漏洞响应平台提交了这个漏洞。目前补天已经将相关详情通知给各大厂商的安全响应中心(src),请各厂商及时自查和修复。
漏洞原理
由于安卓应用的升级都需要重新安装程序,为了避免频繁升级给用户体验和开发都带来了不便,所以现在市面上的app大都使用插件机制来做到无缝升级和扩展功能,APP只需要引入相应的插件文件即可完成升级。但这种做法却隐藏了不为人知的安全隐患。
VulpeckerTeam的安全专家黎博解释,APP插件机制的实现方式是把相关功能编写成单独apk或jar文件,然后在程序运行时用DexClassLoader函数动态加载,进行反射调用。由于安卓应用的代码缓存机制会优先加载运行APK的缓存代码odex,因此如果针对插件的odex文件进行攻击,开发者对于插件文件所做的各种保护都将失效。
安全人员检测了市面上一些用到插件机制的主流app,发现部分app对DexClassLoader的第一个参数的插件文件做了校验,在加载前对要加载的apk或jar做了签名或者MD5等校验。这在一定程度上保护了插件的完整性,防止了代码注入攻击。但是,几乎没有厂商对第二个参数的缓存文件进行保护,这就导致了新的攻击点的出现。
经过在安卓源码中的验证,攻击者需要修改odex文件中的两个参数crc及modWhen以实现恶意代码的注入。
上图dex_old是修改前的odex文件,dex_new是修改后的dex文件。两个文件的md5不一样,但是crc及modWhen却是一样的,这样就可以绕过DexClassLoader的校验。
APK缓存代码感染虽然危害很大,但是一般情况下,开发者都会将odex的缓存目录选在App的私有目录下,google官方文档也提示开发者,不要将odex的缓存路径选择在外部存储器(SD卡)上。所以,攻击者如果没有足够的权限是无法替换app私有目录下的文件的。
在之前安全牛报道过的三星输入法漏洞利
用中,攻击者用到了安卓zip解压缩的一个漏洞。可以遍历目,并在解压zip文件时以原app的权限覆盖任意文件。如果该app用到了插件机制,则对应的插件的odex文件也会被备份。攻击者可以先用adb backup备份用户数据,对备份下来的odex文件进行修改,然后用adb restore恢复回去,就可以替换掉正常的odex文件,造成代码劫持。
漏洞危害
安全人员测试了市面上几款主流的app,凡是用到了这种插件机制的app,都没有对DexClassLoader的第二个参数做校验,一旦攻击者将恶意代码注入APK的缓存代码(odex)中,开发者对apk/jar做的各种保护都将失效。而且这种攻击,APK自身很难发现,即使重启或关机,只要app一运行,恶意代码就会随之运行。
为了证明漏洞危害的严重性,安全人员选择了三类代表性的APP验证漏洞:
输入法类APP:搜狗输入法,百度输入法等
可感染代码输出logcat
浏览器类APP:UC浏览器等
替换支付宝SDK,盗取密码
通用类SDK:高德地图SDK,微信SDK等
感染代码输出logcat
由于验证漏洞需要花大量精力,因此还需要各大厂商自查并修复。
解决方案
APK缓存劫持漏洞的核心有两点,一个是软件开发者没有考虑odex的安全问题,另一个是没有对zip解压缩时的恶意文件名做检测,所以防护上也应该从这方面做考虑。
•odex完整性校验
由于对odex一般是由系统(DexClassLoader)自动生成的,且odex与apk/jar是相对独立的,开发者事先无法知道odex文件的MD5等信息,所以很难通过MD5校验等手段保护odex的完整性;同时,系统的DexClassLoader函数只是校验了odex中的crc、modWhen字段,可以很轻易的被绕过。所以,目前对odex的防护只能由app自身来做,可以在每次运行DexClassLoader之前,清楚已经
存在的odex;另外,在odex第一次生成之后,存储odex文件的MD5值,以后每次调用DexClassLoader的时候都对odex文件进行
MD5校验。
•对劫持odex的攻击入口的修复
对zip解压缩的漏洞,只需要在调用
zipEntry.getName()的时候,过滤返回值中的"../"跳转符。对于引用的第三方的zip库也需要注意,可以用上面的测试用例测试一下第
三方库是否有zip解压缩的漏洞;调用DexClassLoader动态加载dex的时候,第二个参数不要指定在sdcard上;在manifest里指
定allowBackup=”false”。