【翻訳】JSON Web Tokenライブラリの危機的な脆弱性

以下の内容は、JSON Web Tokenを扱うライブラリの脆弱性に関する報告内容を和訳したものです。

https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/


JSON Web Tokenライブラリの危機的な脆弱性

最近、いくつかのJSON Web Token実装のセキュリティについてレビューしていた間に、私は検証処理をバイパスする攻撃が可能な危機的な脆弱性を持つ多くのライブラリを発見しました。多くの実装や言語を横断して、同じ2つの欠陥が見つかりました。そして、問題が起こる場所を書き上げることが助けになると考えました。私は、標準に対する変更が将来の脆弱性を防ぐ助けになることができると信じています。

不慣れな人々のために、JSON Web Token(JWT)はいくつかのクレーム(訳注: ある主体に関するひとまとまりの情報)を検証するためのトークンを作成するための標準です。例えば、サーバは”管理者としてログインした”という属性を持つトークンを生成し、クライアントに提供することができます。その後、クライアントは彼らが管理者としてログインしたことを証明するためにトークンを使うことが可能です。トークンはサーバの鍵によって署名され、そしてサーバはトークンが本物かどうか検証するためにそれを使うことができます。

JWTは一般的に3つのパートを持っています:ヘッダ、ペイロード、そして署名。ヘッダは、どのアルゴリズムで署名が作られたかを示しますが、それは以下のような感じです。

header = '{"alg":"HS256","typ":"JWT"}'

HS256は、HMAC-SHA256を使って署名されたトークンであることを示しています。

ペイロードは、私達が欲しいクレームを含みます。

payload = '{"loggedInAs":"admin","iat":1422779638}'

JWT仕様で提案された通り、私達はiatと呼ばれるタイムスタンプを含みます。iatは、”issued at”の省略です。

署名は、ヘッダ、ペイロード、そしてそれらをセパレータとしてピリオドで連結したものをBAE64URLエンコードで計算されます。

key = 'secretkey'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)

全てを一緒にするために、私達は署名をBASE64URLエンコードし、そしてピリオドを使って3つのパートを一緒につなげます。

token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
# token is now:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

すばらしい。では、それの何が悪いの?

えーっと、トークンの検証をしてみましょう。

最初に、私達は署名の作成に何のアルゴリズムが使われたのかを決定する必要があります。問題ないです、ヘッダ内のalgフィールドが私達にそれを伝えてくれます。

しかし待ってください、私達はこのトークンをまだ検証していません。つまり、ヘッダも検証されていません。これは私達を困った状況にします: トークンを検証するために、私達は署名を検証するために使う手法をどれにするか、攻撃者にその選択を許してしまうのです。

これは、いくつかの実装でひどい結果となります。

noneアルゴリズムは、JWTへの奇妙な追加です。それは、トークンが既に検証されている状況で使われることが想定されました。興味深いことに、それは実装が必須なたった2つのアルゴリズムのうちの1つです(もう一つはHS256)。

不幸なことに、いくつかのライブラリは、noneアルゴリズムを使って署名されたトークンを、署名が検証された有効なトークンとして扱いました。その結果は?誰もが、あるシステムで任意のアカウントにアクセスすることを可能にする、彼らが好きなペイロードを持つ彼ら自身で”署名された”トークンを発行することが可能になります。

そのようなトークンを共に配置することは簡単です。上記の例のヘッダに、HS256の代わりに、”alg”:”none”を含むように修正してください。そして、ペイロードに必要な変更を行います。更に、空の署名を使います(例: signature = “”)。

ほとんど(きっと全部?)の実装は、現在この攻撃を防ぐために基本的なチェックをします: もしsecret keyが提供された場合、その時トークン検証はnoneアルゴリズムを使ったトークンに対しては失敗するでしょう。これは良いアイディアですが、根本的な問題の解決ではありません: 攻撃者はアルゴリズムの選択を制御します。掘り続けていきましょう。

RSA or HMAC?

JWT仕様はまた、(RSAやECDSAに基いて)数多くの非対称な署名アルゴリズムを定義しています。それらのアルゴリズムを使って、トークンは作成され、秘密鍵を使って署名され、対応する公開鍵を使って検証されます。これはとても巧みです: もしあなたが公開鍵を公開し、秘密鍵をあなた自身で所持している場合、あなただけがトークンに署名することができ、与えられたトークンが正しく署名されたかどうかを誰もがチェック可能です。

私が検証したほとんどのJWTライブラリは、以下のようなAPIを持っていました:

