
Claude CodeもCodex Securityも見落とした、CSRFトークン漏洩の脆弱性
はじめに
こんにちは。オフェンシブセキュリティ部の山崎です。突然ですが、以下はRailsで実装されたSAML IdPのフォームで、SPに認証結果をPOSTで送信するために利用されるものです。
<body onload="document.forms[0].submit()">
<%= form_tag saml_acs_url, method: :post do %>
<%= hidden_field_tag "SAMLResponse", @saml_response %>
<%= hidden_field_tag "RelayState", @relay_state %>
<% end %>
</body>
Railsとしてごく自然なコードに見えますが、違和感を覚えた方もいるかもしれません。
この記事では、フレームワークのフォームヘルパーがCSRFトークンを暗黙的に外部へ漏洩させるという実装例を紹介します。このパターンはSAML IdPライブラリのサンプルコードにも含まれていた典型的な実装で、人間にとってもLLMにとっても見落としやすいと思われます。Claude CodeやCodex Securityに検査させた場合も、いずれもこの脆弱性を検出できませんでした。
(※ Claude Code Securityは執筆時点では検証できていません。)
なお、この記事はLLMによる脆弱性スキャンツールの限界を議論するものではありません。適切なプロンプトや十分な時間をかければ、LLMでもこの脆弱性を発見できる可能性は十分にあります。しかし、そもそもこのようなアンチパターンが広く認知されていれば、実装段階で避けられ、スキャンで見落とされることも減るはずです。この記事によって、人と同様にLLMも学習することでこうした実装が減り、また検知されやすくなることを願っています。
SAMLとは
本題に入る前に、SAMLについて簡単に解説します。
SAML(Security Assertion Markup Language)は、異なるドメイン間でユーザの認証情報を安全にやり取りするためのXMLベースの標準仕様です。主にエンタープライズ環境でのシングルサインオン(SSO)に使われています。
SAMLの登場人物は主に3つです。
- IdP(Identity Provider):ユーザの認証を行い、認証結果(SAML Assertion)を発行する側。例:社内の認証基盤、Okta、Microsoft Entra IDなど
- SP(Service Provider):IdPの認証結果を受け取り、ユーザにサービスを提供する側。例:各種SaaSアプリケーション
- ユーザ(ブラウザ):IdPとSPの間を仲介する
SAMLによるSSO認証の基本的な流れ(SP-initiated)は以下のとおりです。
- ユーザがSPにアクセスする
- SPがSAML Requestを生成し、ユーザのブラウザを介してIdPにリダイレクトする
- IdPでユーザが認証を行う(ログインなど)
- IdPがSAML Response(認証結果)を生成し、ユーザのブラウザを介してSPにPOSTで送信する
- SPがSAML Responseを検証し、ユーザのログインを許可する
今回重要なのは、ステップ4の部分です。IdPからSPへのSAML Responseの送信には、一般的にHTTP POST Bindingが使われます。これはIdPがHTMLフォームを生成し、ユーザのブラウザからSPに対してPOSTリクエストを送信する方式です。
Claude Codeで SAML IdPを実装してみる
「SAML IdPをRailsで実装して」という指示でClaude Codeに実装させてみました。

いくつか今回問題となる部分と関係のない箇所で不具合があり、追加の修正指示は出しましたが、概ね一発で動作する実装が完成しました。
実装結果:https://github.com/tyage/rails-idp-sample
脆弱性:CSRFトークンの漏洩
SAML IdPではSPに対してPOSTリクエストで認証結果を送信します。Claude Codeが生成したそのフォームのコードは以下のとおりです。
https://github.com/tyage/rails-idp-sample/blob/main/app/views/saml_idp/saml_post.html.erb
<body onload="document.forms[0].submit()">
<noscript>
<p>JavaScriptが無効です。以下のボタンを押して続行してください。</p>
</noscript>
<%= form_tag saml_acs_url, method: :post do %>
<%= hidden_field_tag "SAMLResponse", @saml_response %>
<% if @relay_state.present? %>
<%= hidden_field_tag "RelayState", @relay_state %>
<% end %>
<noscript><%= submit_tag "続行" %></noscript>
<% end %>
</body>
一見、これはRailsとしてはごく自然なコードに見えます。しかし、ここにCSRFトークンの漏洩の脆弱性が潜んでいます。
何が問題なのか
Railsの form_tag ヘルパーは、単にHTMLの <form> 要素を生成するだけではありません。CSRF対策として、自動的にCSRFトークンをhiddenフィールドとして挿入します。そのため、実際にレンダリングされるHTMLは以下のようになります。
<form action="https://sp.example.com/saml/acs" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token"
value="2Jmc36_JPxYg8qMVYMNT321TMF56rZGM8Yz8sabyg_Bt7srmqUy5agRjSS2A4L4ITgwuyfHFPbap2ya2jD1Y4A"
autocomplete="off" />
<input type="hidden" name="SAMLResponse" id="SAMLResponse"
value="...[SAML Response]..." autocomplete="off" />
<noscript>
<input type="submit" name="commit" value="続行" data-disable-with="続行" />
</noscript>
</form>
2行目の authenticity_token がRailsのCSRFトークンです。このトークンはIdP内部のCSRF対策に使われるものですが、フォームの送信先(action)は外部のSPです。つまり、IdPのCSRFトークンが外部のSPに対して送信されてしまいます。
なお、これはClaude Codeが生成したコードに限った話ではありません。RailsのSAML IdPライブラリである saml_idp gem のサンプルコードにも、まさにこのパターンが含まれていました(修正PR)。
脆弱性の影響
攻撃者がSAML SSOを開始するURLを作成し、それを他のユーザにアクセスさせることに成功した場合、そのユーザのCSRFトークンがIdPからSPに漏洩してしまいます。IdPによってはSPが信頼済みサイトに限定されている場合もありますが、攻撃者が用意したSPとも接続できるIdPであれば攻撃者にCSRFトークンが漏洩してしまいます。
CSRFトークンが漏洩した場合、攻撃者は被害者のブラウザを介してIdP上の状態変更操作を実行できる可能性があります。IdPの実装次第ではパスワードやメールアドレスの変更が可能になり、最悪の場合アカウントの乗っ取りにつながります。
簡単なPoC: https://github.com/tyage/rails-idp-sample-with-poc/blob/main/poc/README.md
また、CSRFトークンと同時にSAML ResponseもSPに送信されますが、SAML ResponseはそのSP用に発行されたものであるのに対し、CSRFトークンはそうではありません。そのため、CSRFトークンの漏洩に関しては、同じIdPでログイン可能な別のSPに対しても影響が波及する可能性があります。
実際にはRails 8.2からはCSRFトークンを使わないCSRFの対策方法も導入されましたし、SameSite Cookieの設定や、Railsの設定値でforgery_protection_origin_checkやper_form_csrf_tokensが有効であればCSRFトークンの漏洩だけでは悪用が難しくなります。しかし、これらの設定値が導入される前からRailsを利用している場合には互換性維持のためにこれらの設定が有効化されていない場合もあり、一定数、影響が生じる環境は存在します。
RailsのCookieStoreでセッションを管理していればSameSite=Lax属性がつくため影響が緩和されますが、それ以外のRedis storeなどを使っている場合はデフォルトでSameSite属性が付与されません。特にGoogle ChromeではSameSite属性が設定されていなければ2分間はクロスサイトのPOSTでもCookieが送信されるため(いわゆる、2分間ルール)、攻撃が成立する可能性があります。
LLMセキュリティツールの検査結果
このソースコードをCodex SecurityとClaude Codeの「/security-review」コマンドに検査させてみました。
Codex Security の検出結果

