文章来自Automated Discovery of Deserialization Gadget Chains, Blackhat2018
演讲视频:https://www.youtube.com/watch?v=wPbW6zQ52w8
工具:https://github.com/JackOfMostTrades/gadgetinspector
原理介绍
看下来的感觉就是将jar包里的(可以反序列化的)所有类的属性设为污点(因为反序列化时攻击者都可以控制),接下来做污点传播,Source为toString(),equals()等方法,Sink为Runtime.exec(),Method.invoke(),URL.openStream等方法,而污点清除函数就是黑名单类中的所有函数。
Step1
枚举包内的所有类和方法,对于类收集其父类和子类集合:
Output:
1 | List<ClassReference> discoveredClasses |
Step2
发现函数传递的数据流,如代码段1中invoke可以传递对象value(0),代码段2中invoke既可以传递arg(1)也可以传递value(0),因为我们假设(a)受污染的对象中,所有成员变量都是受污染的(b)程序中所有分支都是可达的(污染传播都是这样做的),0和1为参数index,0代表this。在此之前要先分析函数调用关系,因为如果有A函数:FuncA(){return FuncB();}
,那么A函数的返回与B函数有关。
output:
1 | Map<MethodReference.Handle, Set<Integer>> passthroughDataflow |
Step3
构建调用图,即函数A将自身参数arg1传递给函数B的参数arg2 [email protected]>[email protected]
Output:
1 | Set<GraphCall> discoveredCalls |
构图时,只考虑存在污点的边:
这里发现gadgetinspector.TaintTrackingMethodVisitor#getStackTaint有点问题,每次get的时候并未拿到Taint
Step4
枚举入口,如hashCode,toString,程序只能发现已知入口的POP链
Output:
1 | List<Source> discoveredSources |
Step5
BFS搜索图找到chains
程序分析
断点打gadgetinspector.GadgetInspector#main
污点传播(Step2-Step3)
Step2-Step3都是通过ASM做的,例如Step3:
其中继承了基本Visitor:
gadgetinspector.TaintTrackingMethodVisitor,该类模拟函数执行,主要污点传播逻辑都在这
visitMethodInsn
字节码调用函数时会触发该函数,对于污点传播而言,应该做如下事情
- 从堆栈取出参数
- 检查函数的参数是否存在污点
- 返回函数结果是否存在污点
- 将结果推入堆栈
实际上就是模拟了一个函数的调用过程,如果函数中有嵌套调用那么情况会更复杂
其属性savedVariableState.StackVars记录着函数参数,及其是否有污点:
如上图,该函数(StringBuilder.append())有两个参数,第一个参数是this,有污点,第二个参数无污点。
特殊的传播
在gadgetinspector.TaintTrackingMethodVisitor#PASSTHROUGH_DATAFLOW处定义了一些特殊的传播,保证污点能够向后传播,比如说String类型A+Taint
的传播,idx1,idx2,idx3分别是类,函数名,函数签名,后面是能够传播的污点参数idx,如StringBuilder.append()会将参数0,参数1传播:
而gadgetinspector.TaintTrackingMethodVisitor#visitMethodInsn这里需要处理函数参数调用传播污点的情况,如A(B(xxx))
SINK点
Sink点定义在gadgetinspector.GadgetChainDiscovery#isSink:
判断该类是否可以反序列化
对于java原生序列化,判断逻辑在:gadgetinspector.javaserial.SimpleSerializableDecider#applyNoCache,其他序列化方法找对应的包名。
黑名单
对于java原生序列化,逻辑在:gadgetinspector.javaserial.SimpleSerializableDecider#isBlacklistedClass,可以追加黑名单。
程序的一个Issue
原版污点传播存在一个Bug,考虑如下情况:
1 | class A { |
这里程序无法发现gadget,因为在读取getcmd()字节码后,污点传播执行如下代码(TaintTrackingMethodVisitor.java#L675)向外传播污点:
这一意思是说,如果passthroughDataflow(第二步)计算得到getcmd能够传播污点,那么该污点将作为参数推入堆栈(即传播到exec()函数参数上),但是这里的passthroughDataflow并没有B.getcmd()的记录,只有A.getcmd()记录——因为B.getcmd()继承A.getcmd(),因此污点传播中断了。
因此需要寻找其父类查看是否有该方法,将图中的代码块换成如下:
1 | if (passthroughDataflow != null) { |
但是这样就还存在一个问题,因为gadgetinspector.data.InheritanceMap#getSuperClasses返回的是一个集合,因此,我们并没法知道真实调用exec()的是哪一个函数:
考虑如下例子:
1 | class A { |
在这里,前面的代码会返回getcmd()传播污点——父类的任意一个getcmd()能传污点就有污点,但是实际上其并没有污点,因为实际上getcmd()调用的是B.getcmd(),因此会产生误报,预计使用接口的话这一点会更加明显。
缺陷
无视控制流,导致误报
无法识别反射的SINK点,导致漏报
相关代码:https://github.com/Anemone95/gadgetinspector
附录:ASM源码
其污点传播用ASM做的,因此记一下关于ASM的一些使用方法
ClassReader
ClassVisitor
定义在读取Class字节码时会触发的事件,如类头解析完成、注解解析、字段解析、方法解析等。
MethodVisitor
定义在解析方法时触发的事件,如方法上的注解、属性、代码等。这里只是操作码不含操作数
visitInsn:IALOAD~SALOAD触发该函数
visitVarInsn(opcode, var):ILOAD~ALOAD触发该函数,var为操作数
B4 getfield