codeql是github security lab开发的一种代码查询语言,可以利用codeql方便的进行代码的污点追踪分析,通过像SQL查询语言一样的对代码的查询方式,可以让使用者不用去过于关心污点追踪的实现细节,具体的codeql的语法和使用方法可以在官网上查看
https://securitylab.github.com/tools/codeql
首先要清楚,fastjson的利用链主要集中在getter和setter方法中,如果getter或者setter的方法中存在一些危险操作,比如JNDI查询之类的调用的话,如果参数可控就可以导致JNDI注入,而且fastjson的防御方式为黑名单,所以会层出不穷fastjson的绕过gadgets
鉴于fastjson的漏洞原理较为简单,且source(用户输入的源头)和sink(危险的函数)较为明确,所以可以使用codeql对一些常见的库进行fastjson利用链的挖掘
fastjson的source相对比较好定义,所有fastjson的入口函数都是getter和setter这些函数,所以对应的source就为这些getter和setter,在codeql查询中,其实相当于将所有的函数按照用户所需要的过滤规则拿出来,所以我们只需要定义过滤的规则
对于getter和setter的规则,这里其实并不是一定要有对应的属性,只要前三个字母开头是get,并且第四个字母大写即可
getter的规则:
setter的规则:
所以我们可以通过这几个规则写出对应的fastjson gadgets入口点的ql描述为:
class FastJsonSetMethod extends Method{ FastJsonSetMethod(){ this.getName().indexOf("set") = 0 and this.getName().length() > 3 and this.isPublic() and this.fromSource() and exists(VoidType vt | vt = this.getReturnType() ) and this.getNumberOfParameters() = 1 } } class FastJsonGetMethod extends Method{ FastJsonGetMethod(){ this.getName().indexOf("get") = 0 and this.getName().length() > 3 and this.isPublic() and this.fromSource() and this.hasNoParameters() } }
这里危险函数不仅仅是JNDI注入的函数,也可以是DNS查询之类的函数
JNDI函数规则:
所以用ql语言描述为:
class JNDIMethod extends Method{ JNDIMethod(){ this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") and this.hasName("lookup") } }
因为在fastjson中,有两个输入点,一个是get方法所在类的属性,一个是在fastjson触发的时候所传入的参数,为了方便起见,没有定义确定的source,一般来说能满足get方法最后到lookup的类相对较少,所以可以在查询结束以后再人工进行一次确认
这里没有用fastjson的全局的污点追踪,而是直接通过语法结构查找对应的利用链
先放一下代码,然后解释一下为什么这么写:
MethodAccess seekSink(Method sourceMethod){ exists( MethodAccess ma, Method method| (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and method = ma.getMethod()) or (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().isAnonymous() and method = ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().getAMethod())| if method instanceof JNDIMethod then result = ma else result = seekSink(method) ) }
首先,我们需要获取到所有getter方法内部的方法调用,这里使用了ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*()
做选择,如果这个方法调用围绕的结构正是我们getter方法内部的一个子结构的话,那么证明这个方法调用是在getter中的
我们先跳过or这里的定义,来看后面:
后面对内部调用进行判断,看它是否是一个JNDI查询的方法,如果不是的话,因为还有内部调用,所以继续递归查询内部调用的内部调用,这样就可以获取到更深调用的JNDI查询
也就是or后面的这一段,在fastjson 1.2.66中有这么一个gadgets:
入口点为:org.apache.shiro.jndi.JndiObjectFactory
用上面的ql是查不出来的,因为这里的lookup是在一个匿名类里面,并且是在函数参数中定义的,所以增加一个新的sink点
ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().isAnonymous() and method = ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().getAMethod())
,和前面一样,函数必须是内部调用的函数,并且函数的第一个参数为一个匿名类,之后获取到匿名类中的方法,即可获取到这种特殊的sink点
shiro挖掘结果:
common-configuration挖掘结果:
现在的这个ql还是有很多问题的,还需要继续去完善
PS:shiro在编译的时候会报错,用mvn compile -fn
可以忽略编译错误,成功构建database
fastjson.qll import java import semmle.code.java.dataflow.DataFlow class FastJsonSetMethod extends Method{ FastJsonSetMethod(){ this.getName().indexOf("set") = 0 and this.getName().length() > 3 and this.isPublic() and this.fromSource() and exists(VoidType vt | vt = this.getReturnType() ) and this.getNumberOfParameters() = 1 } } class FastJsonGetMethod extends Method{ FastJsonGetMethod(){ this.getName().indexOf("get") = 0 and this.getName().length() > 3 and this.isPublic() and this.fromSource() and this.hasNoParameters() } } class JNDIMethod extends Method{ JNDIMethod(){ this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") and this.hasName("lookup") } } predicate isClassField(RefType classType, string fieldName){ classType.getAField().hasName(fieldName) } MethodAccess seekSink(Method sourceMethod){ exists( MethodAccess ma, Method method| (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and method = ma.getMethod()) or (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().isAnonymous() and method = ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().getAMethod())| if method instanceof JNDIMethod then result = ma else result = seekSink(method) ) } fastjson.ql import java import semmle.code.java.dataflow.DataFlow import fastjson from FastJsonGetMethod getMethod select getMethod, seekSink(getMethod)