Adobe Commerce CVE-2026-75650 CVSS 10.0 の「S:C」が何を意味するか

Adobe Commerce と Magento Open Source に、テンプレートエンジンの不適切な無害化による任意コード実行の脆弱性がある。CVE-2026-75650 で、CVSS 3.1 の基本値は 10.0、CISA の KEV カタログには 2026年9月8日に収載され、是正期限は 2026年9月11日である。収載から3日しか置かれていない。

数字の話から入る。CVSS 3.1 の基本値で 10.0 が出るのは、条件がかなり限られている。 同じ日に KEV へ入った Windows の2件は 7.8 で、直近によく見る Critical も 9.8 が多い。9.8 と 10.0 の差はどこから来るのか。答えはベクタの S:C の1文字である。

この記事では、①Adobe と NVD が公表している事実、②S の値が計算式のどこを変えるのかを Python で実際に走らせて確かめる、③EC サイト特有の侵害確認の手順、の3つを扱う。攻撃コードは書かない。

公表されている事実

NVD の記述は次のとおりである。

Adobe Commerce is affected by an Improper Neutralization of Special Elements Used in a Template Engine vulnerability that could result in arbitrary code execution in the context of the current user. An attacker could exploit this vulnerability to execute arbitrary code. Exploitation of this issue does not require user interaction. Scope is changed.

日本語にすると、テンプレートエンジンで使われる特殊要素の不適切な無害化により、現在のユーザーの権限で任意コード実行に至りうる。悪用に利用者の操作は不要で、スコープは変更される、という内容である。

確認できる項目を並べる。

