Jul 27, 2026BTA Team8 min read

The Fastjson Safety Check Fetches the Attacker's JAR: CVE-2026-16723 Ships Unauth RCE With No Gadget and No Patch

Somewhere in production right now, a Spring Boot service is reaching out to an attacker-hosted URL to fetch a JAR file it will never intentionally use, and that outbound fetch is the exploit. The Java process is not vulnerable in the classic AppSec-101 sense; AutoType is off, no JdbcRowSetImpl or TemplatesImpl gadget lives on the classpath, the JSON body arriving at JSON.parseObject looks unremarkable, and the SIEM sees a normal 200. What we at BlueTeamAutomation cannot stop turning over about CVE-2026-16723 is that the trigger is the safety check itself. Fastjson asks the network "is this class safe to deserialize," and the network answers by running code inside the JVM.

Alibaba published the advisory on July 21, 2026 after a report from FearsOff Cybersecurity, and by July 25 both Imperva and ThreatBook confirmed active exploitation against production Spring Boot services in Financial Services, Healthcare, Computing, and Retail, almost entirely US-based. About a third of the attacker traffic Imperva logged comes from clients written in Ruby and Go, the rest from browser-impersonating user agents. No fixed Fastjson 1.x release exists yet; the recommended path is either SafeMode, the 1.2.83_noneautotype coordinate, or a migration to Fastjson 2.

The type validator that goes to the network to decide

The class that has stood between users and every fastjson RCE since the 2017 gadget era is ParserConfig.checkAutoType. On paper its job is boring: given a @type value carried in the JSON body, decide whether the class named there is one the parser is willing to construct. In practice, before it applies any allowlist logic, it wants to know whether the target class carries the @JSONType annotation, which fastjson treats as a "the developer explicitly marked this deserializable, so it is trusted" hint. To learn that, it does the thing you would never expect a security check to do: it calls getResourceAsStream(typeName.replace('.', '/') + ".class") on the value the untrusted caller just supplied. On a Spring Boot executable fat-JAR, the class loader answering that call is LaunchedURLClassLoader, which happily follows URL syntax like jar:http://attacker/x.jar!/POC off-host, fetches the JAR, and caches it in the JVM's sun.net.www.protocol.jar file-descriptor table. No object has been allocated, no cast has happened, and the parser is still deciding whether to trust a type it just downloaded from a stranger over HTTP.

How the two halves of one JSON body close the chain

The cached descriptor stays open under /proc/self/fd/N for as long as the JVM keeps that JAR entry alive, which is where the researchers at dinosn/fastjson-jsontype-rce-lab demonstrated the pivot. The second half of the same JSON body carries another @type, this one shaped as jar:file:/proc/self/fd/N!/attacker/Bootstrap, under a class name that legitimately carries @JSONType inside the attacker's JAR. Fastjson reopens the descriptor, reads the class metadata off it, sees the annotation, and short-circuits into "trusted" mode. The JVM then defines the class, which triggers its static initializer (<clinit>), and the attacker's code runs before fastjson gets around to constructing an instance. Because execution happens at class definition time, any downstream cast to a fixed DTO type is beside the point. The bug is class definition, not object construction, and once you write it out the whole chain feels inevitable, which is the property that made it stick with us all week.

The load-bearing detail: this is a resource-probing type validator answering a security question with a network round-trip. Fastjson 2 closed the class of bug by stripping the resource probe, dropping @JSONType as a trust signal, and putting an allowlist first, which is why the same organisation ships two libraries and only one is on fire.

Why "AutoType is off" and "we have a WAF" both feel like enough, and are not

Every hardening story most Java shops tell themselves about fastjson from the last five years centres on two moves: turn AutoType off, and drop signatures at the edge for the historical gadget names (JdbcRowSetImpl, TemplatesImpl, BadAttributeValueExpException). CVE-2026-16723 fires with AutoType off, needs no known gadget, and the malicious @type value looks like a URL rather than a class name. Signature WAFs walk it through. Inventory tooling that keys on library name misses half the fleet too, because an SBOM entry reading com.alibaba:fastjson:1.2.83 looks identical at a glance to com.alibaba:fastjson2:2.0.53, and only one of those is vulnerable.

