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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
Magento-CVE-2016-4010 — Magento の未認証リモートコード実行 (CVE-2016-4010) | Kitploit
ツール/GitHubGitHub/brianwrf/magento-cve-2016-4010
脆弱性分析コード分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト学習と教育ラボと実践
GitHubbrianwrf/magento-cve-2016-4010

Magento-CVE-2016-4010

Magento の未認証リモートコード実行 (CVE-2016-4010)

リポジトリを見る
631310年前未レビュー

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有

Magentoの未認証リモートコード実行脆弱性(CVE-2016-4010)の分析と悪用


0x00 はじめに

5月17日、海外のセキュリティ研究者Netanel Rubin氏は、Magentoの未認証リモートコード実行の脆弱性(CVE-2016-4010)を公開しました。この脆弱性は実際には複数の小さな脆弱性から構成されており、攻撃者が脆弱性のあるMagentoサーバー上で未認証のままPHPコードを実行することを可能にします。Magentoは非常に人気のあるEコマースプラットフォームであり、2011年にeBayに買収されました。サムスン、ニコン、レノボなどの有名企業や多くの小規模ECサイトが使用しています。Magentoは25万のオンラインショップで使用されており、毎年600億ドル以上の取引が行われています。

0x01 分析

この脆弱性の悪用条件:

  • MagentoがRPCs(RESTまたはSOAP)を有効にしている(ほとんどはデフォルトで有効)
  • MagentoのCE&EEバージョン < 2.0.6

MagentoのWeb APIは、REST RPCとSOAP APIという2種類のRPCを許可しています。この2つの方法は同じ機能を提供しますが、唯一の違いは、前者がJSONとHTTPリクエストを使用して入力を渡すのに対し、後者はXMLを使用する点です。

一部のモジュールのAPIだけを公開するために、Magentoは開発者に便利な方法を提供しています。それは「webapi.xml」ファイルに、アクセス可能にしたいモジュールのAPIだけを宣言するというものです。webapi.xmlファイルには、公開する必要があるWeb APIのクラスとメソッドがすべて含まれ、各メソッドは必要な具体的な権限も指定します。それらの権限は次のとおりです。

  • anonymous - 誰でもアクセスできるようにするメソッド
  • self - 登録ユーザーと特定の管理者権限のみを許可します。例:「Magento_Backend::admin」権限は、サーバー設定を編集できる管理者のみアクセスを許可します。

もちろん、開発者がwebapi.xmlファイルを使用してシステムのフロントエンドとバックエンド(Web API)の間で通信するこの方法は、実際にはモジュールの中核に直接アクセスできるバックドアを開くことになります。

さらに、仮に「anonymous」権限を手に入れても、値を動的に渡す方法がまだ必要です。ここで言及しているのは、システム内で使用できるさまざまなオブジェクトです。例えば、「CustomerRepositoryInterface::save()」API機能を使用すると、「$customer」変数に「CustomerInterface」のオブジェクトを渡せます。コードの原型は次のとおりです。

interface CustomerRepositoryInterface
{
/**
 * Create customer.
 */
public function save(\Magento\Customer\Api\Data\CustomerInterface $customer);
}

では、RPCインターフェースを使用してオブジェクトを作成するにはどうすればよいでしょうか? 実は、この答えはMagentoがSOAPサーバーをどのように設定するかにあります。

Magentoは、PHP標準の「SoapServer」を同梱したSOAPサーバーを使用します。正しく設定するには、「SoapServer」はWSDLファイルが必要です。そのファイルで、すべてのメソッド、パラメータ、および実際のRPCリクエストで使用されるカスタム型を定義します。Magentoは、XML-RPC機能をサポートする各モジュールに対し異なるWSDLファイルを生成し、モジュールのwebapi.xmlファイルの値を直接設定します。

RPCリクエストがサーバーによって解析されるとき、サーバーはWSDLファイルで見つけたデータを使用してリクエストが有効かどうかを判断し、リクエストのメソッド、パラメータ、型をチェックします。リクエストが有効な場合、解析済みのリクエストオブジェクトをMagentoに渡してさらに解析します。非常に重要な点は、「SoapServer」が何らかの形でMagentoとやり取りすることはなく、モジュールのメソッドとパラメータに関するすべての情報はWSDLファイルから取得されるということです。この時点では、送信されたリクエストは依然としてネストされた配列で構成されており、SoapServerの解析段階ではオブジェクトは作成されません。必要なオブジェクトを作成するために、Magentoは独自に入力を処理し続けます。

