聊聊对目前Passive IAST的思考
之前一直有在研究Java插桩应用于安全防御以及检测方面的东西,主要分为RASP和IAST。IAST又叫交互式应用安全测试,它目前主要分为主动式(Active)和被动式(Passive)两种,上个暑假有机会重点接触了一下Passive IAST的一些研究工作。这里打算简单聊聊,也算是一个总结与思考。不会涉及太多技术细节,简单分享下我对的被动式IAST的应用以及优势还有劣势的看法以及存在的一些问题和新的思路。希望能和大家一起交流探讨。
Passive IAST原理
核心
利用Instrumentation API我们可以提供一个Agent代理用来监测和协助运行在JVM上的程序,可以在程序启动前修改类的定义。简单来说就是在运行的应用中织入一个我们的程序。而在这个程序中我们就拥有了获取当前应用的上下文,在应用运行中实时分析数据流以及调用栈的能力,同时也可以通过ASM对已经加载的class进行分析与修改。
在织入我们检测的逻辑代码后,被动式IAST主要是通过污点跟踪的方法来对漏洞进行检测,因为是实时数据流,所以这里我们称为动态污点传播分析。这是与静态扫描中污点分析的一个小区别,而其优势和劣势也主要在这里。
插桩&ASM
插桩技术是在保证目标程序原有逻辑完整的情况下,在特定的位置插入代码段,从而收集程序运行时的动态上下文信息。目前基于插桩技术实现Java程序的动态交互安全监测已经有一些实现形式,如RASP,IAST。在Java中插桩通过Instrument以及字节码操作工具(如:ASM,Javassist,Byte Buddy等)实现。细节此处也不展开了,具体可以参考这篇文章插桩技术在Java安全中的应用简述
动态污点传播
污点分析可以抽象成一个三元组<sources,sinks,sanitizers>
的形式,其中,source即污点源,代表直接引入不受信任的数据或者机密数据到系统中;sink即污点汇聚点,代表直接产生安全敏感操作(违反数据完整性)或者泄露隐私数据到外界(违反数据保密性);sanitizer即无害处理,代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害。污点分析就是分析程序中由污点源引入的数据是否能够不经无害处理,而直接传播到污点汇聚点。如果不能,说明系统是信息流安全的;否则,说明系统产生了隐私数据泄露或危险数据操作等安全问题。简单来说就是我们安全中常说的:一切输入都是有害的。
污点分析的处理过程可以分成以下3个阶段。
(1) 识别污点源和汇聚点
(2) 污点传播分析
(3) 无害处理

除了<sources,sinks,sanitizers>
,在实际过程中应该还有个Propagator,它主要是针对一些污点可能丢失的地方增加的方法。
接着我们具体说下动态污点传播,原理都是一样的,而动态主要是结合了插桩技术之后的产物。
举个简单的SSRF例子:
1 2 3 4 |
String urlString = request.getParameter("url"); URL url = new URL(urlString); URLConnection urlConnection = url.openConnection(); |
根据代码我们在IAST中设置RequestFacade.getParameter(java.lang.String)为我们设置的Source点;设置Propagator污点传播:如果URL<init>
接受的构造参数为污点,那么return值也标记为污点;Sink点最后设置为URL的openConnection()方法,检测接受的污点参数是否为自身。
触发请求
http://localhost:8888/011-ssrf-urlconnection.jsp?url=http://0x7f000001
查看IAST日志

可以看到我们从Source点中获取的污染return值,继续跟踪调用栈
之后接着是URL url = new URL(urlString);

可以看到日志中触发到了URL的构造方法,同时被污染参数也传进去了,程序判断后将return值也标记为污点。
跟进代码可以看到没有问题,此时spec参数是被污染参数。

继续跟进URLConnection urlConnection = url.openConnection();

查看应用中的调用栈,可以看到触发java.net.URL.openConnection()

最后一直往下实际会触发到sun.net.www.protocol.http.openConnection()
方法
查看IAST日志

可以发现记录到了这次调用栈,同时发现Sink点java.net.URL.openConnection()
进入的自身URL参数为污点,所以报告存在漏洞,由此一次动态的污点跟踪完成。
其实配合插桩动态污点分析主要优势是实时性,这样其实是相对静态污点分析的一个好处,因为静态污点分析更多的依赖分析出数据流图的算法,是可能存在路径爆炸成环问题,而在Java中存在各种动态的语言特性如反射,多态以及新的语法形式,静态污点分析往往对这种特性无能为力。

