
パッチからRCEへ:CVE-2010-4221(ProFTPD TELNET IACスタックオーバーフロー)向けに手作業で構築したエクスプロイト。失敗から学ぶ全プロセスを詳細に記録。
CVE-2010-4221 — ProFTPDのpr_netio_telnet_gets()における認証前スタックバッファオーバーフロー — に対する、完全に再現可能なラボと手書きのrawソケットエクスプロイトです。脆弱性研究とエクスプロイト開発の学習演習として構築されました。
すべての失敗は記録されています。順調な道のりは嘘であり、回り道こそが教訓です。
このリポジトリは教育的な成果物です。メンターやトレーニングを受ける余裕のない人々が、メモリ破壊エクスプロイトが実際にどのように生まれるかを学ぶために存在します:パッチから、失敗を経て、自分が所有するラボ内での動作する概念実証まで。
レッドチーム業務 — 本物のプロフェッショナルな攻撃的セキュリティ — は、一言で定義されます:許可。プロフェッショナルが行うすべては、書面による合意の中で行われます:スコープ、ターゲット、許可された技術、時間枠、承認した人物を明記した署名済みの交戦規則文書です。その書類がなければ、まったく同じキーストロークは職業ではなく、地球上のほぼすべての法域で犯罪です。
したがって、このリポジトリの契約は以下の通りで、交渉の余地はありません:
技術は学ぶ価値があります。技術は、それに伴う規律があって初めて価値を持つのです。
ProFTPDはFTP制御チャネル上でTELNETエスケープシーケンスを処理します。TELNETでは、0xFF(IAC、「Interpret As Command」)がエスケープバイトであり、リテラルの0xFFはとして送信されます。
0xFF 0xFFpr_netio_telnet_gets()はクライアントのバイトをスタックバッファ(pr_cmd_readのchar buf[PR_DEFAULT_CMD_BUFSZ+1]、glibcのMAXPATHLEN=4096で4104バイト)にコピーし、残り容量をbuflen — size_t、符号なし — で追跡します。
脆弱なパス(1.3.3a、netio.c):
case TELNET_IAC:
switch (cp) {
...
default:
*bp++ = TELNET_IAC; // 書き込み #1
buflen--; // デクリメント #1
telnet_mode = 0;
break;
}
break;
...
*bp++ = cp; // 書き込み #2
buflen--; // デクリメント #2 <-- 間にチェックなし
2回の書き込み、2回のデクリメント、その間にゼロチェックなし。buflenがちょうど1のとき、このペアはそれを0にデクリメントし、その後SIZE_MAX(1800京)にアンダーフローします。ループはバッファが無限であると信じ、攻撃者が制御するバイトをスタック上に書き続けます — 保存されたレジスタ、保存されたRBP、そしてリターンアドレスを越えて。
認証前。 この関数はUSER/PASSが処理される前に実行されます。
修正(コミット
3cc69b8388、
「Bug#3521 - Telnet IAC processing stack overflow」、1.3.3cでリリース)
は12行です。セキュリティ境界全体は以下の通りです:
if (buflen == 0) {
break;
}
patch.diffを参照してください。パッチを読むことで傷がどこにあったかがわかります — それがスキルです。
Dockerfileは、歴史的なDebianスナップショットソースからProFTPD 1.3.3aをコンパイルします。意図的に安全ではありません(これが2010年の姿です):
-fno-stack-protector — カナリアなし-z execstack — 実行可能スタック(NXなし)-no-pie — 固定バイナリアドレスgdbの下で実行。デフォルトでASLRを無効化 → 決定論的スタックdocker build -t proftpd-133a .
docker rm -f lab133 2>/dev/null
docker run -d --name lab133 --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined -p 127.0.0.1:2122:21 \
proftpd-133a sh -c 'gdb -batch -ex "set follow-fork-mode child" \
-ex "run" -ex "continue" --args /usr/local/sbin/proftpd -n -d1 \
> /tmp/gdb.txt 2>&1; sleep 600'
python3 exploit.py
期待される出力:
[S] 220 ProFTPD 1.3.3a Server (lab-iac) ...
[S] THE SERVER SAID: b'PWNED!!PWNED!!'
(シェルコードはfd 0、1、2に書き込みます。これは、どれが制御チャネルを担うかを知ることに依存したくなかったためです — そのうちの2つが応答します。)
"SITE " + NOPスレッド + シェルコード + [\xff\xffフラッド] + パディング + [ret] + "\n"
^^^^^^^^^^^^^^^^^^^^ ^^^^
シェルコードはコマンド オーバーフローの末尾は
バッファ内に存在する — 1つのアドレスだけを
誰も触れない領域 配信する
buf[4102]だけが触れられます(切り詰めNUL)。フレームの生存ローカル変数より下はすべて静かです。buflenをアンダーフローに導きます(パリティ問題については「旅路」を参照)。pr_cmd_readが解析後にreturn 0に達すると、CPUはスレッドに着地し、シェルコードに滑り込みます。この逆転アーキテクチャ — ペイロードが最初、フラッドが2番目、アドレスが最後 — は、同じレイアウトを使用する標準的なMetasploitモジュール(proftp_telnet_iac)に対して検証されました。彼らのターゲットにはNXがあったため、resポインタの「4重デリファレンス」を備えたROPチェーンが必要でした。私たちのラボには実行可能スタックがあるため、単一の直接リターンで十分です。
最終的なエクスプロイトは60行です。その代償:
盲目的な\xffフラッド → 何も起きない。 サーバーは丁寧にセッションを閉じました。根本原因:buflenは4102(偶数)から始まり、各IACペアは2ずつデクリメントするため、1ではなく0にきれいに着地します。アンダーフローにはパリティの破壊が必要です。教訓:状態機械を読むことはスプレーよりも優れています。
誤ったバッファサイズ。 最初の較正試行は1024バイトのバッファを想定しました。実際はLinux/glibcでMAXPATHLEN+8 = 4104です。フラッドはターゲットの3KB手前で停止しました。教訓:ターゲットを想定せず、測定してください。
最初のSIGSEGV。 周期的なde Bruijnパターン(Aa0Aa1...)はリターンスロットをbuf+4152に配置し、2回相互検証されました(フレーム計算+パターンオフセット)。教訓:周期的パターンは巻尺であり、エクスプロイトではありません。
RIP制御。 スロットを0x4141414141414141に設定すると、ret命令自体がクラッシュしました — x86-64は非正規アドレスを拒否し、フォールトはret上に発生し、私たちの値はバックトレースで待機しています。教訓:フレーム内に自分の値がある状態でretでクラッシュ = 制御。
retスロットより上のシェルコード → 破壊される。 8バイトがヒープポインタ(0x4d7838 — 後にcmd_recプール割り当てと特定)によって上書きされました。スロットより上のスタックフレームは、着地と乗っ取りの間もまだ動作している関数に属しています。教訓:オーバーフローが最後の書き込みではありません。プログラムは、あなたが破壊したばかりのスタック上で生き続けます。
スロットより下の「デッドゾーン」→ 同様に書き荒らされる。 pr_cmd_read自身のローカル変数(cmd、buflen、cp)がすぐそこに存在し、解析中に保存され続けます。
ハードウェアウォッチポイントによるフォレンジック。 gdbでのwatch *(long*)ADDRは謎をカメラに変えました:破壊されたアドレスへのすべての書き込みを、バックトレース付きで、順番に記録します。教訓:「誰がこのメモリを書いたのか」という疑問に対する答えは、1つのgdbコマンドで得られます。
リファレンスを読み、そして理解する。 標準モジュールは逆転アーキテクチャを確認しました。独自のメンタルモデルを構築した後に他のエクスプロイトを読むことは研究です。前に読むことはコピーです。
size_tは決して負にならない — 巨大になる。 残り容量カウンタの整数アンダーフローは、追加の手順を伴うスタックオーバーフローです。\x0a(読み取りを終了)を回避し、設計により\xff(TELNETエスケープ)を生き延びます。argv[0]の11バイトの違い(ビルドツリーのバイナリとインストール済みバイナリ)がスタック全体を0x40シフトさせ、完璧なエクスプロイトを静かに無効化しました。modules/exploits/linux/ftp/proftp_telnet_iac.rb
(rapid7/metasploit-framework)diegslvaによって構築されました。公開で学習しています — 「エクスプロイトを書いたことがない」状態から、手作りのシェルコードによる認証前RCEまで、記録された1日で到達しました。このリポジトリが何かを教えてくれたなら、それを次の人に渡してください。