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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
php2shell-deep-dive — PHP2Shellの詳細な技術解説:メモリ不足によるクラッシュとuse-after-freeという2つのPHPコアCVEを連鎖させ、ヒープグルーミング、ASLRリーク、リモートコード実行へと至る手法を解説します。 | Kitploit
ツール/GitHubGitHub/djad04/php2shell-deep-dive
メモリフォレンジック脆弱性分析エクスプロイトリバースエンジニアリングウェブアプリケーション悪用CTF論文と研究学習と教育バイナリエクスプロイト
GitHubdjad04/php2shell-deep-dive

php2shell-deep-dive

PHP2Shellの詳細な技術解説:メモリ不足によるクラッシュとuse-after-freeという2つのPHPコアCVEを連鎖させ、ヒープグルーミング、ASLRリーク、リモートコード実行へと至る手法を解説します。

634日前未レビュー

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有
リポジトリを見るウェブサイト

この研究は2026年10月26日に書き始めたもので、いつ完成するかは正直なところまだ分かっていません。

執筆時点では、PHP2Shellに関する情報はまだ非常に少なく、特に脆弱性の詳細な技術分析や悪用プロセスに関する情報はほとんど存在していません。

正直なところ、表面をなぞるだけで内部で実際に何が起きているのかを説明していない解説を見つけるのに疲れていました。そこで、何か違うことをしたいと考えました。それは、プロセス全体を探求し、重要な詳細を一つひとつ検証し、何が起きるかだけでなく、なぜ、どのように起きるのかを説明する、きちんとした詳細な技術解説です。

この研究は、PHPのソースコードに関する私自身の理解、実験、そしてこれらの脆弱性を理解しようとする中で行った調査に基づいています。これらの断片をすべてまとめ、自分の発見を共有し、根底にある概念のいくつかを他の人にとって少しでも分かりやすくできればと思っています。

主な目標の一つは、技術的な深さを犠牲にせずに、この内容を理解しやすくすることでした。C言語の基礎知識と一般的なプログラミング経験があれば、低レベルのメモリ悪用の経験がなくても読み進められるようにしたいと考えています。

また、現在あまり説明されていない、あるいは誤解されている事柄のいくつかを、この研究が明らかにする助けになればとも思っています。ただし、正直に言っておきたいのは、これは私自身による脆弱性の技術的理解であり、公式な分析でも絶対的な信頼に足るリファレンスでもないということです。私自身まだ学び、調査し、点と点をつなげている途中です。途中で誤りや不正確さ、誤解があるかもしれません。

もし何か誤りに気づいたり、異なる解釈をお持ちだったり、さらに調査する価値があると思われる点があれば、ぜひ私にご連絡ください。この研究をより正確で、誰にとっても役立つものにする助けとなる訂正や議論、貢献を心から歓迎します。

結局のところ、これを共有する目的は、共に学び、より多くの人が内部で実際に何が起きているのかを理解できるようにすることなのです。


クイックナビゲーション