那IAST的动态污点分析则从本质上排除了种种问题,(如上图)我们通过分析应用实时结果与数据流可以轻松的跳过动态语言特性导致的问题,以及可达性分析算法可能出现的问题。除此以外在结合插桩技术后我们可以获得更多的程序运行实时的上下文信息,这极大的方便了检测时对各类数据分析的全面需求,可以更好的进行一些普通黑盒或者白盒不能完成的漏洞检测。但这同样会导致了一些问题,功在实时,过也在实时。完全的依赖程序自身的运行逻辑是可能导致一些隐式污点传播路径被我们忽略。也就是说这种方法更加依赖交互式自动化测试的全面性,因为它可能出现一些隐式的污点传播路径没有被触发到。其优点和缺点我们后面具体再讨论。
如何存储与处理污点传播
那么在进行插桩中如何存储污点以及如何判断污点呢?虽然我们知道Instrumentation API结合ASM可以修改类,那直接往类里面加一个标记属性不就好了,但我们还要注意的是因为JVM双亲委派的类加载方式的存在,你没有办法轻易的修改Bootstrap ClassLoader加载的核心API,比如Integer,String。当然也是有方法的比如写一个Integer类并编译
1 2 3 4 5 |
public Integer(int value) { System.out.println( "Customer Integer Initialized" ); this.value = value; } |
1 2 3 4 5 6 7 8 |
public class MyIntegerTest { public static void main( String[] args ) { System.out.println( new Integer( 100 ) ); } } |
在启动时通过java -Xbootclasspath/p:myinteger.jar MyIntegerTest
参数去替换核心API
输出如下:
1 2 3 |
Customer Integer Initialized 100 |
但这样也有点麻烦,而且遇到需要IAST热部署的场景就不行了。
除此以外你可能还会碰见用户自己写的实体类也需要整个标记污点,如果只给几个String,Interger这样的传播参数进行标记显然覆盖面比较少,灵活性不好。同时Java也是存在基本类型的比如byte数组,char这些是没法添加属性的。
如何解决?比较好的方法应该是通过一个Map将所有标记为污点的参数缓存下来(一定时间内删除),同时在其他栈中方法进入参数时去判断参数对象是否Map中containsValue,那么核心就是要设计一个合适的containsValue算法,会涉及到hashcode,equals一些的内容。还有就是将Propagator覆盖全,会减少污点丢失的情况。
Passive IAST优势
大致原理也已经简要说了一些,我们再转过头来看看被动式IAST优势,以及它主要是覆盖什么应用场景。
优势:
- 漏报率低,采用污点分析
- 检测中无重放数据,不会产生“脏”数据
- 检测精度高,可以结合数据流以及请求上下文来分析
- 支持代码层面的分析
- 支持数据包加密场景
- 实时性强,同时可以构造验证漏洞请求包
- 操作简单,方便漏洞定位与修复
- 解决应用系统安全测试与效率的矛盾
应用场景:
- 用于SDL中安全测试环节
- 适用于上线前的测试,测试人员无需了解安全
- 针对APP存在数据流量加密
- 安全人力缺乏状况
被动式与主动式IAST对比
主动式我们以百度开源的openrasp-iast来看,可以关注下https://github.com/baidu-security/openrasp-iast
主动式IAST实际上可以说是DAST+RASP,百度这个项目做了很好的融合,它通过python对Agent转发来的流量进行添加payload后重放,之后通过RASP去判断是否到达最后的Hook点。
它的优势是通过有效的攻击payload去测试,这样可以有一定效果的走入应用中的条件判断,也就是说被动式如果接受一个id参数,应用先去数据库去查询有没有这个值再去进行后面的流程,被动式是没办法自动的感知到这个过程的,会导致当后面触发sink点也会报出来;而主动式因为重放修改过id参数所以可以明确的知道这个值无法往下的流程走,则会减少这样的误报。当然这也引出了另一个问题,主动式会可能有脏数据,虽然说最后漏洞的触发点hook住了,但是没法保证在这个过程中是否有一些安全的方法进行了存储的操作并将这个没用的测试脏数据存进去。
被动式会完全相信用户设置的sanitizers,当污点进入清洁器后是没办法判断用户写的过滤函数是否有效,主动式在这方面有一定优势,虽然payload不会各种各样大量的发,但是有特征如单引号可以在一定程度上提高检测用户写的过滤函数的有效性。也是因为这个原因,实际上被动式漏报会更少,而主动式误报会更少。
至于场景方面,主动式因为要构造请求包重放,当遇到一些加密数据包的场景是没办法很好的覆盖的,反而被动式不需要重放则可以覆盖。不过复现的话主动会更轻松一点,因为它已经给你配好了payload,被动则需要自己再构造一下。在分布式中,各有利弊,被动式污点存取和传播需要考虑更好的方法,主动式对于输出的调用栈可能不是很全面,都可以参考下全链路监控的解决方法。

