UserCSP
- 目を醒ませ僕らのなんちゃら〜
- CSP 導入準備のため、実際に Content-Security-Policy ヘッダが出力されたときに、ウェブブラウザがどのような挙動になるかを調べていた
- ウェブサーバの設定を変えながら試行錯誤するのは、いかにも面倒くさい
- ブラウザ側で HTTP レスポンスに含まれるヘッダを変更できないか
- できるが、標準機能ではない
- レスポンスに任意のヘッダを追加したり変更できる、ブラウザのアドオンや拡張機能が存在する
- 単純な機能なので、安心のために自作する
- 拡張機能の上記設定画面で、Content-Security-Policy ヘッダと Content-Security-Policy-Report-Only ヘッダを設定すると、出力されるようになる
- Content-Security-Policy-Report-Only: default-src 'self' で はてな にアクセスすると、ウェブコンソールに、設定されたポリシーに従ってリソースを拒否することがレポートされる
The Content Security Policy 'default-src 'self'' was delivered in report-only mode, but does not specify a 'report-uri'; the policy will have no effect. Please either add a 'report-uri' directive, or deliver the policy via the 'Content-Security-Policy' header. Navigated to http://www.hatena.ne.jp/ (index):1 [Report Only] Refused to load the script 'https://cdn.ad-hatena.com/js/river.js' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'default-src' is used as a fallback. (index):27 [Report Only] Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-xYOawB5vZJsporgKCEyYks2LJ5r/svEP1UgyihxaEAg='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback. (index):30 [Report Only] Refused to load the script 'http://www.googletagmanager.com/gtm.js?id=GTM-WPVF7X' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'default-src' is used as a fallback. (index):39 [Report Only] Refused to load the image 'http://n.hatena.ne.jp/my/profile/image?size=16&type=icon' because it violates the following Content Security Policy directive: "default-src 'self'". Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback. ...
- Content-Security-Policy-Report-Only ではなく Content-Security-Policy: default-src 'self' とすると、レポートだけではなく、本当にいくつかのリソースが拒否され、ページが機能不全になった
- ページが完全に機能し、かつ、ある程度セキュアな Content-Security-Policy は下記のようなものだろうか(見やすさのため適宜改行)
script-src 'unsafe-inline' 'unsafe-eval' http://*.hatena.ne.jp/ http://img.ak.impact-ad.jp/ http://pagead2.googlesyndication.com/ http://platform.twitter.com/widgets.js http://static.criteo.net/js/ld/publishertag.prebid.js http://www.googletagmanager.com/gtm.js http://www.googletagservices.com/activeview/js/current/osd.js http://www.hatena.com/ http://y.one.impact-ad.jp/imp https://adservice.google.co.jp/adsid/integrator.js https://adservice.google.com/adsid/integrator.js https://c.amazon-adsystem.com/aax2/apstag.js https://cdn.ad-hatena.com/ https://*.st-hatena.com/ https://platform.twitter.com/js/ https://securepubads.g.doubleclick.net/gpt/ https://static.xx.fbcdn.net/rsrc.php/ https://tpc.googlesyndication.com/ https://www.googletagservices.com/activeview/js/current/osd_listener.js https://www.googletagservices.com/tag/js/gpt.js stats.g.doubleclick.net/dc.js www.google-analytics.com/analytics.js
- だが、ユーザとしては、これらすべてのリソースを信頼できるか判断するのは難しい
- ページ機能の一部が失われるが、信頼できそうなリソースのみを許可するポリシーを考える
script-src 'unsafe-inline' 'unsafe-eval' http://*.hatena.ne.jp/ http://www.hatena.com/ https://cdn.ad-hatena.com/ https://*.st-hatena.com/ http://www.googletagmanager.com/gtm.js
- こうしておけば、マルバタイジングを始めとした何者かの侵略を、食い止めることができる
- あらゆるページのために、このようなポリシーを作成するのは現実的ではないが
Have I been pwned? にパスワードを入力するとどうなるか
Have I been pwned? Pwned Passwords (以下 HIBP と呼ぶ)が公開されました。ここでは、過去に情報漏えいで実際に暴露された 5 億個のパスワードのオンライン検索とダウンロードが提供されています。セキュリティに関心のある方は、検索するために入力したパスワードを HIBP の管理者に知られることに懸念を感じるでしょう。I've Just Launched "Pwned Passwords" V2 With Half a Billion Passwords for Download では、検索されたパスワードの秘密がどのように守られるかが解説されています。
ダウンロードとオフライン検索
その前に、安全にオフラインで検索する方法を説明します。
HIBP では、パスワードはソルトなしの SHA-1 ハッシュのリストで管理されています。利用者が自らのパスワードを検索したい場合、そのパスワードの SHA-1 ハッシュを求める必要があります。利用者が HIBP から 5 億個のパスワードのハッシュリストの全体をダウンロードし、それを検索すれば、管理者に自らのパスワードを知られるおそれはありません。ですが、そのリスト全体のサイズは約 9 ギガバイトあり、配布や更新にコストがかかります。
オンライン検索
それでは、利用者がパスワードのハッシュのみを HIBP に送信し、一致しているか結果を返す方法はどうでしょうか。
残念ながら、HIBP はハッシュする前の生のパスワードリストを持っているので、一致している場合、管理者に利用者のパスワードを知られてしまいます。一致していない場合も、レインボーテーブル - Wikipedia により、総当たり攻撃が可能です。
そこで、HIBP はパスワードハッシュの最初の 5 文字のみを送信するというアイデアを採用しました。
例えば "P@ssw0rd" というパスワードを検索するには、そのハッシュである "21BD12DC183F740EE76F27B78EB39C8AD972A757" の先頭の "21BD1" を HIBP に送信 します。HIBP はプレフィックス "21BD1" に一致するハッシュの残り 35 文字のリストを返します。
1. (21BD1) 0018A45C4D1DEF81644B54AB7F969B88D65:1 (password "lauragpe")
https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/
2. (21BD1) 00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2 (password "alexguo029")
3. (21BD1) 011053FD0102E94D6AE2F8B83D76FAF94F6:1 (password "BDnd9102")
4. (21BD1) 012A7CA357541F0AC487871FEEC1891C49C:2 (password "melobie")
5. (21BD1) 0136E006E24E7D152139815FB0FC6A50B15:2 (password "quvekyny")
6. ...
利用者は、このリストに "(21BD1) 2DC183F740EE76F27B78EB39C8AD972A757" が含まれているかを調べ、あれば漏えいしていると判断できます。
実際のところ Have I been pwned? Pwned Passwords の検索フォームは上記のように動作します。また、同様のことをおこなう Bash スクリプト も存在します。
1. Every hash prefix from 00000 to FFFFF is populated with data (16^5 combinations)
https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/
2. The average number of hashes returned is 478
3. The smallest is 381 (hash prefixes "E0812" and "E613D")
4. The largest is 584 (hash prefixes "00000" and "4A4E8")
それぞれのプレフィックスは、最小でも 381 個のハッシュが存在しており、そこに利用者のパスワードが含まれていたとしても、管理者にはどれに一致するかわかりません。この HIBP が返すデータが持つ性質を k-匿名性 - Wikipedia と呼びます。
管理者が最悪でパスワード候補を 381 個までしか絞り込めないという意味であり、前述の完全なハッシュを送信するケースと比較して、リスクが緩和されています。利用者のパスワードが含まれていない場合は、ハッシュの残り 35 文字が不明なため 16^35 パターンの可能性があり、総当たり攻撃は現実的ではありません。
パスワードを推測されやすいシナリオ
ここからは筆者の考えですが、管理者がより正確にパスワードを推測できる利用シナリオがあります。
HIBP はハッシュしか返さないため、説明のために小規模なパスワードリストから生成したハッシュと、生のパスワードのペアを返すエンドポイントを用意しました。HIBP は 5 億ですが、ここでベースにしたパスワードリストは 100 万と小さいため、プレフィックス 3 文字で平均 244 個(最小 193 個)のハッシュとパスワードを返します。
https://asannou.github.io/badpasswords/21b.txt
利用者
あるサービスのアカウントを作るにあたって、新しく作成したパスワードが漏えい済みかを確かめる。
新しいパスワード "trustme" をチェックするため、そのハッシュ "b6a34a9f8b81a6964ff5b983bcc739ff2efb569f" のプレフィックスを送信する。
$ curl -s https://asannou.github.io/badpasswords/b6a.txt | grep ^b6a34a9f8b81a6964ff5b983bcc739ff2efb569f b6a34a9f8b81a6964ff5b983bcc739ff2efb569f trustme
リストに含まれていたので、続いて "trustme2", "trustme3" のハッシュ "30d22e311391fb2da70422f7402eb240908cf8e5", "42c540d61d64f38f334b2f350ea0e32d3a208a2e" をチェックする。
$ curl -s https://asannou.github.io/badpasswords/30d.txt | grep ^30d22e311391fb2da70422f7402eb240908cf8e5 30d22e311391fb2da70422f7402eb240908cf8e5 trustme2 $ curl -s https://asannou.github.io/badpasswords/42c.txt | grep ^42c540d61d64f38f334b2f350ea0e32d3a208a2e $
"trustme3" はリストになかったため、アカウントのパスワードとして決定する。
管理者
このとき、送られたプレフィックス "b6a", "30d", "42c" をみて、以下の推測ができる。
まず "b6a" と "30d" に含まれるパスワードで前方一致するものを探す。
$ wget -q https://asannou.github.io/badpasswords/b6a.txt https://asannou.github.io/badpasswords/30d.txt $ cut -f 2 b6a.txt | while read password; do cut -f 2 30d.txt | grep ^$password; done trustme2
この結果から、最初のリクエストで "trustme" 次のリクエストで "trustme2" が検索された可能性が高い。最後のリクエストを "trustme3" と仮定してハッシュを計算すると "42c..." が一致する。
考察
是非は別として、作成したパスワードが漏えい済みだった場合、文字を付加して回避することは、利用者の行動としてありえるでしょう。特に Nextcloud will check passwords against database of HaveIBeenPwned のように導入する場合は、パスワードが推測されないよう、適宜 HIBP にダミーのリクエストを送るなどの対策が必要と考えます。
ジェネリックトレネ
振動を検知して荷物の盗難を防ぐデバイス「トレネ」のクラウドファンディングが、目標金額を達成しています。
- モニタリングアラーム「トレネ」でクラウドファンディングを活用 | ニュース 2017年 | ファイルとテプラのキングジム
- 一人での外出をもっと快適に!荷物を見守る小さなパートナー「TRENE(トレネ)」 | クラウドファンディング - Makuake(マクアケ)
要素技術が、振動センサーと Bluetooth ということで、Raspberry Pi Zero W と少しのパーツで似た機能の実現を目指しました。盗られたことがわかるので、「トラレ」と呼ぶことにします。
緑色のミンティア(カテキンミント)の中に収まっているのが Raspberry Pi で、ケーブル接続されている黒い物体は一般的なモバイルバッテリーです。(ミンティアから飛び出している赤外線 LED は今回関係ありません)
まずは振動センサーですが、15 円/個くらいの 水銀スイッチ - Wikipedia を Raspberry Pi の GPIO18 ピンと GND にはんだ付けしました。水銀スイッチは取り扱いを誤ると、環境に悪影響を及ぼすため 金属球で感知するタイプ もあります。GPIO18 がオフからオン、または逆に変化したとき、傾きが発生したとみなすことができます。
「トラレ」と iOS 端末の距離は、Raspberry Pi が Bluetooth の RSSI 値を定期的に取得することで監視します。今回は GitHub - ewenchou/bluetooth-proximity: Bluetooth Proximity Detection using Python を利用しました。
iOS 端末が一定以上離れていて、Raspberry Pi が傾きを検知したとき、iOS 端末に通知をおこないます。本家「トレネ」では、本体が警告音を発する仕様ですが、誤動作で注目を集めるのは望むところではないので、そのようにしました。Bluetooth による通知といえば、一時期ブームだった iBeacon - Wikipedia という仕組みがあるため、それを用います。
これらを組み合わせて「トラレ」として動作させるために Raspberry Pi 上で実行する TRARE · GitHub を作成しました。さらに、iOS 端末で iBeacon を受信するためのアプリのインストールが必要です。iBeacon advertisement を受信できるアプリは数多くありますが、任意の UUID を指定でき、バックグラウンドでも通知可能な 「Find My Stuff - 鍵やお財布から車まで、あなたの所持品を即座に位置確認!」をApp Storeで を採用しました。
Bluetooth が届く距離にいないと通知が受け取れないという懸念はあります。試した限りでは、壁を隔てた隣の部屋くらいであれば問題ないようです。圏外にいたとしても iBeacon advertisement は発信され続けるため、圏内に入った時点で通知されます。
Raspberry Pi Zero W は $10 なので、「トレネ」の予定価格 6,800 円と比較すると、コストパフォーマンスの高さが際立ちます。