LastPassでさえも盗まれうる – LastPassのセキュリティを探る

私はAlberto Garciaと一緒に、アムステルダムで開かれたBlackhatで“Even the LastPass Will be Stolen, Deal with It!(LastPassでさえも盗まれるうのです。それでも我慢しましょう)″という調査を口頭発表してきました。その会議は大成功で、聴衆から素晴らしいフィードバックを得ることができました。たくさんの人からビデオやスライドなどの資料が欲しいと言われたので、講演の詳細を記した記事を書くことは価値があると考えました。

動機

Albertoのチームの1つがペネトレーションテストをした時、彼は複数のコンピュータへのアクセスに成功し、その全てがLastPassを参照するファイルを持っていることを発見しました。彼は私のところへ来て、LastPassの仕組みをチェックし、もし可能ならLastPassの認証情報を盗んでみたら面白いのではないかと言いました。調査に充てられるのは私たちの時間の10パーセントですので、小規模なプロジェクトとして進めることにしました。

私たちは、認証情報がローカルにどのように保存されるのかを見つけ、危険にさらされた全てのコンピュータからAlbertoがvaultのコンテンツを抽出できるように、Metasploitのプラグインを書きました。モジュールのおかげで、彼は重要なサーバに入るSSHのキーも得ることができました。ペネトレーションテストは成功しました。

私は過去にこのことをブログに書きredditで非常に人気になりました。たくさんのフィードバックや質問、コメント、提案などをもらいました。セキュリティのコミュニティは、LastPassのセキュリティに対して非常に気にしているということがはっきりしました。そのため、この問題にもう少し時間を割き、適切な調査をする価値があると考えました。

焦点

Research focus
調査の焦点

私たちは、過去に行われた調査を見てみました。すると、DNSキャッシュポイズニングとインラインフレームの脆弱性を使ったパスワードマネージャに関する調査や、クロスサイトスクリプティング(XSS)を通して特定の認証情報を盗む攻撃ベクトルに関する調査が見つかりました。

私たちは特定の秘密を漏らすのではなく、vault(保管庫)を直接攻撃するための方法を見つけ、全てのコンテンツにアクセスできるようにすることに焦点を定めました。それを、次の3つのシナリオ全てで実行したいと思いました。

  • クライアント側の攻撃: エクスプロイト以降のシナリオで、攻撃者が被害者のコンピュータに入るための特定のアクセスが可能(ルートアクセスは不要)
  • LastPass側の攻撃: LastPassの社員かサーバを危険にさらした攻撃者、あるいはコネクションの中間者攻撃を行う誰かが攻撃者となる
  • 外部からの攻撃者: クライアント側でもLastPassのサーバ側でもない攻撃者

クライアント側の攻撃

ここでの目的は、ブラウザのプラグインをリバースエンジニアリングし、システムに保存された全てのファイルを分析し、vault(これ以降はvaultキーとします)を復号するためのキーを手に入れられないか見てみます。

それを行うための異なる方法を見つけました:

平文パスワードの復活

以前の記事で説明した方法です。

でも、“パスワードを記憶する”がクリックされないときは、どうでしょう?

クッキーを使う

最初の試みとして、vaultキーを手に入れるために、シンプルにクッキーを使ってみます。これは分かりやすいように思えますが、LastPassの設計方法により、クッキーはLastPassがサーバに保存しているものしか入手できず、そしてみなさんの予想どおり、その中にはvaultキーは含まれていないのです。

実はvaultキーは、暗号化されてローカルに保存されていることが分かりました。vault キーを復号するための復号キーはどこにあるのでしょう? それをどこから引き出せるかは、LastPassにヒントがあるようです。

Cookie auth flow
クッキー認証のフローチャート

このフローチャートで分かるように、LastPassに問い合わせてpwdeckeyの値を得るために、セッションクッキーを使うことができます。一度それを得れば、SHA256(pwdeckey)を実行することでキーを引き出すことができます。あとはSQLiteDBから暗号化されたvaultキーを取り出し、その引き出したキーを使って復号することが必要なだけです。

しかし、2段階認証(2FA)が有効になっている場合はどうでしょうか?

