前段时间有人问我,你挖掘过哪些比较厉害的漏洞?有什么比较坎坷的漏洞挖掘经历可以拿出来讲讲吗?

这些年一直搞内部的安全审计,直接的结果就是思维上只关注风险,而不在极限利用,也没有怎么挖掘过外面的漏洞,惭愧了

比如我发现一个地方有Zip解压缩的路径穿越,那我就直接会去风险平台报一个相关的漏洞,业务线提交修复,我再审一遍,这个风险的生命周期就算完成了

在这个过程中,我始终关注的是风险本身,目的是发现它的存在,而没有去思考它能造成的危害

我们来看最近一个小米手机系统的漏洞,也是一个Zip解压缩导致的路径穿越,它会进行一个HTTP请求,那HTTP请求在局域网就有被劫持的攻击场景

  • https://bugs.chromium.org/p/apvi/issues/detail?id=50

如果是以前,我只会想到这里有一个路径穿越,它能覆写掉指定路径下的文件,路径穿越常见的利用是覆写掉一个可执行的二进制文件,比如从应用私有路径下去动态加载一个Dex或者So文件,说要利用,类似这种的我思路是有,但没有太多的实践,所以真要动起手来去利用,我也没有太多的信心一定能行

我对MIUI半年前的一个ROM做了一次扫描,扫出来的漏洞路径如下

IMAGE

我解释下为什么路径回溯停止在方法run():在我们构建函数调用图的时候,经常会碰到多线程的语法,多线程有相当多的场景是创建一个扩展Runnable的类来实现,使用前就会创建一个类对象实例,所以我对Path Finder的处理是在这个类的构造方法<init>里添加对方法run()的调用,这个处理方式未必在所有场景都准确,好处是控制流图比原先完整,能搜到很多有趣的路径,缺点是图太大,原先一分钟的扫描扫了好几个小时都没扫描完,我目前怀疑是图成环了,还有待解决

再回到一开始说的,我没怎么有拿得出手的漏洞可以跟人聊,所以最近我在优化了Path Finder之后,对某大厂的多个应用做了一次客户端安全审计,有一个案例很有意思,接下来我会脱敏讲讲这个漏洞是怎么发现的,可能的利用方式以及坎坷的分析过程

我们在审计一个应用的时候,一定是先看AndroidManifest文件,看哪些组件能调用,哪些组件有权限限制

当我按照常规分析一个导出组件的时候,我发现它会获取其中一个字段进行校验,校验是单独使用一个加解密类来实现,它调用的是解密方法,而在解密方法下面,是一个加密方法,这个地方引起了我的思考,按理来说,如果是RSA算法,一般不会存在加密的方法,客户端只进行解密,所以我继续往里分析,发现它最终调用到一个So库里的解密方法

这时候需要分析So了,简单看了下对应的解密函数,是使用的TEA算法,但我真的不想分析,怎么办呢?

此时我想起前面的加密方法,可以合理猜测,是不是存在一种情况,可以让我们先构造出一个能通过校验的值,然后调用加密方法,获得加密后的值作为校验字段的值传入,在校验的过程中解密,最终通过校验

使用Frida主动调用的方式进行加解密操作,输出的结果验证了我的猜想

到这里就绕过了导出组件调用者的校验,剩下的就是漏洞点,它会拿到我调用这个组件时传入的Intent里的一个字段作为调用组件的名字,再把传入的Intent作为Extras,那这里的利用场景就有了些许苛刻了

以前我们拿到私有组件越权调用的漏洞,会去分析FileProvider的配置,获取到FileProvider的读写权限,利用代码的模板如下,先打开漏洞组件,然后漏洞组件拿传入Intent里的"next"字段作为Intent直接打开,这是一种可利用的模型

Intent next = new Intent();
next.setClassName(getPackageName(), getClass().getCanonicalName());
next.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
next.setData(Uri.parse("content://com.xxx.xxx.fileprovider/..."));    // 需要获取读写权限的私有文件路径
Intent intent = new Intent();
intent.setClassName("com.xxx.xxx", 漏洞组件名);
intent.putExtra("next", next);
startActivity(intent);