基本はもうご存じですか?興味のある部分へ直接ジャンプしてください:

  • はじめに: スタックとヒープ、メモリ割り当ての基本
  • これはどのように危険なのか?: 古いポインタの物語、UAF の読み取りと書き込み
  • 古いポインタ:その出どころ: PHP のセッションと OOM の相互作用の内部における UAF の起源
  • 攻撃リクエストの構築: OOM を引き起こす実際の HTTP リクエスト
  • 目標: 何をリークする必要があり、その理由(ヒープポインタ + コードポインタ)
  • 対象の構造体:zend_string: セッション Cookie を構築する際に PHP が読み取る構造体
  • メモリの再利用: リクエスト #2 と対象の zend_string 構造体
  • ヒープグルーミングの詳細: 偽造データを正確に適切なメモリスロットに配置する方法
  • multipart_buffer_read_body の謎: 私の頭をほぼ壊しかけた隠れた割り当て
  • 2 つのリーク、1 つではない: ヒープリークとコードリークに異なる調整が必要な理由
  • コードリーク(ステージ 3): ASLR を突破するための関数ポインタのリーク
  • フィンガープリンティング: 正しいポインタを取得したことを確認する方法
  • ヒープリークの解析: 256 バイトの混沌からチャンクのベースを見つける
  • 1+8 パイプライン: 両方のリークが実際にどのように配信されるか
  • アロケータを再利用して構造体を偽造する: pDestructor ハイジャックのアイデア
  • トリガー: PHP が汚れ仕事をこなす、実際の zend_hash_destroy コード
  • 1 つに 3 つの構造体: 384 バイトに ForgedArray + FakeExecuteData + FakeString
  • 最終ペイロード(ステージ 4): HashTable を偽造して pDestructor をハイジャックし RCE を取得する
  • プライマー機構: 解放されたプライマーの bucket[2] 内の残骸が破壊チェーンをトリガーする仕組み
  • コマンド出力のトリック: /proc 内で自身の TCP ソケットを見つける
  • 完全なパイプライン: 4 つのステージすべてをエンドツーエンドで
  • 異なるビルド向けの調整: すべての数値が特定の PHP ビルドのヒープ解析からどのように導かれるか
  • 現実世界の摩擦: 実際の本番サーバーでこの攻撃がはるかに難しい理由
  • Steal My Bitcoin CTF: すべての始まりとなったチャレンジ
  • 謝辞: クレジットと参考文献

ヒープエクスプロイトが初めての方、あるいは全体像を体験したい方は、そのまま読み進めてください。これは最初から最後まで通読できるように書きました。


はじめに

本題に入る前に、誰もが心に留めておくべきことがいくつかあります。これらは研究を進めるにつれて特に役立つようになります。先ほども述べたように、このウォークスルーは誰にでも理解できるものにしたいので、より基礎的な概念についても時間をかけて説明します。すでにご存じの方は、読み飛ばしていただいて構いません!

スタックとヒープ

典型的なプログラムでは、メモリはスタックやヒープを含むいくつかの異なる領域に整理されています。

たとえば、C++ では次のようになります。

int main() {
    int a = 30;       // Typically stored on the stack
    int* b = new int(30);  // b is typically on the stack,
                           // but the integer it points to is on the heap
}

ここで、a は値 30 を保持するローカル変数です。ポインタ b もローカル変数ですが、ヒープ上に確保された整数のアドレスを保持しています。

メモリ割り当ての仕組み

PHP は主に C で書かれているため、メモリ割り当てがどのように機能するかを理解する必要があります。

OS はプロセスに独自の仮想アドレス空間を提供します。その後、プロセスのメモリアロケータが、利用可能なメモリ内での割り当てを管理します。

例えば、C++ では new、malloc()、その他のメモリ割り当て方法があります。

アロケータは多くの場合、毎回 OS に新しいメモリを要求するのではなく、解放されたメモリを再利用しようとします。

また、オブジェクトのライフタイムが終了しても、そのメモリに格納されていたデータが必ずしもすぐに消去されるわけではありません。そのメモリは単に再利用可能としてマークされるだけです。


これをよりよく理解するために、次のコードを少し見てみましょう:

#include <cstdlib>
#include <iostream>

int main() {
    void* a = malloc(64);

    free(a);

    void* b = malloc(64);

    std::cout << a << "\n" << b << std::endl;

    free(b);
    return 0;
}

ここで a と b を出力すると、それらが同じ数値のメモリアドレスを含んでいることに気づくかもしれません。

なぜでしょうか? free(a) を呼び出した後、malloc() は以前に a に割り当てられたメモリを再利用する可能性があり、別のメモリブロックを取得する必要がなくなるからです。

これは毎回必ず起こるとは限りませんが、よくある動作です。


