
2026/04/18 3:43
「readme.txt」の内容を表示するだけでも安全とは限りません。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
iTerm2 の SSH 統合機能に重大なセキュリティ上の欠陥が存在し、攻撃者は偽装されたファイルを読み込ませることで、SSH コンダクターのワークフローを模倣し、局所上で恣意的なコードを実行できるようにしている。この脆弱性は、iTerm2 が不正な
DCS 2000p フックおよび OSC シーケンス(例:有効な返信を模倣したもの)を受信する際、正当なリモートコンダクターセッションから発信されたかのように無批判に受け付けることに起因している。これらの偽装メッセージ内の sshargs 値を操作することにより、攻撃者はパス解決を制御し、ベース64符号化されたペイロードの最終 128 バイトチャンクに埋め込まれた偽装された相対パス名を通じてローカルの実行可能ファイルへのポインタを指させることができる。これにより、iTerm2 はリモート実行意図の入力を局所コマンドとして扱い、標準的なセッション機能を外部サーバーへのアクセスが必要ない直接的な攻撃ベクターへと変質させてしまう。発見後まもなくパッチが策定されコミットされたものの、現在の安定版を使用しているユーザーは修正がリリースされるまで脆弱にさらされており、安全なリモート操作と危険な局所操作を区別するにおける深刻な信頼の欠如を浮き彫りにしている。本文
先日の Vim および Emacs の AI によるバグ発見に関する投稿では、一見無害なワークフローが、予期せぬコード実行行為へと至る境界線如何に焦点を当てていました。今回もその考えを一層深化させ、「readme.txt を cat コマンドで表示すること」自体が安全なのかどうかを検証します。驚くべきことに、 iTerm2 を使用している場合、この操作は決して安全ではありません。一見すると非合理的に見えるこの脆弱性は、 iTerm2 が正当な機能として何を目指しており、 PTY(擬似終端)をどのように利用しているか、そして終端エミュレータからの出力が、その機能のプロトコルの一端を不正に模倣できるようになった場合に何が起きるかを理解すれば明らかになります。本プロジェクトにおいて OpenAI と協力してくださったことへの謝意を表したいと考えています。
iTerm2 は SSH 統合機能を搭載しており、これによりリモートセッションに対するより深い理解が可能となっています。この機能を実現するためには、リモートシェルに対して命令を盲目的に打ち出すだけでは不十分であり、代わりにリモート側で tiny helper スクリプト「conductor」を展開してブートストラップ(初期化)を行います。
大まかなモデルは以下の通りです:
- iTerm2 は通常、 it2ssh を通じて SSH 統合を起動します。
- iTerm2 は既存の SSH セッションを通じて、リモート側のブートストラップスクリプトである「conductor」を送信します。
- そのリモートスクリプトが iTerm2 に対するプロトコル対話者(peer)となります。
- iTerm2 とリモートの conductor は、端末エスケープシーケンスをやり取りし、ログインシェルを検知したり、Python の有無を確認したり、ディレクトリを変更したり、ファイルをアップロードしたり、コマンドを実行したりといった作業を調整します。
重要な点は、別個のネットワークサービスが存在しないということです。conductor は単にリモートシェルセッション内で実行されているスクリプトであり、プロトコルは通常の端末入出力(I/O)を通じて運搬されます。
かつて終端(ターミナル)は、キーボードと画面をマシンに接続した真のハードウェアデバイスでした。プログラムはそのデバイスから入力を読み取り、結果として出力をそのデバイスに書き戻しました。 iTerm2 のような終端エミュレータは、このハードウェア終端の近代ソフトウェア版です。画面を描画し、キーボード入力を受容するとともに、端末制御シーケンスを解釈します。しかし、シェルやその他のコマンドラインプログラムは、まだ真の終端デバイスのように見えるものと対話することを期待しています。そのため、OS は PTY(擬似終端)を提供します。PTY は古いハードウェア終端に対するソフトウェア的な代替物であり、終端エミュレータと最前列のプロセスの間を挟んでいます。
通常の SSH セッションでは:
- iTerm2 は PTY へバイト列を書き込みます
- 最前列のプロセスは ssh です
- ssh はそれらのバイト列をリモートマシンへ転送します
- リモートの conductor はその stdin からそれらを読み取ります
したがって、 iTerm2 が「コマンドをリモートの conductor に送信したい」と考え、ローカルで実行する実際の動作は、PTY へバイト列を書き込むことです。
SSH 統合プロトコルは端末エスケープシーケンスを転送層(transport)として利用します。ここで重要なのは以下の二点です:
- DCS 2000p は SSH conductor のフック(接続)に使用されます
- OSC 135 はプレフレームされた conductor メッセージに使用されます
ソースコードレベルでは、DCS 2000p が iTerm2 で conductor パーサーの実装(インスタンス化)を促します。その後、パーサーは begin command output lines end runhook のような OSC 135 メッセージを受け取ります。つまり、正当なリモート conductor は完全に端末出力を通じて iTerm2 と通信することができます。
この脆弱性の根源には「信頼の欠如」があります。 iTerm2 は、実際には信頼できない(真の conductor セッションではない)ものから発信されていると判断される端末出力からも、SSH conductor プロトコルを受け付けてしまいます。言い換えると、信頼できない端末出力がリモート conductor を模倣できるのです。これは、悪意のあるファイル、サーバー応答、バナー(Banner)、あるいは MOTD が以下の内容を表示することを可能にします:
- 偽造された DCS 2000p フック
- 偽造された OSC 135 レプライ そして iTerm2 は、あたかも正規の SSH 統合エクスチェンジの最中にあるかのように振る舞い始めます。これが攻撃のプリミティブ(基本単位)です。
exploit ファイルには、偽物の conductor スクリプトトランスクリプトが含まれています。被害者が
cat readme.txt を実行すると:
- iTerm2 はファイルをレンダリングしますが、そのファイルは単なるテキストではありません。それは、conductor セッションを宣言する偽の DCS 2000p 行と、 iTerm2 のリクエストに対する答えとなる偽の OSC 135 メッセージを含んでいます。
フックが受け入れられると、 iTerm2 は通常の conductor ワークフローを開始します。アップストリームのソースコードでは、
が即時にConductor.start()
を送信し、それが成功するとgetshell()
を送信します。したがって、exploit はこれらのリクエストを注入する必要はありません。 iTerm2 がそれら自体を発行するだけで、悪意のある出力は返信を模倣すればよいのです。pythonversion()
偽の OSC 135 メッセージは最小限でありながら精密です。これらは以下のことを実行します:
- getshell のコマンドボディを開始する
- シェル検知出力のように見える行を返す
- そのコマンドを成功として終了させる
- pythonversion のコマンドボディを開始する
- そのコマンドを失敗として終了させる
- アンフック(フック解除)を行う
これだけで、 iTerm2 を通常のフォールバックパスへと導くのに十分です。その時点で、 iTerm2 は SSH 統合ワークフローを十分に完了し、次のステップである
run(...) コマンドの構築と送信に移ると信じています。
偽造された DCS 2000p フックには、いくつかのフィールドが含まれており、その中には攻撃者制御可能な sshargs が含まれます。この値は重要であり、 iTerm2 は後で conductor の
run ... リクエストを構築する際にそれをコマンド資料(材料)として利用するからです。exploit は sshargs を選択しており、 iTerm2 が run <padding><magic-bytes> を base64 符号化した場合の最後の 128 バイトチャンクが「ace/c+aliFIo」になるようにしています。
この文字列は恣意的なものではありません。以下のようにして選ばれています:
- conductor エンコーディングパスからの有効な出力であること
- 有効な相対パスマイン(パス名)であること
正当な SSH 統合セッションでは、 iTerm2 は base64 符号化された conductor コマンドを PTY に書き込み、 ssh がそれらをリモート conductor へ転送します。しかし exploita のケースでは、 iTerm2 もまだそれらのコマンドを PTY に書き込みますが、真の SSH conductor はいません。ローカルのシェルはそれらを単なる入力として受け取ります。
そのために、セッションは録画するとこのような外観になります:
- getshell は base64 として表示される
- pythonversion も base64 として表示される
- その後、長い base64 符号化された run ... ペイロードが表示される
- 最後のチャンクは「ace/c+aliFIo」である
前のチャンクは無意味なコマンドとして失敗しますが、最後のチャンクは、そのパスがローカルに存在し実行可能であれば動作します。
元のファイルベースの PoC は genpoc.py を使って再現できます:
- これにより、「ace/c+aliFIo」という実行可能なヘルパースクリプトを作成します
- readme.txt は、悪意のある DCS 2000p および OSC 135 シーケンスを含むファイルです 前者は iTerm2 を偽の conductor と対話させる欺くために機能し、後者は最後のチャンクが届いた際、シェルに真正な実行対象を提供します。
この exploit が動作するためには、「ace/c+aliFIo」を含むディレクトリ内から
cat readme.txt を実行する必要があります。これにより、攻撃者によって形成された最後のチャンクが、実際に存在する実行可能パスへと解決されます。
3 月 30 日:当方は iTerm2 に対してこのバグを報告しました。 3 月 31 日:該バグはコミット a9e745993c2e2cbb30b884a16617cd5495899f86 で修正されました。執筆時点では、その修正はまだ安定版リリースに含まれていませんでした。
パッチコミットが適用された際、当方はパッチ単独を使用して exploit をゼロから再構築を試みました。それを用いるためのプロンプトは prompts.md に記載されており、得られる exploit は genpoc2.py です。これは genpoc.py と非常に類似した仕組みを持っています。
本投稿へのコメント さらに興味深く議論したいですか?