項目 出所
CVSS 3.1 基本値 10.0(Critical) Adobe PSIRT(psirt@adobe.com
ベクタ CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H 同上
CWE CWE-1336(テンプレートインジェクション) NVD
NVD 公開 2026年9月7日 NVD
KEV 収載 2026年9月8日 / 是正期限 2026年9月11日 CISA KEV
ベンダ速報 APSB26-146 Adobe

適用範囲は NVD の CPE に Adobe CommerceAdobe Commerce B2BMagento Open Source の3系統が並び、しかもパッチレベル単位で列挙されている。2.4.4:p1 2.4.4:p10 のような粒度である。つまり「2.4.x を使っているから大丈夫」という判定は成立しない。自分の環境のパッチレベルを APSB26-146 の表と突き合わせるしかない。

Magento Open Source も対象である。 NVD の CPE は cpe:2.3:a:adobe:magento:*:*:*:*:open_source:*:*:* を明示的に含む。商用版だけの話ではない。

S:C は計算式の2か所を変える

「Scope が変わる」は、脆弱なコンポーネントの外側にまで影響が及ぶ、という意味である。文章としてはそれで終わりだが、点数の上では2か所が変わる。仕様書(CVSS v3.1 Specification Document)を読むより、走らせたほうが早い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""CVSS 3.1 の基本値を Scope の違いだけ入れ替えて計算する。
仕様: https://www.first.org/cvss/v3.1/specification-document"""
import math

W_AV = {"N": 0.85, "A": 0.62, "L": 0.55, "P": 0.2}
W_AC = {"L": 0.77, "H": 0.44}
W_UI = {"N": 0.85, "R": 0.62}
W_CIA = {"H": 0.56, "L": 0.22, "N": 0.0}
# PR は Scope で重みが変わる。ここが Scope の2つ目の効き目
W_PR_U = {"N": 0.85, "L": 0.62, "H": 0.27}
W_PR_C = {"N": 0.85, "L": 0.68, "H": 0.50}


def roundup(x):
# 仕様の Roundup: 小数第1位へ切り上げ(浮動小数の誤差を避ける整数演算)
i = int(round(x * 100000))
return i / 100000.0 if i % 10000 == 0 else (math.floor(i / 10000) + 1) / 10.0


def base_score(vector):
m = dict(p.split(":") for p in vector.split("/")[1:])
scope_changed = m["S"] == "C"
iss = 1 - (1 - W_CIA[m["C"]]) * (1 - W_CIA[m["I"]]) * (1 - W_CIA[m["A"]])
if scope_changed:
impact = 7.52 * (iss - 0.029) - 3.25 * (iss - 0.02) ** 15
else:
impact = 6.42 * iss
pr = (W_PR_C if scope_changed else W_PR_U)[m["PR"]]
exploitability = 8.22 * W_AV[m["AV"]] * W_AC[m["AC"]] * pr * W_UI[m["UI"]]
if impact <= 0:
return 0.0
raw = impact + exploitability
if scope_changed:
raw = 1.08 * raw
return roundup(min(raw, 10.0))


CHANGED = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
UNCHANGED = CHANGED.replace("S:C", "S:U")

for v in (CHANGED, UNCHANGED):
print(f"{v} -> {base_score(v)}")

# 参考: 権限が要るだけでどれだけ落ちるか
for pr in ("N", "L", "H"):
v = CHANGED.replace("PR:N", f"PR:{pr}")
print(f"S:C / PR:{pr} -> {base_score(v)}")

手元(Python 3.14)での出力はこうなる。

1
2
3
4
5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H  ->  10.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H -> 9.8
S:C / PR:N -> 10.0
S:C / PR:L -> 9.9
S:C / PR:H -> 9.1

1文字を S:U に変えるだけで 9.8 に落ちる。 よく見る 9.8 の Critical は、まさにこの S:U 版である。

コードの中で scope_changed が効いているのは3か所だが、実質は次の2つである。

  1. Impact 副次式の形が変わる。 S:U6.42 * ISS の線形だが、S:C7.52 * (ISS - 0.029) - 3.25 * (ISS - 0.02) ** 15 になる。この15乗の項は、ISS が 1 に近づくと急に効いて Impact を押し下げるので、影響が最大のときは両者の差が縮む。ここだけ見ると S:C は有利にすら見える。
  2. 最後に 1.08 倍される。 これが決定打で、合計に効く。
  3. (加えて)PR の重みテーブルが差し替わる。 上のコードで W_PR_UW_PR_C を分けているのがそれで、PR:L は 0.62 から 0.68 へ、PR:H は 0.27 から 0.50 へ上がる。権限が要る脆弱性ほど、Scope の変更による底上げが大きい。

3つ目の効き目は、出力の下3行に出ている。S:U なら PR:H は 8.x 台まで落ちるところが、S:C では 9.1 で止まる。「認証が要るから後回し」という判断が、S:C の案件では成り立ちにくいということである。

なお roundup を素直に math.ceil(x * 10) / 10 で書くと、仕様の付録が挙げている境界(浮動小数の表現誤差で 4.02 が 4.1 になるような場合)でずれる。仕様が整数演算での実装を示しているので、それに合わせてある。

テンプレートインジェクションが EC で持つ意味

CWE-1336 は、テンプレートエンジンに渡る文字列の無害化不足である。Magento 系はメール文面・商品説明・CMS ブロックなど、運用者が編集する場所でテンプレート構文を評価する設計になっている。そこへ攻撃者の文字列が無害化されずに入ると、テンプレートの評価がそのままコード実行になる。

到達する先が問題である。決済処理と注文データを持つプロセスにコードが載る。

ここで思い出すべきなのは、Magento を狙う攻撃が歴史的にどう振る舞ってきたかである。サーバを壊す方向ではなく、チェックアウト画面にカード情報を読み取るスクリプトを差し込む方向に進んできた。この形の侵害は、表示にもログにも異常を残さない。注文は通る。売上は立つ。画面は壊れない。だから「動いているから無事」という判断が、この案件では成立しない。

侵害確認は2方向で行う

サーバ側は通常どおりである。pub/ var/ app/code/ 配下に身に覚えのない PHP ファイルが増えていないか。app/etc/env.php や cron の定義が書き換わっていないか。Web サーバのアクセスログで、テンプレート構文({{)を含む POST / GET が無いか。管理者アカウントが増えていないか。

フロント側が EC 固有である。チェックアウトページで実際に読み込まれるスクリプトの一覧を取り、自ドメイン以外の読み込み元を洗い出す。ブラウザで対象ページを保存してから、次のスクリプトに掛ける。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"""保存したチェックアウトページの HTML から <script src> を取り出し、
自ドメイン以外の読み込み元を一覧にする。標準ライブラリだけで動く。"""
import sys
from html.parser import HTMLParser
from urllib.parse import urlparse


class ScriptSrc(HTMLParser):
def __init__(self):
super().__init__()
self.srcs = []
self.inline = 0

def handle_starttag(self, tag, attrs):
if tag != "script":
return
src = dict(attrs).get("src")
if src:
self.srcs.append(src)
else:
self.inline += 1


def audit(path, own_host):
p = ScriptSrc()
with open(path, encoding="utf-8", errors="replace") as f:
p.feed(f.read())
external = []
for src in p.srcs:
host = urlparse(src).hostname
if host and host != own_host and not host.endswith("." + own_host):
external.append((host, src))
print(f"script 要素: 外部 {len(p.srcs)} / インライン {p.inline}")
for host, src in sorted(set(external)):
print(f" [外部] {host} {src}")
if not external:
print(" 自ドメイン以外の読み込み元はありません")
return external


if __name__ == "__main__":
audit(sys.argv[1], sys.argv[2])

手元で作った検体に掛けた出力である。

1
2
3
$ python3 scriptaudit.py sample.html example-shop.jp
script 要素: 外部 3 / インライン 1
[外部] analytics.tracker-xyz.net https://analytics.tracker-xyz.net/collect.js

https://cdn.example-shop.jp/js/checkout.js はサブドメインなので除外され、見知らぬホストだけが残る。このスクリプトは判定をしない。 出てきたホストが正当な計測タグなのか、そうでないのかは人が決める。決めるための一覧を、毎回同じ手順で作るためのものである。

インラインの件数を出しているのにも理由がある。 差し込みが src ではなく、CMS ブロックや design/head/includes(管理画面から任意のスクリプトを入れられる箇所)経由のインラインで入ることがあるためで、件数が前回と変わっていたら中身を読む合図になる。この形は src の一覧だけを見ていると素通りする。

いま何をするか

  1. APSB26-146 が指定するパッチを当てる。 これが本体である。自分の環境の版とパッチレベルを先に確認する。
  2. 当てるまでの間は、テンプレート評価に外部入力が届く経路を絞る。 管理画面をインターネットから直接見えないようにする。WAF で {{ を含むリクエストを遮断する。CMS ブロックとメールテンプレートの編集権限を必要な人だけにする。ただし PR:N なので、管理画面を閉じただけで安全になるとは限らない。「パッチまでの時間稼ぎ」として扱う。
  3. 上の2方向で侵害を確認する。 特にチェックアウトのスクリプト一覧は、パッチを当てた後にも一度取る。侵入済みの環境では、パッチは入口を塞ぐだけで、既に置かれたものは消えない。
  4. 決済を扱っているなら、カード会社・決済代行への連絡手順を先に確認しておく。 侵害が確定してから探すものではない。

是正期限の 2026年9月11日 は米国連邦機関向けの期限である。ただし CVSS 10.0 かつ悪用確認済みという条件で、民間だから緩めてよい理由は特にない。

(本記事の CVE 情報は NVD と CISA KEV カタログを 2026年9月9日に直接取得して確認した。掲載中の CVE は セキュリティ脆弱性情報 にまとめている。)


Adobe Commerce CVE-2026-75650 CVSS 10.0 の「S:C」が何を意味するか
https://blog.hashito.biz/2026/09/09/adobe-commerce-cve-2026-75650-cvss-10-scope-changed/
著者
hashito
作成日
2026年9月9日
著作権