若按配置完整后的效果来说,当被动式IAST的Source,Sink,Propagator,Sanitizers覆盖范围全,用户提供的sanitizers有效,同时其中的validation可以将一些会进行数据库判断的值排除掉。被动式IAST会更胜一筹,因为它可以获取应用运行时状态的上下文信息更多,主动式目前只是主要关注sink点,而被动式在source和sink都有确定后是可以获取运行上下文以及前后请求的返回数据包,这样其实更能覆盖一些漏洞场景,比如涉及到前端渲染后页面触发的漏洞。
另一方面,也是前面提到过无论被动式还是主动式IAST是依赖用户的测试能力的,比如当执行一个条件判断,可以进入两个分支a,b。而测试只进入了a分支,导致b分支可能触发的漏洞没有发现。实际上也是去静态分析的一个区别。
对特定漏洞场景的新检测方式
因为被动式拥有获取整个应用上下文能力以及实时修改类的能力,那当我们利用这个特性可以达到一些在特定语言规则下检测之前没办法检测漏洞的能力。这里主要提一个对Java中JPA(Java Persistence API)的存储型XSS检测的例子简单说一下。
主要利用了一个DynamicSource,可以说是热更新增加Source点,使用了retransform机制。
大致思路路:在类加载时检测实体类是否带有persistence的注解比如@Entity,@Embedded等,也就是表明这些类与数据库对应表有映射关系,接下来运行中判断下污点进入该类的⽅法名开头是不是set,如果是去找这个类有没有对应getXxx方法,如果有就把getXxx⽅法标记并动态去增加⼀个source点(通过热更新类的方式)。

我们再来整体看一下这个流程,原理其实就是当一个类和数据库有映射关系时,那么如果我们执行过set对应属性,也就是代表了往这个表存过这个字段的值,我就可以断定这个值是可控的,所以它的get属性方法可以提取到我们写入的污点,则从数据库表中获取这个字段。所以getXxx动态成为一个新的source点。也由此达到了存储型XSS的检测方式。
目前存在的难点与挑战
- 多classLoader环境可能导致的错误插桩
- 分布式污点存储以及对比的方法
- Sink,Source,Propagator需要不断完善
- 热插拔以及热更新动态策略
- 欠污染,过污染,隐式污点传播,条件分支判断
- Sanitizers执行效果验证
- 插桩对native-image有无解决方案
思考
由于动态执行轨迹并不能完全反映出被执行的指令之间的控制依赖关系,那么是否可以实时加载类后采用静态分析得出的数据流图+Passive IAST,两者相互补充完善,来获取一个更全面的应用数据流图去解决一些分支问题或者隐式污点的预测。
主被动结合,将主动重放的优势配合动态污点,获取一个更准确的可能攻击流和验证Sanitizers效果来减少误报。
缓存每个session中请求触发的调用栈,通过相似度判断来区分当前session的用户身份,是否可能实现一些越权检测的能力。
参考
http://www.jos.org.cn/html/2017/4/5190.htm
https://github.com/firmianay/CTF-All-In-One/blob/master/doc/5.5_taint_analysis.md
https://www.ibm.com/developerworks/cn/java/j-lo-jse61/
https://www.freebuf.com/articles/web/216185.html
Passive IAST有什么产品没
可以看看Contrast,默安的雳鉴等 百度openrasp-iast商业版也要支持passive了
悬镜的灵脉iast灰盒测试,已经支持被动模式了。
开源网安VulHunter,国内做IAST比较早的产品
火线最近开源的洞态IAST也是passive方式的,分析发现检测方式还是挺有特色的