REST API（更新系）に対するCSRFにどう対処すべきか

# 結論

* 攻撃者サイトが用意したページにおける`<form>`での攻撃に対しては、カスタムヘッダ必須によるプリフライトチェックを入れると楽に対処できる。
* XSSに対しては、トークンチェックやカスタムヘッダ必須によるプリフライトチェックは通用しない。XSSに対して鉄壁の防御を構築するしかない。
    * IEはWindows Updateしなければまだ使える（edgeの強制起動を回避できる）ので、IEの面倒を見続ける必要のあるサイトというのもあると思う。IEは[CSP](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)で`<script>`を禁止できない。このため、CSPではIEを守りきれない。PJでの対応ブラウザにIEが入っていなくても、それとは無関係にIEが標的にされることはあり得るので、IEはやっぱり無視できない。
    * なので、結局のところコードレビューなどで、「XSSが絶対にできない」という状態を維持していくしかなさそう。（辛い・・）

# `<form>`を利用した攻撃については？

攻撃者のWebサイトに用意されたボタンを押下することで、`<form>`をサブミットしてしまうケース。他に、XSSによって非表示で差し込まれることなどもある。

認証CookieのSameSite属性をLaxにしておけば、攻撃者サイトから本体サイトに遷移するときに認証Cookieが送信されない。ただし、それで守れるのはPOSTだけであり、GETは守れない。なので、GETでの単純リクエストで更新できてしまうREST APIはNG（現場ではそういうものに遭遇することがある）。

### 単純リクエスト

CORS保護されていても、`<form>`で発行されたリクエスト（単純リクエスト）ではCORSチェックがされない。

単純リクエストの定義については、[CORS解説ページ](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)を参照。

例えば、GET/POSTで`Content-Type`が`application/x-www-form-urlencoded`のリクエストは送信できる。更新系のREST APIでこういったリクエストを受け付けるようになっていればアウト。

他にもenctype属性で許可されている`Content-Type`（`text/plain`など）もNG。REST APIはJSONのみを受け付けるようにした方が安全。

### 単純リクエスト以外

#### `Content-Type`=`application/json`のリクエスト

現時点では、`<form>`では送信できない。

[拡張仕様](https://www.w3.org/TR/html-json-forms/)が提案されているが、

> This specification is no longer in active maintenance and the HTML Working Group does not intend to maintain it further.

とあるし、仮に実現したら確実にセキュリティホールとなってしまうため、実現される可能性は限りなく低い。なので考慮不要。

#### `method`=`PUT/DELETE`のリクエスト

現時点では、`<form>`では送信できない。

[拡張仕様](https://www.w3.org/TR/form-http-extensions/)が提案されているが、前項と同じ理由で考慮不要。

#### その他

* `<form>`への対処については、カスタムヘッダ必須によるプリフライトチェックが有効。HTMLのみでは、カスタムヘッダを挿入する手段がないため。

* これまでに述べてきた動作をしないとんでもなく古いブラウザや、誰かが悪意を持って作ったブラウザがあるかもしれない。それらについては、そもそもそんなブラウザを使っているユーザが悪く、万が一何か起きても問題にはならないはず。つまり、守る必要性が薄く、切り捨ててOK。
    * サーバサイドで、有効なUser-Agentヘッダのみを許可する、という仕組みを入れることはできるだろうが、それだってヘッダの値を偽装されることだってある。そういう意味でも、切り捨てるしかない。


# XHRを利用した攻撃については？

### CORS許可されていないドメインで表示されたページ

攻撃者が用意したWebサイトで表示するページについては、REST APIサーバー側でCORS保護していたとしても、単純リクエストの場合はCORSチェックされない。例えば、GET/POSTで`Content-Type`が`application/x-www-form-urlencoded`のリクエストは送信できる。これに対応するREST APIが更新処理であればアウト。

他にもenctype属性で許可されている`Content-Type`（`test/plain`とか）もNG。

`Content-Type`=`application/json`などについては、CORSのプリフライトチェックで保護できる。

### CORS許可されているドメインで表示されたページ

何らかの方法でXSSが成立し、本物のドメインで表示されたページに任意のHTML（`<script>`タグ）を埋め込みできれば、任意のJSを実行させることでCORS（プリフライトチェック）の壁をクリアしてXHRできる。JSONもPOST/DELETE/PUTできてしまうし、Cookieも送信できてしまうので、何でも可能。

これについては、XSSに鉄壁の防御があるのであれば、リスクは極小と言えなくもない。

例えば、[CSP](https://developer.mozilla.org/ja/docs/Web/HTTP/CSP)で、`<script>`や`onclick`属性などのJavaScriptを無効化することができる。

ただし、[IEはCSPに対応していないに等しい](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Security-Policy)ため、Windows Updateせずに（edgeが強制的に起動されずに）IEを使い続けられることを想定しているサイトについては、この方法は使えない。IEが完全に駆逐されたら、CSPで`<script>`の一律禁止でOKな世の中になるはず。

XSSができないこと（ユーザ入力の無害化などがきちんと実装されているか）をコードの全量チェックで保証できれば良いが、仮に一時点でチェックできても保守でチェックし続けるのはかなり難しい。

なので、トークンチェック or カスタムヘッダ必須によるプリフライトチェックといった対策が必要、という流れになるかもしれないが、仮にこういった対策がされていてもXSSで`<script>`を入れられてしまえば、こういった対策も簡単に突破できてしまう。

なぜなら、トークンチェックの場合は、XSSで表示したページにお目当てのトークンがあるのでそれを送信してしまえば良いから。カスタムヘッダ必須によるプリフライトチェックでも、カスタムヘッダーを入れて送信してしまえば良い。なので、結局のところXSSを防げるかどうか、というのがCSRF対策の鍵ということになる。

※XSSできるということは、もはやCSRFではない。CSはクロスサイトという意味だけど、本体サイトからリクエスト投げてるので、もはやクロスサイトじゃない。そういう意味で、CSRFと呼ぶのは違うかもしれない。

ということで、XSSができないこと（ユーザ入力の無害化などがきちんと実装されているか）をコードの全量チェックで保証していくしかない。

# （CSRFとは関係ないけど）CURLとかでの直接実行についてはどう？

JSESSIONIDなどのCookieで認証情報を管理し、APIサーバー側でチェックしているなら問題なし。