stealmybitcoin.com を運営して分かったこと
彼は 500 ドル分のビットコインを PHP サーバーに置き、リンクを Hacker News に投稿した。数万人がそれを奪おうとし、flag を手にしたのは一人だけだったが、彼が見つけた鍵ではウォレットを開けなかった。
日本語
コピー

先週、私はニューヨーク・タイムズのサイトのトップページで、PHP に深刻な脆弱性が見つかったという記事を目にした。その記事と他の同種の報道は、攻撃者が認証なしの Web リクエストを数回送るだけで、PHP2Shell 脆弱性を突いて、世界中で稼働する数百万台の PHP サービス上で任意のリモートコード実行を達成できると主張していた。
職場の Slack は大騒ぎになり、セキュリティ界隈の同僚や友人との Signal のグループチャットも同じだった。
噂が飛び交った。あるテック企業が実環境での悪用を理由にエンバーゴを破り、コミュニティの協調的開示のわずか数時間前に脆弱性の情報を漏らしたという話。ハッカーがすでにこの脆弱性を悪用してサイトを攻撃していて、パッチをインターネットの 70% に届けるには 1 時間たりとも無駄にできないという話。
疑問
PHP2Shell は新しい時代の幕開けなのか。AI を武器にした誰もが、モデルに巧みなプロンプトを数行書くだけで、どんなサイトにも侵入できるようになったのか。
窓の外を見た。地平線に炎が照り返す終末の残光が見え、社会がリアルタイムで崩壊しつつあるのだと思った。何もなかった。
ネット上の記事やツイートは、十一段階の警報でサイバー終末を煽るものばかりだったが、具体的にどのサイトがやられたのかに触れた箇所は一か所も見つからなかった。
その後数日、流れに乗らない少数の人々が、この脆弱性は実際にはかなり扱いにくいと指摘した(Elias Taylor、Lucas Luitjes)。ハッカー集団も当初はその可能性に色めき立ったが、やがて実用的でなく悪用が難しいと考えるようになった(KELA)。
しかし一方で、国内外のメディアは、PHP ベースのサイトをネットワーク全体で洗い出して修正するという困難な緊急対応を、今も大々的に報じ続けている。
ただ待って推測するより、自分で確かめることにした。根本的な疑問は二つある。
- PHP2Shell はラボ環境の外で本当に悪用可能なのか。
- 技能は乏しいが AI と PHP のソースコードは使える攻撃者は、この種の脆弱性を悪用できるのか。
結果
この二つの問いに直接答える。
1) PHP2Shell はラボ環境の外で本当に悪用可能なのか?
可能だ。 間違いなく。チャレンジの勝者は振り返り記事を公開しており、その中で公衆インターネット経由で私のサーバーを直接攻撃し、まさに PHP2Shell 脆弱性を使ったと述べている(つまり、私の設定にある他の脆弱性は利用していない)。彼は、私のこの PHP インストールの具体的な設定とサーバー上での配置方法に合わせた専用の exploit を作り上げた。
2) AI と PHP のソースコードを手にした、技術的にさほど高明でない攻撃者は、この種の脆弱性を悪用できるのか?
現時点ではできない。 悪用を試みた人数は数万に上る。最初の突破が成功した後、チャレンジはさらに 9 時間開放された(詳細は後述)。その間に print-flag バイナリの起動に成功したのはわずか 2 人で、アプリ内の有効な内容を抽出できたのは 1 人、すなわちチャレンジの勝者だけだった。
実験
基本的な考え方は単純だ。安い VPS を数週間借り、そこに PHP のサイトを置き、ビットコインウォレットの秘密鍵も置いて、誰かがウォレットを空にするかどうか待つ。奇跡的に「stealmybitcoin.com」というドメインはまだ登録されておらず、私はこれを天の思し召しと受け取り、この実験は天命を受けたものだと決め込んだ。
きれいなウォレットに 500 ドル分のビットコインを入れ、ホスティング業者から月額 2.50 ドルの Debian VPS を借り、Codex に簡単な Web ページを書かせて PHP で公開した。サーバーは、実行可能なバイナリ print-flag だけが WIF を含む flag ファイルを読めるように設定し、ファイアウォールを切り、リンクを Hacker News に投稿して、試したい人がいるか見ることにした。

そこからは少し手に負えなくなった。投稿から 1 時間も経たないうちにリンクは Hacker News のトップページに駆け上がり(ほぼ一日中居座った)、月額 2.50 ドルの VPS は負荷が高すぎて SSH セッションすら維持できなくなった。
最終的に VPS は 1 vCPU・メモリ 500 MB・ディスク 10 GB から、2 vCPU・メモリ 4096 MB・SSD 80 GB まで段階的に上げた。チャレンジ期間中、VPS 業者は二度私のサイトを一時停止した。一度は分散型サービス拒否攻撃を疑って、もう一度は「CPU リソースの使用状況が過大であると判断した」ためだった。
さらに、この脆弱性の悪用そのものの性質上、リクエスト量の多さ、部分的に成功したメモリ悪用の試み、その他さまざまな雑な操作によって、単一の PHP worker が落とされ続けた。
最終的には比較的安定した状態に持っていけた(たとえば大半の人が大半の時間つながる状態)。真の高可用ホスティングの代わりに、定期的なリセットとヘルスチェックでしのいだ。リンクが Hacker News のトップページから落ちると、トラフィックもかなり静まった。
いくつかのデータ
監視が不安定だったため、代表的な訪問者数やトラフィックパターンの統計を取るのは難しい。print-flag バイナリがいつ呼び出されたかを知るためのかなり信頼できるログを組み、加えてその前後で何が起きたかを把握しようと Web リクエストのヘッダーとボディをできる限り記録した。話題になった最初の数時間は、サイトへの注目のせいで、logrotate の設定(約 80MB のストレージ)がおよそ 4 分ごとにいっぱいになった。
そのため、一部の PHP クラッシュログと print-flag ファイル関連のアクティビティ記録は、チャレンジ期間のほぼ全体にわたって残っている。一方、より広範な Web リクエストログがカバーしているのは、チャレンジの約 1,600 分のうち 600 分程度にすぎない。
信頼できる Web リクエストログが残っている 10 時間について:
- 約 20,000 個の独立した IP が、単なるトップページへのアクセスではなく、何らかの実質的な形でチャレンジに参加した。
- 約 120,000 個の異なる送信元 IP がサイトにアクセスした。
- サーバーは 1,300万回を超える HTTP リクエストを受けた。
- ピーク時には毎分 18,000 回を超えるリクエストが届いた。
欠落した netflow データでは統計的に信頼できる外挿はできないが、はっきり言えるのは、数万人のプレイヤーが私のビットコインを盗もうとしたということだ。
PHP サーバーのエラーログと print-flag の実行ログははるかに信頼できる。高負荷時にもローテーションされておらず、サイズも比較的小さいからだ。これらのログを約 26 時間分調べたところ、次のことがわかった:
- 約 2,075 件の異なる PHP
SIGSEGVイベント。PHP の別のバグが原因の可能性もあるが、通常は見られないものであり、おおむね脆弱性のトリガーに近い試行と関連していると考えられる。 print-flag実行ファイルの起動に成功した IP は 2つ だけであり、サーバーからのデータ窃取に成功する形でそれを成し遂げたのは 1人 だけだった。
タイムライン
| UTC 日時 | イベント |
|---|---|
| 10月24日 20:30 | チャレンジの計測開始。 |
| 10月24日 20:30 | 「Show HN: Steal My Bitcoin」を投稿(https://news.ycombinator.com/item?id=50224916)。 |
| 10月25日 01:14 | 計測開始後、初めて PHP SIGSEGV を記録。注:これは php-version.php より前であり、この初期バージョンのチャレンジではその後さらに 7 回の PHP SIGSEGV が発生した。 |
| 10月25日 12:59 | php-version.php を追加。PHP のバージョンと UTC 時刻を返す。 |
| 10月25日 15:44 | ping.php を追加。小さな POST エンドポイントで、message=ping に対して pong を返す。 |
| 10月25日 15:47 | PHP プロセスが print-flag の実行に成功。この実行は最終的な勝者と関連している。ping.php ページはエクスプロイトチェーンでは使われなかった。 |
| 10月25日 20:32 | 勝者からメールが届く。サーバー上の鍵に対応するビットコインウォレットのアドレスが間違っているのではないか、という内容だった。勝者は上記 15:47 のイベントに対応する正確な flag、タイムスタンプ、IP アドレス、payload に加え、勝ちにつながったエクスプロイトを説明する writeup を提供した。 |
| 10月25日 21:06 | 勝者の主張をサーバーログと照合し、flag がサーバー上の flag と完全に一致すること、および実行成功前後の Web トラフィックログと整合することを確認。 |
| 10月25日 22:38 | Hacker News で解答の告知を投稿(https://news.ycombinator.com/item?id=50236736)。 |
| 10月25日 22:59頃 | 勝者の最終 writeup を確認し、ウォレットの中身をそのまま送金。 |
途中でぶつかった困難
当初はトラフィックの少ないハニーポットになると思っていた。Hacker News で数人がつつき、最後にどこかのボットネットかスキャナ、あるいは自動エクスプロイトツールに目をつけられてビットコインを盗まれる。あとは短いブログを書いて、脆弱性が悪用される前後1時間ほどのログを貼って終わり、という筋書きだ。
これほど話題になるとは全く予想していなかった。話題になるのは良いことだ。多くの人がこのチャレンジに注目し、研究上の問いにはすぐ答えが出た。ビットコインの賞金に金を入れて金額を増やしてくれた人までいた。
話題になったことでつらかった面もある。突然、本物の PHP セキュリティ専門家が大勢(専門家でない人もたくさん)私自身、私の動機、そして私のサイト運営能力について意見を持つようになったのだ。
バックエンドはどうあるべきだったのか
最初に来た批判は、このサーバーはそもそも悪用できないというものだった。実態は nginx のページにすぎない(PHP すら動いていない)と言う人もいれば、サーバーの具体的な設定がエクスプロイトの成功を不可能にしていると言う人もいた。
これらの主張がどこまで正確なのか、今でも 100% 確信があるわけではない。チャレンジの初期バージョンの時点で SIGSEGV を起こせた人がいたからだ。
ただ、私はサイトにいくつか変更を加えた。チャレンジの実現可能性を高める妥当な調整だと考えているが、サーバーをわざと脆弱にするものではない。
まず、php-version.php ページを追加し、もともと PHP がホストしていた index.html ページと並べて配信した。エクスプロイトに PHP インタプリタの実際の関与が必要な場合に備えてのことだ。PHP2Shell に関する初期の報道の一部は、これが悪用の必須条件ではないことを示唆していた(例:https://www.wiz.io/blog/php2shell-php-rce「このエクスプロイトはアプリケーションコードの欠陥に依存しない。影響を受ける PHP バージョン上で動く単純な『Hello World』アプリでも脆弱になりうる」)。だが私の見方では、PHP SAPI サーバーの中で .html ファイルの配信を担う部分は、これらのタスクの影響を受けない可能性がある。
さらに ping.php ページも追加した。誰かがサーバーに値 ping を POST すると、pong という単語を返すだけのページだ。この極めてシンプルなアプリを足したのは、エクスプロイトがユーザー入力に動的に反応する PHP を必要とする場合があると考えたからだ。ユーザー入力への応答と .php ページの実行というこの 2 つの要件は、実際のアプリではごく一般的だろう。だからこれらのコードパスをサポートすることは、今回のチャレンジの趣旨に反しないと判断した。
そのほかにもフィードバックはいくつか届いたが、ほぼ無視した。PHP アプリの具体的なバリエーションを用意してほしいという要望や、サイトの手前に置いた(ごく基本的な)nginx プロキシ設定を変更してほしいという要望だ。この設定はルーティングの補助、チャレンジの分離、ログ記録に使っている。この方向でもう少し手を加えれば、解けた人は増えたかもしれない。だが、そうなるという具体的な証拠は何もない。
ビットコインの鍵は結局何だったのか
2つ目の主要なホスティング上の問題は、動画で見たほうがわかりやすい。以下は、チャレンジで使ったビットコインウォレットの WIF 秘密鍵をエクスポートしたときの様子だ(注意:ここに映っているのは別の空のウォレットだ。この鍵は動画にしてネット上に公開してしまったので、使わないでほしい!):
この手順でウォレットの鍵をチャレンジに入れたつもりだった。だが違った。エクスポートした WIF は、ビットコインを保有していたウォレットアドレスの派生パスの、親にあたる鍵だった。ビットコインを盗めるようにするには、どの認証情報を渡すべきだったのか、今も完全にはわかっていない。今後同じようなチャレンジをやるなら、個別の秘密鍵ではなく、ウォレットの完全な復元キーを flag にするだろう。
つまり、勝者はサーバーから本物のビットコイン鍵を手に入れたものの、それを使って資金を動かすことはできなかった。資金は、その親鍵から派生した子ウォレットの中にあったからだ。
幸い、勝者はメールで連絡してくれ、極めて詳細な証拠を添えてくれた。サーバー上のウォレット鍵の全文、そのハッシュ、サーバーを攻略した正確なタイムスタンプ、攻略に使った正確な IP アドレス、攻略に使った正確な payload、エクスプロイト開発中の AI との会話記録、そして完全なエクスプロイトの手順書だ。
この情報をもとにログを調べ、完全な記録を見つけた。攻撃時刻を print-flag バイナリのログと秒単位で突き合わせ、サーバー上の flag ファイルの完全な値を勝者が確かに知っていたことを確認した。
勝者に祝意を送りたい。チャレンジを解いただけでなく、隠れた追加チャレンジ、つまり見事なエクスプロイトログを残すという課題も達成したのだから。
結論
この経験から何を学んだのか。
このサイトを公開したとき、私はどこか懐疑的だった。深刻な PHP の CVE について報道は溢れかえっているのに、現実の被害者がほとんど見当たらないではないか、と。
正直に言う。それは私の誤算だった。PHP2Shell は本物だ。実際に悪用可能な脆弱性であり、十分に動機のある相手が AI の助けを借りれば、メモリアドレスがわからず、攻撃経路を調整するために物理サーバーにも触れないとしても、これを使ってウェブサイトを乗っ取ることは十分に可能だ。
今 PHP ベースのサイトを運用しているなら、PHP2Shell には必ずパッチを当てるべきだ。
今後数か月で、実用的な PHP2Shell の汎用エクスプロイトチェーンがますます増えると、私は確信している。
運用しているサービスが、誰かがわざわざ手間をかけるだけの価値や知名度を持つなら、stealmybitcoin.com での経験から言えるのは、あなたのシステム構成に合わせて調整した実用的な PHP2Shell エクスプロイトが作られるのは、起こるかどうかではなく時間の問題だということだ。
逆に、stealmybitcoin.com は AI 支援によるサイバー攻撃の現状をよく示しているとも思う。公開されていて既知(しかも悪用可能!)の CVE を抱えたサイトを数万人が攻撃しようとし、そのうち脆弱性を発火させられたのは 2 人だけだった。
悪用しやすいように私がサーバー側で加えた変更が効いた可能性はある(実際、勝者が解いたのは php-version.php をサーバーに追加してから約 3 時間後だった)。だが、勝者が解いてからチャレンジを終了するまでの約 9 時間、数万人の攻撃者の誰も flag バイナリを発火させられなかった。
はっきりしているのは、平均的な攻撃者が一流の LLM を手にし、PHP2Shell のような手強い CVE と組み合わせれば、それを信頼できるサイバー攻撃の道具に変えられる段階には、まだ遠く及んでいないということだ。
それでも、その地点には確実に日々近づいている。この猫を袋に戻す方法があるとは思わないが、今後数か月、オープンソースコミュニティとシステムセキュリティのエコシステムが共に取り組むことで、手強い脆弱性が手強いままであり続けてほしいと願っている。
私のキャリアのかなりの部分は攻撃的セキュリティの研究に費やしてきた。その立場から見て、PHP2Shell はまた一つ示している。善意の人が素早く公開の場で脆弱性を見つけ、誰もが先にパッチを当てられるようにすることの重要性は、かつてなく高まっている。