これはどのように危険なのか? 陳腐化ポインタの物語

これが問題の核心に導きます。メモリが再利用されることは分かりましたが、それらのメモリアドレスを保持していた変数はどうなるのでしょうか?

free(a) を呼び出すと、メモリ割り当てシステムは単に内部記録を更新し、そのメモリチャンクを再利用可能としてマークします。しかし、そのメモリ内のデータは消去されず、ポインタ変数自体も魔法のように NULL にリセットされるわけではありません。プログラマが明示的にクリアしない限り、ポインタは以前とまったく同じメモリアドレスを保持し続けます。

この残された変数を、私たちは陳腐化ポインタ(またはダングリングポインタ)と呼びます。

陳腐化ポインタを持つことは、いくつかの点で危険です。最も重大な2つは、元のオブジェクトにもはや属さないデータを読み取ること、そして古いポインタを通じて書き込むことで新しいオブジェクトを破壊することです。

別のオブジェクトのデータを読み取る(Use-After-Free)

ユーザーアカウント用のメモリを割り当てる場面を想像してみましょう:

struct User {
    char username[24];
    int is_admin;
};

int main() {
    // 1. Allocate memory for a user
    User* a = (User*)malloc(sizeof(User));

    // Initialize the original user
    strcpy(a->username, "alice");
    a->is_admin = 0;

    // We keep a copy of the pointer to demonstrate the bug
    User* a_stale = a; 

    // 2. Free the memory. The object is gone, but a_stale still holds the address!
    free(a);

ここで、この直後に、プログラムが別の場所で別の割り当てを行い、アロケータが同じアドレスを再利用するとします:

User* b = (User*)malloc(sizeof(User));

strcpy(b->username, "jack");
b->is_admin = 1;

つまり、b は今や a が以前存在していたまさにその場所に存在しています。しかし、a を解放したため a_stale は依然としてそこを指しており、a_stale を更新するものは何もありませんでした。

そのため、依然として a_stale を使用しているコードは、Alice のデータを見ていると考えていますが、実際には Jack のデータにアクセスしている可能性があります:

if (a_stale->is_admin) {
    // This may run, even though Alice was never an admin
    printf("Welcome admin %s\n", a_stale->username);
}

これにより、本来見てはいけないデータ(Jackのユーザー名など)にアクセスできる可能性があり、プログラムが誤った認可判断を下すかもしれません。

技術的には、解放済みメモリへのアクセスはC/C++における「未定義動作」であるため、コンパイラやシステムによって正確な結果は保証されません。しかし、これは根本的な問題を明らかにしています。

ただし、現実にはそれほど単純ではありません。おそらくこう思っているでしょう:「そうか、これはいいね、でも……」

これが機能するには、まず最初に古いポインタ(stale pointer)が必要です。実際の攻撃では、被害者のプログラムを書いているわけではないので、a_stale でやったように意図的に変数へのポインタを保存することはできません。バグはすでにコード内に存在していなければなりません……

攻撃者が影響を与えられるのは、その解放済みメモリスロットに何が入るかだけです。

今のところは、これを深く理解しておくことだけを心がけてください。なぜなら、その問いへの答えが、これから議論するPHP CVEの最も重要な部分になるからです。

別のオブジェクトのデータを変更する(メモリ破壊)

次に逆のケースを想像してみましょう。プログラムが、メモリが解放された後も古いポインタを使い続けて書き込みを行う場合です。

#include <cstdlib>
#include <cstdio>

int main() {
    int* a = (int*)malloc(sizeof(int));
    *a = 42;

    int* stale = a;  // Keep another pointer to the allocation

    free(a);

    // Another allocation may reuse the same address
    int* b = (int*)malloc(sizeof(int));

    if (b != nullptr) {
        *b = 100;

        // BUG: stale still holds the old address.
        // Writing through it is undefined behavior.
        *stale = 999;
ツールをダウンロード