-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## The header AS2-TO names the user.\n\nThe PoC's shape: post a request to `/WebInterface/function/` twice in parallel, sharing one cookie. One thread sends AS2 protocol headers. The other does not.\n\n```python\n# Thread A: AS2 dispatch\nheaders = {\n \"AS2-TO\": \"\\\\crushadmin\",\n \"Content-Type\": \"disposition-notification\",\n \"Cookie\": f\"CrushAuth=1755657772315_Nr7FSH4jd2l6RueteEaaEDpY1CcdU{c2f}; currentAuth={c2f}\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n}\ndata = {\"command\": \"getUserList\", \"serverGroup\": \"MainUsers\", \"c2f\": c2f}\n\n# Thread B: WebInterface command, no AS2 headers\nheaders = {\"Cookie\": , \"X-Requested-With\": \"XMLHttpRequest\"}\ndata = \n```\n\nThe script starts both threads, joins them, scans either response for `` tags. If either request was authorized, that is, if either response carried the user list back, the run hit. The race repeats up to 5000 times, rotating the four-character `c2f` value every fifty rounds.\n\nWhat the AS2 thread says to CrushFTP: this HTTP request is also an AS2 protocol message addressed to the AS2 partner named `\\crushadmin`. AS2 is the EDI-over-HTTPS protocol that retailers and pharmaceutical wholesalers use to exchange purchase orders and shipping notices, ratified in 2002, mandated by major retail networks for B2B onboarding. The `AS2-TO` header is the receiver-name field. The leading backslash on `\\crushadmin` is not an escape; it is the path separator CrushFTP uses to bypass partner-name resolution and return the value as a raw username inside the `MainUsers` group. The `Content-Type: disposition-notification` declares the body is an AS2 MDN, the protocol-level receipt acknowledgement, the kind of message that arrives at an AS2 endpoint claiming to confirm a previously sent file.\n\nWhat the regular thread says: post the same body without claiming to be AS2. `getUserList` is a privileged WebInterface command. Without an authenticated session it returns nothing. With one, it returns every account on the server.\n\nBoth threads share a cookie. Both threads target the same servlet. The AS2 thread mutates the session associated with the cookie. The regular thread reads from it. The race is whether the regular thread observes the session in the moment between when the AS2 thread writes `crushadmin` into the session's user slot and when the AS2 thread tears it down.\n\n## The cookie is a session lookup, not a session credential.\n\nRead this cookie carefully. It is not random.\n\n```\nCrushAuth=1755657772315_Nr7FSH4jd2l6RueteEaaEDpY1CcdU{c2f}; currentAuth={c2f}\n```\n\n`1755657772315` is a fixed millisecond timestamp baked into the script. `Nr7FSH4jd2l6RueteEaaEDpY1CcdU` is a fixed 28-character random string baked into the script. The PoC sends the same prefix on every request, run after run, for as long as you let it run. The only variable component is `{c2f}`: four random alphanumeric characters appended to the end of `CrushAuth` and repeated as the entire value of `currentAuth`.\n\nThe cookie is doing one thing. It tells CrushFTP which session-table slot to load. CrushFTP keeps an in-memory map keyed by the four-character `c2f` value; the rest of the cookie is recorded once when the session is created and is not re-validated on subsequent requests. The first request with a new `c2f` causes CrushFTP to allocate a fresh session and key it under that `c2f`. The fresh session, on allocation, has no authenticated user.\n\nThat session, with no authenticated user, is the one the AS2 thread mutates. Setting `AS2-TO: \\crushadmin` on a request whose `Content-Type` declares an AS2 message dispatches the AS2 handler before the WebInterface command handler runs. The AS2 handler establishes the message's sender or receiver identity by writing it into the session that the cookie maps to. The regular thread, racing on the same `c2f`, reads the session at a moment when its user slot says `crushadmin`. The cookie did not authenticate anyone. The header did.\n\n## The AS2 receiver and the admin console are the same servlet.\n\n`/WebInterface/function/` is the endpoint that handles every authenticated WebInterface command. `getUserList` is registered there. So is `setUserItem`, the function the in-the-wild attackers used to add new admin accounts to compromised servers (watchTowr's analysis names `setUserItem` as the operative call observed in their captured traffic). So is the AS2 message dispatcher.\n\nThe dispatch is implicit. CrushFTP reads the inbound headers; if `AS2-TO` is present and `Content-Type` declares an AS2 message type (`disposition-notification`, `application/edi-x12`, the others), the AS2 protocol handler runs against the request before the WebInterface command handler runs. For customers operating CrushFTP as an AS2 gateway this is documented behavior: the same servlet accepts both AS2 messages and WebInterface administrative requests, demultiplexed by the headers. The AS2 dispatch is how partner-record permissions get applied to inbound EDI traffic.\n\nThe customers running CrushFTP as an AS2 gateway are a minority. AS2 is a B2B protocol; the broader CrushFTP customer base runs the product for SFTP, FTPS, S3, HTTPS, and WebDAV exchange with no AS2 partners and no AS2 traffic. They are exposed to the AS2 dispatch anyway, because the AS2 dispatch happens on the endpoint that handles everything else. CrushFTP customers do not need to use AS2 to be exposed to it. They needed to use CrushFTP.\n\nThe vendor's advisory says the bug \"does not affect deployments using the DMZ proxy feature.\" The DMZ proxy is a separate CrushFTP component customers can run in front of the main server to terminate inbound connections in a DMZ network segment and forward sanitized requests to the back end. It is a deployment topology, not a security control. watchTowr's analysis dismisses the mitigation note in one line: the DMZ proxy is \"not enabled by default and doesn't appear to be widely used.\" The DMZ proxy did not stop this bug because it was hardening. It stopped this bug because it happened to filter out the protocol headers the AS2 dispatch needed.\n\n## The race window is the moment between mutation and cleanup.\n\nThe race is real. It is also engineered.\n\nThe AS2 dispatch in CrushFTP is request-scoped: when the AS2 handler completes, the session's user slot is reset back to whatever it was before the dispatch began. For a session that started unauthenticated, the slot returns to no-user after the AS2 thread finishes. The window during which the regular thread sees `crushadmin` in the slot is the window between the AS2 mutation and the AS2 cleanup.\n\nThat window is small. The PoC widens it by issuing the AS2 thread's request at the same instant as the regular thread's, so the regular thread arrives at the session-lookup phase while the AS2 thread is still mid-dispatch. With Python `threading.Thread.start()` and a shared cookie, that synchronization is approximate; the race wins maybe one in a few hundred attempts. The PoC budgets 5000 attempts to make the win statistically reliable. A five-thousand-attempt race that completes in seconds is, on the wire, indistinguishable from `getUserList` succeeding the first time it was tried.\n\nThe CVE description names this a race condition (CWE-362) layered on improper authentication (CWE-287). The race is what makes the bug exploitable from a script. It is not what makes the bug exist. The bug exists because the same servlet accepts an authentication-irrelevant protocol header and uses it to write a username into a session that a different protocol's command handler will read.\n\n## This is the third one in fifteen months.\n\nCrushFTP shipped CVE-2024-4040 in April 2024: a server-side template injection in the WebInterface that let an unauthenticated attacker escape the VFS sandbox, read files, gain administrative access, and execute code. CISA added it to KEV. CrowdStrike and other responders reported in-the-wild exploitation against U.S. organizations. The mechanism was template injection inside an admin-console URL parameter. CVSS 9.8.\n\nCrushFTP shipped CVE-2025-31161 in April 2025: an authentication bypass in the same WebInterface that let an unauthenticated attacker forge a `CrushAuth` cookie the server accepted as belonging to an admin session. We covered it in [CrushFTP CVE-2025-31161: MFT Is the Target Now](/posts/crushftp-pre-auth-mft-is-the-target). The vendor disclosed via silent patch: a fixed version was released without a CVE, the CVE filed only after researcher pressure. CISA added it to KEV within days. The mechanism was a cookie-format trust gap on the same endpoint.\n\nCrushFTP shipped CVE-2025-54309 three months after that. Same product, same admin endpoint, different protocol confusion. The mechanism is AS2 dispatch writing into a WebInterface session.\n\nThis is the [Design Debt Driver](/patterns/design-debt-driver) shape, on a product whose architecture is composed: FTP, FTPS, SFTP, HTTPS, WebDAV, S3, and AS2 are different listeners or dispatchers that all converge on the same session abstraction and the same WebInterface command surface. Each protocol is an opportunity for confusion at the seam where it hands authority over to the shared session model. CVE-2024-4040 was the template-engine seam. CVE-2025-31161 was the cookie-format seam. CVE-2025-54309 is the AS2 seam. The patches close the instances. The seam architecture is unchanged.\n\nFor customers in regulated industries who chose CrushFTP because of its [MFT compliance posture](/patterns/mft-as-primary-target), the cadence is a subscription. The April 2024 patch closed the 2024 instance. The April 2025 patch closed the spring 2025 instance. The July 2025 patch closes the AS2 instance. The protocol where the next instance shows up is whichever one the next researcher, or the next attacker, gets around to.\n\n## The CVE arrived after the breach.\n\nCrushFTP filed the wiki page at `Wiki.jsp?page=CompromiseJuly2025` because customers were compromised before the bug had a name. The page documents what the vendor learned about the technique by examining victim systems. The CVE assignment came four days after the page was created, because that is the order this happens in.\n\nwatchTowr Labs published their analysis on August 27, 2025. Their `Attacker Eye` honeypot infrastructure had captured live exploit traffic against the bug; the firm replayed the captured requests against an instrumented CrushFTP, confirmed the replay worked, and shipped a cleaned-up Python script as the public PoC. The blog post title is \"the one where we just steal the vulnerabilities crushftp cve-2025-54309.\" The verb is theirs. The technique was harvested from attack traffic, not derived from researcher-side vulnerability discovery.\n\nThis is the [Disclosure After Exploitation](/patterns/disclosure-after-exploitation) pattern in a form that leaves no ambiguity. The vendor learned about the bug by looking at compromised customers. The security firm learned about the bug by looking at attack traffic against their honeypot. Neither was first. Whoever first held the AS2 race technique used it on production systems for an unbounded period before either of the visible-to-defenders parties caught up. The defender-visible timeline starts on July 22 with a CVE number and a CVSS vector. The exploit-visible timeline starts somewhere earlier and is not bounded by anything in the public record.\n\nPoC: [watchtowrlabs/watchTowr-vs-CrushFTP-Authentication-Bypass-CVE-2025-54309](https://github.com/watchtowrlabs/watchTowr-vs-CrushFTP-Authentication-Bypass-CVE-2025-54309)","closing_line":"CrushFTP customers do not need to use AS2 to be exposed to it. They needed to use CrushFTP.","hook_md":"The wiki page CrushFTP wrote to document this CVE is reachable at `Wiki.jsp?page=CompromiseJuly2025`. The page was created on July 18, 2025. MITRE assigned CVE-2025-54309 four days later. CISA added it to the Known Exploited Vulnerabilities catalog the same week. The first working public PoC was published thirty-six days after that, by a security firm whose blog post title for the writeup reads, verbatim, \"the one where we just steal the vulnerabilities crushftp cve-2025-54309.\" The post says they did not find the bug. Their honeypot infrastructure captured an attacker using it. They replayed the captured request against an instrumented CrushFTP. The replay worked.\n\nThe CVE is documentation of the breach. The breach was not documentation of the CVE.","post_id":57,"slug":"crushftp-cve-2025-54309-as2-receiver-authenticated-the-admin","title":"CVE-2025-54309: The AS2 Receiver Authenticated the Admin","type":"initial","unreadable_sentence":"CrushFTP customers do not need to use AS2 to be exposed to it. They needed to use CrushFTP."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCahxpagAKCRDeZjl4jgkQ JlInAP44VCn9gMHHY7PFJsVmOVA0UUuzBxIRO7j3BibxN8iSAgEA5TgwUuvXB/4M QV2K07sw/si5Yjcx/U8LATCxWO5MdQA= =SAxI -----END PGP SIGNATURE-----