浅析洞态iast产品
2022-9-30 12:24:53 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

之前的工作中我处理过一些洞态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));   // 1289696681
System.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#L250

public 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#L26
public 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#L31
public 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#L31
public 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#L103

protected 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#L131

Method 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

@Override
public 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);
    }
    ...
}

后端服务用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/


文章来源: https://mp.weixin.qq.com/s?__biz=MzkyMDIxMjE5MA==&mid=2247485228&idx=1&sn=458ac9727f63f4cfb504c1efde062767&chksm=c197009df6e0898bdf09bb8c958df19ebf88b98fcca5d0226ad38032e85eb5236815e790d503&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh