そこで、通信路上に攻撃者がいる典型例として、公衆無線LANの偽AP(アクセスポイント)があるケースを題材として、「HTTPSを使ってもCookieの改変は防げない」ことを説明します(Secure属性使うと盗聴は防げますが、改変は防げません)。長いエントリなので結論を先に書いておきます。
- Secure属性がないCookieはHTTPSでも盗聴できる
- Cookieの改変についてはSecure属性でも防ぐことはできない
偽APを用意する
まるでハッカージャパンの記事みたいな内容なので、この節は文体もハッカージャパンを真似してみましょう。ここで紹介する実験は、「偽アクセスポイント」の偽物を作ることである。そのまま悪用可能なので、良い子の皆さんは絶対に悪用しないように。約束だぞ。
偽APの構成を下図に示そう。
(1)無線APの準備
適当に設定すればよいが、SSIDと事前共有鍵を実在の公衆無線LANと同じにすればだまされやすいだろう。ここではだますことが目的でなく実験なので、そこまではやらない(やるなよ)。無線の暗号化は強固なものにしても問題ない。盗聴・改ざんは有線で行うからだ。むしろ、強固な暗号化を選択した方が、だまされやすくなると思うぞ(だが、やるなよ)。
(2)DHCPサーバーの設定
DHCP(ルーターの機能を利用しても良いし、解析用PCにDHCPサーバーを立てても良い)の設定で、DHCPサーバーが配信するデフォルトゲートウェイを「解析用PC」のIPアドレスにしておく。これにより、被害者からの通信を解析用PCに誘導する。
(3)解析用PCの設定 解析用PCはデフォルトゲートウェイとして動作するので、Ubuntuの設定で、/etc/sysctl.confを以下のように修正する。
この後、PCを再起動する。これで、AP→解析用PC→ルータ→インターネットという通信が可能となる。#net.ipv4.ip_forward=1 ↓ コメントを取る net.ipv4.ip_forward=1
この段階で既に、哀れな被害者がこのAPに接続したら、その通信は解析用PCを全て通過するので、Wiresharkを起動すれば、暗号化されていない通信は全てキャプチャされる。以下にYahoo!にリクエストした場合の様子を示そう。
ここで示しているのはリクエストだが、もちろんレスポンスについてもキャプチャできる。すなわち、偽APの利用者の通信は、文字通りだだ漏れになるのだ。しかし、SSL通信については暗号化されているため中身を見ることはできない。
Secure属性のないCookieを盗聴する
さて、ここからが本題です。文体も元に戻します。SSLを使っていても、Cookieを盗聴できる場合があるというお話です。タイトルにつけたように、CookieにSecure属性がないと、盗聴が可能になります。まず、よくあるのは、サイト全体はHTTP(平文)だが、個人情報を扱うページのみHTTPSというサイトです。この場合、CookieにSecure属性がないと、平文通信でもCookieが送信されてしまいます。以下、実験で確認しましょう。
まず、被害者が https://www.city.machida.kanagawa.jp/login.php にアクセスしていて、セッションクッキーが付与されているとしましょう。クッキー情報は下図の通りです。
Secure属性がついていないことに注目してください。
この状態で、前述のAPを利用して http://www.city.machida.kanagawa.jp/ (HTTPSではない)にアクセスすると、以下のリクエストが送信されます。ばっちりCookieが見えていますね。
次に、「うちのサイトは、HTTPSのみで80番ポート閉じているから大丈夫」と言う意見をよく目にしますが、これは誤解です。少し手順は増えますが、Cookieを盗聴することは可能です。
それは、罠をしかけて、 http://www.city.machida.kanagawa.jp:443/ (httpsではなくhttp)にアクセスさせることです。罠の例を以下に示します。利用者(被害者)の通信は全て解析用PCを通過するので、わざわざ罠サイトを作る必要はなく、適当にHTTPレスポンスを改変して以下を突っ込めばよいことになります。
これによるリクエストは下記となります(デスティネーション443ポートをHTTPとしてデコードするように指定しています)。Cookieを含むHTTPリクエストが平文でキャプチャされています。<img src="http://www.city.machida.kanagawa.jp:443/" width="1" height="1">
【結論】Secure属性のないCookieは盗聴の危険性がある
ということで、秘密情報を含むCookie(セッションIDのCookieを含む)にはSecure属性をつけましょう。
クッキーを強制する
次に、この環境を用いて、Cookieの強制(追加あるいは変更)をしてみましょう。ここまで出てきたツールではCookieの変更はできないので、Burp Suiteを使うことにします。Burp Suiteを透過Proxyとして使用するために以下の2点を設定します。- iptablesにより80番ポート宛のパケットをローカルの8080ポートにリダイレクトする
$ sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
- Burp Suiteをinvisibleモードに設定する
これで、偽APを通る80番ポート向けTCPパケットは、全てBurp Suiteを通ることになります。
次に、Burp Suiteの機能で、全てのレスポンスにSet-Cookieヘッダを付与する設定を追加します。
以上で、「偽APを通過した80番ポート宛のHTTPリクエストすると、もれなくPHPSESID=ABCD12345というクッキーが設定される」という罠ができあがりです。
さっそく試してみましょう。罠を使うなどして、偽AP利用者に http://www.city.machida.kanagawa.jp/ を閲覧させます。Burp suiteの画面は下記となります。
確かに、Set-Cookie: PHPSESSID=ABCD12345 というレスポンスヘッダが付与されています。
80番ポートを閉じているサイトの場合のクッキー強制方法
次に、攻撃対象サイトが80番ポートを閉じている場合について検討します。この条件では上記の方法は使えません。その理由は、HTTPレスポンスが帰ってこないので、そのレスポンスにSet-Cookieヘッダを付与することもできないからです(リクエストは飛ぶので、リクエストのCookieヘッダを見ることは可能です)。しかし、やりたいことはSet-Cookieヘッダを含むHTTPレスポンスを返すだけなのですから、PROXYではなく、Webサーバーを立てて、それにリクエストをリダイレクトすることにしましょう。ということで、今度はApacheの出番です。
これにより、偽APから IPアドレス133.242.129.62 (偽町田市サイトのIPアドレス)宛のTCPパケットを解析用PC上のapacheにリダイレクトします。$ sudo iptables -t nat -L --line-numbers ← 現在の状態を確認 Chain PREROUTING (policy ACCEPT) num target prot opt source destination 1 REDIRECT tcp -- anywhere anywhere tcp dpt:http redir ports 8080 ... 省略 $ sudo iptables -t nat -D PREROUTING 1 ← 1番のルールを削除 $ sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dst 133.242.129.62 --dport 80 -j REDIRECT --to-port 80 ← 133.242.129.62:80来たパケットをローカルに
さらに、クッキー設定用のPHPスクリプト(image.php)を解析用PC上に配置します。Base64エンコードされた文字列は、1ピクセル×1ピクセルのGIF画像です。
後は、利用者に罠を仕掛けて、http://www.city.machida.kanagawa.jp/image.php を閲覧させるだけです。このリクエストは、解析用サーバー上のapacheにリダイレクトされて、1x1のGIF画像を表示するとともに、PHPSESSID=image.phpというCookieが、利用者のPCにセットされます。<?php header('Content-Type: image/gif'); setcookie('PHPSESSID', 'image.php'); echo base64_decode('R0lGODlhAQABAIgAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==');
Cookieの強制はサイト側で防御できない
以上のように、元サイト側で80番ポートを閉じている・いないに関わらず、HTTPSでCookieを利用しているサイトは、中間者攻撃により偽のCookieをセットされてしまうことが分かりました。これは、以下の理由によるものです。- Cookieはポートやプロトコル(http/https)をまたがって共有されている
- Cookieのsecure属性は平文でCookieを送信しないという設定であり、Cookieをセットする(受信する)場合には効果がない
- 既にsecure属性つきCookieがあっても、HTTPのsecure属性なしCookieで上書きされる(IE10、Google Chrome、Firefoxで確認)
Cookie Monster Bugの影響のないドメイン名でもセッション固定対策はしっかり行おう
ここまで説明したように、HTTPSを利用しているサイト(通信路上の盗聴・改ざんを許容していない)では、外部からCookieを強制されること自体は防御できないので、「Cookieを改変されても構わないようにサイトを設計する」必要があります。これには、以下が重要です。- セッション固定脆弱性対策を行う。具体的には、ログイン成功後にセッションIDを振り直す
session_regenerate_id(true); - Cookieには、外部から変更されると困る値は入れない
- Cookieに攻撃文字列を入れるタイプのXSSの影響を受けるので、攻撃経路がCookieだからという理由で許容してはいけない
緩和策としてHTTP Strict Transport Securityが有効
先ほど、Cookieの強制はWebサイト側で防御することはできないと書きましたが、緩和策としてであれば、HTTP Strict Transport Security (HSTS) が有効です。HSTSとは、特定サイトへの通信をHTTPSに強制する機能です。HSTSを利用するには、HTTPレスポンスヘッダとして下記をブラウザに送信します。includeSubdomainsはサブドメインまで含めてHSTSを強制するというオプションです。Strict-Transport-Security: max-age=有効期間(秒); includeSubdomainsこれを受け取った後は、max-ageで指定した期間、指定サイトにHTTPで接続することはできなくなり、強制的にHTTPSで接続されます。このため、HTTP(平文)でCookieの盗聴や強制も防御されます。PHPでの利用例を下記に示します。
header('Strict-Transport-Security: max-age=2592000; includeSubdomains'); // HSTS30日間有効HSTSが完全な防御ではなく緩和策である理由は下記の通りです。
- 対応ブラウザが本稿執筆時点でGoogle Chrome、Firefox、Operaであり、IEとSafariは未サポート
- 利用者が初めて訪問するサイトや、HSTSの期限切れの状態では効果がない
一方、従来よく用いられてきたサーバー側でのHTTPSへの強制リダイレクトは効果がありません。この場合は、最初のリクエストはHTTPで送信されますが、Cookieの盗聴・強制には1リクエストあれすば十分だからです。
※追記(2013/9/30 10:15) このエントリ公開時にincludeSubdomainsの指定が抜けておりましたが、これがないと、サブドメイン上のページ(DNSも偽装すれば、存在しないサブドメインでも攻撃できる)でCookieの改変が可能になることを、はせがわようすけさんから指摘いただきました。ありがとうございます。もしも、既にHTTPで稼働しているサブドメインがある場合は、includeSubdomainsは指定できないので、HSTSによる緩和策は有効ではありません。
まとめ
通信路上に攻撃者がいる場合でも、SSLの正しい利用により通信路上でのHTTPメッセージの盗聴・改ざんを防ぐことができますが、Cookieに関して言えば、Secure属性の付与により盗聴は防げるものの、改ざん(強制・改変)については防御できないことを示しました。これにより、セッション固定攻撃の他、Cookieを攻撃経路とするXSS等の攻撃が現実の脅威となります。結論としては、Cookieを改変できるかどうか(通信路上の攻撃者、Cookie Monster Bug)とは無関係に、Cookieを攻撃経路とする脆弱性は常に対処することを推奨します。また、盗聴防止として、CookieのSecure属性は必ず設定しましょう。
良く分かっていないので、的外れかも知れませんがお許し下さい。
返信削除>http://www.city.machida.kanagawa.jp:443/ (httpsではなくhttp)にアクセスさせる
で、こんなことができるのかと思い、自サイトで本来https:の代わりにhttpでポートを443に指定してリクエストはFirefoxで出すと、こんなメッセージが帰って来て、成功しません。
Bad Request
Your browser sent a request that this server could not understand.
Reason: You're speaking plain HTTP to an SSL-enabled server port.
Instead use the HTTPS scheme to access this URL, please.
Centos7, Apache httpd 2.4.26, tomcat 8.5.15 環境です。
とりあえず、リクエストは跳ね返しています。
Twitter おっとと
これはサーバーが返しているエラーメッセージですよね。これで構わないです。ブラウザからリクエストが出ている時点でクッキーは送出されているからです。レスポンスは必要ないので、エラーでも構いません。
削除