参考
定义fastjson的入口点
fastjson的source相对比较好定义,所有fastjson的入口函数都是getter和setter这些函数,所以对应的source就为这些getter和setter,在codeql查询中,其实相当于将所有的函数按照用户所需要的过滤规则拿出来,所以我们只需要定义过滤的规则
对于getter和setter的规则,这里其实并不是一定要有对应的属性,只要前三个字母开头是get,并且第四个字母大写即可
getter的规则:
- 以get开头
- 没有函数参数
- 是我们的code database中的函数
- 为public方法
- 函数名长度要大于3
setter的规则:
- 以set开头
- 函数参数为一个
- 是我们code database中的函数
- 为public方法
- 函数名长度大于3
- 返回值为void
所以我们可以通过这几个规则写出对应的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函数规则:
- 这个函数名为lookup
- 这个函数所在的类实现了”javax.naming.Context”接口
所以用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还是有很多问题的,还需要继续去完善
- 这种函数参数的匿名类,可能不在第一个索引,需要对所有的参数做判断
- 对应的sink可以继续去拓展,可能有其他的调用方式没有加入(例如lambda表达式)
- 危险方法不一定是JNDI查询,也可以是其他的方法
- 没有使用到污点追踪,所以需要一点人工成本
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)