申請番号を1つ変えたら
会社の経費精算システムで、自分の申請を開きました。アドレスの末尾は 3041 です。ふと、3042 に変えてみました。
別の同僚の申請が表示されました。名前も、金額も、領収書も見えます。何も壊していません。ただ、そのシステムは「誰の申請か」を確かめていなかったのです。
たった1つのルール
注意
攻撃してよいのは、自分専用のラボのコピーだけです。このラボは、わざと弱く作った店で、インターネットにはつながっていません。許可のない他人のシステムを攻撃すると、不正アクセス禁止法に触れる犯罪になります。
このレッスンのゴール
メモ
あるリクエストを許してよかったかどうかを、判断できる。
今日のキーワード
| ことば | いみ | れい |
|---|---|---|
| 認証 | 「あなたは誰か」を確かめることです。 | パスワード |
| 認可 | 「それをしてよいか」を確かめることです。 | |
| ロール | 権限をまとめた呼び名です。 | admin |
| ID | 1件のデータを指す番号や名前です。 | 申請番号 3041 |
| トークン | サインイン済みの印として、ブラウザが送る署名つきの値です。 | |
| ステータスコード | 返事の先頭にある、結果を表す番号です。 | 401 |
学生が入力するセルは、このプレビューでは空白です。
サインインと許可は別もの
サイトは2つのことを尋ねます。1つ目は 認証 で、「あなたは誰か」です。2つ目は 認可 で、「それをしてよいか」です。
弱いサイトは、1つ目だけを尋ねて、2つ目を忘れます。社員だと分かったので、頼まれたものは何でも見せてしまいます。他の人のものまで見せてしまいます。
ステータスコードは結果の番号
すべての返事は、ステータスコードで始まります。200 は「どうぞ」です。401 は「あなたが誰か分かりません」です。403 は「誰かは分かりますが、だめです」です。
次の3つの Run は、サインインせずに、ラボの店へ3つのものを頼みます。どれを渡してくれるかを見ましょう。
誰にでも見せるもの
curl -s -o /dev/null -w "%{http_code}\n" http://juice-shop:3000/api/Products/1ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サインイン後のもの
curl -s -o /dev/null -w "%{http_code}\n" http://juice-shop:3000/api/Usersここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
断るときの返事
curl -s http://juice-shop:3000/rest/basket/1 | grep -o "<title>[^<]*</title>"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
例: 3つの答えを読む
| リクエスト | 番号 | 分かること |
|---|---|---|
| /api/Products/1 | 200 | 商品は公開なので、誰でも読める |
| /api/Users | 401 | 人の一覧は、誰が頼むかを知ってから出す |
| /rest/basket/1 | 401 | かごは個人のもの。まずトークンを求める |
学生が入力するセルは、このプレビューでは空白です。
アドレスの ID は書き換えられる
申請、注文、請求書には番号があります。その番号は、よくアドレスの中にあります。誰でも書き換えられます。
だからサイトは、リクエストのたびに確かめます。このアカウントがその持ち主か、読めるロールを持っているか。推測しにくい番号は、確認の代わりになりません。
例: 短いリクエストの記録
| ユーザー | ロール | リクエスト | 許す? |
|---|---|---|---|
| tanaka | 一般 | GET /claims/17 (自分の) | 許す。持ち主だから |
| tanaka | 一般 | GET /claims/18 (suzuki の) | 許さない。自分のではない |
| yamada | 管理者 | GET /claims/18 | 許す。管理者は全部読める |
| suzuki | 一般 | GET /admin/users | 許さない。管理者だけ |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
「ボタンやリンクを隠せば守れる」と考える人がいます。それは違います。ブラウザは、リンクがなくても、どんなアドレスでも頼めます。確認は、返事を作るサーバー側のコードに置きます。リンクも隠してよいですが、それだけに頼ってはいけません。
サーバーは毎回確かめる
安全な処理は、3つのことを順に行います。誰が頼んだかを調べます。データを読み込みます。持ち主かロールかを確かめて、どちらも合わなければ断ります。
断るときは 403 を返します。確認は、1か所のサーバーのコードに置きます。そうすれば、確認を忘れるページが出ません。
持ち主か管理者かの確認
def may_read(user, role, owner):
if role == "admin":
return True
return user == owner
# 名前を変えて実行します。次に、"support" のロールにも読ませてみましょう。
print(may_read("tanaka", "member", "tanaka"))
print(may_read("tanaka", "member", "suzuki"))
print(may_read("yamada", "admin", "suzuki"))