背景
之前的工作中我处理过一些洞态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_POOL
和TAINT_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);
}
...
}
举个例子:怎么检测接口是否存在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
参考资料
洞态iast: https://doc.dongtai.io/
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...