
これはCVE-2024-6387用に書いたPOCです。
Qualys Security Advisory
regreSSHion: OpenSSHサーバーにおけるRCE、glibcベースのLinuxシステム上 (CVE-2024-6387)
概要 SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3 (Debian 3.0r6, 2005年)
必要なのは信仰の跳躍だけ
-- The Interrupters, "Leap of Faith"
予備的注記: OpenSSHは世界で最も安全なソフトウェアの一つです。この脆弱性は、ほぼ完璧な実装における一度のミスです。その多層防御設計とコードは模範であり、インスピレーションを与えるものであり、OpenSSHの開発者の模範的な仕事に感謝します。
私たちはOpenSSHサーバー(sshd)において、シグナルハンドラの競合状態の脆弱性(CVE-2024-6387)を発見しました。クライアントがLoginGraceTime秒(デフォルト120秒、古いOpenSSHバージョンでは600秒)以内に認証しない場合、sshdのSIGALRMハンドラが非同期に呼び出されます。しかし、このシグナルハンドラは非同期シグナルセーフではないさまざまな関数(例: syslog())を呼び出します。この競合状態は、sshdのデフォルト設定に影響します。
調査の結果、この脆弱性は実際には2006年にMark Dowd氏によって報告されたCVE-2006-5051(「OpenSSH 4.4以前のシグナルハンドラ競合状態により、リモート攻撃者がサービス拒否(クラッシュ)を引き起こし、任意のコードを実行する可能性がある」)の回帰であることがわかりました。
この回帰は2020年10月(OpenSSH 8.5p1)に、コミット752250c(「OpenSSHのための改訂されたログ基盤」)によって導入されました。このコミットは、sshdのSIGALRMハンドラから直接呼び出される関数sigdie()から"#ifdef DO_LOG_SAFE_IN_SIGHAND"を誤って削除しました。言い換えれば:
この脆弱性はglibcベースのLinuxシステム上でリモートから悪用可能であり、syslog()自体が非同期シグナルセーフでない関数(例: malloc()やfree())を呼び出します。これは認証されていないリモートコード実行をroot権限で引き起こします。sshdの特権コードに影響し、サンドボックス化されておらず、完全な権限で実行されるためです。私たちは他のlibcやオペレーティングシステムについては調査していません。ただし、OpenBSDは明らかに影響を受けません。なぜなら、そのSIGALRMハンドラはsyslog_r()を呼び出しますが、これはOpenBSDが2001年に発明したasync-signal-safeなsyslog()のバージョンだからです。
この脆弱性をリモートから悪用するために(私たちの知る限り、CVE-2006-5051はこれまでに正常に悪用されたことはありません)、私たちは2001年にMichal Zalewski氏によって発表された先見性のある論文「Delivering Signals for Fun and Profit」からインスピレーションを得ました。
https://lcamtuf.coredump.cx/signals.txt
それでも、私たちは直ちに3つの主要な問題に直面しました。
これらの3つの問題に集中し、最新のオペレーティングシステムの保護機構(特にASLRとNX)にすぐに立ち向かわなくて済むように、まずi386上の古いOpenSSHバージョンをエクスプロイトし、その後その経験に基づいて最近のバージョンをエクスプロイトすることにしました。
最初に、"SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3"("debian-30r6-dvd-i386-binary-1_NONUS.iso"から)を取り上げました。これは、デフォルトで権限分離が有効になっている最初のDebianバージョンであり、当時の重大な脆弱性(特にCVE-2003-0693とCVE-2002-0640)に対してパッチが適用されています。
このバージョンをリモートから悪用するために、free()の呼び出しをSIGALRMで中断し(sshdの公開鍵解析コード内)、ヒープを矛盾した状態にし、SIGALRMハンドラ内の別のfree()呼び出し中にこの矛盾した状態を悪用します。
実験では、この競合状態に勝つには平均で約10,000回の試行が必要でした。つまり、600秒(LoginGraceTime)あたり10接続(MaxStartups)が受け入れられる場合、リモートルートシェルを取得するには平均で約1週間かかります。
2番目に、"SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3"("ubuntu-6.06.1-server-i386.iso"から)を取り上げました。これは、CVE-2006-5051(「OpenSSH 4.4以前のシグナルハンドラ競合状態」)の影響を受ける最後のUbuntuバージョンです。
このバージョンをリモートから悪用するために、pam_start()の呼び出しをSIGALRMで中断し、PAMの構造の1つを矛盾した状態にし、SIGALRMハンドラ内のpam_end()呼び出し中にこの矛盾した状態を悪用します。
実験では、この競合状態に勝つには平均で約10,000回の試行が必要でした。つまり、120秒(LoginGraceTime)あたり10接続(MaxStartups)が受け入れられる場合、リモートルートシェルを取得するには平均で約1~2日かかります。
最後に、"SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2"("debian-12.5.0-i386-DVD-1.iso"から)を取り上げました。これは現在のDebian安定版であり、CVE-2006-5051の回帰の影響を受けます。
このバージョンをリモートから悪用するために、malloc()の呼び出しをSIGALRMで中断し(sshdの公開鍵解析コード内)、ヒープを矛盾した状態にし、SIGALRMハンドラ内の別のmalloc()呼び出し中(より正確にはsyslog()内)にこの矛盾した状態を悪用します。
実験では、この競合状態に勝つには平均で約10,000回の試行が必要でした。したがって、120秒(LoginGraceTime)あたり100接続(MaxStartups)が受け入れられる場合、約3~4時間です。最終的に、リモートルートシェルを取得するには平均で約6~8時間かかります。なぜなら、glibcのアドレスを正しく推測できるのは半分の時間だけだからです(ASLRのため)。
この研究はまだ進行中です。
amd64での作業を開始してから数日後、sshdのSIGALRMハンドラにおけるデッドロックに関する以下のバグレポート(OpenSSHの公開Bugzilla内)に気付きました。
https://bugzilla.mindrot.org/show_bug.cgi?id=3690
したがって、私たちはすぐにOpenSSHの開発者に連絡し(このデッドロックが悪用可能な脆弱性によって引き起こされていることを知らせるため)、amd64の作業を保留し、このアドバイザリの作成を開始しました。
しかし、それは私らしくない、私は解放されている
-- The Interrupters, "Haven't Seen the Last of Me"
このOpenSSHバージョンのSIGALRMハンドラはpacket_close()を呼び出し、それはbuffer_free()を呼び出し、さらにxfree()、そしてfree()を呼び出します。free()はasync-signal-safeではありません。
そこで、このDebianのglibc(2.2.5)のmallocコードを調べ始めました。free()への最初の呼び出しがSIGALRMによって中断され、SIGALRMハンドラ内の2回目のfree()呼び出し(上記の341-344行)中に悪用される可能性があるかどうかを確認するためです。このglibcのmallocは、2000年にSolar Designer氏が先駆的に開発したunlink()テクニックに対して強化されていないため、chunk_free()(free()から内部的に呼び出される)の中にすぐに興味深いコードパスを見つけました。
このコードパスを悪用するために、sshdのヒープを以下のレイアウトに配置します(chunk_X、chunk_Y、chunk_Zはmalloc()されたメモリチャンクであり、p、s、f、bはそれぞれprev_size、size、fd、bkフィールドです)。