长亭百川云 - 文章详情

浅析洞态iast产品

leveryd

88

2024-07-13

背景

之前的工作中我处理过一些洞态iast[1]的漏报误报案例,也逐渐了解这个项目。

本文记录我对洞态iast基本原理的理解,内容包括:

  • 洞态做漏洞检测的原理

  • 洞态中的污点是什么

  • 源码分析java-agent的业务逻辑

  • 举个例子:洞态怎么检测mybatis写的sql是否存在sql注入

怎么做漏洞检测?

如上,用户可以在server端配置四类规则:

  • 污点源方法:是获取api、rpc请求信息的接口或者类签名,比如javax.servlet.ServletRequest.getParameter(java.lang.String)

  • 传播方法:是字符串拼接、编码等接口或类签名,比如java.lang.String.<init>(java.lang.String)

  • 危险方法:是高危函数,比如javax.naming.Context.lookup(java.lang.String)

源码中有三个重要的数据结构,TAINT_POOL存放污点对象,TAINT_HASH_CODES存放污点对象的hashCode值,TRACK_MAP存放调用关系

当代码执行到被hook的传播方法时,会根据用户配置的"污点来源"规则,拿到对象(一般是函数的某个参数)去TAINT_POOLTAINT_HASH_CODES搜索匹配。如果能匹配上,就会根据用户配置的"污点去向"规则,生成污点对象并放到TAINT_POOL中,并将污点对象的hashCodes存放到TAINT_HASH_CODES中,最后将传播方法的调用关系存放到TRACK_MAP

当代码执行到被hook的危险方法时,和传播方法的逻辑比较类似,不过没有"污点去向"。

这里的"污点"是什么呢?

污点是什么?

最重要的概念是对象的hashcode/identifyHashCode,hashcode/identifyHashCode作为数据的唯一跟踪方法会被加入到污点池中,也会被用来判断是否在污点池中。

下面我带你通过一个我遇到过的误报案例来理解这个概念。

因为Java中相同字符串对象的hashcode/identifyHashCode是不变的,如下

String a = "123";String b = "123";System.out.println(System.identityHashCode(a));   // 1289696681System.out.println(System.identityHashCode(b));   // 1289696681

所以有时候即使危险函数的参数完全不可控,也会报警。如下代码中的iast17接口之前会误报(现已修复),因为iast会认为f.getName()返回的字符串对象123是污点。

