-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 The device that receives your suspicious files, detonates them in an isolated VM, and tells your SOC whether they're malicious, that device is running an unauthenticated root shell endpoint. One request. No credentials. No token. No session. Root on the appliance that sits between your edge and your security team's confidence in the verdict "clean." ``` curl -s -k --get "http://$HOST/fortisandbox/job-detail/tracer-behavior" \ --data-urlencode "jid=|(id > /web/ng/out.txt)|" ``` Then fetch `/web/ng/out.txt`. The output is `uid=0(root) gid=0(root)`. That's CVE-2026-39808, found by Samuel de Lucas Maroto at KPMG Spain, reported November 2025, patched April 2026. The person running the FortiSandbox is a security analyst. Their job is to trust the verdicts it issues. They submitted a suspicious attachment from yesterday's phishing campaign, the sandbox said clean, they closed the ticket. They have no mechanism to know the sandbox's verdict engine is now running attacker code. The verdicts look the same. The dashboard looks the same. The only thing that changed is who decides what "clean" means. ## Why the Endpoint Has No Auth This is the part nobody's explaining. `/fortisandbox/job-detail/tracer-behavior` is a polling endpoint. FortiSandbox doesn't operate in isolation, it's a node in the Fortinet Security Fabric. FortiGate, FortiMail, and FortiProxy submit samples to it via API and then poll for verdicts. The submission creates a job. The job ID comes back in the submission response. The polling client uses that job ID to check status and retrieve results. The design assumption baked into this endpoint is: the job ID is the implicit credential. Only the system that submitted the job knows its ID, so authentication on the retrieval endpoint is redundant. The job ID *is* the auth token, it's just that nobody documented it as one, nobody validated its format, and nobody isolated the parameter from the shell command that processes it. This is a pattern. Systems that treat opaque identifiers as implicit authorization tokens skip authentication on retrieval endpoints constantly. It's usually fine until someone reads the parameter directly into a shell call. The deeper architectural reason: FortiSandbox is designed to live inside the Fortinet Security Fabric, reachable only by FortiGate, FortiMail, and FortiProxy on the internal network. The Fabric trust model is network-based: if you can reach the appliance, you are a trusted fabric member. Authentication on the polling endpoint was considered redundant because network access was the gate. This is the perimeter security model applied to an API: the perimeter was supposed to keep untrusted callers out, so the API didn't need to. The CVE exists because in some deployments the appliance is internet-reachable, and in others the "fabric" network is reachable by an attacker who has already compromised something else inside the perimeter. The assumption held until the perimeter didn't. ## Why It Runs as Root FortiSandbox's analysis engine has to run as root. It creates and destroys VMs, mounts and unmounts filesystem images, manipulates virtual network interfaces for sample isolation, injects agents into guest OSes. You cannot do that from an unprivileged process. The web server and backend that serve the UI and API endpoints run in the same privilege context, or at minimum, the subprocess they invoke to handle `jid` lookups does. There's no privilege separation wall between "the interface that answers HTTP requests" and "the engine that detonates malware." The endpoint gets root because the appliance needs root, and separating the two was never a design priority. ## What the Backend Call Looks Like The advisory says OS command injection via the `jid` parameter. The PoC uses `|` to inject. That specific character, not `;`, not `$(...)`, but pipe, tells you something about the backend implementation. Semicolon injection (`;cmd`) works when the parameter is appended to a shell command and the entire string is evaluated. Dollar-sign injection (`$(cmd)`) works in the same context but with a different quoting style. Pipe injection (`|cmd`) works when the parameter is passed to a shell invocation where the output of the first command is being piped somewhere, the attacker's `|` terminates whatever the backend was piping to and starts a new command in its place. The backend is almost certainly something like: ```sh get_trace $jid | process_output ``` or equivalent Python/PHP subprocess with `shell=True`. The `jid` value breaks the pipe chain. The redirect to `/web/ng/out.txt` works because the web root is writable by the process, again, root. ## The Endpoint That Explains What Malware Did Can Now Act Like Malware The specific endpoint is `tracer-behavior`. Not a login page. Not a file upload handler. The behavioral trace endpoint, the one that returns the record of what a detonated sample did inside the VM: filesystem writes, network connections, registry modifications, process spawns. The forensic output of the sandbox. The endpoint that tells your SOC what malware does is the endpoint that lets you do what malware does. That's not a coincidence of naming. It means the code path that retrieves behavioral trace data, already wired into the analysis engine, already running as root, is the attack surface. ## What Owning the Verdict Engine Means Coverage of this vulnerability is treating it as a generic unauth RCE on a Fortinet box. That framing undersells it. FortiSandbox's role in the Security Fabric is to be a trust authority. FortiGate doesn't decide whether a file is malicious, FortiSandbox does, and FortiGate enforces that verdict. FortiMail doesn't detonate attachments, it forwards them to FortiSandbox and delivers or quarantines based on what comes back. The entire downstream enforcement chain is predicated on the sandbox's verdicts being correct. When you control the sandbox, you control the verdict database. You can write verdicts. Every sample submitted after your shell is established gets evaluated by an engine you control. You can make malware look clean, the forwarded file gets delivered, the attachment opens, the download proceeds, none of the enforcement triggers fire. You can make clean files look malicious, targeted harassment, triggering false alerts that burn out analysts, poisoning threat intelligence feeds. The credentials the appliance uses to push verdicts upstream, the API keys and certificates that authenticate FortiSandbox to FortiGate and FortiMail, are stored on the box. Root access is access to those credentials. You're not just inside the sandbox. You're holding the badge that every downstream system trusts unconditionally. The analyst's ticket backlog is also in scope. FortiSandbox maintains a database of verdicts for samples already submitted. An attacker with root access can write to that database directly. Every sample sitting in the pending-review queue, everything flagged in the last week and still under investigation, becomes a candidate for verdict modification. The analyst closes tickets. The items they close are the ones the attacker cleared. ## The Five Months Discovered November 2025. Patched and disclosed April 2026. Responsible disclosure windows exist to give vendors time to patch before the public knows what to exploit. They also define, precisely, the window during which defenders cannot patch because the patch doesn't exist. Every organization running FortiSandbox 4.4.x was running a vulnerable system from November through April. They couldn't know. They couldn't act. The disclosure window is by design a period of mandated exposure. The question for five months of a pre-auth root shell on an appliance with this much access isn't whether the vulnerability was known, it was, to the researcher and to Fortinet. The question is whether it was independently discovered by someone who didn't report it. There's no mechanism to answer that question after the fact. Two PoC repos from researchers who aren't Samuel de Lucas Maroto appeared within days of public disclosure: - - [samu-delucas/CVE-2026-39808](https://github.com/samu-delucas/CVE-2026-39808) (★3), the original researcher's own PoC, authoritative - - [Lechansky/CVE-2026-39808](https://github.com/Lechansky/CVE-2026-39808) (★0), independent reproduction The gap between "two PoC repos" and "operational tooling in criminal infrastructure" is measured in days, not weeks. That's the window you're now in. - --- *PGP signature: [fortisandbox-cve-2026-39808-unauth-rce.md.asc](/sigs/fortisandbox-cve-2026-39808-unauth-rce.md.asc), Key fingerprint: `5FD2 1B4F E7E4 A3CA 7971 CB09 DE66 3978 8E09 1026`* -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaeUiawAKCRDeZjl4jgkQ JkgyAQD8LvfciSIov3oRnKAaLcwbYxyuzDxZOxhI2zszbmAtxQEArUXrQEwX3Y4W nIhGxvKMPhKmfyJ4hsP8HFV5fr4BGg0= =/r2K -----END PGP SIGNATURE-----