这里的漏洞场景并不符合上面这种利用,所以我思考了其它的方式

第一种是WebView相关的,看能不能进行WebView相关的漏洞利用,发现了一处跟小米那个漏洞相似的,它硬编码了HTTP域名,并且开启了JS执行与文件访问,那配合局域网劫持就可以进行私有文件读取

第二种是逻辑相关的,比如我打开这个组件之后,会进行某些业务逻辑的执行,这个之前也发现过不少,但是这一次我并没有发现太多有危害的

第三种是我比较喜欢的通知弹窗,这个是跟逻辑相关,也是我花了最多时间进行分析的一个环节,现在各家都有推送组件,不过一般是使用Service作为后台保活,在服务端远程下发消息进行弹窗,那这里直接是使用不了的

第四种是把私有组件打开转换为任意广播发送,这里的广播是带权限的那种或者带调用者判断的,以及任意服务调用,也是带权限的那种

先从第三种开始说起,我并不是直接从组件入口开始分析,而是等待通知的出现,最终出现弹窗的时候,我去获取调用路径,Objection的启动配置如下

android hooking watch class_method android.app.NotificationManager.notify --dump-args --dump-backtrace --dump-return

第一次触发了之后,我发现它的调用路径回溯截止在一个方法run()

(agent) [373750] Called android.app.NotificationManager.notify(int, android.app.Notification)
(agent) [373750] Backtrace:
	android.app.NotificationManager.notify(Native Method)
	...
	com.xxx.xxx.xxx.b.f$a.run(Native Method)
	android.os.Handler.handleCallback(Handler.java:790)
	android.os.Handler.dispatchMessage(Handler.java:99)
	android.os.Looper.loop(Looper.java:164)
	android.os.HandlerThread.run(HandlerThread.java:65)

显然这就是我们上面提到过的多线程导致的路径截断,解决办法也很简单,我们勾住它的构造方法

android hooking watch class_method com.xxx.xxx.xxx.b.f$a.$init --dump-args --dump-backtrace --dump-return

此时我们又得到了另一个方法run()

(agent) [556440] Called com.xxx.xxx.xxx.b.f$a.f$a(com.xxx.xxx.xxx.b.f, android.content.Context, android.content.Intent, com.xxx.xxx.xxx.xxx)
(agent) [556440] Backtrace:
	com.xxx.xxx.xxx.b.f$a.<init>(Native Method)
	...
	com.xxx.xxx.xxx.b.f$1.run(Native Method)
	android.os.Handler.handleCallback(Handler.java:790)
	android.os.Handler.dispatchMessage(Handler.java:99)
	android.os.Looper.loop(Looper.java:164)
	android.os.HandlerThread.run(HandlerThread.java:65)

循环如此即可得到最上面的调用者,有个小缺点就是,这个方法需要运营小妹妹给我发推送消息,每出现一次推送,我才能触发一次注入脚本,得到新的调用路径,这个环节我花了近两天,Java层调用路径接近十五层,最后可惜的是,我们不可控这部分的逻辑,它是Native往Java层传回来的

第四种情况,我分析了不同的APK,发现了一个可以调用导出的任意服务,它会获取我传入的Intent里的一个字段作为Intent去启动服务,但是这个服务有限制,必须是导出的,不过它只限制了导出,并没有限制权限,所以如果我们能够找到一个有权限的导出服务,并且里面有可利用的逻辑,那就有的玩,花了一天时间,没找到能用的

我一直很喜欢逻辑漏洞的利用,经常看我文章的同学们是比较清楚的,但近些年来,很多组件都设置为不导出,真要找这种攻击入口难度大了不少

时代在变化,除了这些厂商对外的应用之外,或许可以更多关注下系统ROM的应用安全,我知道很多手机厂商都在搞,但几百个应用,总会有漏洞的,就像开头小米那个漏洞,对吧

IMAGE