2段階認証を回避する

2FAは、アカウントにもう1段階追加されたセキュリティです。これにより、攻撃者があなたの認証情報を持っていてもあなたのvaultにアクセスできません。認証はあなたが知っていることと、あなたが持っている物、あるいはあなたが誰であるかということに基づいて行われます。今回のケースでは、あなたのマスターパスワードがあなたの知っていることであり、2FAがあなたの持っている物(何らかの機器、トークンなどの形で)ということになります。

LastPassは膨大な種類の2FAのメカニズム、例えばGoogle Authenticator、Yubikey、Toopherなどをサポートしています。もしも私の過去の記事を読んだことがあれば、どのようにマスターパスワードを盗むかはもうご存知でしょう。しかし被害者が2FAを有効にしていたら、あなたはログインすらできません。そのことに注意しましょう。

2FA request
2FAリクエスト

最初のアプローチは、Burpをスタートし、信頼できるブラウザからのログインのリクエストを確認することです。私たちはLastPassが設定した”信頼できるクッキー”を見ることを期待していましたが、そうはなりませんでした。リクエストパラメータを、2FAと関係のあるものであると特定しようと書き換え、2FAコードに再びプロンプトで指示を求められるまで1つずつ取り去りました。違いを生み出したパラメータは、UUIDでした。

UUIDは32文字のランダムな文字列で、0~9、A~Z、a~z、!@#$%^& ()_の文字から成っています。これは信頼できるトークンとして用いられ、全てのリクエストに送られます。LastPassはサーバ側でこれを比べ、トークンが信頼できる機器のリストの一部であるか確認します。これはプラグインのインストール時に発生し、コンピュータブラウザのローカル記憶装置に保存されます。

設計の問題

LastPassは2FAの一般的な実装に執着すべきでしたし、信頼できるクッキーを使うべきです。現在のLastPassの実装ではうまくいかないことがたくさんあり、悪い結果となっています。それは以下のようなものです。

  • ブラウザはローカルストレージを暗号化しない。これによりトークンは、ルートを必要とせずアクセスが可能になります。クッキーの値はいくつかのブラウザによってコード化されます。
  • 2FAのトークンはプレーンテキストに保存される。
  • このトークンはDOMの中に送られる。 XSSによって攻撃者に2FAのトークンを盗むことを許してしまいます。
  • ブラウザの全てのユーザに同じトークンが使われる。同じ2FAのトークンを他のLastPassのブラウザユーザと共有することになります。
  • トークンは決して変化しない。疑い深いブラウザは実際の効果も持たず、新しいQRコードを作ることもありません。
  • トークンの固定。攻撃者は、2FAが作動している時に使われる、すでに分かっているトークンを設定することができます。
  • 事前対策となるトークンを盗む。攻撃者は2FAが実行される前にトークンを盗むことができ、後で被害者が2FAを実行した時に使うために保存しておくことができます。

しかし、有効なセッションのクッキーがない場合はどうでしょうか?

暗号化キーを得るためにアカウント復旧を悪用する

vaultキーを得る方法をいくつか発見したのですが、まだ問題はありました。ユーザがログインしていなかったり、もしくは保存されている有効なセッションクッキーがない場合、vaultキーを引き出すのに使う何らかのもとをLastPassに問い合わせることができなくなってしまうのです。他にも、複数のLastPassアカウントがある場合は、起動中のアカウントからでしかvaultキーを得ることはできない(他のアカウントはセッションクッキーがない状態です)、という制限があります。

何か特効薬が必要です。革新的でクリーンなLastPassという装置から全ての秘密を盗むことのできる良い方法が。有効なセッションクッキーがなかったとしても、ユーザが”パスワードを記憶する”をクリックしなくても、2FAがなかったとしても使えるやり方が。

LastPass他の機能に目を向けたところ、私たちは”アカウント復旧”という機能を見つけました。それはLastPassが提供している機能で、マスターパスワードを忘れてしまってもユーザがvaultを復旧できるようになるものです。皆さんの言いたいことは分かっていますよ。どうしてマスターパスワードが分からずに、vaultを復号できるのでしょうか。何しろ、暗号化キーはマスターパスワードから派生したものなのですから。私たちもこの機能に直面したときは同じ疑問を持ちました。そして、どのような仕組みになっているのか理解したいと思いました。

