How this bug class keeps shipping

Path traversal in file-upload and file-write code paths ranks among the oldest documented web vulnerabilities. It still ships. It still appears in security appliances. It still lands on CISA's Known Exploited Vulnerabilities list. On 2026-10-14, CISA added CVE-2026-76519 to KEV: an arbitrary file write in the Advanced Malware Protection (AMP) feature of Cisco Secure Email Gateway, reachable by an unauthenticated remote attacker through a crafted email attachment. Cisco's own advisory, republished on 2026-10-20, describes the mechanism plainly: "improper handling of email attachments when the file reputation service is enabled," leading to arbitrary file write and command execution "with root privileges on the underlying operating system."

That is the anatomy of the class. This post covers why it keeps shipping, what the attacker needs, and how an application-security team can assess whether a given deployment is exposed. A second KEV addition from the same week, CVE-2025-61624 in Fortinet products, illustrates the authenticated variant of the same mistake.

The shape of the bug

Every path-traversal file-write bug has three ingredients:

  1. An input intended to name a file (an attachment filename, a form field, a CLI argument, an archive entry).
  2. A sink that opens a file for writing, derived from that input and a server-controlled base directory.
  3. A validator that is either absent, syntactic-only, or applied in the wrong order relative to normalization.

The canonical defect: treating basedir + "/" + user_input as safe because basedir is a constant. It is not. If user_input contains .. segments, absolute path prefixes, or encoded separators, the resolved path escapes basedir. On a security appliance that runs its mail pipeline as root, writing one carefully chosen file (an SSH authorized_keys, a cron entry, a Python site-packages shim, an init script) is a full compromise.

The broken pattern, in the shape that keeps shipping:

# INSECURE. Do not copy.
UPLOAD_DIR = "/var/spool/amp/attachments"

def store_attachment(filename: str, body: bytes) -> str:
    # "sanitize" filename
    filename = filename.replace("\x00", "")
    dest = os.path.join(UPLOAD_DIR, filename)
    with open(dest, "wb") as f:          # sink
        f.write(body)
    return dest

Three flaws compound here. First, os.path.join discards UPLOAD_DIR entirely if filename is absolute (/etc/cron.d/pwn). Second, it returns /var/spool/amp/attachments/../../../etc/cron.d/pwn unchanged when filename contains .. segments. Third, on some stacks, percent-encoded or UTF-8-overlong separators survive the "sanitize" step and are decoded only by the filesystem layer. MIME filename parsing compounds the problem: RFC 2231 continuation parameters, RFC 2047 encoded-words, and conflicting Content-Disposition versus Content-Type names let an attacker present one filename to the AV scanner and a different one to the writer.

The fix is not more regex. Compute the final path, resolve all symbolic links and .. segments, then assert the resolved path still carries the base directory as a prefix. In Python:

from pathlib import Path

BASE = Path("/var/spool/amp/attachments").resolve()

def safe_dest(filename: str) -> Path:
    # Generate the storage name ourselves. Never trust the client's.
    name = secrets.token_hex(16)
    dest = (BASE / name).resolve()
    if BASE not in dest.parents:
        raise ValueError("path escape")
    return dest

The attacker-controlled string is not used as a path component. Keep it as metadata if the application needs the original filename for display; the on-disk name is server-generated. This pattern survives parser quirks because the write path has no parser.

Why it keeps shipping on appliances

The AMP file write (CVE-2026-76519) and the authenticated FortiOS/FortiPAM/FortiProxy/FortiSwitchManager traversal (CVE-2025-61624) are both appliance bugs. That is not an accident. Three structural pressures push this class into security appliances.

First, these products run as root (or equivalent) because they manage the kernel's packet-filter state, inspect raw mail, or write to privileged directories. A single bad open() is therefore maximally damaging; there is no degraded "write arbitrary file as nobody" mode.

Second, the input surfaces are protocols, not forms. SMTP attachments, CLI commands, SCP-style transfers, and vendor-specific config imports arrive through code paths that predate the modern web stack's input-validation libraries. Teams write custom parsers because the standard libraries do not model the protocol. Custom parsers drift from the sanitizer.

Third, "authenticated" is a weaker mitigation than it sounds. CVE-2025-61624 requires an admin profile with read-write permissions. In an environment that uses SAML, SCIM, or shared service accounts for appliance management, that is one phished session, one leaked API token, or one compromised jump host away. CISA listed it as actively exploited on 2026-10-16 despite the auth requirement. That is the empirical answer to the "but you need admin" objection.

Telling whether you are exposed

For the Cisco Secure Email Gateway case, three questions decide exposure:

filename =~ /(^\/|\.\.\/|\.\.\\|%2e%2e|%2f|\x00)/i

If the appliance logs attachment names verbatim, that query surfaces probing attempts. If it does not log the raw filename, treat the appliance as a blind spot and prioritize patching over forensics.

The remediation obligation

Federal civilian agencies are bound by BOD 26-04 (Prioritizing Security Updates Based on Risk), effective 2026-06-10, which carries forward the KEV criteria from the directive it supersedes. For CVE-2026-76519, CVE-2025-61624, and the other seven KEV additions preceding 2026-10-20, that means patching per each entry's due date and discontinuing use where no fixed version exists. Private-sector teams should treat KEV as the same signal: these are exploited vulnerabilities, not theoretical ones.

The pattern predates the appliances it keeps appearing in. The fix is server-generated filenames, resolved-path assertions, and least-privilege write processes. The anatomy repeats; the only question is whether the next release ships the fix.

Verifiable security.