パラメータ名とデータ型を抽出するために、Magentoはリクエストのメソッドからシグネチャを取得します(前述のコードを参照)。文字列、配列、ブールなどの基本的なデータ型については、システムは入力を対応する型にマッピングします。しかし、オブジェクト型の場合、解決方法はより厄介です。

パラメータのデータ型がクラスのインスタンスである場合、Magentoは提供された入力を使用してインスタンスを作成しようとします。ここで、入力は単なる辞書であり、そのキーはプロパティ名、値はプロパティ値であることに注意してください。

まず、Magentoは必要なクラスの新しいインスタンスを作成します。次に、以下の方法で設定を試みます。

  1. プロパティ名を取得する(入力の辞書のキーから)
  2. 「Set[Name]」というパブリックメソッドを探す([Name]はプロパティ名)
  3. そのようなメソッドがある場合、プロパティ値をパラメータとして実行する
  4. そのようなメソッドがない場合、そのプロパティを無視して次のプロパティを確認する

Magentoはこの方法で、ユーザーが設定しようとしている各プロパティを処理します。すべてのプロパティがチェックされると、Magentoはインスタンスの設定が完了したとみなし、次のパラメータを処理します。すべてのパラメータがこのように処理されると、最終的にMagentoはこのAPIメソッドを実行します。

要するに、Magentoはオブジェクトを作成し、そのパブリックプロパティを設定し、最後にRPCを介して「Set」で始まる任意のメソッドを実行させます。そして、まさにこの動作がMagentoの脆弱性の原因となっています。

調査によると、一部のAPI呼び出しでは、ショッピングカートに特定の情報を設定できます。その情報には、配送先住所、商品、さらには支払い方法も含まれます。

Magentoがショッピングカートインスタンスに私たちの情報を設定するとき、インスタンスの「save」メソッドを使用して、新しく追加されたデータをデータベースに保存します。

では、「save」メソッドがどのように機能するか見てみましょう。

/**
* Save object data
*/
public function save(\Magento\Framework\Model\AbstractModel $object)
{
...
// If the object is valid and can be saved
if ($object->isSaveAllowed()) {
    // Serialize whatever fields need serializing
    $this->_serializeFields($object);
    ...
    // If the object already exists in the DB, update it
    if ($this->isObjectNotNew($object)) {
        $this->updateObject($object);
    // Otherwise, create a new record
    } else {
        $this->saveNewObject($object);
    }
     
    // Unserialize the fields we serialized
    $this->unserializeFields($object);
}
...
return $this;
}
// AbstractDb::save()

Magentoはオブジェクトが有効であることを確認し、シリアライズが必要な部分をすべてシリアライズしてデータベースに保存し、最後に先ほどシリアライズした部分をアンシリアライズします。

とても簡単に見えますよね? 実はそうでもありません。Magentoがどの部分をシリアライズすべきかをどのように判断しているのか、見続けましょう。

/**
* Serialize serializable fields of the object
*/
protected function _serializeFields(\Magento\Framework\Model\AbstractModel $object)
{
// Loops through the '_serializableFields' property
// (containing hardcoded fields that should be serialized)
foreach ($this->_serializableFields as $field => $parameters) {
    // Get the field's value
    $value = $object->getData($field);
     
    // If it's an array or an object, serialize it
    if (is_array($value) || is_object($value)) {
        $object->setData($field, serialize($value));
    }
}
}
// AbstractDb::_serializeFields()

見てのとおり、シリアライズできるのは、ハードコードされた辞書「_serializableFields」に存在するフィールドだけです。最も重要なのは、このメソッドはフィールドの値が配列またはオブジェクトであることを確認した後にのみシリアライズを続行する点です。

次に、Magentoがどの部分をアンシリアライズすべきかをどのように判断しているかを見てみましょう。

