はまやんはまやんはまやん

hamayanhamayan's blog

WebセキュリティにおけるJWT関連問題への傾向と対策

本まとめはセキュリティコンテスト(CTF)で使えるまとめを目指すのが主です。
悪用しないこと。勝手に普通のサーバで試行すると犯罪っぽいです。

JWTとは

JSONをベースとしたトークン。以下のようなことができる。

  • ある程度の情報を含めることができる
  • 署名することができ、改ざん防止ができる
  • シリアライズされたurl-safeな文字列がトークンとなるので、これをやり取りするだけでOK

これでやるとセッションを作らなくていいとか、Cookie以外の環境でも自然に使えるだとか、利点はいろいろある。
あるのだが、注意点がある。

  • トークンに含める情報は簡単に見ることができる
  • (署名による検証を回避するための)攻撃方法が色々ある

攻撃方法について

アルゴリズムをnoneにすることで検証を回避する

JWTの署名アルゴリズムをnoneにすることで、署名アルゴリズムトークンから取得するような実装をしているサービスで検証を回避できる。pyjwtを使って、生成するのが簡単。

import jwt
payload = { "param": "evil" }
res = jwt.encode(payload, '', algorithm='none')
print(res)

鍵を何とか調べる(HMAC キー)

まあ、ただのパスワードクラック。JWTは生データと署名後データがセットになっているので、ローカルでクラック作業ができてしまう。
HMACアルゴリズムで、簡単なパスワードを使っていると、クラックされる。

  • flask-unsignツールを使うと簡単にブルートフォースで解析可能
    • インストール pip install flask-unsign flask-unsign[wordlist]
    • 中身見る flask-unsign -d -c "[cookie]"
    • パスワード解析 flask-unsign -c "[cookie]" --unsign
    • 作り直す flask-unsign --sign --secret password1 --cookie "{'flagship': True, 'username': 'toto'}"
  • 辞書攻撃が効く問題も見たことがある

RS256での署名時にHS256に変換して署名をする

RS256で署名されていて、公開鍵が分かっているとする。この状況では、署名アルゴリズムをHS256に変更して署名をかいくぐることができる。

jwks spoofing

jwks
JSON Web Key、署名の検証方法を外部ファイル化しておき、そのファイルを読み込んで、それに基づいて署名を検証する
spoofing
なりすまし。jwksのアクセス先urlは任意のものを指定できるので、本来あったURLではなく、攻撃者が用意したURLにすり替えられる

{
  "alg": "RS256",
  "jku": "http://localhost:5000/static/jwks.json",
  "kid": "sqcE1a9gj9p08zNMR1MWbLLvuaPyUeJEsClBhy7Q4Jc"
}

ヘッダーのjkuにjwkの参照情報が含まれる。jwtをもらった側は、検証時にjkuのURLへアクセスして署名検証方法を取得する。
以下、偽装方法メモ。

JWKSを自作するときはmkjwk - JSON Web Key Generatorが便利

RSAの時は、

  • Key Size: 2048
  • Key Use: Signature
  • Algorithm: RS256 (RSASSA-PKCS1-v1_5 using SHA256)
  • Key ID: kidとして入力されているやつ

とすると、公開鍵と秘密鍵が「Public and Private Keypair」として出てきて、JWKSが「Public Key」として出てくる。

Public and Private KeypairはJWK形式なので、一般的なPEM形式にするときは、
jwk to pem convertor, pem to jwk convert online, jwk openssl format pem
ここを使うといい。

ツールを使ったやり方→ctfs/Write-ups.md at master · saw-your-packet/ctfs

kid path traversal

JWTにkidという開発者が自由に書けるプロパティがあるが、kidを元にトークンの検証を行う場合に実装依存脆弱性が入り込むことがある。
ざっくり言うと、kidを元に検証用の秘密鍵を取ってきている場合、悪意のあるkidを指定すると任意の秘密鍵での検証をサーバ側に強制させられる。
kidの値をどのように使っているかで以下のような攻撃を仕掛けられる。

  • directory (path) traversal
    • /dev/nullとすれば、鍵として何も帰ってこないので、署名なしを強制できる。header.payloadで送ればいい javascript { "typ": "JWT", "alg": "HS256", "kid": "/dev/null" }
    • こんな感じにheaderにして、header.payload.signatureを作る。
    • /proc/sys/kernel/randomize_va_spaceを使っている解法もある
      • 試してないけど、0\n,1\n,2\nのどれかが定数で帰ってくる(普通は2\n?)
  • SQL injection

CTF問題

Practicalな話

実装時に気を付けること

(CTFじゃ使えないけど)テストツール

ticarpi/jwt_tool: A toolkit for testing, tweaking and cracking JSON Web Tokens
これはCTFでも使えるか。
rockyou.txtとかで辞書攻撃したら鍵が出てくるかも。