使用codeql挖掘fastjson利用链

2020-04-01 约 314 字 预计阅读 2 分钟

声明:本文 【使用codeql挖掘fastjson利用链】 由作者 f1ight 于 2020-04-01 16:00:55 首发 先知社区 曾经 浏览数 165 次

感谢 f1ight 的辛苦付出!

使用codeql挖掘fastjson利用链

什么是codeql

codeql是github security lab开发的一种代码查询语言,可以利用codeql方便的进行代码的污点追踪分析,通过像SQL查询语言一样的对代码的查询方式,可以让使用者不用去过于关心污点追踪的实现细节,具体的codeql的语法和使用方法可以在官网上查看

https://securitylab.github.com/tools/codeql

利用codeql挖掘fastjson利用链

首先要清楚,fastjson的利用链主要集中在getter和setter方法中,如果getter或者setter的方法中存在一些危险操作,比如JNDI查询之类的调用的话,如果参数可控就可以导致JNDI注入,而且fastjson的防御方式为黑名单,所以会层出不穷fastjson的绕过gadgets

鉴于fastjson的漏洞原理较为简单,且source(用户输入的源头)和sink(危险的函数)较为明确,所以可以使用codeql对一些常见的库进行fastjson利用链的挖掘

定义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描述为:

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函数规则:

  1. 这个函数名为lookup
  2. 这个函数所在的类实现了"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还是有很多问题的,还需要继续去完善

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

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)

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now