DongTai IAST 实现分析

警告
本文最后更新于 2023-02-24,文中内容可能已过时。

# 模块

DongTai-agent-java 由agent.jar、dongtai-core.jar 、dongtai-inject.jar、dongtai-servlet.jar四部分构成,其中:

  1. agent.jar用来管理 agent 的生命周期和配置。agent 的生命周期包括下载、安装、启动、停止、重启、卸载。agent 的配置包括配置应用启动模式、漏洞检验模式、是否开启代理等。
  2. dongtai-core.jar是核心 jar 包,其主要功能是:字节码插桩、数据采集、数据预处理、数据上报、第三方组件管理等。
  3. dongtai-inject.jar是间谍 jar 包,用于注入至BootStrap ClassLoader,后续在目标应用中调用dongtai-core.jar中的数据采集方法。
  4. dongtai-servlet.jar用于获取应用发送的请求以及收到的响应,用于数据展示以及请求重放功能。

# dongtai-agent模块

dongtai-agent/pom.xml:109

指定了agent的入口点

image.png

Premain-Class是通过java -javaagent:agent.jar -jar app.jar这种指定agent方式中首先进入的agent程序入口点 premain Agent-Class 是通过attach的入口点 agentmain

io.dongtai.iast.agent.AgentLauncherio.dongtai.iast.agent.Agent

Agent类通过io.dongtai.iast.agent.Agent#doAttach jattach.exe 将jar包注入到jvm进程中

image.png

其实就是调用了 io.dongtai.iast.agent.AgentLauncher#agentmain

不管是premain还是agentmain最终都会进行安装判断,进入io.dongtai.iast.agent.AgentLauncher#install

image.png

上报注册事件,然后进入io.dongtai.iast.agent.AgentLauncher#loadEngine

image.png

将inst对象传给io.dongtai.iast.agent.manager.EngineManager创建一个引擎管理器的单例对象,然后用这个对象创建一个监控线程,调用io.dongtai.iast.agent.monitor.MonitorDaemonThread#startEngine 启动引擎

并且启动了agentMonitorDaemonThread后台监控线程

image.png

监控线程会启动更多的子线程来监控心跳、性能、配置等等

关键在 io.dongtai.iast.agent.monitor.MonitorDaemonThread#startEngine

image.png

extractPackage判断自身依赖包agent.jardongtai-core.jardongtai-inject.jardongtai-servlet.jar是否存在

install将dongtai-inject.jar加入到BootstrapClassLoader,新建了一个IastClassLoader对象加载dongtai-core.jar中的com.secnium.iast.core.AgentEngine类,调用其install函数

image.png

由此,该模块就是注入了一个dongtai-inject.jar到bootstrapclassloader,然后反射调用com.secnium.iast.core.AgentEngine#install

# dongtai-core模块

com.secnium.iast.core.AgentEngine#install 根据上面的反射传递过来的参数来初始化AgentEngine引擎,然后run

image.png

构造函数中有两个IEngine对象 ConfigEngine TransformEngine

image.png

在初始化引擎init时会调用这两个对象的init,并且传递属性配置和策略管理对象

image.png

io.dongtai.iast.core.init.impl.ConfigEngine#init 中加载策略

image.png

io.dongtai.iast.core.init.impl.TransformEngine#init 中初始化Transformer转换对象

image.png

在引擎run时会调用IEngine的start

image.png

io.dongtai.iast.core.init.impl.TransformEngine#start 中调用java.lang.instrument.Instrumentation#addTransformer(java.lang.instrument.ClassFileTransformer, boolean)进行类文件转换(重写class)

image.png

io.dongtai.iast.core.bytecode.IastClassFileTransformer 这部分应该是dongtai的核心

IastClassFileTransformer实现了java.lang.instrument.ClassFileTransformer接口的transform函数

1
2
3
4
5
6
7
byte[]
transform(  ClassLoader         loader,
            String              className,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer)
    throws IllegalClassFormatException;

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

image.png

这里先通过 findForRetransform 找到需要重写字节码的类,然后调用java.lang.instrument.Instrumentation#retransformClasses重写类

先看findForRetransform

image.png