- Hardcoded SAML signing key and open SP allowlist
SAMLの署名鍵がコードベースにハードコードされている問題と、IdPが任意のSPを受け入れるオープンな状態であることが指摘されました。
Claude Code /security-review の検出結果

- Unrestricted ACS URL — Signed SAML Assertion Theft via Crafted SAMLRequest
IdPが任意のSPを受け入れるため、SAML Response(中のAssertion)が盗まれる可能性があるという指摘でした。Codex Securityと同じ指摘内容です。
共通の結論
両者とも、CSRFトークンがSPに漏洩するという問題は検出されませんでした。(また、実を言うとこのフォームにはXSSの脆弱性もあるのですが、意外なことにそれについても指摘されませんでした。)
また、両者の指摘に従ってSPのホワイトリストを追加した上で再度スキャンを実行したり、CSRFトークンの漏洩によって実際に影響が発生するようなパスワード更新機能や設定を追加したりしましたが、やはりCSRFトークンの漏洩は検出されませんでした。
対策
対策はシンプルです。外部にPOSTするフォームでは、フレームワークのformヘルパーを使わず、素のHTML <form> タグを使うことです。
<form action="<%= saml_acs_url %>" method="post">
<input type="hidden" name="SAMLResponse" value="<%= @saml_response %>" />
<% if @relay_state.present? %>
<input type="hidden" name="RelayState" value="<%= @relay_state %>" />
<% end %>
<noscript><input type="submit" value="続行" /></noscript>
</form>
このようにすれば、CSRFトークンは挿入されず、外部への漏洩を防げます。
同類の問題が起こりうるフレームワーク
この問題はRailsに限らず、フォームヘルパーがCSRFトークンを自動挿入するフレームワーク全般で発生する可能性があります。
Spring Boot + Thymeleaf
Thymeleafで th:action を使った場合も、Spring SecurityのCSRFトークンが自動挿入されます。
Claude CodeにSpring BootをベースにしたSAML IdPの実装を依頼してみたところ、Railsの例と同様にCSRFトークンが送信されるフォームが生成されました。
<form id="samlForm" method="post" th:action="${acsUrl}">
<input type="hidden" name="SAMLResponse" th:value="${samlResponse}">
<input type="hidden" name="RelayState" th:value="${relayState}" th:if="${relayState}">
<noscript>
<button type="submit">Continue</button>
</noscript>
</form>
独自のカスタムヘルパー
フレームワーク標準のヘルパーだけでなく、プロジェクト内で独自に作成したフォームヘルパーがCSRFトークンを自動挿入している場合にも、同様の問題が発生しえます。
おわりに
今回のケースでは、フレームワークがCSRFトークンを暗黙的に挿入するという挙動がソースコード上には現れず、さらにこのパターン自体がSAMLライブラリのサンプルコードにも記載されていた典型的な実装であったことから、LLMにとっても人間にとっても見落としやすいものだったのではないかと考えています。(ただし、 saml_idp gem のサンプルコードに関しては修正済みであるため、最新版を参考に実装した場合にはこの脆弱性は再現しづらくなっているかもしれません。)
今回紹介した内容は複雑な脆弱性ではないため、注意深く読めば問題があることに気づけるものですし、LLMにも適切な文脈を与えれば検出できるはずです。しかし、そのハードルを下げることも今後は重要になってくるかもしれません。
最近はSec-Fetch-Siteヘッダで対策するといった方法もあるため、CSRFトークン自体が脆弱性の対策方法として古いものになりつつありますが、まだしばらくは利用されるのではないかと思います。この記事が、開発者にとってもLLMにとっても、こうした脆弱性パターンを減らす一助となれば幸いです。
