たいていの人は、共有リンクについてあまり深く考えません。チャットにファイルを 落とし、URLを貼り、相手がそれを開く。ファイルが機微なら、パスワードをかける かもしれません。それで完了。
問題は、そのパスワードが執念深い攻撃者の手に渡ったとき、「本当に安全か?」が、 平均的な製品が正しく実装できていない細部に左右されることです。私たちは前四半期の 終わりに、攻撃者はすでにURLを入手している、という前提で自分たちを監査し、共有 リンクの仕組みを作り直しました。変わった点をご紹介します。
SHA-256からbcryptへ
パスワード保護リンクの最初の実装では、保存前にパスワードをSHA-256でハッシュ化して いました。一見問題なさそうです — SHA-256は名の知れた暗号学的ハッシュですから。 しかしSHA-256は、パスワード保存には間違った道具です。高速になるよう設計 されており、その「高速さ」こそ、6文字の組み合わせをすべて試す攻撃者が欲しがるもの なのです。
最新のGPUリグは、1秒あたり数十億回のSHA-256ハッシュを計算できます。6文字の英小文字 と数字のパスワード(約20億通り)に対しては、およそ1秒の計算で終わってしまいます。
私たちはbcryptへ移行しました。これは一般的なハードウェアで1ハッシュ あたり約100ミリ秒を意図的にかけます。同じ総当たりが、専用ハードウェアでも数か月 がかりのプロジェクトになり — その時点で攻撃者は、別の標的を狙ったほうが得だと 気づきます。
遅いハッシュだけでなく、ロックアウトも
遅いハッシュが意味を持つのは、攻撃者が各推測を検証するために私たちのサーバーと やり取りを続けなければならない場合だけです。そこで私たちは、保護されたすべての リンクの上に、アカウント方式のロックアウトを重ねました。
- 15分以内に5回パスワードを間違えると、リクエスト元のIPに対してリンクがロックされます。
- ロックアウトされたIPには汎用的な403を返します(リンクがまだ存在することを裏づける「パスワードが違います」というフィードバックは出しません)。
- 失敗した試行は、リンク所有者が確認できるよう、タイムスタンプとIPとともに記録されます。
リンクIDそのものが秘密
各共有リンクには128ビットのランダムな識別子が付きます。リンクを知っていること自体が 認証情報の半分 — 保護されていないリンクでも、推測できないものであるべきです。作成時にcrypto.getRandomValues() を使い、IDを再利用することは決してありません。
有効期限と表示回数の上限はデフォルトで有効
ユーザーがリンクを作成するとき、有効期限(デフォルト7日)と任意の表示回数の上限を 選びます。どちらも、ファイルがストレージから復号される前にサーバー側で強制されます。 期限切れや表示回数超過のリンクは、存在しないリンクと同じ404を返します — かつて存在 していたという手がかりは一切残しません。
これがあなたに強いるコスト
リンクの送信者としては、何もありません。デフォルトは妥当です(7日の有効期限、表示 回数の上限なし、任意のパスワード)。仮想の攻撃者としては、パスワード保護されたリンク 1本のために数か月分の専用計算を費やし、その間ずっとリンク所有者は失敗試行の通知を 受け取り続けます。
保護された共有リンクの損得勘定は、正当な所有者の側にはっきり傾くべきだと私たちは 考えています。そのためにこうしているのです。
