問い合わせ画面で動くプログラム
ある会社の問い合わせフォームに、お客さんの文面が届きます。サポート担当は、管理画面でその一覧を開いて読みます。
ある日、文章ではなく、小さなプログラムが書き込まれました。担当者が一覧を開くと、ブラウザが静かにそれを動かしました。会社は、その文面を一行も書いていません。
たった1つのルール
注意
攻撃してよいのは、自分専用のラボのコピーだけです。このラボは、わざと弱く作ったサイトで、インターネットにはつながっていません。許可のない他人のシステムを攻撃すると、不正アクセス禁止法に触れる犯罪になります。
このレッスンのゴール
メモ
打ち込まれた文字を、ページの中で実行させないようにできる。
今日のキーワード
| ことば | いみ | れい |
|---|---|---|
| クロスサイトスクリプティング | 打ち込まれたスクリプトを、ページが動かしてしまう弱点です。 | XSS |
| スクリプト | ブラウザがページの中で動かす、小さなプログラムです。 | |
| エスケープ | 特別な文字を、安全な文字に置き換えることです。 | < を < にする |
| 反射型 | リクエストで送った文字が、そのまま返事に出ることです。 | |
| 格納型 | サイトが保存し、あとから開く全員に出ることです。 | |
| Cookie | ブラウザが持ち、サイトへ送り返す小さな値です。 |
学生が入力するセルは、このプレビューでは空白です。
打った文字がページの一部になる
ブラウザは、ページをタグの入った文字として読みます。<b> は太字にします。<script> はプログラムを動かします。
サイトが利用者の文字をそのままページに入れると、ブラウザは、サイトのタグか利用者の文字かを見分けられません。打ち込まれたスクリプトが、サイト自身のものとして動きます。
例: 反射型と格納型
| 反射型 | 格納型 | |
|---|---|---|
| 文字の行き先 | リクエストから、そのまま返事へ | サイトの記録に保存され、あとのページへ |
| 被害を受ける人 | 細工したリンクを開いた人 | そのページをあとで開く全員 |
| 例 | 「〇〇を検索しました」と出す検索ページ | 掲示板や商品のレビュー |
| 格納型が重い理由 | リンクを送らなくても、多くの人に届く |
学生が入力するセルは、このプレビューでは空白です。
ラボのサイトを見てみる
ラボでは、これから試すサイトが動いています。起動して、次の3つを実行しましょう。見るのはサインインページだけです。サインインも、スクリプトの入力もしません。
Cookie が大切です。ページの中で動くスクリプトは、その Cookie を読めます。ただし、Cookie に HttpOnly の印があれば読めません。
サイトは動いているか
curl -s -o /dev/null -w "%{http_code}\n" http://dvwa/login.phpここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
サイトが出す Cookie
curl -s -I http://dvwa/login.php | grep -i "^set-cookie"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
HttpOnly はいくつあるか
curl -s -I http://dvwa/login.php | grep -ic httponlyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
3つの答えを読む
1つ目は 200 を表示します。2つ目は2行で、security という Cookie と、セッションを表す PHPSESSID です。3つ目は 0 を表示します。
HttpOnly の付いた Cookie はありません。このサイトで動いたスクリプトは、セッションの Cookie を読めます。XSS からセッションの乗っ取りになることが多いのは、そのためです。
エスケープは文字を文字にする
直し方は、利用者の文字がページに入るときに エスケープ することです。< を < に、> を > に置き換えます。ブラウザは同じ文字として表示しますが、タグとしては読みません。
& は、最初に置き換えます。最後にすると、< の中の & まで & になって、表示が崩れます。
エスケープする関数
def escape(text):
text = text.replace("&", "&")
text = text.replace("<", "<")
text = text.replace(">", ">")
return text
# note を変えて実行します。次に、& の行を最後に移して、もう一度実行します。
note = "おいしいパン <b>と</b> ケーキ & お茶"
print("<p>" + escape(note) + "</p>")
よくある間違い
注意
「保存するときに script という語を止めれば十分だ」と考える人がいます。それは違います。img のようなタグもスクリプトを運びますし、書き方を変えればすり抜けます。ページに書き出すその場所で、すべての値をエスケープします。
出力でエスケープし、層を重ねる
エスケープは、保存するときではなく、ページに書き出すときに行います。同じ文字が、あとでページにもメールにもファイルにも使われるからです。それぞれに合った方法があります。
後ろにも守りを重ねます。セッションの Cookie に HttpOnly を付けます。動かせるスクリプトを絞る Content-Security-Policy ヘッダーを送ります。1か所を見落としても、被害が小さくなります。