認証・認可の脆弱性:ログインとIDOR
役割をパラメータで送ってはいけない
この回でやること
役割をリクエストやCookieやJWTのペイロードに入れて、その名乗りを信じてしまう形を扱います。署名を検証していないJWTの危うさと、役割はサーバー側の記録から引く直し方をあわせて見ます。
- 読む 約 7 分
- 最後にクイズ 1 問
このレッスンで学ぶこと
役割をパラメータで送る作りは、役割をリクエストやCookie、JWTのペイロードに入れて、届いた値をそのまま判定に使う欠陥です。この回は、その名乗りを信じてしまう形を具体的に見ます。
読み終えると、判定に使っている役割がどこから来た値なのかを、自分のサービスで指せるようになります。
クライアントから届く値は、書き換えられる
練習用のショップで、ログインの応答が役割を返し、以降のリクエストがそれを送り返す作りだとします。
プレーンテキスト
POST /login → {"user":"tanaka","role":"user"}
GET /admin/users Cookie: role=user
ここで role=admin に書き換えて送る
→ 200 管理者用の一覧が返るroleはブラウザの中にあり、送る前に自由に書き換えられます。リクエストの本文でもクエリでもCookieでも同じで、クライアントの手元を通る値は、届いた時点で信用できません。サーバーがそのroleを読んで判定していたら、userをadminに変えるだけで昇格します。
署名を検証しないJWTは、ただの書き換え可能な文
「JWTに入っているなら安全では」という誤解がよくあります。JWTのペイロードは暗号化ではなく、誰でも読めて誰でも書き換えられる形式です。改ざんを防ぐのは末尾に付く署名で、サーバーがその署名を検証してはじめて、中身が発行時のままだと確かめられます。
プレーンテキスト
ヘッダ . ペイロード . 署名
role=user ← ここを admin に書き換える
← 署名を検証しなければ書き換えは通る署名を検証しないままペイロードのroleを読む実装は、Cookieにroleを置くのと危うさが変わりません。さらに、一部のトークンは「署名なし」を表す形式を選べるため、署名の欄を空にしたトークンを受け入れてしまう実装もあります。届いたトークンは、正しい鍵で署名が検証できたときだけ信じてください。
守りになるのは、サーバーが署名を検証していることであって、JWTという形式を使っていること自体ではありません。検証しない、あるいは署名なしを受け入れる実装では、ペイロードの役割は名乗りにすぎません。
直し方は、役割をサーバー側から引く
判定に使う役割は、リクエストが運んでくる値ではなく、サーバー側の記録から引きます。セッションに結び付けて保存した役割、あるいは利用者IDからデータベースで引いた役割を使います。トークンに役割を載せるなら、必ず署名を検証したうえで、その値だけを信じます。