0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事で書くこと

  • JAuth(Web Exploitation)のwriteup
  • JWTって何なのか

JWTってよく聞く割りに理解が浅かったので、自分用の整理として残します。

本記事で扱う内容はセキュリティ業務や娯楽目的のハッキング(CTF)において役立つ知識としてまとめるもので、許可を得ずに第三者のサイトに利用(悪用)することは不正アクセス行為の禁止等に関する法律に抵触する可能性があるため、絶対にお控えください。

JAuth - 問題概要

picoCTFのWeb Exploitationカテゴリ、難易度Mediumの問題です。

image.png

インスタンスを起動すると、シンプルなログイン画面にアクセスできます。

image.png

テストユーザーの情報が与えられるのでログインしてみます。
username:test Password:Test123!

image.png

/privateページに遷移し、ログインできました。しかしflagを入手するにはadmin権限でのログインが必要なようです。

Burp Suiteでログイン時の動作を見てみましたが特に有益な情報は得られず、そういえば見てなかったなと調査ツールでCookieを見ると、なんか入っていました。

image.png

JWTとは

Cookieの中に入ってたこの文字列は JWT(Json Web Token) と呼ばれ、ユーザーの認証情報などを含むデータを安全にやり取りするためのトークン形式です。平たく言うと、署名付きのJsonデータをBase64でエンコードしたものです。

JWTはサーバーが発行し、Cookie等を利用してクライアント側に保存されます。Base64エンコードがされているだけで誰でも読み取ることができるため、パスワードや機密情報は持たせてはいけません。

クライアント側で認証情報を持たせると改ざんが容易なのでは?と思いますが、JWTではHMACによる署名を使い改ざんを防いでいます。(後ほど解説)

JWTを読み解く

Cookieの中の文字列をよく見るとeyJ*******.eyJ*******.*******という形式となっており、.で区切られた3セクションで構成されています。それぞれヘッダー、ペイロード、署名です。
CyberChefでBase64デコードしてみます。

image.png

Jsonが復元できました。
ただし署名部分はハッシュ形式なので謎の文字列になっていますね。

// デコードしたJson文字列
{
    // ヘッダー
    "typ":"JWT",
    "alg":"HS256"
}
{
    // ペイロード
    "auth":1752318647015,
    "agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
    "role":"user",
    "iat":1752318647
}
// 署名
—y©©â8XÁRµöԓ’°ÄmÑ=f2ÞªVR7„v‘

ヘッダー

ヘッダーにalgとあるのは署名に用いられるアルゴリズムです。(HS256 = HMAC-SHA256)

HMACでは、サーバーだけが知っている秘密鍵を使って、トークンの中身(ヘッダーとペイロード)から署名を作ります。そのためクライアント側で中身を書き換えても正しい署名は作れないので、サーバー側で無効と判断されます。

// サーバーがクライアントにJWTを渡す
{ "typ":"JWT", "alg":"HS256" }.{ "user":"john" }.<署名A>

// クライアント側でペイロードを改変してサーバーに送信
{ "typ":"JWT", "alg":"HS256" }{ "user":"john_the_ripper" }.<署名A>

// => サーバーが自身の秘密鍵を使って再計算したHMACが署名Aと異なるため、改ざんを検知できる

これがクライアント側で認証情報を持つことができる理由です。
「ヘッダーとペイロードはBase64エンコードなだけなので誰でも読める、しかし署名があるので改ざんはできない」のがJWT。

ペイロード

ペイロードはクレームと呼ばれるKey:Valueセットで構成されます。開発者が任意に指定できるものと既に規定されている予約クレームが存在するようです。(例えばiatはJWTの発行日時と決まっており、UNIX時間の値が入ります)

ちなみに予約クレームに異なる意味の値をセットするとどうなるのか調べてみると以下のような問題が。

  1. JWTライブラリとの互換性喪失
    例えばexp(JWTの有効期限)に異なる意味の値を入れることで、トークンが期限切れと誤認識されてしまったり、誤ったユーザーを認証してしまう可能性がある
  2. 可読性・保守性の低下
    他の開発者が見た時に混乱を招き、予期せぬバグの温床となる

予約クレームは守りましょう。
今回の場合だとroleというクレームが気になります。このroleadminにして送信できればadmin権限を持った状態で/privateページにアクセスでき、flagが手に入りそうです。

解法

roleadminに変更したものをBase64でエンコードし、Cookieにセットしてアクセスしてみますが、前述の通り署名があるため上手くいきません。

しかし調べてみると、alg=none攻撃なるものがあることが分かりました。

alg=none攻撃とは

勘の良い方はすぐピンと来るかもしれませんが、ヘッダーのalgの値のnoneに変更することで、署名による検証がされなくなるという仕様を悪用した手法です。繰り返しますが、JWTの仕様です。

jwt.ioという便利なサイトがあるので、改変したヘッダー+ペイロードからJWTを作ってもらいます。

念のためもう一度testアカウントでログインし、最新のタイムスタンプを持ったJWTを取得します。
CyberChefでデコードしてJsonを復元したら、jwt.ioでヘッダーのalgnoneに、ペイロードのroleadminに書き換え、新たなJWTを生成。

JWTに署名セクションがなく.で終わっていることが確認できます。

image.png

あとは調査ツールからCookieの値に新JWTをセットし、/privateへアクセスするとflagが表示されました。

image.png

なぜこの攻撃が成功するのか

そもそもalgnoneにするだけで署名検証しなくなるってザルすぎない?と思ったので調べてみたら、4年前に語られていた話題でした。(以下の記事が大変参考になります)

JWTを作成する際に利用した署名アルゴリズムをサーバー側で記憶しておき、検証の際にはヘッダーの署名アルゴリズムが作成時のアルゴリズムと同一であることを検証すればこのような問題は起きません。

ではなぜ発生するのかと言われれば、RFCにalg=noneの仕様が規定されている以上、結局は実装次第ということらしいです。

RFCというのは「何ができるか」を定める技術的仕様書に過ぎないため、危険な挙動でも「仕様上あり得る」ことは記述されます。
ただしRFCを見ると、alg=noneを使うことはUnsecuredであることが明記されていました。

実際のユースケースにおいて

現在主流のJWTライブラリにおいてはnone指定はデフォルトで無効化されていることがほとんどらしいです。当然かもしれませんが。

ただし一部の独自の実装によっては発生する可能性は0ではないということで、alg=noneを見かけたら危険という意識は持っておこうと思います。

まとめ

picoCTFの問題を軸にJWTについて解説してきましたが、色々と疑問に思ったことを調べていったらかなり勉強になりました。

便利なものは用法を誤ることで脆弱になる、という良い例だったのではないでしょうか。

以上です。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?