参考

https://xz.aliyun.com/t/7482

定义fastjson的入口点

fastjson的source相对比较好定义,所有fastjson的入口函数都是getter和setter这些函数,所以对应的source就为这些getter和setter,在codeql查询中,其实相当于将所有的函数按照用户所需要的过滤规则拿出来,所以我们只需要定义过滤的规则

对于getter和setter的规则,这里其实并不是一定要有对应的属性,只要前三个字母开头是get,并且第四个字母大写即可

getter的规则:

  1. 以get开头
  2. 没有函数参数
  3. 是我们的code database中的函数
  4. 为public方法
  5. 函数名长度要大于3

setter的规则:

  1. 以set开头
  2. 函数参数为一个
  3. 是我们code database中的函数
  4. 为public方法
  5. 函数名长度大于3
  6. 返回值为void

所以我们可以通过这几个规则写出对应的fastjson gadgets入口点的ql描述为:

  1. class FastJsonSetMethod extends Method{
  2. FastJsonSetMethod(){
  3. this.getName().indexOf("set") = 0 and
  4. this.getName().length() > 3 and
  5. this.isPublic() and
  6. this.fromSource() and
  7. exists(VoidType vt |
  8. vt = this.getReturnType()
  9. ) and
  10. this.getNumberOfParameters() = 1
  11. }
  12. }
  13. class FastJsonGetMethod extends Method{
  14. FastJsonGetMethod(){
  15. this.getName().indexOf("get") = 0 and
  16. this.getName().length() > 3 and
  17. this.isPublic() and
  18. this.fromSource() and
  19. this.hasNoParameters()
  20. }
  21. }

定义危险函数

这里危险函数不仅仅是JNDI注入的函数,也可以是DNS查询之类的函数

JNDI函数规则:

  1. 这个函数名为lookup
  2. 这个函数所在的类实现了”javax.naming.Context”接口

所以用ql语言描述为:

  1. class JNDIMethod extends Method{
  2. JNDIMethod(){
  3. this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") and
  4. this.hasName("lookup")
  5. }
  6. }

确定搜索方法

因为在fastjson中,有两个输入点,一个是get方法所在类的属性,一个是在fastjson触发的时候所传入的参数,为了方便起见,没有定义确定的source,一般来说能满足get方法最后到lookup的类相对较少,所以可以在查询结束以后再人工进行一次确认

这里没有用fastjson的全局的污点追踪,而是直接通过语法结构查找对应的利用链

先放一下代码,然后解释一下为什么这么写:

  1. MethodAccess seekSink(Method sourceMethod){
  2. exists(
  3. MethodAccess ma, Method method|
  4. (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and
  5. method = ma.getMethod()) or
  6. (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().isAnonymous() and method = ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().getAMethod())|
  7. if method instanceof JNDIMethod
  8. then result = ma
  9. else result = seekSink(method)
  10. )
  11. }

基础版

首先,我们需要获取到所有getter方法内部的方法调用,这里使用了ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*()做选择,如果这个方法调用围绕的结构正是我们getter方法内部的一个子结构的话,那么证明这个方法调用是在getter中的

我们先跳过or这里的定义,来看后面:

后面对内部调用进行判断,看它是否是一个JNDI查询的方法,如果不是的话,因为还有内部调用,所以继续递归查询内部调用的内部调用,这样就可以获取到更深调用的JNDI查询

进阶版

也就是or后面的这一段,在fastjson 1.2.66中有这么一个gadgets:

挖掘fastjson - 图1

入口点为: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挖掘结果:

挖掘fastjson - 图2

common-configuration挖掘结果:

挖掘fastjson - 图3

改进点

现在的这个ql还是有很多问题的,还需要继续去完善

  1. 这种函数参数的匿名类,可能不在第一个索引,需要对所有的参数做判断
  2. 对应的sink可以继续去拓展,可能有其他的调用方式没有加入(例如lambda表达式)
  3. 危险方法不一定是JNDI查询,也可以是其他的方法
  4. 没有使用到污点追踪,所以需要一点人工成本

PS:shiro在编译的时候会报错,用mvn compile -fn可以忽略编译错误,成功构建database

  1. fastjson.qll
  2. import java
  3. import semmle.code.java.dataflow.DataFlow
  4. class FastJsonSetMethod extends Method{
  5. FastJsonSetMethod(){
  6. this.getName().indexOf("set") = 0 and
  7. this.getName().length() > 3 and
  8. this.isPublic() and
  9. this.fromSource() and
  10. exists(VoidType vt |
  11. vt = this.getReturnType()
  12. ) and
  13. this.getNumberOfParameters() = 1
  14. }
  15. }
  16. class FastJsonGetMethod extends Method{
  17. FastJsonGetMethod(){
  18. this.getName().indexOf("get") = 0 and
  19. this.getName().length() > 3 and
  20. this.isPublic() and
  21. this.fromSource() and
  22. this.hasNoParameters()
  23. }
  24. }
  25. class JNDIMethod extends Method{
  26. JNDIMethod(){
  27. this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") and
  28. this.hasName("lookup")
  29. }
  30. }
  31. predicate isClassField(RefType classType, string fieldName){
  32. classType.getAField().hasName(fieldName)
  33. }
  34. MethodAccess seekSink(Method sourceMethod){
  35. exists(
  36. MethodAccess ma, Method method|
  37. (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and
  38. method = ma.getMethod()) or
  39. (ma.getEnclosingStmt() = sourceMethod.getBody().getAChild*() and ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().isAnonymous() and method = ma.getArgument(0).(ClassInstanceExpr).getAnonymousClass().getAMethod())|
  40. if method instanceof JNDIMethod
  41. then result = ma
  42. else result = seekSink(method)
  43. )
  44. }
  45. fastjson.ql
  46. import java
  47. import semmle.code.java.dataflow.DataFlow
  48. import fastjson
  49. from FastJsonGetMethod getMethod
  50. select getMethod, seekSink(getMethod)