メインコンテンツまでスキップ

脆弱性診断でCSRFの脆弱性が検出されました。Kurocoではどのように対応すればいいですか?

CSRF(Cross-Site Request Forgery)は、利用者のブラウザが認証情報を自動的に付与してしまうことを利用した攻撃です。 そのため、回答はAPIに設定しているセキュリティの種類によって異なります。

セキュリティ認証情報の送信方法CSRFの成立可否
静的アクセストークンリクエストヘッダーに明示的に指定成立しません
動的アクセストークンリクエストヘッダーに明示的に指定成立しません
特権付き静的トークンリクエストヘッダーに明示的に指定成立しません
Cookieブラウザが自動的に付与基本的に成立しません(一部のエンドポイントは設定が必要です)
なし認証情報がありません成立しません(別の観点での確認が必要です)

セキュリティの種類についてはAPIセキュリティをご覧ください。

Image from Gyazo

注記

診断結果への回答を作成する前に、まずCSRF対策が成立しない設定をご確認ください。 本記事で説明する対策はいずれもこれらの設定に依存しており、該当する場合は対策が働きません。

トークン認証(静的・動的・特権付き静的)の場合

これらのセキュリティでは、アクセストークンをリクエストヘッダーX-RCMS-API-ACCESS-TOKENに明示的に指定します。 ブラウザが自動的に付与する情報ではないため、攻撃者のサイトから送信されたリクエストにトークンが含まれることはありません。

したがって、CSRFは構造的に成立しません。 追加の設定は不要です。

注記

静的アクセストークンをフロントエンドに埋め込んでいる場合、トークンは第三者も取得できる値です。他者になりすますCSRFは成立しませんが、トークンを取得した第三者がAPIを実行できる状態である点は、別の観点として確認してください。

Cookie認証の場合

Cookie認証では、ブラウザがCookieを自動的に付与します。 Kurocoが発行するCookieの属性は以下のとおりです。

属性
SameSiteAPI:None / 管理画面:Strict
PartitionedCookieでPartitionedを利用するが有効な場合に付与されます(デフォルトで有効)
HttpOnly付与されます
Secure付与されます

Partitioned属性の設定は、[環境設定] -> [管理画面]の[CookieでPartitionedを利用する]で確認できます。

APIのCookieはSameSite=Noneのため、クロスサイトからのリクエストであってもCookieは送信されます。

ただし、KurocoのAPIエンドポイントは、そのエンドポイントが受け付けるContent-Typeをあらかじめ定義しており、それ以外のContent-Typeのリクエストは400で拒否します。 HTMLフォームから送信できるContent-Typeはapplication/x-www-form-urlencodedmultipart/form-datatext/plainに限られるため、application/jsonのみを受け付けるエンドポイントでは、攻撃者のサイトのフォームから送信されたリクエストは処理される前に拒否されます。

text/plainは、エンドポイントが受け付けるContent-Typeの設定が存在しないため、常に拒否されます。 Content-Typeヘッダーを付与しないリクエストも、拒否されます。

注記

ログアウトのエンドポイントのみ、Content-Typeヘッダーを付与しないリクエストを受け付けます。 ログアウトはセッションを破棄する操作であり、他者になりすまして更新や情報の取得を行うものではないため、CSRFとしての影響は限定的です。

これらの拒否は、CSRF対策が成立しない設定に該当する場合には働きません。

備考

CookieのSameSite属性がStrictではない点は、脆弱性診断で指摘を受けることがあります。この指摘に対する見解は脆弱性診断で指摘を受けたのでどうすればいいか教えてくださいをご覧ください。

デフォルトの動作

Cookie認証のAPIへのPOSTリクエストのうち、後述の条件を満たさないリクエストは、デフォルトでは拒否されず、検知ログに記録されるのみです。

これは、CSRF保護を有効にする前に、影響を受けるリクエストをログから洗い出せるようにするためです。 検知したリクエストはアプリケーションログに記録されます。[キーワード]にCsrfGuardを入力して絞り込んでください。

リクエストを実際に拒否するには、次の設定を有効にします。

Cookie認証のCSRF保護を有効にする

