DongTai IAST 实现分析
# 模块
DongTai-agent-java 由agent.jar、dongtai-core.jar 、dongtai-inject.jar、dongtai-servlet.jar四部分构成,其中:
- agent.jar用来管理 agent 的生命周期和配置。agent 的生命周期包括下载、安装、启动、停止、重启、卸载。agent 的配置包括配置应用启动模式、漏洞检验模式、是否开启代理等。
- dongtai-core.jar是核心 jar 包,其主要功能是:字节码插桩、数据采集、数据预处理、数据上报、第三方组件管理等。
- dongtai-inject.jar是间谍 jar 包,用于注入至BootStrap ClassLoader,后续在目标应用中调用dongtai-core.jar中的数据采集方法。
- dongtai-servlet.jar用于获取应用发送的请求以及收到的响应,用于数据展示以及请求重放功能。
# dongtai-agent模块
dongtai-agent/pom.xml:109
指定了agent的入口点
Premain-Class是通过java -javaagent:agent.jar -jar app.jar
这种指定agent方式中首先进入的agent程序入口点 premain
Agent-Class 是通过attach的入口点 agentmain
io.dongtai.iast.agent.AgentLauncher
和io.dongtai.iast.agent.Agent
Agent类通过io.dongtai.iast.agent.Agent#doAttach
jattach.exe 将jar包注入到jvm进程中
其实就是调用了 io.dongtai.iast.agent.AgentLauncher#agentmain
不管是premain还是agentmain最终都会进行安装判断,进入io.dongtai.iast.agent.AgentLauncher#install
上报注册事件,然后进入io.dongtai.iast.agent.AgentLauncher#loadEngine
将inst对象传给io.dongtai.iast.agent.manager.EngineManager创建一个引擎管理器的单例对象,然后用这个对象创建一个监控线程,调用io.dongtai.iast.agent.monitor.MonitorDaemonThread#startEngine 启动引擎
并且启动了agentMonitorDaemonThread后台监控线程
监控线程会启动更多的子线程来监控心跳、性能、配置等等
关键在 io.dongtai.iast.agent.monitor.MonitorDaemonThread#startEngine
extractPackage判断自身依赖包agent.jar
、dongtai-core.jar
、dongtai-inject.jar
、dongtai-servlet.jar
是否存在
install将dongtai-inject.jar加入到BootstrapClassLoader,新建了一个IastClassLoader对象加载dongtai-core.jar中的com.secnium.iast.core.AgentEngine
类,调用其install函数
由此,该模块就是注入了一个dongtai-inject.jar到bootstrapclassloader,然后反射调用com.secnium.iast.core.AgentEngine#install
# dongtai-core模块
com.secnium.iast.core.AgentEngine#install 根据上面的反射传递过来的参数来初始化AgentEngine引擎,然后run
构造函数中有两个IEngine对象 ConfigEngine TransformEngine
在初始化引擎init时会调用这两个对象的init,并且传递属性配置和策略管理对象
io.dongtai.iast.core.init.impl.ConfigEngine#init 中加载策略
io.dongtai.iast.core.init.impl.TransformEngine#init 中初始化Transformer转换对象
在引擎run时会调用IEngine的start
io.dongtai.iast.core.init.impl.TransformEngine#start 中调用java.lang.instrument.Instrumentation#addTransformer(java.lang.instrument.ClassFileTransformer, boolean)
进行类文件转换(重写class)
io.dongtai.iast.core.bytecode.IastClassFileTransformer 这部分应该是dongtai的核心
IastClassFileTransformer实现了java.lang.instrument.ClassFileTransformer接口的transform函数
|
|
Once a transformer has been registered with addTransformer, the transformer will be called for every new class definition and every class redefinition. Retransformation capable transformers will also be called on every class retransformation. The request for a new class definition is made with ClassLoader.defineClass or its native equivalents. The request for a class redefinition is made with Instrumentation. redefineClasses or its native equivalents.
这个接口的作用在于:一旦使用java.lang.instrument.Instrumentation#addTransformer(java.lang.instrument.ClassFileTransformer, boolean)注册了转换函数之后,在jvm中每一次获取类定义或者类重定义时都会调用这个transform函数
也就是说,dongtai重写了所有jvm中存在的class字节码,看一下dongtai是怎么转换的
io.dongtai.iast.core.init.impl.TransformEngine#start
中调用addTransformer之后继续调用 io.dongtai.iast.core.bytecode.IastClassFileTransformer#reTransform
这里先通过 findForRetransform 找到需要重写字节码的类,然后调用java.lang.instrument.Instrumentation#retransformClasses重写类
先看findForRetransform
做了几步操作
- configMatcher.canHook(clazz)
- classDiagram.getDiagram(className); 获取类族(类的继承、接口等关系)
- PolicyManager.isHookClass(clazzName) 等策略判断
canHook移除了不能修改的类、接口类,还有一些动态代理、aop类、agent自身的类、数组类及一些在黑名单的类等等
classDiagram.getDiagram(className)会将类的父类和父接口返回
PolicyManager.isHookClass(clazzName)会进行策略判断是否应该hook class。
然后继续调用inst.retransformClasses(clazz)重写类,retransformClasses函数会调用io.dongtai.iast.core.bytecode.IastClassFileTransformer#transform
transform中调用io.dongtai.iast.core.bytecode.sca.ScaScanner#scanForSCA实现SCA组件漏洞功能。
也就是这个
接着用asm读写类的字节码,asm的基础可以谷歌搜一下看看,这里不细说,就是一个操纵字节码的库。
ClassVisitor cv = plugins.initial(cw, classContext, policyManager)
用插件分发创建对应的AbstractClassVisitor对象
AbstractClassVisitor有多个子类,分别对不同功能进行适配
跟进io.dongtai.iast.core.bytecode.enhance.plugin.PluginRegister#initial
注册了多个插件,拿DispatchJdbc来讲
根据classname的不同适配不同的数据库,在io.dongtai.iast.core.bytecode.enhance.plugin.service.jdbc.MysqlJdbcDriverAdapter
中
在parseURL下hook,交由MysqlJdbcDriverParseUrlAdviceAdapter
在parseURL执行完之后会执行onMethodExit,其中invokeStatic(ASM_TYPE_SPY_HANDLER, SPY_HANDLER$getDispatcher);
调用java.lang.dongtai.SpyDispatcherHandler#getDispatcher
除了一些特殊的函数hook以外,还有一个通用的DispatchClassPlugin
他的dispatch中用了一个ClassVisit对方法进行切面,当函数匹配策略中的函数时,返回一个io.dongtai.iast.core.bytecode.enhance.plugin.core.adapter.MethodAdviceAdapter实例,处理对应的策略函数
ClassVisit 构造函数中声明了三个适配器,分别对应sink、source、propagator规则
MethodAdviceAdapter继承自AbstractAdviceAdapter抽象类
AbstractAdviceAdapter有两个抽象函数before和after
分别会在onMethodEnter和onMethodExit触发,相当于切面切在了方法前/后执行
MethodAdviceAdapter重写onMethodEnter和onMethodExit函数
以此来执行sink、source、propagator的method事件,onMethodEnter和onMethodExit最终会调用io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl中的相关函数。
当一次http请求结束后,由io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl#leaveHttp构建调用图发送给云端
http请求的切面由io.dongtai.iast.core.bytecode.enhance.plugin.framework.j2ee.dispatch.ServletDispatcherAdviceAdapter处理,由DispatchJ2ee插件进行dispatch
接着看,以SinkAdapter为例 onMethodEnter中会进行trackMethod来捕获当前方法的状态及数据
trackMethod进一步调用io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl#collectMethod
在collectMethod处理sink、source、propagator规则,并用MethodEvent封装
进一步处理sink、source、propagator污点传播
将被切面的函数导出之后发现,sink点如图中所示,多了几行函数调用
source点
# 如何实现污点传播
io.dongtai.iast.core.EngineManager 关键类,存储了三个关键的全局变量
分别对应污点路径、污点hash、污点范围
在处理source时io.dongtai.iast.core.handler.hookpoint.controller.impl.SourceImpl#solveSource
用trackTarget来跟踪对象标记污点
在trackObject中对不同的数据类型进行拆开处理
在最后将污点值的hash及TaintRange的hash放入对应的污点池中
io.dongtai.iast.core.handler.hookpoint.controller.impl.PropagatorImpl#solvePropagator 在传播处理中,根据配置判断传播节点的参数是否存在于污点池中,如果是,则将传播节点event放入EngineManager.TRACK_MAP中。
传播方式由污点来源和污点去向构成
- 对象O
- 方法参数P
- 方法返回值R
由这三者做交叉组合构成传播方式,传播完毕后同样加入到TRACK_MAP、TAINT_HASH_CODES、TAINT_RANGES_POOL中
最后是sink的处理
io.dongtai.iast.core.handler.hookpoint.controller.impl.SinkImpl#solveSink
io.dongtai.iast.core.handler.hookpoint.vulscan.dynamic.DynamicPropagatorScanner#scan
在sinkSourceHitTaintPool中判断污点是否命中污点池
TaintPoolUtils.poolContains(parameter, event) 判断
命中之后加入TRACK_MAP。最终在http结束后在io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl#leaveHttp构建调用图上报云端
# 总结
插桩,根据规则收集污点,维护一个method pool和taint hash pool实现污点传播,上报云端。
代码写的不太好看。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
如果你觉得这篇文章对你有所帮助,欢迎赞赏或关注微信公众号~