This is a loaded trapdoor, not an open front door
CVE-2026-42167 is an SQL injection flaw in ProFTPD mod_sql affecting upstream releases before 1.3.9a. The bug sits in how attacker-controlled values are treated as already-escaped text when SQLNamedQuery expands log variables such as %U, %r, %f, or similar. The nastiest pre-auth path is when admins log failed USER events via SQLLog ERR_* or broad SQLLog * rules and include %U; then an unauthenticated client can feed SQL into the logging path before login succeeds.
The vendor's HIGH 8.1 baseline is directionally right because this can become auth bypass or even DB-host RCE. But real-world exploitability is much narrower than raw CVSS implies: you need ProFTPD exposed, mod_sql enabled, vulnerable SQLLog/SQLNamedQuery usage, and usually a cooperative backend such as PostgreSQL or SQLite for the cleanest abuse. That config dependency keeps this out of CRITICAL for most enterprises, but if you run shared-hosting-style ProFTPD with SQL auth/logging, it deserves urgent attention.
4 steps from start to impact.
Find reachable ProFTPD targets
Shodan, or a nuclei workflow to locate FTP services presenting ProFTPD banners. Public tooling already references this CVE, so discovery is cheap. The problem is not every ProFTPD host is actually exploitable; version and banner checks only identify candidates.- Internet-reachable FTP service
- ProFTPD deployed on the target
- Many enterprises do not expose ProFTPD to the public internet
- Banner/version checks cannot prove
mod_sqlor vulnerable logging is enabled
Trigger the pre-auth SQL injection via USER
ZeroPathAI repository send crafted usernames that abuse mod_sql variable expansion in SQLNamedQuery. The clean pre-auth case requires SQLLog ERR_* or similarly broad logging plus an attacker-controlled token like %U so the failed USER request is written into SQL before authentication. ProjectDiscovery nuclei has also published a detection template, which lowers attacker effort.mod_sqlenabled- A vulnerable
SQLNamedQueryuses attacker-controlled fields SQLLog ERR_*orSQLLog *reaches the vulnerable query- The target value is inserted through the flawed escaping path
mod_sqlis an extension, not a universal default- Many deployments do not log failed
USERrequests into SQL - Admins may use SQL auth without the vulnerable logging pattern
USER payloads, but benign username oddities create noise.Turn SQL injection into auth bypass
- SQL backend is also used for ProFTPD authentication
- Backend/query path supports writes useful to the attacker
- DB account used by ProFTPD can modify the relevant auth tables
- Separate logging and auth databases break the neatest abuse chain
- MySQL is harder here because
CLIENT_MULTI_STATEMENTSis not enabled in the described path - Least-privileged DB roles can limit table writes
INSERT statements into user/auth tables; most package scanners will miss this because the risk depends on live config and DB privileges.Escalate to database-host RCE
COPY ... TO PROGRAM after the SQL injection lands. That produces OS command execution on the database host as the postgres user, which can then be parlayed into credential theft, persistence, or lateral movement. This is real, but it is the narrowest branch of the tree.- PostgreSQL backend in use
- ProFTPD DB role has superuser-equivalent rights needed for
COPY TO PROGRAM - Backend allows the injected statement form to execute
- Many sane PostgreSQL deployments do not grant ProFTPD a superuser role
- Managed DB services often block dangerous program-execution features
- RCE lands on the DB host, not necessarily the FTP host
The supporting signals.
| In-the-wild status | No confirmed CISA KEV listing and no public source found showing a named mass-exploitation campaign. VulnCheck says no public threat-intel feed had tagged a dedicated campaign at publication time, but judged opportunistic exploitation likely because a working PoC was already public. |
|---|---|
| PoC availability | Public exploit material is available: ZeroPathAI/proftpd-CVE-2026-42167-poc documents pre-auth backdoor-user and PostgreSQL RCE paths, and ProjectDiscovery nuclei-templates added a template for this CVE. |
| EPSS | EPSS from your intel block is 0.0699. That's not top-tier internet fire, but it is high enough that defenders should expect commoditized testing once exposure is identified. |
| KEV status | Not KEV-listed based on the user-supplied intel and no CISA search result for this CVE. That removes the strongest current exploitation amplifier. |
| CVSS vector meaning | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H means unauthenticated network reachability with high attack complexity. That AC:H is doing real work here because exploitability depends on a specific logging/query/backend design, not just a vulnerable binary. |
| Affected versions | Upstream ProFTPD before 1.3.9a is affected. Debian also tracked the issue as affecting stable and oldstable package lines, including 1.3.8.c+dfsg-4+deb12u4 and 1.3.7a+dfsg-12+deb11u5 before distro backports. |
| Fixed versions | Upstream fix is 1.3.9a; the first release-candidate line 1.3.10rc1 also announced a fix. Debian proposed/backported fixes include 1.3.8.c+dfsg-4+deb13u2 for trixie. |
| Exposure data | VulnCheck reported a Shodan query returning roughly 615,000 ProFTPD banners on port 21 globally, while citing ZeroPath's estimate that at least ~1% of publicly accessible ProFTPD instances may be vulnerable to the pre-auth path. That suggests the truly exploitable population is likely in the low thousands, not hundreds of thousands. |
| Disclosure timeline | Public disclosure landed on 2026-04-28. The openwall summary cites report date 2026-03-28, fix commit on 2026-04-27, and release of 1.3.9a on 2026-04-27. |
| Researcher / reporting | The issue discussion and PoC trail point to ZeroPath Research and GitHub issue #2052 (LeftenantZero) as the public technical reporting path. |
noisgate verdict.
The decisive factor is configuration reachability: exploitation usually requires mod_sql plus a vulnerable SQLLog/SQLNamedQuery pattern, which sharply reduces the exposed population versus a generic pre-auth daemon RCE. It stays in HIGH because the reachable branch is still unauthenticated remote abuse with public PoCs, and the resulting impact can be auth bypass or RCE on the database host.
Why this verdict
- Downward pressure: requires more than a version match — attackers need ProFTPD,
mod_sql, and a vulnerableSQLLog/SQLNamedQuerydesign using attacker-controlled substitutions such as%Uor%f. - Downward pressure: pre-auth reachability is a special case — the headline pre-auth path specifically hinges on logging failed
USERrequests withERR_*or broad wildcard logging, which many deployments will not do. - Downward pressure: RCE is not the default outcome — the clean RCE branch depends on PostgreSQL plus a role powerful enough for
COPY TO PROGRAM; many environments will top out at SQL abuse or auth bypass, not OS command execution. - Upward pressure: attacker position is excellent when the config exists — no credentials, no user interaction, public PoC, and raw FTP exposure mean low operational cost for adversaries once they find the right target.
- Upward pressure: blast radius can still be ugly — if SQL auth and logging share the same backend, attackers can plant an account and convert a logging bug into durable file-system access through the daemon.
Why not higher?
This is not a broad-spectrum internet RCE against all ProFTPD instances. The exploit chain compounds multiple prerequisites: external exposure, mod_sql, vulnerable SQL logging, and sometimes backend-specific privileges. Those are real narrowing factors, so calling it CRITICAL would overstate the population actually one-packet-away from compromise.
Why not lower?
Once the vulnerable config exists, this is still unauthenticated remote abuse with working public exploit material. The auth-bypass path is operationally meaningful even without PostgreSQL COPY TO PROGRAM, and internet-facing FTP services tend to attract automated testing quickly after disclosure.
What to do — in priority order.
- Disable risky SQL logging paths — Remove or rewrite
SQLLog ERR_*,SQLLog *, and anySQLNamedQuerythat expands attacker-controlled tokens like%U,%u,%r,%f,%F,%J, or%Sinto SQL. This is the highest-value temporary control and should be deployed within 30 days for a HIGH verdict. - Restrict FTP exposure — Put ProFTPD behind VPN, partner allowlists, or internal-only network boundaries so unauthenticated internet traffic cannot hit the vulnerable
USERpath. If the service must remain online, reduce source IPs aggressively within 30 days. - Downgrade database privileges — Ensure the ProFTPD database account cannot execute dangerous backend features and cannot write to authentication tables unless absolutely required. This shrinks auth-bypass and PostgreSQL
COPY TO PROGRAMimpact and should be completed within 30 days. - Separate auth and log backends — If ProFTPD must use SQL, split logging from authentication so a logging injection cannot directly plant login-capable accounts. That architectural mitigation is slower than a config change, but it is still a valid risk reducer within 30 days for exposed high-value hosts.
- Disable
mod_sqlwhere unused — Many estates carry optional modules they no longer need. Ifmod_sqlis not required for business function, remove or stop loading it and collapse the attack surface within 30 days.
- A WAF does not meaningfully help on raw FTP control traffic unless you have a protocol-aware FTP security gateway inline.
- Hiding or changing the FTP banner does not remove the vulnerable logging path; it only makes coarse fingerprinting slightly harder.
- MFA for admins does not stop the unauthenticated
USER-logging path, and it does nothing for SQL injection into the daemon's backend.
Crowdsourced verification payload.
Run this on the target ProFTPD host as root or a user that can read ProFTPD config files. Save as check-cve-2026-42167.sh, then run sudo bash check-cve-2026-42167.sh /etc/proftpd/proftpd.conf; if no argument is supplied, it will inspect common config paths. It checks upstream version, whether mod_sql is loaded, and whether risky SQLLog/SQLNamedQuery patterns appear; because includes and distro packaging vary, ambiguous results return UNKNOWN.
#!/usr/bin/env bash
# check-cve-2026-42167.sh
# Detect likely exposure to CVE-2026-42167 on a ProFTPD host.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -u
TARGET_CONF="${1:-}"
TMPFILE="$(mktemp 2>/dev/null || echo /tmp/proftpd-cve-2026-42167.$$)"
trap 'rm -f "$TMPFILE"' EXIT
say() { printf '%s\n' "$*"; }
unknown() { say "UNKNOWN: $*"; exit 2; }
patched() { say "PATCHED: $*"; exit 0; }
vuln() { say "VULNERABLE: $*"; exit 1; }
have_cmd() { command -v "$1" >/dev/null 2>&1; }
ver_lt() {
# returns 0 if $1 < $2 using sort -V
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" != "$2" ] && [ "$1" != "$2" ]
}
collect_confs() {
if [ -n "$TARGET_CONF" ] && [ -f "$TARGET_CONF" ]; then
printf '%s\n' "$TARGET_CONF"
return 0
fi
for p in \
/etc/proftpd/proftpd.conf \
/etc/proftpd.conf \
/usr/local/etc/proftpd.conf; do
[ -f "$p" ] && printf '%s\n' "$p"
done
}
expand_includes() {
local seed="$1"
awk '
BEGIN { print FILENAME }
/^[[:space:]]*Include[[:space:]]+/ {
for (i=2; i<=NF; i++) print $i
}
' "$seed" 2>/dev/null | while read -r item; do
case "$item" in
*"*"*|*"?"*|*"["*)
for f in $item; do [ -f "$f" ] && printf '%s\n' "$f"; done
;;
*)
[ -f "$item" ] && printf '%s\n' "$item"
;;
esac
done
}
get_version() {
local v=""
if have_cmd proftpd; then
v="$(proftpd -v 2>/dev/null | sed -n 's/.*Version \([0-9][^ ]*\).*/\1/p' | head -n1)"
[ -z "$v" ] && v="$(proftpd -vv 2>/dev/null | sed -n 's/.*Version \([0-9][^ ]*\).*/\1/p' | head -n1)"
fi
printf '%s' "$v"
}
VERSION="$(get_version)"
[ -z "$VERSION" ] && unknown "Unable to determine ProFTPD version via proftpd -v"
CONF_LIST="$(collect_confs)"
[ -z "$CONF_LIST" ] && unknown "Could not find a ProFTPD configuration file"
: > "$TMPFILE"
for c in $CONF_LIST; do
[ -f "$c" ] && printf '%s\n' "$c" >> "$TMPFILE"
expand_includes "$c" >> "$TMPFILE"
done
sort -u "$TMPFILE" -o "$TMPFILE"
[ ! -s "$TMPFILE" ] && unknown "No readable configuration files found"
if ! ver_lt "$VERSION" "1.3.9a"; then
patched "Upstream version $VERSION is not older than 1.3.9a; verify distro backport status separately if your package versioning differs"
fi
if ! grep -Eisq '^[[:space:]]*(LoadModule[[:space:]]+mod_sql|Include[[:space:]].*mod_sql|ModulePath|SQLAuthTypes|SQLEngine[[:space:]]+on|SQLBackend)' "$TMPFILE"; then
patched "Version $VERSION is old, but no obvious mod_sql usage was found in the inspected configs"
fi
RISKY_TOKENS='%U|%u|%r|%f|%F|%J|%S|%A|%D|%d|%l'
HAS_SQLLOG_WIDE=0
HAS_RISKY_QUERY=0
HAS_POSTGRES=0
HAS_SQLITE=0
HAS_MYSQL=0
if grep -Eisq '^[[:space:]]*SQLLog[[:space:]]+([*]|ERR_\*|ALL)' "$TMPFILE"; then
HAS_SQLLOG_WIDE=1
fi
if grep -Eisq "^[[:space:]]*SQLNamedQuery[[:space:]].*(${RISKY_TOKENS})" "$TMPFILE"; then
HAS_RISKY_QUERY=1
fi
if grep -Eisq '^[[:space:]]*SQLBackend[[:space:]]+postgres' "$TMPFILE"; then HAS_POSTGRES=1; fi
if grep -Eisq '^[[:space:]]*SQLBackend[[:space:]]+sqlite' "$TMPFILE"; then HAS_SQLITE=1; fi
if grep -Eisq '^[[:space:]]*SQLBackend[[:space:]]+mysql' "$TMPFILE"; then HAS_MYSQL=1; fi
DETAILS="version=$VERSION mod_sql=present"
[ "$HAS_SQLLOG_WIDE" -eq 1 ] && DETAILS="$DETAILS sqllog=wide" || DETAILS="$DETAILS sqllog=not-wide"
[ "$HAS_RISKY_QUERY" -eq 1 ] && DETAILS="$DETAILS namedquery=risky" || DETAILS="$DETAILS namedquery=no-obvious-risky-token"
[ "$HAS_POSTGRES" -eq 1 ] && DETAILS="$DETAILS backend=postgres"
[ "$HAS_SQLITE" -eq 1 ] && DETAILS="$DETAILS backend=sqlite"
[ "$HAS_MYSQL" -eq 1 ] && DETAILS="$DETAILS backend=mysql"
if [ "$HAS_SQLLOG_WIDE" -eq 1 ] && [ "$HAS_RISKY_QUERY" -eq 1 ]; then
vuln "$DETAILS"
fi
if [ "$HAS_RISKY_QUERY" -eq 1 ]; then
unknown "$DETAILS; risky SQLNamedQuery found, but pre-auth reachability depends on which commands are bound to SQLLog"
fi
unknown "$DETAILS; old version with mod_sql present, but no decisive vulnerable pattern found in inspected configs"If you remember one thing.
mod_sql from the much smaller set using SQL logging with attacker-controlled substitutions. For anything internet-facing in that second bucket, apply compensating controls from the noisgate mitigation SLA within 30 days by removing risky SQLLog/SQLNamedQuery paths or restricting exposure, and complete the actual upgrade to 1.3.9a or the relevant distro backport under the noisgate remediation SLA within 180 days.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.