DongTai IAST Java Agent分析

渗透技巧 1年前 (2022) admin
543 0 0

0x1 背景知识

IAST 概念虽然很早就提出了,但是在实际使用情况中,因为其生产无侵入性与漏误报情况,实际使用效果还是不过的。

1.1 IAST

  • DAST (Dynamic Application Security Testing)
  • IAST (Interactive Application Security Testing)
  • RASP(Runtime Application Self-protection)
  • SAST (Static Application Security Testing)

属IAST 最模糊,与三者都有纠缠。

1.2 主动 与 被动

1.2.1 主动IAST

主动IAST很简单,可以简单理解为 动态IAST = DAST + RASP

DongTai IAST Java Agent分析

1.2.2 被动IAST

被动型IAST是指使用动态污点分析技术,不需要扫描端,直接判断敏感参数是否为用户可控。插桩程序需要实现动态污点分析逻辑,实时监控程序污点数据变化,检测污点是否由source传播到sink点。

DongTai IAST Java Agent分析

单看解释是有些疑问的:

  1. 如果经历了安全函数,理论上用户输入会被过滤,变得不可控,这个怎么在被动上实现?
  2. 如果只是判断用户输入是否可控,为什么需要整个source 到sink 的stack,只看source 和sink 不行吗?

0x2 DongTai IAST

洞态IAST,原灵芝IAST,是国内首款开源的IAST,采用的也是“被动IAST” 技术,带着以上的问题,我们探究探究DongTai IAST的实现。

建议先看看su18师傅的《洞态 IAST 试用》,参考【1】,su18师傅的文章写的极好。

2.1 前提

DongTai 开源的有很多安全项目,这里只关注这两个

DongTai 支持Java/Python/PHP/GO,这里仅对Java Agent 进行分析。
一开始以为Server功能比较简单,其实不是,Server也实现了一套污点逻辑,通过Agent上报的调用链进行判断,不过这里暂时先不关注。

2.2 Server

Server 采用Django + Celery,Celery broker 用的Redis,Django 数据库用的MySQL。重点关注自定义规则

2.2.1 规则

  • 污点源方法 Source
  • 传播方法 Propagate
  • 过滤方法 Filter
  • 危险防范 Sink
DongTai IAST Java Agent分析
  • 规则详情:方法的signature
  • 污点输入:O/Px
  • 污点输出:O/Px/R
  • Hook深度:是否也允许Hook子类的方法

2.2.2 传播方法PROPAGATE

如果分析过GadgetInspector 或者Tabby 的污点传播,那么对这些规则应该不会陌生。

传播方法描述的是一个污点在函数的路径,其中

  1. 污点输入可以是:
    • this,也就是object
    • args,方法的参数
  2. 污点输出可以是:
    • this,可以是object、field
    • return,返回值
    • args,注意这里,也就是Tabby 对GI的改进点

Source、Filter、Sink 都可以看作一种特殊的传播方法propagate。

2.2.3 污点分析逻辑

其实在Server 上,也是有一套污点判断逻辑,@TODO 待补充。

2.3 Agent

2.3.1 JAVA INSTRUMENTATION 插桩

Java 两种插桩方式

  • premain agent 方式
  • agentmain attach 方式
1
2
3
4
5
6
7
8
9
10
11
12
13
public class AgentMainTest {
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        instrumentation.addTransformer(new DefineTransformer(), true);
    }

    static class DefineTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            System.out.println("premain load Class:" + className);
            return classfileBuffer;
        }
    }
}

在transfrom 中实现动态修改类,从而实现方法的Hook。

在DongTai中,具体为io.dongtai.iast.agent.AgentLauncher

1
2
3
4
io.dongtai.iast.agent.AgentLauncher#premain/agentmain
io.dongtai.iast.agent.AgentLauncher#install
io.dongtai.iast.agent.AgentLauncher#loadEngine
io.dongtai.iast.agent.manager.EngineManager#install

install 会从server下载

  • dongtai-core.jar
  • dongtai-spy.jar