[環境設定] -> [サイト管理]をクリックし、[共通]の[Cookie認証APIのCSRF保護を強制する]にチェックを入れて保存します。 この設定はサイト単位です。API単位では設定できないため、有効にするとサイト内のすべてのCookie認証APIに適用されます。

設定画面の詳細はサイト管理をご覧ください。

有効にすると、Cookie認証のAPIへのPOSTリクエストは以下のいずれかを満たす必要があり、満たさないリクエストは403で拒否されます。

条件説明
Content-Type: application/jsonHTMLフォームからは送信できないContent-Typeです。
X-Requested-With: XMLHttpRequestHTMLフォームからは付与できないリクエストヘッダーです。
Sec-Fetch-Site: same-origin 提供版: β同一オリジンのページから送信されたリクエストです。front.example.jpapi.example.jpのようにサブドメインが異なる構成は同一オリジンではないため、この条件には該当しません。
OriginがCORS設定で許可されたオリジン 提供版: βAPIのCORS_ALLOW_ORIGINSに一致するオリジンから送信されたリクエストです。

Sec-Fetch-SiteOriginはブラウザが設定するリクエストヘッダーで、ページ内のJavaScriptから偽装できません。 そのため、自サイトのページからの送信と、攻撃者のサイトからの送信を区別できます。

