Style Smuggler (CVE-2026-75650) とは?
CVE-2026-75650 (StyleSmuggler) は、eコマースプラットフォームで実際に悪用されている重大なサーバーサイド・テンプレート・インジェクション (SSTI) です。本記事では、この脆弱性の概要と、Fastly のお客様が安全を維持するための方法について説明します。

概要
Sansec は、Adobe Commerce および Magento に存在する脆弱性を発見し、パッチがリリースされる前の2026年9月4日には、すでに実際の攻撃で悪用されていたことを確認しました。
Adobe は2026年9月7日に、脆弱性の連鎖の一部に対処するため、APSB26-146 を公開しました。
Fastly は CVE-2026-75650 に対する仮想パッチをリリースしており、お客様はこれを有効にすることで、この脆弱性を標的とするリクエストをブロックできます。
脆弱性の分析
CVE-2026-75650 は、認証を必要としないサーバーサイド・テンプレート・インジェクション (SSTI) の脆弱性で、リモートコード実行 (RCE) に直接つながります。この脆弱性の悪用は、4件のリクエストを通じて3つの段階で行われます。
ログに PHP コードを仕込む
カートをセットアップし、カートの住所情報に SSTI ペイロードを挿入する
SSTI を実行し、支払い失敗通知メールを通じて RCE を引き起こす
ステージ1 : ログに PHP コードを仕込む
最初の段階の目的は、サニタイズされていない PHP コードを標的のディスク上に配置することです。当社のラボでは1つの方法を再現しました。また、実際の攻撃では少なくとも、もう1つの方法が確認されています。この段階を実行する方法は、ほかにも数多く存在する可能性が高い点に注意が必要です。私たちがローカル環境で再現した手法では、無効なストアコードに関するエスケープされていないエラーメッセージをログに記録し、リクエストに埋め込まれた悪意のある PHP コードをそのまま保持します。リクエストは以下のようになります。
POST /graphql HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
Store: <?=eval(base64_decode('{{payload_b64}}'));?>
{"query":"{ storeConfig { store_code } }"}ペイロードは Store ヘッダーに埋め込まれます。実際の攻撃では、検知を回避するためにエンコードされた内容が含まれる可能性が高いです。Magento がこのエラーをログに記録する際、<?= はエスケープされずに記録されます。この点がステージ3で重要になります。実際の PHP コードは、攻撃の方法によって異なります。
ステージ2 : 住所情報に SSTI ペイロードを挿入する
2番目のリクエストでゲストカートをセットアップし、3番目のリクエストではカートの住所情報に SSTI ペイロードを含めます。2番目のリクエストには特筆すべき点がないので、簡潔にするために省略します。
SSTI ペイロードを理解するには、Magento のテンプレートエンジンの一部を理解する必要があります。Magento のテンプレートエンジンは、ディレクティブ (var や block など) を使用して変数を置換します。各ディレクティブは、それぞれ異なる正規表現で識別されます。ディレクティブの処理は「フィルター」が担い、2回のパスで行われます。1回目のパスでは、フィルターは処理可能なディレクティブを解決します。2回目のパスでは、ネストされたフィルターによって署名された「deferred」ディレクティブを処理します。ディレクティブの処理時に親フィルターがあり、処理結果がディレクティブ自体と同じである場合、そのディレクティブには親フィルターが解決できるように「署名」(*Sig*{{directive}}*Sig*) が付けられます。
ここから問題が始まります。ディレクティブが署名される際、出力に対して str_replace が使用され、元のディレクティブが署名済みのディレクティブに置き換えられます。しかし、この出力には現在のディレクティブだけでなく、それまでに処理されたすべてのディレクティブが含まれています。それでは、Style Smuggler がこれらのディレクティブをどのように利用するのか詳しく見ていきましょう。
Style Smuggler は、ネストされたフィルターで住所フィールドを解析する決済失敗通知メール用のテンプレートを利用します。住所を処理するフィルターは、「block」ディレクティブを処理できません。そこで、3番目のリクエストの住所フィールドに以下を含めます。
street: {{if postcode}}{{var postcode}}{{/if}}{{/var}}{{if postcode}}{{var postcode}}{{/if}}{{if city}}{{block class=Magento\\Email\\Block\\Adminhtml\\Template\\Preview}}{{/if}}
postcode: {{var postcode}}
postcode が住所フィルターで処理されると、処理結果が元のディレクティブの内容と一致し、署名されます。しかし、str_replace が出力全体に適用されるため、住所の street フィールドに {{var postcode}} を挿入した箇所にも署名が付けられます。ディレクティブを識別する正規表現の判定に不備があるため、最終的な「処理済み」の住所には {{var postcode}}{{var postcode}}Sig{{blockclass=Magento\\Email\\Block\\Adminhtml\\Template\\Preview}}Sig<br /> が含まれたまま、メールフィルターに渡されます。メールフィルターは、住所の street フィールドに紛れ込ませた署名済みの「block」ディレクティブを解決します。Adobe によるパッチ適用前は、「block」ディレクティブは、指定されたクラスが「Block」クラスかどうかに関係なく、そのクラスをインスタンス化していました。その結果、任意のオブジェクトを作成できました。
ステージ3 : SSTI を RCE につなげる
この時点で、1) ログファイル内の PHP コードと、2) 任意のクラスをインスタンス化できる SSTI がそろっています。この2つを結び付けるには、ステージ1でログファイルに仕込んだコードを実行できるクラスをインスタンス化する必要があります。ここで、挿入した Magento\Email\Block\Adminhtml\Template\Preview が重要になります。これは、「text」「styles」「type」のクエリパラメータを使用してテンプレートをレンダリングするクラスです。そこで、以下のクエリパラメータを指定してリクエストを送信します (URL エンコード済み)。
text: {{block class=Magento\Backend\Block\Widget\Grid\ColumnSet rowUrl=$this.template_styles cache_key=y}}
style (JSON エンコードされたクエリパラメータとして指定)
{
"first": "../var/log/system.log",
"generatorClass": "Aws\\S3\\S3Client",
"region": "us-east-1",
"version": "latest",
"s3_us_east_1_regional_endpoint": "regional",
"with_resolved": [
{
"instance": "Magento\\Setup\\Module\\Di\\Code\\Scanner\\ArrayScanner"
},
"collectEntities"
],
"path": "dummy"
}type: 2
このリクエストでは同時に、GraphQL の handlePayflowProResponse ミューテーションを使用して、決済失敗通知メール用テンプレートの処理を開始します。これにより、次の一連の処理が発生します。
先ほど挿入した「Preview」ブロックがメールテンプレートの処理中に処理され、「text」クエリパラメータからもう一つの block ペイロード「ColumnSet」が作成されます。
ColumnSet は「styles」配列を引数として受け取り、generatorClass として指定された「Aws\S3\S3Client」を使って「UrlGeneratorFactory」を呼び出します。
S3Client の構築中に、指定した「Magento\Setup\Module\Di\Code\Scanner\ArrayScanner」が作成され、「collectEntities」メソッドを呼び出します。
collectEntities は、styles 配列内で指定した「first」の値に対して「include」を呼び出します。これは、細工を施したログファイルです。
ログファイルに配置したコードは、
<?=がエスケープされていないため、include の実行中に実行されます。これにより、RCE が可能になります。
これが「Style Smuggler」という名前の由来です。Preview クラスが使用する「styles」パラメータを利用して、コード実行を引き起こすために必要な追加のガジェット情報を紛れ込ませます。また、Magento のコードベースに、ほかにも利用可能なガジェットが存在する可能性がある点にも注意が必要です。実際に、「Preview」ブロックとは異なるペイロードを使用する亜種を少なくとも1つ確認しています。
Nuclei テンプレート
Style Smuggler に対して脆弱なインスタンスをコミュニティがテストできるよう、CVE-2026-75650 の Nuclei テンプレートを公式の nuclei-templates リポジトリに提出しました。このテンプレートは私たちが独自に開発したものですが、これまで非公開だった脆弱性の情報を開示しないよう、Graycore が脆弱性の詳細を投稿し、Sansec がそれを確認するまで提出を控えました。また、複数のバージョンで正しく動作することを確認するため、このテンプレートを Magento v2.4.9 と v2.4.7-p2 に対してローカル環境でテストしました。
CVE-2026-75650 の仮想パッチ
Fastlyは、Style Smuggler による攻撃の各段階に対応する CVE-2026-75650 向けの仮想パッチをリリースしました。これにより、攻撃チェーンの一部における亜種に対する多層防御を実現するとともに、運用担当者がシステムにパッチを適用するための時間を確保できます。この仮想パッチは、管理者によるテンプレートの更新を含む通常のオペレーションでは誤検知が起きにくく、他の記事で説明されている誤検知とは無関係です。







