正規表現のアンカー漏れが認証バイパスになる — Symfony CVE-2026-45063 を読む
Symfony に認証バイパスの脆弱性が公開された。CVE-2026-45063、NVD の一次スコアは CVSS 3.1 基本値 9.8(Critical)、分類は CWE-290(なりすましによる認証バイパス)。影響範囲は 5.4.52 未満、6.0.0 以上 6.4.40 未満、7.0.0 以上 7.4.12 未満、8.0.0 以上 8.0.12 未満で、修正版は 5.4.52 / 6.4.40 / 7.4.12 / 8.0.12 である。
原因が「正規表現の先頭を固定していなかった」という一点なのが、この件を読み物として面白くしている。実装の細部としてはありふれた見落としで、しかし置かれた場所が認証だったために深刻度が最大級になった。順に追う。
何が起きているのか
問題があるのは X509Authenticator である。これはクライアント証明書認証を担当するコンポーネントで、TLS のクライアント証明書からユーザーを特定する。特定の手掛かりとして使うのが、証明書の識別名(Distinguished Name、以下 DN)に含まれる emailAddress フィールドだ。
DN は複数の属性をカンマで区切って並べた文字列で、たとえばこんな形になる。
1 | |
ここから alice@example.com を取り出したい。素直に書くと、こういう正規表現になる。
1 | |
これで動く。動くのだが、この正規表現は DN のどこにある emailAddress= にも一致する。先頭を固定するアンカーが無い(unanchored)。アドバイザリが指摘しているのはまさにこの点である。
なぜ「どこにでも一致する」ことが問題になるのか
DN の属性のうち、CN(Common Name)のような項目には、かなり自由な文字列を入れられる。証明書の発行フローによっては、申請者が指定した名前がそのまま CN に入ることも珍しくない。
そこで攻撃者が、自分の証明書の CN にこう書いたとする。
1 | |
DN 全体を先頭から走査する正規表現は、最初に見つかった emailAddress= に一致する。それは CN の中に埋め込まれたほうだ。結果として、抽出されるメールアドレスは admin@example.com になる。証明書としては正当に発行されたもので、署名の検証も通る。それでもアプリケーションは、この証明書の持ち主を admin@example.com のユーザーだと判断する。
つまり、信頼された CA から証明書を発行してもらえる立場の攻撃者が、同じ CA を信頼している他のユーザーになりすませる。CVSS ベクタが AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N になっているのは、この構図を反映している。
修正の考え方
Symfony 側の修正は、正規表現をアンカーする方向で入っている。要するに「DN の属性の区切りとして現れた emailAddress=」だけを拾うようにする、ということだ。
自前で同じ処理を書いている場合に、同種の間違いを避ける原則は3つある。
1つ目は、構造のあるデータを正規表現で切らないこと。 DN は RFC 4514 で定義された構文を持つ構造化データで、エスケープ規則もある。パーサがあるならパーサを使う。正規表現で属性を抜くのは、文字列を「たまたまその形をしている塊」として扱っているだけで、構文を解釈していない。
2つ目は、抽出ではなく照合にすること。 「DN からメールアドレスを取り出して、それをユーザー識別子として信じる」という設計そのものが弱い。取り出した値をそのまま信頼するのではなく、あらかじめ登録された識別子と突き合わせる形にすれば、想定外の値が入ってきたときに照合が失敗して止まる。
3つ目は、アンカーを省略しないこと。 入力全体に対する検証で preg_match('/^...$/') の ^ と $ を落とすと、「その形を含む文字列」まで通る。これはメールアドレスの検証やパス検証でも繰り返し起きている型の間違いで、DN に限った話ではない。PHP の場合、$ は末尾の改行1つを許容するので、厳密に末尾を固定したいときは \z を使う。
1 | |
影響を受けるかどうかの見分け方
まず前提として、**X509Authenticator(クライアント証明書認証)を使っていない構成は影響を受けない**。Symfony を使っているというだけで慌てる必要はない。セキュリティ設定でクライアント証明書認証を有効にしているかを先に確認する。
使っている場合は、次の順で見る。
composer.json/composer.lockの Symfony のバージョンを確認する。5.4.52 未満、6.0.0 以上 6.4.40 未満、7.0.0 以上 7.4.12 未満、8.0.0 以上 8.0.12 未満なら該当する。- 信頼している CA が誰なのかを確認する。自組織が発行する証明書だけを受け付けているのか、外部の CA も含むのかで、攻撃者になれる範囲が変わる。
- 証明書の発行フローで、申請者が
CNなどの属性値に任意の文字列を入れられるかを確認する。ここが塞がっていれば、実際に悪用する難度は上がる。
対応は composer update で 5.4.52 / 6.4.40 / 7.4.12 / 8.0.12 以降に上げることだ。Symfony はディストリビューションのパッケージではなく composer で入れるのが通常なので、OS のセキュリティ更新では直らない点に注意する。コンテナで動かしているならイメージの再ビルドとデプロイが要る。
この件から持ち帰れること
正規表現のアンカー漏れは、レビューで見つけにくい。テストを書いても、正常系のDN(CN=Alice, ..., emailAddress=alice@example.com)は通るので気づけない。気づくには「属性値の中に区切り文字や属性名の形をした文字列が入ってきたら」というケースを、意図して書く必要がある。
自分のコードベースで同じ型の問題を探すなら、preg_match / RegExp の第一引数を機械的に洗い出して、入力全体を検証する意図なのにアンカーが無いものを拾うのが手っ取り早い。認証・認可・パス解決・URL 検証のあたりから見ると、当たりが出やすい。
本記事は NVD の CVE-2026-45063 の記載と、Symfony のセキュリティアドバイザリ(GHSA-ph86-p8f6-f9r2)および修正コミット 59ef484029601a7af06f5e06c6ed921fdcea5a0d を確認して書いている。DN の具体的な攻撃文字列は、アドバイザリの記述(先頭を固定しない正規表現が DN 内のどこにある emailAddress= にも一致し、CN などのフィールドに emailAddress の値を埋め込むことで他のユーザーになりすませる)から構成を説明するために組み立てたもので、実際の PoC ではない。