ProFTPD CVE-2026-42167: Brief Summary of a Pre-Auth SQL Injection Leading to RCE via mod_sql

A short review of CVE-2026-42167, a pre-authentication SQL injection in ProFTPD's mod_sql module that can lead to authentication bypass and remote code execution. Includes patch details and affected configurations.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-04-28

ProFTPD CVE-2026-42167: Brief Summary of a Pre-Auth SQL Injection Leading to RCE via mod_sql
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A logic error in ProFTPD's SQL escaping routine means that a single crafted FTP username, sent before any authentication takes place, can inject arbitrary SQL into the server's logging queries. Depending on the database backend, this can escalate from a simple injection to full remote code execution on the host.

ProFTPD is one of the three most widely deployed open source FTP daemons globally, alongside vsftpd and Pure-FTPd. Its modular architecture supports SQL database integration (via mod_sql), LDAP, and TLS, making it a common choice for organizations that need feature-rich FTP services. The breadth of its deployment means this vulnerability has a substantial potential blast radius for any environment running mod_sql with certain logging configurations.

Technical Information

Root Cause: The is_escaped_text Heuristic

The vulnerability lives in contrib/mod_sql.c, inside the is_escaped_text function. ProFTPD calls this function to determine whether a string has already been SQL-escaped before interpolating it into a database query. The function uses a purely syntactic heuristic: it returns TRUE (meaning "already escaped, skip escaping") if the input string:

  1. Starts with a single quote
  2. Ends with a single quote
  3. Contains no internal single quotes

This heuristic operates on raw, attacker-controlled input from the FTP session. A payload like '|| (SELECT 1) ||' satisfies all three conditions. It starts and ends with a single quote, and contains no unmatched internal quotes. When mod_sql substitutes this value into a configured SQL template that already wraps the variable in single quotes, the resulting query becomes valid, unescaped SQL.

Trigger Conditions

Two configuration conditions must be met simultaneously for a server to be exploitable:

  1. The administrator has defined a SQLNamedQuery for an INSERT or UPDATE statement that interpolates an attacker-controlled variable wrapped in single quotes. Vulnerable variables include %U (original username), %m (FTP method), %r (full command), and %{basename} (filename).

  2. The administrator has bound that query to a SQLLog directive for an FTP command the attacker can reach.

The most critical attack vector is the pre-authentication path. If the server uses the wildcard SQLLog ERR_* directive combined with the %U variable, the logging mechanism fires on a failed USER command. This means an attacker can trigger the SQL injection without ever providing valid credentials.

Attack Flow

  1. The attacker connects to the FTP service and issues a USER command with a crafted payload as the username, for example: '|| (SELECT 1) ||'
  2. The USER command fails authentication (as expected).
  3. Because the server is configured with SQLLog ERR_* referencing a SQLNamedQuery that uses %U, the logging subsystem fires.
  4. mod_sql calls is_escaped_text on the attacker's username. The heuristic returns TRUE because the payload starts and ends with a single quote and has no internal single quotes.
  5. The raw payload is interpolated directly into the SQL INSERT or UPDATE statement without escaping.
  6. The database engine executes the injected SQL.

Database Backend Impact

The vulnerability exists in the shared SQL framework, so all database backends are affected. However, the exploitation impact varies significantly:

PostgreSQL: Supports stacked queries. An attacker can append entirely new SQL statements to the logging query. This enables authentication bypass by injecting backdoor accounts into the users table via INSERT INTO users. If the ProFTPD database role has superuser privileges, the attacker can escalate to full remote code execution using COPY TO PROGRAM to execute arbitrary operating system commands.

SQLite: Also supports stacked queries. The SQLite database runs under the root process in the worker, allowing backdoor account injection. RCE via the database itself is not directly available.

MySQL: The mysql_real_query function is called without multi-statement support, which prevents trailing INSERT commands. Authentication bypass is significantly harder, and RCE via the database is not feasible through this vector.

Origin of the Vulnerability

The maintainer noted in the GitHub issue comments that the vulnerable logic was originally introduced by commit 86bdcf5, which addressed Issue #1149. This change shipped in ProFTPD 1.3.8a and was backported to 1.3.7b, establishing the window of affected versions.

