One unauthenticated POST, one arbitrary file on disk, one webshell. CVE-2025-31324 is a missing authorization check in SAP NetWeaver’s Visual Composer, and it carries the maximum CVSS score for the least interesting of reasons: the component that accepts an upload never asks who is sending it. SAP shipped an emergency patch in April 2025, by which point it had already been exploited in the wild as a zero-day. This is an n-day reconstruction of how a single absent check turned a widely deployed application server into a drop site.
The component
Visual Composer is a model-driven UI tool that ships with the NetWeaver Application Server Java stack. It is not installed by default, but it is enabled on a large number of production systems because entire generations of dashboards and Adobe-form flows were built on it, and nobody rips out a working component. Inside it lives a servlet reached at /developmentserver/metadatauploader, whose job is to accept metadata files during development work. The relevant fact is the one that always matters in this class of bug: that endpoint is exposed over HTTP, and the authorization decision that should have guarded it was simply not wired in.
The bug
The metadata uploader accepts a POST and writes the request body to a file. What it does not do is check that the caller holds a session, a role, or any authorization at all. An unauthenticated request reaches the handler, and the handler writes. Worse, the destination sits under a web-served path on the J2EE cluster, so a file written there is a file the attacker can then request back over the same interface.
The reproduction is unglamorous. A POST to the uploader with a JSP body, and a follow-up GET to retrieve it:
POST /developmentserver/metadatauploader?CONTENTTYPE=MODEL HTTP/1.1
Host: sap.target.example
Content-Type: application/octet-stream
<contents of a JSP written into a web-accessible directory>
There is no memory corruption here and no exotic primitive. The application was asked to store a file and did, in a place it then served, to a caller it never identified. The uploaded JSP runs inside the NetWeaver Java process, which means code execution as the SAP service account, and from <sid>adm the rest of the system follows. Public tracking of the campaigns catalogued a long list of shell filenames landing on victim hosts; the names varied, the mechanism did not.
Patch is not eviction
SAP Security Note 3594142 added the missing authorization check and closed the uploader to anonymous callers. That stops the next upload. It does nothing about the webshells already sitting on hosts that were hit during the weeks the flaw was live, and it did not close the whole surface: attackers pivoted to CVE-2025-42999, an insecure-deserialization flaw in the same Visual Composer stack patched a few weeks later, to keep reaching code execution without the uploader. This is the same shape as the PAN-OS chain we walked earlier: the fix removes the entrance, not the access the attacker already established through it, and not the second door beside it.
The window nobody was watching
The interesting interval is between the upload and the discovery. A JSP appearing in a NetWeaver servlet directory, the Java process spawning a shell, an SAP host suddenly making outbound connections it never made before: every one of those is a signal, and every one of them sat on the box for the days or weeks before anyone correlated it to a public advisory. Catching it means watching the application server as an endpoint that can be attacked rather than as trusted infrastructure, in close to real time, which is exactly the continuous watching that organisations without a night shift of their own increasingly outsource to managed detection and response, whose remit is precisely that interval between a webshell landing and someone noticing it. The behaviour is well-mapped: dropping a server-resident script for later access is Web Shell, T1505.003 in ATT&CK, and the first responders who reconstructed the live exploitation, ReliaQuest and Onapsis, both documented indicators that were visible on the host well before the patch narrative caught up.
Why SAP keeps landing here
ERP systems occupy an awkward seam. They are business-critical enough that patching windows are measured in quarters, exposed enough that management and integration endpoints reach the network, and specialised enough that the team running them and the team watching for intrusions are usually different people who rarely compare notes. A missing authorization check on an obscure uploader is a small bug. It became a maximum-severity one because it lived on a system that is patched slowly, trusted broadly, and monitored thinly. The reconstruction here is not the exploit, which was trivial. It is the reminder that the same three properties that make an ERP host valuable are the ones that make an unremarkable flaw on it worth a zero-day. For the wider picture of where else these systems expose themselves, our modern SAP attack surface writeup covers the rest of the seam.
One absent check, one writable path that was also readable, one process running as the account that owns the system. SAP fixed the check. The lesson underneath it is older than the CVE: an upload endpoint that does not know who is calling is a place to store an attacker’s code, and the only thing standing between that and a foothold is whether anyone is watching the host it lands on.