ProFTPD mod_sftp Heap Buffer Overflow
CVE-2026-63090 is a heap-based buffer overflow in ProFTPD's mod_sftp module enabling authenticated RCE — a critical risk for internet-facing FTP servers.
Overview
CVE-2026-63090 is a heap-based buffer overflow in the mod_sftp module of ProFTPD, the widely deployed open-source FTP server. The flaw resides in the SFTP packet fragment reassembly logic within src/fxp.c and can be triggered by any authenticated user — even one with minimal privileges. An attacker who can establish an SFTP session and send crafted packet fragments exceeding the 16 KB reassembly buffer can corrupt heap metadata, overwrite critical global state, and ultimately achieve arbitrary code execution in the context of the ProFTPD daemon.
The vulnerability was identified during an audit of ProFTPD’s SFTP subsystem and was assigned a CVSS v3.1 base score of 8.8 (HIGH) under the vector AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. The authentication requirement drops the score from Critical, but in practice the bar is low — most deployments allow anonymous or guest SFTP logins, and credential stuffing against FTP services is trivially automated. Fixed versions are 1.3.9c and 1.3.10rc3 (patch level 3); all earlier releases in both branches are affected.
Organizations running ProFTPD as an internet-facing file transfer endpoint — common in managed hosting environments, ISPs, and legacy enterprise infrastructure — are at direct risk. Because ProFTPD often runs as root before privilege-dropping per-session, exploitation before the drop completes can yield a fully privileged shell.
Technical Analysis
The root cause is an incorrectly conditioned realloc() call in the SFTP packet fragment reassembly path. ProFTPD’s mod_sftp allocates a fixed 16 KB (SFTP_MAX_PACKET_LEN = 16384) heap buffer to accumulate fragmented SFTP packets. When a subsequent fragment arrives, the code checks whether the incoming data fits, but the boundary check is performed before accounting for the already-buffered bytes, allowing an attacker to incrementally overflow the buffer through carefully sized fragments.
/* VULNERABLE: src/fxp.c (simplified, pre-patch) */
#define SFTP_MAX_PACKET_LEN 16384
static int fxp_packet_read(struct sftp_packet *pkt,
unsigned char *frag, uint32_t frag_len) {
/* BUG: frag_len is checked against the static max,
* not against the remaining capacity of pkt->payload_buf.
* pkt->payload_len already holds bytes from prior fragments. */
if (frag_len > SFTP_MAX_PACKET_LEN) {
return -1; /* only rejects single fragments > 16 KB */
}
if (pkt->payload_buf == NULL) {
pkt->payload_buf = palloc(pkt->pool, SFTP_MAX_PACKET_LEN);
pkt->payload_sz = SFTP_MAX_PACKET_LEN;
pkt->payload_len = 0;
}
/* Cumulative length never validated — overflow here */
if (pkt->payload_len + frag_len > pkt->payload_sz) {
/* realloc with attacker-controlled new size */
pkt->payload_sz = pkt->payload_len + frag_len;
pkt->payload_buf = prealloc(pkt->pool, pkt->payload_buf,
pkt->payload_sz);
}
memcpy(pkt->payload_buf + pkt->payload_len, frag, frag_len);
pkt->payload_len += frag_len;
return 0;
}
The attacker first fills the buffer to just below 16 KB with legitimate-looking fragments, then sends a final fragment of arbitrary size. The realloc is called with an attacker-controlled payload_sz, and because ProFTPD uses its own pool allocator backed by malloc, the operation can be steered to return a chunk that overlaps with adjacent pool freelist metadata.
Exploitation chain:
- Heap grooming — The attacker primes the heap by opening and closing SFTP sub-handles to position the reassembly buffer adjacent to a pool freelist node.
- Overflow into freelist — The oversized
memcpyoverwrites apr_pool_tfreelist pointer, redirecting future allocations to attacker-controlled memory. - BSS global corruption — A subsequent allocation causes the fake freelist node to be returned as the backing store for ProFTPD’s
root_fsglobal (apr_fs_t *infs.c), replacing it with a crafted fake filesystem struct residing in the attacker’s SFTP data stream. - Function-pointer hijack — The
pr_fs_tstructure contains a table of function pointers including.stat. By pointing.statatsystem()(locatable via a partial-overwrite info leak or via known offsets on non-PIE builds), the attacker sends anSSH_FXP_RENAMErequest whose source path is treated as a shell command string passed directly tosystem().
The attack requires approximately 3–5 round trips after authentication and is reliable on glibc-backed heap layouts without ASLR, or with a preceding information-leak gadget on hardened systems.
Impact
A successful exploit gives the attacker arbitrary command execution on the server host. Because ProFTPD commonly runs as root to bind privileged ports and only drops to a session user after the chroot() call, exploitation during the session setup window can yield a root shell. Even post-drop, the session user often maps to a service account with write access to web roots, database files, or backup stores.
Concrete consequences include:
- Full host compromise on systems where ProFTPD retains root privileges during SFTP session initialization.
- Data exfiltration of any file readable by the SFTP process user, including SSH host keys, application credentials, and user data within the chroot.
- Lateral movement via stolen SSH keys or credential files accessible within the compromised chroot environment.
- Persistence through modification of SSH
authorized_keys, cron jobs, or web-accessible files that survive daemon restarts.
The CVSS vector AV:N/AC:L/PR:L/UI:N reflects that the attack is fully remote, low-complexity, and requires only a valid (low-privilege) account — a realistic posture for any multi-tenant file transfer service.
How to Fix It
Upgrade immediately. The canonical fix is available in:
1.3.9c— stable branch: https://github.com/proftpd/proftpd/releases/tag/v1.3.9c1.3.10rc3(patch level 3) — release-candidate branch: https://github.com/proftpd/proftpd/releases/tag/v1.3.10rc3-3
# Debian / Ubuntu
sudo apt-get update && sudo apt-get install --only-upgrade proftpd-basic
# RHEL / CentOS / Fedora (if packaged)
sudo dnf upgrade proftpd
# From source
wget https://github.com/proftpd/proftpd/archive/refs/tags/v1.3.9c.tar.gz
tar xf v1.3.9c.tar.gz && cd proftpd-1.3.9c
./configure --with-modules=mod_sftp && make && sudo make install
The patch enforces a cumulative size guard before the realloc call:
/* FIXED: src/fxp.c (post-patch) */
static int fxp_packet_read(struct sftp_packet *pkt,
unsigned char *frag, uint32_t frag_len) {
if (frag_len > SFTP_MAX_PACKET_LEN) {
return -1;
}
if (pkt->payload_buf == NULL) {
pkt->payload_buf = palloc(pkt->pool, SFTP_MAX_PACKET_LEN);
pkt->payload_sz = SFTP_MAX_PACKET_LEN;
pkt->payload_len = 0;
}
/* FIXED: reject fragments that would exceed the absolute cap
* regardless of how many fragments have already been buffered */
if (pkt->payload_len + frag_len > SFTP_MAX_PACKET_LEN) {
pr_log_debug(DEBUG3, "MOD_SFTP: fragment reassembly limit exceeded "
"(current=%lu, incoming=%lu), rejecting packet",
(unsigned long)pkt->payload_len,
(unsigned long)frag_len);
errno = EOVERFLOW;
return -1;
}
memcpy(pkt->payload_buf + pkt->payload_len, frag, frag_len);
pkt->payload_len += frag_len;
return 0;
}
If an immediate upgrade is not possible, disabling mod_sftp in proftpd.conf (LoadModule mod_sftp.c commented out) eliminates the attack surface entirely, at the cost of SFTP functionality. Pure FTPS sessions are unaffected.
Our Take
This vulnerability is a textbook example of the cumulative vs. per-chunk boundary-check anti-pattern — one of the most persistent classes of heap overflow in network protocol parsers. The individual-fragment check passes; the multi-fragment state is never validated against the fixed allocation. This pattern appears repeatedly in FTP, SMTP, and media processing code bases because developers reason about single-message correctness without modeling adversarial fragmentation sequences.
For enterprises, the lesson is that protocol-level reassembly code must be treated with the same scrutiny as parsing code. Pool allocators, which are common in C server applications for performance, amplify the impact of overflows because freelist metadata sits inline with application data — there is no separate allocator heap to isolate. Memory-safe languages eliminate the primitive; where C is unavoidable, address sanitizers and fuzzing against fragmented input streams should be mandatory in CI.
From an operational perspective, the low privilege requirement is the key risk multiplier here. Any user with SFTP credentials is a potential attacker. Credential hygiene — short-lived keys, no shared accounts, mandatory MFA for SFTP access — limits the blast radius even before patching.
Detection with SAST
SAST detection of this pattern focuses on CWE-122 (Heap-based Buffer Overflow) and, more specifically, CWE-130 (Improper Calculation of Buffer Size) where a size computation involves a running accumulator that is not bounded against a maximum.
Offensive360’s analyzer flags the following code patterns in C/C++ codebases:
- Accumulator-unguarded
realloc: anyrealloc(buf, existing_len + incoming_len)whereexisting_lenis a mutable field and neither term is independently clamped against a declared maximum. - **Post-allocation
memcpywithout residual-capacity check
Detect this vulnerability class in your codebase
Offensive360 SAST scans your source code for CVE-2026-63090-class vulnerabilities and thousands of other patterns — across 60+ languages.