/**
* Unserialize serializeable object fields
*/
public function unserializeFields(\Magento\Framework\Model\AbstractModel $object)
{
// Loops through the '_serializableFields' property
// (containing hardcoded fields that should be serialized)
foreach ($this->_serializableFields as $field => $parameters) {
    // Get the field's value
    $value = $object->getData($field);
     
    // If it's not an array or an object, unserialize it
    if (!is_array($value) && !is_object($value)) {
        $object->setData($field, unserialize($value));
    }
}
}
// AbstractDb::unserializeFields ()

まあ、非常によく似ています。唯一の違いは、今回はMagentoがフィールドの値が配列またはオブジェクトでないことを確認する必要がある点です。この2回のチェックにより、オブジェクトインジェクション攻撃を実行できるはずです。つまり、シリアライズ可能なフィールドに、特定のルールに従った文字列を設定するだけです。そのように設定すると、システムはオブジェクトをデータベースに保存する前にこのフィールドをシリアライズしません。なぜなら、配列またはオブジェクトではないからです。しかし、システムがそれをアンシリアライズしようとするとき、データベースクエリが実行された後、配列またはオブジェクトではないため、アンシリアライズされます。

しかし、まさにこのほとんど目に見えないほど小さな条件が脆弱性を引き起こしました。残る問題は、どのフィールドが「シリアライズ可能」と見なされるか、そしてそれをどう設定するかです。

もちろん、最初の問題は簡単です。どのクラスが「_serializableFields」プロパティを含むかを検索するだけです。すぐに「Payment」クラスでAPIメソッドが見つかりましたが、パラメータとしてではないため、そのインスタンスのプロパティを作成または制御できません。最も重要なのは、そのシリアライズ可能なフィールド「additional_information」は、「Set[PROPERTY_NAME]」テクニックを追加のセキュリティ対策として使用し、配列としてのみ設定できることです。そのため、作成できないだけでなく、仮にできたとしても文字列に設定することはできません。

しかし、非常に興味深いことに、別の「巧妙な」方法で設定することができます。Magentoがパラメータインスタンスのプロパティを設定するとき、実際にはプロパティを直接設定するのではなく、「_data」という名前の辞書に保存します。インスタンスのプロパティが使用されるとき、この辞書が使用されます。つまり、私たちにとっては、シリアライズ可能なフィールド「additional_information」が、実際には通常のプロパティではなく、内部の辞書に保存されていることを意味します。

したがって、「_data」辞書を完全に制御できれば、「Set[PROPERTY_NAME]」を呼び出す代わりに手動で設定できるため、「additional_information」フィールドの配列制限を簡単に回避できます。

しかし、どうやってこの機密の辞書を制御するのでしょうか?

「Payment」インスタンスを保存する前に、Magentoが行うことの1つは、そのプロパティを編集することです。Magentoは、API入力を「Payment」インスタンスに保存する必要がある支払い情報として扱います。次のとおりです。

/**
* Adds a specified payment method to a specified shopping cart.
*/
public function set($cartId, \Magento\Quote\Api\Data\PaymentInterface $method)
{
 
$quote = $this->quoteRepository->get($cartId); // Get the cart instance
$payment = $quote->getPayment(); // Get the payment instance
// Get the data from the user input
$data = $method->getData();
// Check for additional data
if (isset($data['additional_data'])) {
    $data = array_merge($data, (array)$data['additional_data']);
    unset($data['additional_data']);
}
// Import the user input to the Payment instance
$payment->importData($data);
 
...
}
// PaymentMethodManagement::set()

先ほど見たように、「Payment」データは、「$method->getData()」を呼び出すことで「$method」パラメータから「_data」プロパティを返して取得されます。覚えておいてください。「$method」はAPIメソッドのパラメータであるため、私たちはそれを制御できます。

Magentoが私たちの「$method」パラメータで「getData()」を呼び出すと、パラメータの「_data」プロパティが返され、私たちが挿入したすべての支払い情報が含まれます。その後、「_data」プロパティを入力として「importData()」を呼び出し、私たちの「_data」プロパティで「Payment」インスタンスの「_data」プロパティを置き換えます。これで、私たちが制御できる「_data」プロパティを使用して、「Payment」インスタンス内の機密の「_data」プロパティを置き換えることができるようになりました。つまり、シリアライズ可能なフィールド「addition_information」を設定できるようになったのです。

ツールをダウンロード