プロセス

マスターパスワードを忘れてしまってvaultを復旧(復号)するときには、アカウントを復元にアクセスし、Eメールアドレスを入力して、続行をクリックしてください。そうすると、即座にLastPassから固有のリンクが張られたメールが届きます。そのリンクをクリックすると、”Press to recover account(アカウントを復元する)”と書かれた大きなボタンのあるページに飛びます。そのボタンを押すと、マスターパスワードを入力したり、2FAを飛び越す必要もなく、復号された全てのvaultを見ることができます。

裏では何が行われているのか

Account recovery flow
アカウント復旧のフローチャート

Eメールのリンクをクリックすると、以下のようなURLにリダイレクトされます。/recover.php?&time=1412381291&timehash=340908c353c099c9FAKE6b387002c5a4881ebdf1&username=test%40test.com&usernamehash=fc7be7e5f6cbec9FAKE2995bd3331c097

このURLには4つのパラメータを含んでいます。

  • 時間:アカウント復旧をスタートさせた時間を記録するタイムスタンプ
  • タイムハッシュ:タイムスタンプのソルトされたSHA1ハッシュ
  • ユーザネーム: LastPassのユーザネーム
  • ユーザネームハッシュ:ユーザネームのソルトされたSHA1ハッシュ

私たちは、このURLが私たち自身で生成できるものなのか試してみたくなりました。そうすれば、固有のURLを盗むのに被害者のEメールにアクセスする必要はありません。ただし、問題は2つ。タイムスタンプが正確であることと、そしてそのタイムスタンプとユーザネームを正しくハッシュできるソルトが必要になります。

私たちにはソルトがありませんので、ユーザの正確なURLを取得し(私が自身でアカウント復旧を実行しました)、そのURLからタイムスタンプとタイムスタンプハッシュを割り出し、再利用しました。これで、うまくいきました!

この結果から学んだのが以下です。

  • 全てのユーザに同じソルトが使われている
  • リンクは期限切れになっているのではなく、タイムスタンプがハッシュに対して有効かどうか承認されるだけ
  • アカウント復旧を実行する必要はなく、有効なリンクさえあればいい

ただし、私たちにはソルトが分からないため、正確なユーザネームハッシュは生成できませんでした。では、次のパートではアカウント復旧のプロセスを見ていきましょう。

復旧ボタンをクリックする

復旧ボタンをクリックすると、POSTリクエストが以下のようなURLとともにLastPassで作られます。
/otp.php&hash=ccb2501724FAKE2b575a214e1052d0fa27b0726b6HASHdb2e1da3952e

今回のパラメータは1つで、ハッシュのみです。このパラメータは、生成された”無効にされたワンタイムパスワード”です。そうです。LastPassには2種類のOTP(ワンタイムパスワード)があります。1つは本物のOTP(一度のみ使えるパスワードで、信頼できないコンピュータからログインするときに便利です)で、もう1つはdisabled(無効にされた)OTPで、こちらがアカウント復旧に使用されます。

無効にされたOTP(これ以降はdOTPキーとします)は通常デフォルトで設定されています。ですので、マスターパスワードを盗む攻撃方法(=被害者に前もって”パスワードを記憶する”をクリックさせる必要がある)よりも、この攻撃が都合のよいことが分かります。

From dOTP to vault
dOTPからvaultまで

上記の図から分かるように、プラグインはローカルストレージからdOTPを得ています。それから、SHA256をユーザネームとバイナリのdOTPに適用します。これをもう一度繰り返した後、前述したハッシュパラメータの値を得ることができます。

これで、正しいパラメータ値で直接リクエストを生成できるようになったので、randkeyとともにセッションクッキーを元に戻すことができるようになります。

randkeyとは?

