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
  • こうしておけば、マルバタイジングを始めとした何者かの侵略を、食い止めることができる
    • あらゆるページのために、このようなポリシーを作成するのは現実的ではないが
  • ウェブブラウザには、ページに埋め込まれたスタイルシートスクリプトを、ユーザが任意に上書きできる手段がある
    • それらはユーザスタイルシートやユーザスクリプトと呼ばれ、平成中期によく利用されていた
    • 同様に、ユーザの立場でページに独自のポリシーを定義できても、CSP の目的からそれほど離れないと思う

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")
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. ...

https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/

利用者は、このリストに "(21BD1) 2DC183F740EE76F27B78EB39C8AD972A757" が含まれているかを調べ、あれば漏えいしていると判断できます。

実際のところ Have I been pwned? Pwned Passwords の検索フォームは上記のように動作します。また、同様のことをおこなう Bash スクリプト も存在します。

1. Every hash prefix from 00000 to FFFFF is populated with data (16^5 combinations)
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")

https://www.troyhunt.com/ive-just-launched-pwned-passwords-version-2/

それぞれのプレフィックスは、最小でも 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 にダミーのリクエストを送るなどの対策が必要と考えます。

ジェネリックトレネ

振動を検知して荷物の盗難を防ぐデバイス「トレネ」のクラウドファンディングが、目標金額を達成しています。

要素技術が、振動センサーと Bluetooth ということで、Raspberry Pi Zero W と少しのパーツで似た機能の実現を目指しました。盗られたことがわかるので、「トラレ」と呼ぶことにします。



緑色のミンティアカテキンミント)の中に収まっているのが Raspberry Pi で、ケーブル接続されている黒い物体は一般的なモバイルバッテリーです。(ミンティアから飛び出している赤外線 LED は今回関係ありません)

まずは振動センサーですが、15 円/個くらいの 水銀スイッチ - WikipediaRaspberry Pi の GPIO18 ピンと GND にはんだ付けしました。水銀スイッチは取り扱いを誤ると、環境に悪影響を及ぼすため 金属球で感知するタイプ もあります。GPIO18 がオフからオン、または逆に変化したとき、傾きが発生したとみなすことができます。

「トラレ」と iOS 端末の距離は、Raspberry PiBluetooth の 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 円と比較すると、コストパフォーマンスの高さが際立ちます。