然后调用其中的install进行安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean install() {
    String spyPackage = EngineManager.getInjectPackageCachePath();
    String corePackage = EngineManager.getEnginePackageCachePath();
    try {
        JarFile file = new JarFile(new File(spyPackage));
        inst.appendToBootstrapClassLoaderSearch(file);
        file.close();
        if (IAST_CLASS_LOADER == null) {
            IAST_CLASS_LOADER = new IastClassLoader(corePackage);
        }
        classOfEngine = IAST_CLASS_LOADER.loadClass(ENGINE_ENTRYPOINT_CLASS);
        String agentPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
        classOfEngine.getMethod("install", String.class, String.class, Integer.class, Instrumentation.class,
                        String.class)
                .invoke(null, launchMode, this.properties.getPropertiesFilePath(),
                        AgentRegisterReport.getAgentFlag(), inst, agentPath);
        setRunningStatus(0);
        setCoreStop(false);
1
2
3
4
5
com.secnium.iast.core.AgentEngine#_init
com.secnium.iast.core.AgentEngine#install
	com.secnium.iast.core.AgentEngine#run
		TransformEngine#start
			Instrumentation#addTransformer(IastClassFileTransformer)
1
2
IastClassFileTransformer#retransfrom
IastClassFileTransformer#transfrom

2.3.2 RETRANSFROM: HOOK预处理

IastClassFileTransformer#retransfrom 中实现了对Hook类的修改。具体逻辑如下:

  1. findForRetransform 发现所有的待Hook类,具体通过inst.getAllLoadedClasses 获取所有加载的类
  2. 通过configMatcher.isHookClassPoint(clazz) 简单筛选是否需要Hook,过一遍黑名单
  3. 通过classDiagram 获取该class的父类、接口
  4. 如果其中有在IastHookRuleModel.isHookClass()的,则会进入hook。isHookClass
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public boolean isHookClass(String className) {
        return hookClassnames.contains(className) || superClassHookPoints.contains(className) || hookBySuffix(className);
    }
    
    private static boolean hookBySuffix(String classname) {
        for (String suffix : instance.suffixHookPoints) {
            if (classname.endsWith(suffix)) {
                return true;
            }
        }
        return false;
    }

    DongTai IAST Java Agent分析

问题:可以看到,Hook点内置,并没有控制台配置的sources/propagate/filter/sink,是在哪里实现hook的呢?

答案:不要被hook这个词迷惑,这里的isHookClass只有在findForRetransform中被用到,用来标记需要重载的类,并不是所有需要hook的类,需要hook的泪是在transfrom中的plugins dispatch中实现的。

  1. 随后调用 retransformClasses() 会让类重新加载,从而使得注册的类修改器能够重新修改类的字节码,这要就会调用之前通过 addTransformer() 注册的 IASTClassFileTransformer 中重写的 transform() 方法。

2.3.3 TRANSFROM

具体逻辑如下:

  1. 如果是iast的类,则不进行任何处理
  2. 设置DONGTAI_STATE 标志,表示是否是IAST 内部代码
  3. 实现SCA,这个后续再跟
  4. 判断当前类是否在hook点黑名单。在在blacklist.txt 里维护了个7W多的hook黑名单:
    • agent自身的类
    • 已知的框架类、中间件类
    • 类名为null
    • JDK内部类且不在hook点配置白名单中
    • 接口
  5. 创建 ClassWriter,依然是使用 COMPUTE_FRAMES 自动计算帧的大小,并且重写了getCommonSuperClass() 方法,在计算两个类共同的父类时指定ClassLoader。
  6. 创建 IASTContext 上下文,初始化 PluginRegister,这个类中包含了一个全局常量 PLUGINS,里面保存了很多的处理插件,这些类都实现了 DispatchPlugin 接口,这个接口包含两个方法:
    • dispatch():分发不同的 classVisitor 处理对应的类
    • isMatch():判断是否命中当前插件
  7. 在 ClassVisitor 中又通过重写 visitMethod() ,注册继承至 AbstractAdviceAdapter 的实现类,这些类重写父类的 before()/after() ,实际上是 AdviceAdapter 的 onMethodEnter()/onMethodExit() 实现了字节码的插入。详见下面plugins 一节。
DongTai IAST Java Agent分析

2.3.4 PLUGINS

默认内置的Plugins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public PluginRegister() {
        this.plugins = new ArrayList<DispatchPlugin>();
        this.plugins.add(new DispatchSpringApplication());
        this.plugins.add(new DispatchJ2ee());
        //PLUGINS.add(new DispatchJsp());
        this.plugins.add(new DispatchCookie());
        this.plugins.add(new DispatchDubbo());
        this.plugins.add(new DispatchKafka());
        this.plugins.add(new DispatchJdbc());
        this.plugins.add(new DispatchShiro());
//        this.plugins.add(new DispatchHandlerInterceptor());

        //PLUGINS.add(new DispatchSpringAutoBinding());
        this.plugins.add(new DispatchClassPlugin());
        plugins.add(new DispatchGrpc());
    }

以DispatchShiro为例,重写了readSession方法,在内部增加了SpyDispatcherHandler#getDispatcher SpyDispatcher#isReplayRequest等逻辑调用,具体逻辑在SpyDispatcherImpl 中。

DongTai IAST Java Agent分析
DongTai IAST Java Agent分析

Plugins中,DispatchClassPlugin 比较特殊,dispatch中逻辑交由 PropagateAdviceAdapter/SinkAdviceAdapter/SourceAdviceAdapter处理,也就是控制台配置的Source、Sink、Propagate逻辑。

DongTai IAST Java Agent分析

2.3.5 SPYDISPATCHERIMPL

大部分的插桩逻辑都在SpyDispatcherImpl 中实现,比如SourceAdviceAdapter 的before。

DongTai IAST Java Agent分析

IastClassFileTransformer 在初始化的时候进行了SpyDispatcherHandler的初始化SpyDispatcherImpl,以供全局使用。

0x3 功能分析

3.1 SCA

DongTai IAST Java Agent分析

通过sendReport接口发送给server

DongTai IAST Java Agent分析

3.2 漏洞判断

这块su18师傅已经讲了个大概逻辑,但是没具体讲污点传播,这里详细讲下。

以普通http漏洞为例,这个过程会经历

1
2
3
4
5
6
7
8
9
10
DispatchClassPlugin#dispatch
	SourceAdviceAdapter#before/after
    PropagateAdviceAdapter#before/after
    SinkAdviceAdapter#before/after
      AbstractAdviceAdapter#captureMethodState
          SpyDispatcherImpl#collectMethodPool
              HttpImpl.solveHttp
              SourceImpl.solveSource
              PropagatorImpl.solvePropagator
              SinkImpl.solveSink

3.2.1 污点跟踪逻辑

重点关注全局变量EngineManager

  • EngineManager.TRACK_MAP
    • 存的污点传播路径
    • 当开始准备跟踪时,进行初始化,如SourceImpl#sloveSource.
    • 当跟踪完成时,进行remove,如SpyDispatcherImpl#leaveHttp
  • EngineManager.TAINT_HASH_CODES
    • 存的污点值的hash
  • EngineManager.TAINT_RANGES_POOL
    • 存的污点值所在范围,污点一般为string,例如为hash所对应的string的子串

重点关注变量MethodEvent

其中
source = event.inValue / event.inValueString
target = event.outValue / event.outValueString

source:O = event.object
source:P = event.argumentArray
target:O = event.
target:P = event.
target:R = event.returnValue/event.OutValue

具体的流程如下:

1. HttpImpl#solveHttp

一次请求到达了应用程序,首先进入 http 节点处理逻辑,进行标记和预处理。

  • 重放处理/标记
  • 黑名单/后缀
  • 增加http 头
2. SourceImpl#solveSource

请求进入到 source 点,将 event 放入 EngineManager.TRACK_MAP 中,将 source 的结果放入了 EngineManager.TAINT_POOL 污点池中。

Source的传播比较单一,一般只有

  • O-R
  • P-R

具体逻辑如下:

  • 如果method return为空或者为int之类的无效污染,退出
  • 如果method 为getAttrribute,那么仅允许白名单内的arg,这两步是为了执行效率考虑
  • 将event 加入EngineManager.TRACK_MAP 中
  • 将source 的结果放入了 EngineManager.TAINT_HASH_CODES/TAINT_RANGES_POOL 污点池中:如果source的结果是Map、Collection、Array之类的,会进一步遍历其所有值,都加入TAINT_HASH_CODES/TAINT_RANGES_POOL
DongTai IAST Java Agent分析

疑问:solveSource 中只有P-R,没有处理O-R的情况,是漏掉了?

DongTai IAST Java Agent分析

SourceImpl 是起点,起点的inValue 对结果并没有什么影响。重要的只是outValue,是整个污染连的起点。因此简单讲inValue设置成argumentArray并不会影响结果。

3. PropagatorImpl#solvePropagator

请求进入propagator 节点时,根据配置判断传播节点的参数是否存在于污点池中,如果是,则将传播节点 event 放入 EngineManager.TRACK_MAP 中。

Propagate有很多种传播逻辑:

  • P-R
  • P-O
  • P-P 这个少见
  • O-P
  • O-O 这个也少见,只有一条规则java.lang.StringBuffer.setLength(int)
  • O-R

除此之外,Propagate 还可以由多个条件组合,因此逻辑比较复杂。

入污点的处理逻辑如下:

  • 如果source 为O,则inValue 为event.object,判断inValue 是否在历史的EngineManager.TAINT_HASH_CODES 中,如果在,则
    • 将event 加入EngineManager.TRACK_MAP
    • 调用setTarget,设置出污点
  • 如果source 为Px,则inValue 为数组,其中每个值为event.argumentArray[x],判断event.argumentArray[x] 是否在历史的EngineManager.TAINT_HASH_CODES 中,如果在,则组成新的Array,赋值给inValue,同样
    • 将event 加入EngineManager.TRACK_MAP
    • 调用setTarget,设置出污点
  • 如果有多条件组合
    • 将每个条件分拆,存入inValues数组
    • 每一个条件的inValue值过一遍污点池,不在的则剔除
    • 这里多条件处理取巧了,如果有and,那么必须每个条件都满足conditionSources.length == condition
    • 最后同样的将event.setInValue(inValues.toArray)
    • 将event 加入EngineManager.TRACK_MAP
    • 调用setTarget,设置出污点

出污点的处理逻辑如下:

  • 如果target 为O
    • event.setOutValue(event.object)
    • trackTaintRange
  • 如果target 为R
    • event.setOutValue(event.returnValue);
    • trackTaintRange
  • 如果target 为P,与inValues类似,outValues 为每一个对应的argumentArray[x]
    • event.setOutValue(outValues.toArray());
    • 针对每一个x,trackTaintRange(propagator, event)

疑问:其中trackTaintRange 比较疑惑,还未发现其具体功能?

随着程序的多次调用,程序还会再次进入多次传播节点,这些节点也会被放入 EngineManager.TRACK_MAP 中。

疑问:P-P 逻辑中,都是从event.argumentArray 中获取参数,但是此时是在after中获取的,也就是执行完成,source P 其实已经变成了target P,这里可能存在问题,待验证。

DongTai IAST Java Agent分析

TODO:这里有条规则,待验证。

DongTai IAST Java Agent分析
4. SinkImpl#solveSink

应用程序走到最后的 sink 点时,根据 sink 点的配置,判断 sink 点的参数是不是在 TAINT_POOL 中,如果是,则将 sink 点写入 EngineManager.TRACK_MAP 中。

Sink传播逻辑也很简单,只有source:

  • P
  • O

具体逻辑如下:

  • 如果是P,则获取对应P的值event.argumentArray[x],判断是否在污点池内,如果在则
    • event.setInValue(sourceValue)
    • 将event 加入EngineManager.TRACK_MAP
  • 如果是O,则判断event.object 是否在污点池内,如果在则同样
    • event.setInValue(event.object)
    • 将event 加入EngineManager.TRACK_MAP
DongTai IAST Java Agent分析
5. SpyDispatcherImpl#leaveHttp

在应用程序执行完,回到http 节点,最后执行到 leaveHttp 时,会调用 GraphBuilder 构造污点调用图并发送至云端。

3.3 其他漏洞检测

  • 越权检测
  • CRYPTO_WEEK_RANDOMNESS
  • CRYPTO_BAD_MAC
  • CRYPTO_BAC_CIPHERS
  • COOKIE_FLAGS_MISSING

0x4 测试

暂时不关注,待后续有需求再补充

0x5 总结

被动IAST 的核心逻辑是整个污点链的跟踪,维护了一个污点hash表,从污点source,再通过默认收集的propagate 规则,收集其传播之后的污点值,都收集到污点hash表中,最终在sink中判断是否是污点成功传播至此。

分析过程中发现的一些问题:

  1. filter 规则貌似当作了普通的propagate 规则,并没有做其他处理,这个难道不会造成误报吗?
    DongTai IAST Java Agent分析
  2. propagate P-P传播的时候,都是取得event.argumentArray,这里可能存在问题,待测试。
  3. 比较依赖配置的propagate 规则,这里包含了所有的可能导致污点变化的传播,如果不全可能导致漏报。
  4. Array/Map 之类在传播过程中,会导致误报,因为规则仅支持P级粒度的映射。

在分析DongTai IAST之后,发现自己之前对被动IAST 完全误解了。

0x6 参考

 

 

原文始发于m0d9:DongTai IAST Java Agent分析

版权声明:admin 发表于 2022年11月13日 下午7:49。
转载请注明:DongTai IAST Java Agent分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...