私たちはまだvaultキーを取得できていません。randkeyは暗号化されたvaultキーのことです。私たちが認証のためにdOTPを使用した際に LastPassが送り返してきたのはこれのことだったのです。ということは、このvaultキーを復号すればいいだけのことなのです。では、どうやるのでしょう。vaultキーは、キーもdOTPから生成されるCBCモードでAES256を使用し暗号化されています。具体的に言えば、このキーはSHA256(dOTP)のことです。

dOTPとは何か

考えてみてください。dOTPは強化されたマスターパスワードなのです。

  • 認証に使用できる
  • 暗号化されたvaultキーを得られる
  • vaultキーを復号できる
  • IPの制約を無視できる
  • 2FAを無視できる
  • デフォルトでローカルに保存される

Metasploitモジュール

私たちはこの調査をペネトレーションテスト実施者(ペンテスタ)に役立ててもらいたいと思い、以前に本記事で語ってきたこと全てを自動化するためのpost-exploitationモジュールを書きました。もうすでに私が認証情報を盗むために書いたモジュールを使った人もいるかもしれません。なので、私はさらにアップデートをしました。2FAトークンを盗み、vaultキーを生成するクッキーとdOTPを使用し、復号化のvaultを奪い、vaultの中に保存されている全ての秘密を割り出すサポートを追加したのです。今やこれら全てがあなたの元に!

このアップデートされたモジュールは私のgithubのリポジトリにあります。すぐに公式なリポジトリにプルリクエストを作るつもりですので、皆さんがMetasploitを次にアップデートしたときには、このモジュールは皆さんがより使いやすいようになっているはずです。

LastPass側の攻撃

6月にLastPassサーバへの侵入があり、そしてその時LastPassは顧客のデータにはアクセスされていないと主張したので、私たちは、LastPassと同じデータを持っていたら事前に何ができたのかを検証することにしました。特に私たちが知りたかったのは、誰がvaultを復号することが可能なのかということです。LastPassの人間はどうか、もしくはサーバにアクセスが可能であれば誰でもなのか、それともLastPassのデータベースにアクセスする権限を強要している国家安全保障局はどうだろうか、調べます。

LastPassはこのように主張しています。顧客のデータを復号することなんてできるはずはないし、サーバに侵入してきたハッカーたちは成功させるのが難しいオフラインの攻撃を成功させるためにデータを捨てました。そして、国家安全保障局でも、10万回のPBKDF2に対しては何もできなかったはずだ、と言っています。

このまま続ける前に、1つ言っておきます。私は、これから説明するどの攻撃もLastPassが実行したものであるとは、言ってもないし、ほのめかしてもないし、提唱しているわけでもありません。彼らは決してそんなことしていませんし、私がLastPassを攻撃者としている場面があってもそれは説明がしやすいからという理由だけです。私が意図するところは、「LastPassの社員の怠慢が原因で、ハッカーが彼らのサーバに侵入した場合はどうか」「政府がLastPassに圧力をかけた場合は」という可能性は否定できないということです。

では、一緒に妄想しましょう。

LastPassが実際に見ているものとは?

Auth flow
認証のフローチャート

暗号化キーにはPBKDF2の保護はない

LastPassによって公開されているデータを見ると、認証するために、“認証ハッシュ”は1回のvaultキーのPBKDF2を実行することによって作成されています。PBKDF2は、認証情報からvaultキーを引き出し、LastPassサーバに認証ハッシュを保存しておくのに使用されます。しかし、実際のところ、LastPassはPBKDF2で暗号化キーを保護していません。ただし、ここで勘違いしないでほしいのは、256ビットの総当たり攻撃はいまだにとても困難であるということです。

OTPでの本物の256ビット保護はない

LastPassは暗号化されたvaultキーを何種類か保存しています。その中の1つはdOTPでの認証を説明した際に登場しましたね。dOTPsは32文字なので、256ビットで保護されています。しかし、通常のOTPを使った場合、LastPassは以下のような暗号化されたvaultキーのコピーを持つことになります。

SHA256( SHA256(username+OTP) + OTP)