Sec-Fetch-Siteで許可されるのはsame-originのみです。same-siteは、Kurocoの標準ドメイン(*.g.kuroco.app)のように登録可能ドメインを共有する別サイトのページからの送信でも該当するため、許可していません。 フロントエンドとAPIでサブドメインが異なる構成では、Originによる判定、またはContent-TypeX-Requested-Withの条件で許可されます。 Originによる判定はCORS_ALLOW_ORIGINSの指定内容に依存します。ワイルドカード(*)を指定している場合はCSRF保護として機能しません。サブドメインのワイルドカード(https://*.example.com)は、対象となるサブドメインすべてが管理下にある場合に限り有効です。

判定の対象は、Cookie認証のAPIへのPOSTリクエストのみです。GETは対象外です。 また、Smartyのapi_internalなどによる内部呼び出しも対象外です。

Kuroco標準のGETオペレーションに、データを更新するものはありません。 ただし、Api::request_api(カスタム処理で作成したAPIの実行 (GETメソッド))のように処理内容を利用者が定義するエンドポイントでは、その処理次第で更新が発生します。GETはCSRF保護の判定対象外で、Content-Typeによる拒否も働きません。 更新を伴う処理は、Api::request_api_post(カスタム処理で作成したAPIの実行 (POSTメソッド))などPOSTのエンドポイントに設定してください。Api::proxyApi::aggregateでプロキシ先に副作用がある場合も同様です。

正しいCORS設定が前提になる

CORSは単体ではCSRF対策になりませんが、本記事で説明する対策はいずれもCORS設定が正しいことを前提としています。

CORSはレスポンスの読み取りをブラウザ側で防ぐ仕組みであり、リクエストがサーバーに到達すること自体は防ぎません。 プリフライトリクエストが発生しないリクエスト(HTMLフォームからの送信など)は、CORSで許可されていないオリジンからでもサーバーに到達します。

一方で、CORSで許可されたオリジンからは、プリフライトリクエストが発生するリクエストも到達します。 Content-Type: application/jsonによるCSRFの拒否は、攻撃者のオリジンがCORSで許可されていないために、そのプリフライトリクエストが失敗することで成立しています。

危険

CORS_ALLOW_ORIGINSにワイルドカード(*)を指定すると、Kurocoはリクエスト元のOriginをそのままAccess-Control-Allow-Originに返します。 CORS_ALLOW_CREDENTIALSとあわせて有効にしている場合、すべてのオリジンがContent-Type: application/jsonのリクエストの送信とレスポンスの読み取りを許可されます。 オリジンによる境界がなくなるため、Content-Typeによる拒否も[Cookie認証APIのCSRF保護を強制する]も機能しません。 CORS_ALLOW_ORIGINSには、許可するオリジンを明示的に指定してください。

リクエストにCookieが付与されるかはPartitioned属性にも依存します。組み合わせごとの結果はCSRFの成立条件の一覧をご覧ください。

CORS_ALLOW_ORIGINSを明示的に指定している場合、CORS設定は次の3つの役割を持ちます。

役割内容
レスポンスの保護許可していないオリジンのページからは、レスポンスの内容を読み取れません。
プリフライトの拒否許可していないオリジンからの、プリフライトリクエストが発生するリクエストを失敗させます。
CSRF保護の判定[Cookie認証APIのCSRF保護を強制する]が有効な場合の判定に利用されます。

Partitioned属性による違い

CookieのPartitioned属性は、Cookieをトップレベルサイトごとに分割します。 CookieでPartitionedを利用するが有効な場合、攻撃者のサイトのページからJavaScriptで送信したリクエストには、Cookieが付与されません。

一方、攻撃者のサイトのフォームから画面遷移を伴って送信されたリクエストには、遷移先のトップレベルサイトが一致するためCookieが付与されます。 つまりPartitioned属性が有効な場合、攻撃者が送信できるのはHTMLフォームから送信できるContent-Typeに限られます。

CSRFの成立条件の一覧

application/jsonのみを受け付けるエンドポイントについて、ここまでの条件をまとめます。

Partitioned属性CORS_ALLOW_ORIGINSCSRFの成立可否
有効明示的に指定成立しません
有効ワイルドカード(*)成立しません(Cookieが付与されません)
無効明示的に指定成立しません(400で拒否)
無効ワイルドカード(*)成立します

ワイルドカード(*)を指定している場合、攻撃者のサイトから送信したContent-Type: application/jsonのリクエストは[Cookie認証APIのCSRF保護を強制する]の通過条件も満たすため、設定を有効にしても拒否されません。 Partitioned属性が有効な場合はCookieが付与されないため成立しませんが、CSRF対策がブラウザのCookie分割のみに依存する状態になります。ワイルドカード(*)は指定しないでください。 このほか成立条件に影響する設定はCSRF対策が成立しない設定にまとめています。

form形式でリクエストを送信するエンドポイントについて

エンドポイントが受け付けるContent-Typeは、[API]の設定で指定します。 multipart/form-dataapplication/x-www-form-urlencodedを明示的に指定したエンドポイントでは、HTMLフォームから送信できるContent-Typeを受け付けるため、Content-Typeによる拒否が働きません。

Cookie認証で公開している場合は[Cookie認証APIのCSRF保護を強制する]を有効にしてください。 あわせて、正規のリクエストもContent-Type: application/jsonの条件を満たさないため、フロントエンドの実装でX-Requested-With: XMLHttpRequestヘッダーを付与してください。

X-Requested-WithはHTMLフォームから付与できないリクエストヘッダーであるため、別オリジンのページから送信する場合はプリフライトリクエストが発生します。 ヘッダーを付与する前に、送信元のオリジンをCORS_ALLOW_ORIGINSに登録してください。登録していないと、正規のリクエストが失敗します。

注記

β版では、同一オリジンのページから送信する場合はSec-Fetch-Site: same-originの条件を満たすため、X-Requested-Withを付与しなくても拒否されません。提供版: β front.example.jpapi.example.jpのようにサブドメインが異なる構成は同一オリジンではないため、送信元のオリジンをCORS_ALLOW_ORIGINSに登録してください。提供版: β

セキュリティ「なし」の場合

認証情報を用いないため、他者になりすますCSRFは成立しません。 ただし、更新系のエンドポイントを認証なしで公開している場合は、CSRF以前に誰でも実行できる状態です。 APIセキュリティで適切なセキュリティを設定してください。

CORS設定の確認方法

サイドバーより設定を確認したい[API]をクリックし、[CORS]をクリックします。 CORS_ALLOW_ORIGINSに、許可するオリジンを明示的に指定します。ワイルドカード(*)は指定しないでください。

Image from Gyazo

https://example.com

ワイルドカード(*)は開発時の動作確認にのみ使用し、http://localhost:8080のような開発用のオリジンも、本番環境の設定からは削除してください。

設定項目の詳細はAPIをご覧ください。

CSRF対策が成立しない設定

ここまでに説明した対策は、いずれも以下の設定に依存しています。 Cookie認証のログインセッションは、api_idが異なる場合でもサイト内のCookie認証API間で共有されます。一方、CORS設定・セキュリティ・受け付けるContent-TypeはAPIごとの設定です。 そのため、診断対象のAPIだけでなく、サイト内のすべてのCookie認証APIについて次の項目を確認してください。CORS設定はCORS設定の確認方法で確認できます。

設定影響対処
CORS_ALLOW_ORIGINSにワイルドカード(*)を指定している攻撃者のサイトからContent-Type: application/jsonのリクエストを送信できます。Content-Typeによる拒否も[Cookie認証APIのCSRF保護を強制する]も機能せず、レスポンスの内容も読み取られます。許可するオリジンを明示的に指定します。
CORS_ALLOW_ORIGINSに開発用のオリジン(http://localhost:8080など)が残っているそのオリジンからのリクエストが許可されます。本番環境の設定から削除します。
CORS_ALLOW_ORIGINSにサブドメインのワイルドカード(https://*.example.com)を指定しており、対象となるサブドメインすべてが管理下にあると確認できない管理外のサブドメインが攻撃者に利用可能な状態になると、Originの条件を満たします。外部サービスに割り当てているサブドメインや、使用を終了して第三者が取得できる状態のサブドメインが該当します。提供版: β管理下にあるオリジンを完全一致で指定します。
multipart/form-dataまたはapplication/x-www-form-urlencodedを受け付けるエンドポイントをCookie認証で公開している(form形式でリクエストを送信するエンドポイントについてを参照)Content-Typeによる拒否が働きません。Cookie認証APIのCSRF保護を強制するを有効にします。
GETのエンドポイントのカスタム処理で更新を行っているGETはCSRF保護の判定対象外で、Content-Typeによる拒否も働きません。更新を伴う処理はPOSTのエンドポイントに設定します。
[CookieでPartitionedを利用する]が無効になっている攻撃者のサイトのページからJavaScriptで送信したリクエストにも、Cookieが付与されます。有効にします。

診断結果への説明例

以下の説明例は、CSRF対策が成立しない設定に該当しないことを確認したうえでご利用ください。

トークン認証を利用している場合

本システムはAPIベースのヘッドレスCMSであるKurocoを採用しており、対象APIの認証にはアクセストークン方式を採用しています。 アクセストークンはリクエストヘッダーに明示的に指定する必要があり、ブラウザが自動的に付与する情報ではありません。 したがって、認証情報が自動送信されることを前提とするCSRFは成立しません。

Cookie認証を利用している場合

application/jsonのみを受け付けるエンドポイントであれば、以下のように説明できます。

本システムはAPIベースのヘッドレスCMSであるKurocoを採用しており、対象APIのエンドポイントはContent-Type: application/jsonのリクエストのみを受け付け、それ以外のContent-Typeは処理される前に400で拒否されます。 HTMLフォームから送信できるContent-Typeにapplication/jsonは含まれないため、攻撃者のサイトのフォームから送信されたリクエストは処理されません。

multipart/form-dataapplication/x-www-form-urlencodedを受け付けるエンドポイントについては、[Cookie認証APIのCSRF保護を強制する]を有効にしたうえで、以下のように説明できます。

本システムはAPIベースのヘッドレスCMSであるKurocoを採用しており、Cookie認証のAPIへのPOSTリクエストに対してCSRF保護を有効にしています。 POSTリクエストは「Content-Type: application/jsonであること」「X-Requested-With: XMLHttpRequestヘッダーを持つこと」のいずれかを満たす必要があり、満たさないリクエストは403で拒否されます。 HTMLフォームから送信できるContent-Typeにapplication/jsonは含まれず、X-Requested-Withのような任意のリクエストヘッダーを付与することもできません。 したがって、攻撃者のサイトからのフォーム送信によるCSRFは成立しません。

β版をご利用の場合は、以下の説明を追加できます。提供版: β

上記に加えて、「同一オリジンからの送信であること(Sec-Fetch-Site: same-origin)」「CORS設定で許可したオリジンからの送信であること(Origin)」のいずれかを満たすリクエストも許可しています。 Sec-Fetch-SiteOriginはブラウザが設定するリクエストヘッダーであり、攻撃者のページから偽装することはできません。

関連ドキュメント


サポート

お探しのページは見つかりましたか?解決しない場合は、問い合わせフォームからお問い合わせいただくか、Slackコミュニティにご参加ください。