# sometimes called "decode"
verify(string token, string verificationKey)
# return payload if valid token, else throws an error

HMAC署名を使ったシステムでは、verificationKeyは(HMACは署名と検証のために同じ鍵を使うので)サーバの秘密署名鍵になるでしょう。

verify(clientToken, serverHMACSecretKey)

非対称アルゴリズムを使ったシステムでは、verificationKeyはトークンが検証されるべきそれに対する公開鍵になるでしょう。

verify(clientToken, serverRSAPublicKey)

不幸なことに、攻撃者はこれを不正利用することができます。もしサーバがRSAで署名されたトークンを期待していたけれども、実際にはHMACで署名されたトークンを受け取っている場合は、公開鍵が実際にはMHAC秘密鍵だと考えてしまうでしょう。

これはどのような惨事になるでしょう?HMAC秘密鍵はプライベートに保たれていなければなりませんし、一方公開鍵は公開可能です。これは、あなたが典型的に考えるスキーマスクを被った攻撃者が公開鍵へアクセスし、そしてサーバが受け付けるであろうトークンを捏造するためにこれが利用可能であることを意味しています。

それを実行することはとても単純です。最初に、あなたのお気に入りなJWTライブラリを入手し、そしてあなたのトークンのためのペイロードを選択します。その後、検証鍵としてサーバ上で使われる公開鍵を手に入れます(ほとんどそれはテキストベースのPEMフォーマットでしょう)。最後に、HMAC鍵としてPEMでフォーマットされた公開鍵を使ってあなたのトークンを署名します。大体こんな感じです:

forgedToken = sign(tokenPayload, 'HS256', serverRSAPublicKey)

引っ掛けの部分は、serverRSAPublicKeyがサーバで使われる検証鍵を示していることで確認します。攻撃が成功するために、その文字列は(まさに同じ書式で、追加あるいは欠損した改行がない)完全に一致しなければなりません。

最後の結果は?公開鍵の知識を持つ誰もが、検証を通過するであろうトークンを捏造することが可能です。

ライブラリ開発者への勧め

私は、JWTライブラリがそれらの検証関数にalgorithmパラメータを追加することを提案します。

verify(string token, string algorithm, string verificationKey)

サーバはトークンを署名するために使われるアルゴリズムが何かを事前に知っているべきであり、この値を攻撃者に提供できてしまうことは安全ではありません。

あるサーバが互換性を理由にして1つ以上のアルゴリズムをサポートする必要があると誰かが主張するかもしれません。この場合、別々の鍵をサポートされる各アルゴリズムのために使うことができます(そしてそうすべきです)。JWTは利便性のために、まさにこの目的のために”key ID”フィールド(kid)を提供します。サーバは鍵とそれに関するアルゴリズムをルックアップするためにkey IDを使うことができるようになるので、攻撃者が検証のためにどの鍵が使われるかを制御することはもはやできなくなります。他の場合において、私は、期待されたアルゴリズムと一致するかどうかチェックするかもしれない場合を除いて、JWTライブラリがヘッダ内のalgフィールドを見るべきだともさえ思いません

JWT実装を使う誰もが、異なる署名タイプが使われたトークンが拒否されることが保証されることを確認すべきです。いくつかのライブラリは、アルゴリズムのホワイトリストやブラックリストのためのオプショナルなメカニズムを持っています; それを使うべきであり、使わなかった場合はリスクにさらされるでしょう。更に良いこと: あなたがミッションクリティカルな機能を提供するために使用するいかなるオープンソースライブラリに対するセキュリティ監査の実行ポリシーを持ってください。

JWT/JWS標準の改善

私は、ヘッダのalgフィールドをDeprecated扱いにすることを提案したいです。ここで紹介した通り、その間違った利用は、JWT/JWS実装のセキュリティ上の破壊的なインパクトを持っていると言えます。私が知る限り、key IDは適切な代替を提供します。これは、仕様を変更する正当な理由となります: JWTライブラリがalgへの依存に起因するセキュリティ上の欠陥が書かれ続けます。

JWT(そしてJOSE)は、安全な暗号化の実装のクロスプラットフォーム一式を持つ機会を提示します。これらの変更を使って、それを現実のものにすることを少し近づけてくれることを願います。

このエントリーをはてなブックマークに追加

関連記事

2023年のRemap

Remapにファームウェアビルド機能を追加しました

Google I/O 2023でのウェブ関連のトピック

2022年を振り返って

現在のRemapと今後のRemapについて