@ResponseBody@RequestMapping("/iast17")public String iast17(@RequestParam("name") String name) {    ArrayList<String> a = new ArrayList<>();    a.add("123");    a.add(name); // a对象会被标记成污点    Iterator<String> b = a.iterator();    System.out.println(b.next());    System.out.println(b.next()); // "123"会被标记成污点    File f = new File("123");       return f.getName(); // 返回值"123"被认为是可控的,会产生误报}

iast为什么会认为"123"是污点呢?

因为执行a.add(name)时,下面的传播规则会使得a对象变成污点

在执行b.next()时,iterator.next()传播规则会让123字符串变成污点

流程浅析

collectMethodPool方法串联了"最重要"的业务流程。当java-agent启动时,会拉取server端规则,然后根据规则hook类,确保在被hook的方法执行前或者执行后能调用到collectMethodPool方法。在处理http请求时,collectMethodPool方法会判断当前是属于哪一类规则,并做对应的动作。

你可以从java-agent启动时和请求过来时两个场景来看业务逻辑。

java-agent启动时会找到所有jvm已经加载的类并重写字节码,如下

// https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/IastClassFileTransformer.java#L250public void reTransform() {    ...    Class<?>[] waitingReTransformClasses = findForRetransform();    // 找到所有待重写的类    ...    for (Class<?> clazz : waitingReTransformClasses) {    ...          inst.retransformClasses(clazz);   // 用asm重新生成字节码    ...    }}

因此实现了对污点源方法、传播方法、危险方法的hook,并且使得执行方法前或者执行方法后,调用captureMethodState方法。

// 污点源方法: https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/enhance/plugin/core/adapter/SourceAdviceAdapter.java#L26public class SourceAdviceAdapter extends AbstractAdviceAdapter {  ...  @Override  protected void after(int opcode) {      ...      captureMethodState(opcode, HookType.SOURCE.getValue(), true);      ...  }// 传播方法: https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/enhance/plugin/core/adapter/PropagateAdviceAdapter.java#L31public class PropagateAdviceAdapter extends AbstractAdviceAdapter {  ...  @Override  protected void after(final int opcode) {      ...      captureMethodState(opcode, HookType.PROPAGATOR.getValue(), true);      ...  }// 危险方法: https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/enhance/plugin/core/adapter/SinkAdviceAdapter.java#L31public class SinkAdviceAdapter extends AbstractAdviceAdapter {  ...  @Override  protected void before() {      ...      captureMethodState(-1, HookType.SINK.getValue(), false);      ...  }

captureMethodState 最终会调用collectMethodPool方法

// https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/enhance/plugin/AbstractAdviceAdapter.java#L103protected void captureMethodState(        final int opcode,        final int hookValue,        final boolean captureRet) {    ...    invokeInterface(ASM_TYPE_SPY_DISPATCHER, SPY$collectMethodPool);    pop();}// https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/bytecode/enhance/asm/AsmMethods.java#L131Method SPY$collectMethodPool = InnerHelper.getAsmMethod(        SpyDispatcher.class,        "collectMethodPool",        ...);

请求过来时,就会执行到collectMethodPool方法,方法中根据hookType处理。

// https://github.com/HXSecurity/DongTai-agent-java/blob/v1.7.7/dongtai-core/src/main/java/io/dongtai/iast/core/handler/hookpoint/SpyDispatcherImpl.java#L462@Overridepublic boolean collectMethodPool(Object instance, Object[] argumentArray, Object retValue, String framework,                                 String className, String matchClassName, String methodName, String methodSign, boolean isStatic,                                 int hookType) {    // hook点降级判断    ...    // 尝试获取hook限速令牌,耗尽时降级    ...    ...    MethodEvent event = new MethodEvent(0, -1, className, matchClassName, methodName,            methodSign, methodSign, instance, argumentArray, retValue, framework, isStatic, null);    if (HookType.HTTP.equals(hookType)) {        HttpImpl.solveHttp(event);    } else if (HookType.RPC.equals(hookType)) {        solveRPC(framework, event);    } else if (HookType.PROPAGATOR.equals(hookType) && !EngineManager.TAINT_POOL.isEmpty()) {   // 处理传播方法        PropagatorImpl.solvePropagator(event, INVOKE_ID_SEQUENCER);    } else if (HookType.SOURCE.equals(hookType)) {  // 处理污点源方法        SourceImpl.solveSource(event, INVOKE_ID_SEQUENCER);    } else if (HookType.SINK.equals(hookType)) {    // 处理危险方法        SinkImpl.solveSink(event);    }    ...}

举个例子:怎么检测接口是否存在SQL注入风险?

后端服务用mybatis时,${变量}的sql写法容易造成sql注入,而#{变量}底层会使用预编译通常不会产生sql注入问题,如下

// 第一个sql:存在sql注入select * from user where name=${name}// 第二个sql:不存在sql注入select * from user where name=#{name}

当用户请求/user?name=admin时,iast是怎么检查出第一种接口存在SQL注入风险,而不会对第二种接口误报呢?

实际上如果我们调试一下,就知道#$的写法调用的sql接口是有区别的,如下

// 使用 ${name}时conn.prepareStatement("select * from user where name="admin")// 使用#{name}时pstmt=conn.prepareStatement("select * from user where name=?)pstmt.setString(1, "admin")

洞态iast默认有一个危险方法规则是java.sql.Connection.prepareStatement(java.lang.String),当第一个参数是污点时,就会告警,规则如下。

所以使用 ${name}时,admin字符串对象是污点,"select * from user where name="admin"字符串对象也会被标记成污点,于是命中危险方法规则,产生告警。

总结

学习iast时阅读官方文档和代码调试很有用,java-agent调试可以看 https://doc.dongtai.io/docs/development/dongtai-java-agent-doc/agent-debug

参考资料

[1]

洞态iast: https://doc.dongtai.io/

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2