OTPは16のランダムなバイトです。ユーザネームが知られていて、キーを暗号化するのに使われるPBKDF2がないことを考えれば、誰でもLastPass側なら256ではなくOTP(128バイト)のみを総当たり攻撃すればいいことになります。ここでも勘違いしないでほしいのは、128ビットの総当たり攻撃もまたとても困難であるということです(今日でも)。しかし256よりはかなり楽になります。

“暗号化された”vault

Encrypted vault
暗号化されたvault

上記の画像は暗号化されたvaultのスクリーンショットです。きっと想像していたよりも暗号化されているようには見えないでしょう。つまりvaultは暗号化された大量のデータではなく、いくつかの暗号化された値が入った平文のメタデータなのです。

  • URLやアイコンは暗号化ではなくエンコードされる:つまり、プライバシーはないということです。仮に、いかがわしいポ〇ノが好きだったり、怪しいフォーラムに登録したりすると、あなたのvaultが暗号化されていてもそれを見ている人には全て分かってしまいます。また、どこかのサイトでパスワードを再設定して、LastPassのvaultアカウントをアップデートしプロンプトしたときに、再設定されたパスワードのURLも保存されてしまうかもしれません。Webマスターがリンクを期限切れにさせてしまったら、もう一度パスワードを再設定するためのそのリンクをLastPassに渡すことになります。
  • 認証情報はECBモードでしばしば暗号化される: ECBの暗号化メソッドはもろいです。使うべきではありません。暗号文を見れば、パスワードを再利用したことが分かります。これは良いことではありません。なぜなら、LastPassは、これまでに使っていた破棄されたパスワードをチェックできるので、あなたが、ハッキングされたサイトに登録されていないかを調べ(URLは暗号化されません)、平文のパスワードを見つけることができてしまうからです。ECBモードでは、同じプレーンテキストが同じ暗号文になるので、もしvaultで他のアカウントと同じパスワードを使ってしまうと、暗号文を比較すればそれが分かってしまいます。そして、LastPassはプレーンテキストのパスワードを取得することになり、他のアカウントにもアクセスが可能になります。

custom_js parameter
custom_jsのパラメータ

これまで説明したものはどれも不安にさせますが、まだvaultから全ての秘密を盗む方法を見つけるという目標を達成していません。LastPass APIをいじっていると、暗号化されたvaultをXMLで返すリクエストを偶然見つけました。そのおかげで、全てのアカウントノードの一部である”custom_js”という興味深いパラメータを発見することができました。

custom_jsは何のためにあるのか

LastPassがログインページに認証情報を送るための入力ノードをDOMの中から必ずしも見つけることができるわけではない、ということが判明しました。これは、フォームや送信ボタンのないflashコンテンツで構成したサイトや手の込んだログイン画面をWebマスターが作成することで起こります。LastPassでは、認証情報を送れないと推測されたアカウントにJavaScriptペイロードを送り込むことでこの問題を解決しています。

つまり、LastPassは、JavaScriptペイロードを平文として暗号化されたvaultに追加するのです。このJavaScriptのプログラムは、ページが読み込まれるたびにドメインのコンテキストに送られ実行されます。正当な機能ではあるものの、LastPassが全ての認証情報を盗めるようにしてしまう可能性があります。

Stealing creds with custom_js
custom_jsで認証情報を盗む

上記の図から分かるように、暗号化されたvaultのLastPass側に追加されたペイロードは、ブラウザプラグインによって処理され、DOMに送られます。クライアント側でバリデーションされることはありません。しかも、ログインページだけでなく、全てのページで実行されます。

最悪なのは、LastPassプラグインは、実行中のサイトの平文の認証情報を含む、lpcurruserとlpcurrpassの2つの変数を宣言するため、認証情報が盗みやすくなってしまうことです。

vaultに保存されていないアカウントのサイトからLastPassがデータを盗もうとした場合はどのようなことが起きるのでしょうか。vaultは暗号化された大量のデータではなく、いくつかの暗号化された値が入ったメタデータなので、問題なく盗むことができます。下記の図のように、暗号化されたvaultに好きなペイロードで新しいアカウントを追加して、セッションのクッキーやデータなどを盗むことができます。

custom_js manipulation use case
custom_jsでデータを操作したときのケース

外部からの攻撃

