HTTP クッキー(Cookie) をより安全に使用することができる SameSite 属性 について説明します。
目次
1. HTTP クッキーの基本動作
HTTP クッキー(以下クッキーと書きます)とは、ウェブサーバー側がクライアント(ウェブブラウザ)側に保持させることができるデータのことをいいます。
クッキーの基本的な動作は以下となります。
(1) ウェブブラウザで サイトA にアクセスする
ウェブブラウザで、例えば https://misc.laboradian.com/
にアクセスします。
これは言い換えると、「https://misc.laboradian.com/
というリソースを取得するためのリクエストを misc.laboradian.com
というサーバー(ホスト)に送信した」ということになります。
(2) サーバーは、要求されたリソース(ページ)を返す
サーバーは、要求されたリソース(ページ)を返しますが、このレスポンスにおけるヘッダ部にクッキー(と呼ばれるデータ)をセットして返すことができます。
以下がその例になります。
Set-Cookie: SID=31d4d96e407aad42; Path=/; Domain=example.com
Set-Cookie というフィールド名の右側に、「名前=値;」というフォーマットで各種情報がセットされます。ウェブブラウザはこの情報を保存することになります。
この場合セットされているデータは、以下の3つです。
名前 | 値 |
---|---|
SID | 31d4d96e407aad42 |
Path(属性) | / |
Domain(属性) | example.com |
このうち Path と Domain は、このデータの適用範囲を指定している属性です。
(3) ウェブブラウザで、再度 サイトA にアクセスする
ウェブブラウザで、再度 サイトA にアクセスすると、先程受け取ったクッキーの情報がリクエストヘッダ部に自動的にセットされて送られます。(最初に渡されたクッキーの属性値(Domain や Path)にマッチした場合のみ)
Cookie: SID=31d4d96e407aad42;
この情報をサーバー側がどう使うかは自由ですが、クライアントの同一性を保持してログイン状態を実現するために使われたりします。
2. クッキーの SameSite 属性について (Same-site Cookies)
基礎知識
SameSite 属性 は、draft-west-first-party-cookies-07 – Same-site Cookies という仕様で新しく追加された クッキーの属性で、クッキーをより安全なものにするために追加されました。
もう少し具体的に言うと、SameSite 属性の目的は、
を制御することです。
例えば、https://example.com
というページに、https://laboradian.com/
へのリンクが貼ってあるとします。このリンクをクリックすると、ウェブブラウザは https://laboradian.com/
のリソースを取得するために、laboradian.com というサーバーに HTTP(S)リクエストを送信します。ここで、もし以前 https://laboradian.com
にアクセスしており、何らかのクッキーを受け取っていたのであれば、今回送信するリクエストデータの中にそのクッキーデータがセットされます(クッキーの期限が切れていなければ)。これが従来の処理です。詳細は省きますが、この動作は CSRF などの脆弱性を生む原因になります。
そこで SameSite 属性の出番です。ウェブサーバーが最初にクッキーを発行する際に SameSite属性を指定しておけば、このようなドメインを跨いだ(クロスドメイン)リクエストにそのクッキーをセットさせないことが可能になります。
先程挙げたウェブサーバーからのレスポンスヘッダにおける Set-Cookie フィールドにこの属性が追加されると、以下のようになります。
Set-Cookie: SID=31d4d96e407aad42; Path=/; Domain=example.com; SameSite=Lax
一番後ろに、SameSite=Lax という文字列が追加されています。
SameSite にセットできる値
SameSite に指定することのできる値には、この他に Strict と None があります。それぞれの違いは以下です。
SameSiteの値 | 意味 |
---|---|
Strict |
|
Lax |
|
None |
|
※1 top-level navigation とは、アドレスバーに表示されているURLの変更が伴う遷移のことです(リクエストを送信したら、送信先のページに画面が遷移する場合)。このような遷移は、CSRF の対象にはならず安全であると判断できるためクッキーの付加が許可されているのだと思います。
通常のサイトであれば、SameSite=Lax を前提にして開発するのがよさそうです。
どう使えばよいのか?
普通にWebサイトを開発している立場からすると、
- 自分のサイトから発行するクッキーに関しては、セキュリティの面から SameSite に Lax もしくは Strict をセットしておきたい。
- 自サイトに埋め込んだウィジェットや広告などから外部のドメインに送られるクッキーに関しては、こちらでコントロールできるものではないので何もすることはない。
ということになります。
幸いなことに、Chrome のバージョン80 (2020年2月にリリース) からは SameSite属性のないクッキーはデフォルトで Lax の扱いになるため、特に対応作業は必要ないということになります。
各ブラウザの対応状況
各ブラウザの対応状況は以下のページで分かります。
IE を除けば、既に普通に使える状況になっています。
3. PHP で SameSite属性をセットする
PHP 7.3 では setcookie 関数 を使って SameSite属性を指定することができます。
しかし、PHP 7.2 以下であってもちょっと工夫すると可能であるようです。以下のページにやり方が紹介されていました。
関数のバグを利用しているように見えますが、確かにできます。ちなみに上のページのコード例ではクッキーの有効期限が過去の時刻になってしまいます。第三引数は、例えば以下のように書きましょう。この場合であれば1時間クッキーが保持されます。
setcookie('hoge', 'fuga', time()+3600, '/; SameSite=Strict', '', true, true);
4. デモページ
簡単なデモページを用意しました。
デモページ
このページでは、PHPを使ってクッキーを3種類発行しています。
- SameSite属性なし
- SameSite=Strict
- SameSite=Lax
2回目以降のアクセスであれば、ブラウザに保存されたクッキーが表示されるようになっています。
例えば、別ドメインに以下の index.html
ページを用意して、上記デモページにいろいろなアクセスを試してみると、どのクッキーが送信されたか分かります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 0 20px;
}
</style>
<title>Same-site cookie test (external site)</title>
</head>
<body>
<h1>Same-site cookie test (external site)</h1>
<p>いろいろな方法で、https://misc.laboradian.com/cookie-test/1/ にアクセスします。</p>
<h2>(1) <a> タグによるリンク</h2>
<p><a href="https://misc.laboradian.com/cookie-test/1/">https://misc.laboradian.com/cookie-test/1/</a></p>
<h2>(2) <form> POSTでサブミット</h2>
<form id="form1" action="https://misc.laboradian.com/cookie-test/1/" method="post">
<input type="text" name="foo" value="bar">
<button type="submit">Submit by POST</button>
</form>
<h2>(3) Ajax</h2>
<button type="button" id="m3">Ajax でGETアクセス</button>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
$((e) => {
$('#m3').click((e) => {
$.get("https://misc.laboradian.com/cookie-test/1/");
});
});
</script>
</body>
</html>
5. おわりに
今後、ウェブサイトでは 重要なクッキーに SameSite 属性をセットするのが当たり前になっていくものと思われます。
WordPress などのツールも、どこかのタイミングでこの属性が指定されるでしょう。
といっても、ウェブサイトを利用する側としては、意識する必要はありません。
追記
2020年4月
新型コロナウイルス(COVID-19)の影響を鑑み、Chrome の Cookie の扱いを一時的に元に戻すようです。
紹介記事:? グーグルがSameSite Cookieへの変更を撤回、重要なオンラインサービスへのアクセス確保 | TechCrunch Japan
2020年2月
Chrome 80 から SameSite の指定がないクッキーは SameSite=Lax として扱われるようになります。2月17日の週から一部に向けて(?)この仕様を反映していくようです。
2019年10月
今後、SameSite=None を指定した場合(クロスオリジンであってもクッキーを送信させたい場合)は、Secure属性の付与も必須になります。
※ Chrome の場合は、バージョン 80 以降でエラーになります。
2019年5月9日
SameSite属性が指定されていないクッキーを、SameSite=Lax 扱いにするという話しが出てきているそうです。
一部のサイトでは想定外の動作になるかもしれませんが、Webのセキュリティを考えると、これで良いように思います。
デモサイト、
php7.2以下の書き方なので
php7.3以上にバージョンアップした際にバグってます。
教えて下さってありがとうございました。
対応できました。
そもそもcookieはドメインをまたいで送出されないのでは?
もちろん、Aドメインが発行したクッキーは、Aドメインにしか送られません。
しかし、例えば Bドメインのページ上に、「Aドメインにリクエストを送信するフォーム」が目に見えない形で作られており、それを踏んでしまった場合に危険です。
「Aドメインの(セッション)クッキーがAドメインに送られる」ことで、Aドメインのサイトでのログイン状態が維持されてしまい、本人が意図せずに何かしらの処理を伴うリクエストが成立してしまいます。
SameSite 属性を使うと、この動作を防ぐことができます。