做了几步操作

  1. configMatcher.canHook(clazz)
  2. classDiagram.getDiagram(className); 获取类族(类的继承、接口等关系)
  3. 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组件漏洞功能。

image.png

也就是这个

image.png

接着用asm读写类的字节码,asm的基础可以谷歌搜一下看看,这里不细说,就是一个操纵字节码的库。

image.png

ClassVisitor cv = plugins.initial(cw, classContext, policyManager) 用插件分发创建对应的AbstractClassVisitor对象

AbstractClassVisitor有多个子类,分别对不同功能进行适配

image.png

跟进io.dongtai.iast.core.bytecode.enhance.plugin.PluginRegister#initial

image.png

注册了多个插件,拿DispatchJdbc来讲

image.png

根据classname的不同适配不同的数据库,在io.dongtai.iast.core.bytecode.enhance.plugin.service.jdbc.MysqlJdbcDriverAdapter

image.png

在parseURL下hook,交由MysqlJdbcDriverParseUrlAdviceAdapter

image.png

在parseURL执行完之后会执行onMethodExit,其中invokeStatic(ASM_TYPE_SPY_HANDLER, SPY_HANDLER$getDispatcher);调用java.lang.dongtai.SpyDispatcherHandler#getDispatcher

除了一些特殊的函数hook以外,还有一个通用的DispatchClassPlugin

image.png

他的dispatch中用了一个ClassVisit对方法进行切面,当函数匹配策略中的函数时,返回一个io.dongtai.iast.core.bytecode.enhance.plugin.core.adapter.MethodAdviceAdapter实例,处理对应的策略函数

ClassVisit 构造函数中声明了三个适配器,分别对应sink、source、propagator规则

image.png

MethodAdviceAdapter继承自AbstractAdviceAdapter抽象类

image.png

AbstractAdviceAdapter有两个抽象函数before和after

image.png

分别会在onMethodEnter和onMethodExit触发,相当于切面切在了方法前/后执行

MethodAdviceAdapter重写onMethodEnter和onMethodExit函数

image.png

以此来执行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来捕获当前方法的状态及数据

image.png

trackMethod进一步调用io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl#collectMethod

image.png

在collectMethod处理sink、source、propagator规则,并用MethodEvent封装

image.png

进一步处理sink、source、propagator污点传播

将被切面的函数导出之后发现,sink点如图中所示,多了几行函数调用

image.png

source点

image.png

# 如何实现污点传播

io.dongtai.iast.core.EngineManager 关键类,存储了三个关键的全局变量

image.png

分别对应污点路径、污点hash、污点范围

在处理source时io.dongtai.iast.core.handler.hookpoint.controller.impl.SourceImpl#solveSource

image.png

用trackTarget来跟踪对象标记污点

image.png

在trackObject中对不同的数据类型进行拆开处理

image.png

在最后将污点值的hash及TaintRange的hash放入对应的污点池中

io.dongtai.iast.core.handler.hookpoint.controller.impl.PropagatorImpl#solvePropagator 在传播处理中,根据配置判断传播节点的参数是否存在于污点池中,如果是,则将传播节点event放入EngineManager.TRACK_MAP中。

传播方式由污点来源和污点去向构成

  1. 对象O
  2. 方法参数P
  3. 方法返回值R

由这三者做交叉组合构成传播方式,传播完毕后同样加入到TRACK_MAP、TAINT_HASH_CODES、TAINT_RANGES_POOL中

最后是sink的处理

io.dongtai.iast.core.handler.hookpoint.controller.impl.SinkImpl#solveSink

image.png

io.dongtai.iast.core.handler.hookpoint.vulscan.dynamic.DynamicPropagatorScanner#scan

image.png

在sinkSourceHitTaintPool中判断污点是否命中污点池

image.png

TaintPoolUtils.poolContains(parameter, event) 判断

命中之后加入TRACK_MAP。最终在http结束后在io.dongtai.iast.core.handler.hookpoint.SpyDispatcherImpl#leaveHttp构建调用图上报云端

image.png

# 总结

插桩,根据规则收集污点,维护一个method pool和taint hash pool实现污点传播,上报云端。

代码写的不太好看

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。