Firefoxの動作は他のブラウザとは異なります(ここでは詳細に触れませんが、講演では触れていますので、ビデオが公開されたら見てみてください)。大きく異なるのは、FirefoxがSQLiteのデータベースをストレージに使用していない点です。代わりに、データを複数の異なるファイルとして保存します。LastPassの暗号化された認証情報が保存されているファイルはprefs.jsです。このファイルに認証情報を含め、Firefoxの設定や構成が保存されています。

prefs.js file

上記の図から分かるように、認証情報は次のとおり保存されています。

  • extensions.lastpass.loginusers: ユーザ名のリストを含む
  • extensions.lastpass.loginpws: 暗号化された認証情報を含む

Googleの失態

上を念頭に、Googleで”extensions.lastpass.loginpws”を検索したらどうなるか考えました。推測どおりでした。知らぬうちに、暗号化されたLastPass認証情報は世界中で共有されているのです。さらに、pastebinでも認証情報を見つけることができます。最悪なのは、復号の方法が分かっているため、欲しい情報は全てここで取得できるということです。

ここでの問題は、壊れたFirefox構成の修復方法や、スパイウェアやツールバーの削除方法をインターネット上で問い合わせる際に、自らのprefs.jsをフォーラムなどで公開してしまう人がいることです。また、同時に復号キーを引き出すヒントを含め、LastPassの認証情報をさらしてしまっているのです。私たちは、アンチウィルス・アンチスパイウェアのサイトやフォーラムでダンプファイルを目にしたことがあります。

Windowsでバイナリのプラグインを使用していた幸運なユーザの情報は復号できない場合があります。つまり、データ保護API(DPAPI)を使用して認証情報が暗号化されているため、コンピュータの外では復号できません。私たちの意図は、人の情報をさらすことではなく、可能性を指摘することです。そして、その可能性を良く説明しているのが、この例でしょう。影響のあるユーザに注意を促し、リンクを外せるようLastPassにはこの危険な状況を報告しました。

提案

ユーザへ

  • バイナリのプラグインを使用すること
  • マスターパスワードは保存しないこと
  • 新しいアカウント復旧はSMSで起動すること
  • 悪意のあるJSペイロードがないかvaultを監査すること
  • “password reminder”機能は使用しないこと
  • 2FAを有効にすること
  • 国別制限を設定すること
  • TORでのログインを許可しないこと

LastPassへ

  • custom_jsをなくすこと
  • vault全体をひとまとまりに暗号化すること
  • ECBを使用しないこと
  • クライアントとLastPass間はPBKDF2を使用すること
  • さらに、Certificate Pinningを使用すること
  • オープンソースを受け入れること
  • バグを見つけた人に報酬を遡及払いできる制度の導入

結論

パスワードマネージャは誰もが使うべき素晴らしいツールです。LastPassの弱点を明らかにしましたが、信頼できるツールであり、最後の文字だけを変えたパスワードを転用するよりは良い選択だと思います。ここで説明したような攻撃がされないようLastPassの設定を強化する方法はあります。詳細については、講演やスライドをご覧ください。最後に、私たちの報告に対してLastPassは迅速に対応してくれたこと、問題が数日で修正されたことを言っておきます。LastPassとのコミュニケーションは非常に取りやすく、作業もスムーズに行うことができました。

重要な注意事項

メディアやツイートで私たちが”LastPassをハッキングした”と書かれているのを目にしました。私たちはLastPassをハッキングしていません。このように言われるのは心外です。私たちが行ったことは、バグや悪しき慣行、デザイン問題を発見し、それらを使用してあらゆるシナリオでvaultキーの取得やパスワードの復号を試したにすぎません。バグのないパスワードマネージャソフトウェアは今までもこれからも存在することはありません。そのため、新しいソフトウェアで同じような調査を実施しても、同じような結果を得ることができるでしょう。

よかったらスライドを参照してください。講演のビデオは、Blackhatが近々公開してくれるでしょう。ご質問やご意見がある場合は、お気軽にコメントしください。パスワードマネージャは何を使用した方が良いかと聞かれても答えられませんが、パスワードマネージャを使用することはお勧めします。