The application log sees a well-formed POST, the process log sees a class definition (which is the JVM doing its normal job), Java Flight Recorder logs the class load but nobody watches JFR in real time, and nothing on the SOC console looks off, because in a formal sense nothing is off: the parser is doing exactly what its author wrote it to do, and the class loader is doing exactly what class loaders do.

What defenders can actually see, on the wire and on the host

The observable footprint is real, and it is narrow enough to sign on. On the wire, look for JSON request bodies where any string value contains the substrings jar:http, jar:https, or /proc/self/fd/, especially inside a @type field; benign traffic almost never carries those patterns, so a straight content match is defensible. On the host side, an outbound TCP connection from a Java process on a customer-facing Spring Boot app that fetches a .jar from an external IP is almost always wrong; if you can gate app-tier containers with an egress allowlist, this exploit becomes a lot harder to complete. Inspect /proc/[java-pid]/fd for handles pointing at JAR files that do not match the deployed fat-JAR image, and alert on java spawning bash, sh, curl, wget, or nc, which is the shape post-exploitation usually takes.

In MITRE ATT&CK terms the chain reads as T1190 Exploit Public-Facing Application into T1105 Ingress Tool Transfer (the remote JAR fetch) into T1059.004 Command and Scripting Interpreter, and the mechanism itself is a clean example of T1211 Exploitation for Defense Evasion, because the exploit lives inside the defence mechanism. A tight Splunk-shaped hunt for the wire signal: index=proxy sourcetype=http (uri="*jar:http*" OR body="*jar:file:/proc/self/fd*") | stats count by src_ip, dest_host, http_user_agent. Layer a second query over EDR process-tree telemetry for parent_process="java" AND child_process IN ("bash","sh","curl","wget","nc") and correlate on host and time; a hit on both inside a small window is almost certainly this exploit landing.

Mitigate today, migrate this quarter

There are two moves you can push today, both documented in Alibaba's advisory. First, enable SafeMode at the JVM level with -Dfastjson.parser.safeMode=true, or in code with ParserConfig.getGlobalInstance().setSafeMode(true); SafeMode disables the annotation probe outright and closes the whole chain. Second, if a JVM restart with fresh args is impossible for some services, switch the Maven coordinate to com.alibaba:fastjson:1.2.83_noneautotype, which ships the same version with the probe path stripped at build time. Longer term, plan the migration to Fastjson 2, since that is the only branch getting security attention going forward.

At the edge, add a WAF rule against every JSON endpoint that rejects request bodies containing jar:http, jar:https, or /proc/self/fd/; the primitives are not incidental to the attack, they are the attack. Then verify the fix took hold on each service, not just the ones you think you patched. Spring Boot deployments often do not inherit new JVM args across restarts cleanly under systemd unit files or orchestrators that pin the command line, so the only trustworthy proof is to replay the primitive against staging and confirm the response no longer shows the remote JAR being fetched. Rotate credentials, session tokens, and cloud IAM keys reachable from any app-tier process that ran vulnerable code between July 21 and your patch time, since a successful landing would not show up in application logs. See SecurityOnline's PoC coverage for the payload shape you can safely fire in a lab.

The BTA line

Closing the loop between detection, response, and continuous proof-the-mitigation-still-holds is the motion BlueTeamAutomation automates end to end, which is why we built BASzy to replay primitives like this fastjson @type probe on a schedule rather than once. When the next unpatched RCE lands on a library nobody thought of as an attack surface, your SOC should see the observable while your BAS pipeline has already re-verified that SafeMode is still enabled everywhere it needs to be.

Automate the whole loop, not just the alert.

BASzy replays real exploit primitives against your stack so your detections and your mitigations both stay honest as vendors patch and configurations drift.

Explore BASzy →