Patch Information

The ProFTPD project patched CVE-2026-42167 in the 1.3.10rc1 release, published on April 27, 2026. The fix was authored by TJ Saunders (GitHub: Castaglia) and committed within hours of the responsible disclosure on GitHub Issue #2052. The primary fix commit is af90843 on the master branch, with an identical backport at 415395b for the 1.3.9 branch. Both commits modify a single file: contrib/mod_sql.c (64 additions, 39 deletions).

The patch takes a two-pronged approach:

New already_escaped Parameter

The function signature for sql_resolved_append_text() was changed from:

static int sql_resolved_append_text(pool *p, struct sql_resolved *resolved, const char *text, size_t text_len);

to:

static int sql_resolved_append_text(pool *p, struct sql_resolved *resolved, const char *text, size_t text_len, int already_escaped);

When already_escaped is TRUE, the is_escaped_text() heuristic is bypassed entirely because the caller has already run the proper backend escape function (sql_escapestring) on the value. Only when already_escaped is FALSE does the old heuristic path execute. This cleanly sidesteps the logic error without removing backward compatibility for truly pre-escaped text.

Preemptive Escaping of Client-Supplied Variables

The most critical part of the fix is in sql_resolve_on_meta(), where variable values are hydrated into SQL queries. Before the patch, all variable types (server-controlled and attacker-controlled) fell into the same generic case block. The patch splits them into two distinct groups:

Client-supplied (always escaped): LOGFMT_META_ANON_PASS, LOGFMT_META_BASENAME, LOGFMT_META_CMD_PARAMS, LOGFMT_META_COMMAND, LOGFMT_META_DIR_NAME, LOGFMT_META_DIR_PATH, LOGFMT_META_FILENAME, LOGFMT_META_IDENT_USER, LOGFMT_META_METHOD, LOGFMT_META_ORIGINAL_USER, LOGFMT_META_RESPONSE_STR, LOGFMT_META_REMOTE_HOST, LOGFMT_META_RENAME_FROM, LOGFMT_META_USER, and LOGFMT_META_XFER_PATH. These are now always run through sql_escapestring immediately, and their already_escaped flag is set to TRUE.

Server-controlled (no preemptive escape): Variables like LOGFMT_META_CLASS, LOGFMT_META_LOCAL_IP, LOGFMT_META_VERSION, LOGFMT_META_REMOTE_IP, and similar remain on the old code path.

This means that even if is_escaped_text() would have returned TRUE for a crafted username or filename, the value has already been properly escaped through the SQL backend's actual escape function (e.g., PQescapeStringConn for PostgreSQL) before it ever reaches that check.

The issue (#2052) was closed the same day it was opened.

Affected Systems and Versions

The vulnerable is_escaped_text logic was introduced in commit 86bdcf5, which shipped in ProFTPD 1.3.8a and was backported to 1.3.7b. All versions from that point forward are affected until the fix:

  • ProFTPD 1.3.7b through 1.3.9 (inclusive) when mod_sql is enabled
  • Any ProFTPD installation prior to 1.3.10rc1 on the master branch
  • Any ProFTPD installation prior to 1.3.9a on the 1.3.9 branch

Vulnerable configurations require:

  • mod_sql enabled with a SQL backend (PostgreSQL, SQLite, or MySQL)
  • A SQLNamedQuery that interpolates attacker-controlled variables (%U, %m, %r, %{basename}) wrapped in single quotes
  • A SQLLog directive binding that query to an FTP command reachable by the attacker (especially ERR_* for the pre-authentication vector)

Patched versions: 1.3.9a and 1.3.10rc1.

Vendor Security History

ProFTPD has experienced various security vulnerabilities over its history, including memory corruption and denial of service flaws. The project's CVE history can be reviewed on CVE Details. Notably, mod_sql has been a target before; a prior SQL injection in mod_sql via usernames is documented on Exploit-DB. The project has consistently provided timely patches and transparent release notes, and the response to CVE-2026-42167 was no exception, with the fix committed and released within hours of disclosure.

References

Detect & fix
what others miss

Security magnifying glass visualization