はじめに
昔からある攻撃の1つであるCSRFだが、SvelteKitでは対応不要というのを見かけた。
ほんとに?というのをソースコードを見て安心したいと思ったので、今回はソースコードでどのように実装されているか?を見てみたいと思う。
そしてソースコードを見ていく中で、server routeによる'Content-Type': 'application/json'
を受け入れるエンドポイントを実装した場合は、自分でCSRFの対策をする必要があるように思えたので、それについてもまとめてみたいと思う。
CSRFの対策を実装している部分の実装を見てみる
ずばり以下の部分。
if (options.csrf_check_origin) {
const forbidden =
is_form_content_type(request) &&
(request.method === 'POST' ||
request.method === 'PUT' ||
request.method === 'PATCH' ||
request.method === 'DELETE') &&
request.headers.get('origin') !== url.origin;
if (forbidden) {
const csrf_error = new HttpError(
403,
`Cross-site ${request.method} form submissions are forbidden`
);
if (request.headers.get('accept') === 'application/json') {
return json(csrf_error.body, { status: csrf_error.status });
}
return text(csrf_error.body.message, { status: csrf_error.status });
}
}
...
確かにOriginヘッダーを見てチェックしていることがわかるので、SvelteKitではCSRFの対策はデフォルトで実装されている!\(^o^)/!!
と思ったが、これはForm actionの時だけなのでは…という罠?がある。
is_form_content_type
関数の実装は以下の通りで、コンテントタイプをチェックしてformか?を確認しているため、REST APIのエンドポイントとして実装できるserver routeに対してapplication/json
でfetchを利用してリクエストを送った場合は、このOriginヘッダーのチェックはスキップされる。
export function is_form_content_type(request) {
// These content types must be protected against CSRF
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype
return is_content_type(
request,
'application/x-www-form-urlencoded',
'multipart/form-data',
'text/plain'
);
}
したがって、Form actionsで実装している場合には、上記で見たようにCSRFの追加の対策は不要と考えていいが、server routeでREST APIのエンドポイント('Content-Type': 'application/json'
でPOSTするとき)は自前でCSRF対策を実装すべきという事になる。
server route('Content-Type': 'application/json'
を受け入れるエンドポイント)でのCSRF対策の実装
Webスタンダードのfetch
であれば'Content-Type': 'application/json'
でもOriginヘッダーは送信されるので、Form actionsと同じようにOriginヘッダーを検証するような実装で対策できるだろう。また、CSRFトークンによる検証でもよい。
※CSRF対策が必要になるような具体的なケースは以下など。
まとめとして
SvelteKitでのCSRFの対策はたまに自信がなくなることもあるので、今回実際にソースコードを見てどのような実装になっているのかを確認して理解することで、どのような対策をすべきか?で迷うことがなくなったように思える。