なぜセキュリティを学ぶか
セキュリティインシデントの多くは「知っていれば防げた」脆弱性です。OWASP(Open Worldwide Application Security Project)は毎年 Web アプリケーションの代表的な脆弱性 Top 10 を公開しています。SE としてこれらの攻撃原理と対策を理解することは必須スキルです。
1. SQLインジェクション
攻撃原理: ユーザー入力をそのまま SQL に埋め込むことで、意図しない SQL が実行される
# ❌ 脆弱なコード
def get_user(username):
query = f"SELECT * FROM users WHERE username = '{username}'"
return db.execute(query)
# 攻撃例: username = "' OR '1'='1"
# 実行される SQL: SELECT * FROM users WHERE username = '' OR '1'='1'
# → 全ユーザーが返される
# 最悪のケース: username = "'; DROP TABLE users; --"
# → テーブルが削除される
# ✅ 対策: プリペアドステートメント(パラメータバインド)
def get_user(username):
query = "SELECT * FROM users WHERE username = ?"
return db.execute(query, (username,)) # DB側でエスケープ処理
# ORMを使う(さらに安全)
user = User.objects.filter(username=username).first()
対策まとめ:
- プリペアドステートメント(パラメータ化クエリ)を必ず使う
- ORM を使う(内部でプリペアドを使用)
- ユーザー入力を SQL に直接連結しない
2. XSS(クロスサイトスクリプティング)
攻撃原理: ユーザー入力をそのまま HTML に出力し、悪意のある JavaScript が実行される
<!-- ❌ 脆弱なコード -->
<p>こんにちは、{{ username }}さん</p>
<!-- 攻撃例: username = "<script>document.cookie を外部サーバに送信</script>" -->
<!-- → 他のユーザーのセッションクッキーが盗まれる(セッションハイジャック) -->
# ✅ 対策1: 出力時のHTMLエスケープ
import html
safe_username = html.escape(username)
# < → < > → > & → & " → "
# ✅ 対策2: 現代のフレームワークはデフォルトでエスケープ
# React: JSXはデフォルトでエスケープ(dangerouslySetInnerHTMLは危険)
# Vue: {{ }}はエスケープ済み(v-htmlは危険)
# Django: テンプレートはデフォルトエスケープ(|safeは危険)
# ✅ 対策3: Content Security Policy (CSP) ヘッダ
Content-Security-Policy: default-src 'self'; script-src 'self'
# → 外部スクリプトの実行をブロック
XSSの種類:
- 反射型: URL パラメータを即座に HTML に出力
- 蓄積型: DB に保存されたデータを HTML に出力(最も危険)
- DOM型: JavaScript が直接 DOM を書き換える
3. CSRF(クロスサイトリクエストフォージェリ)
攻撃原理: ログイン中のユーザーが悪意のあるサイトを訪問すると、そのユーザーになりすましてリクエストが送信される
攻撃シナリオ:
1. ユーザーが銀行サイトにログイン(Cookie にセッション保存)
2. 攻撃者サイトに以下の罠を仕掛ける
<img src="https://bank.example.com/transfer?to=attacker&amount=100000">
3. ユーザーが攻撃者サイトを訪問すると画像リクエストが送られ…
4. ブラウザが自動でセッション Cookie を付けて送信 → 送金実行
# ✅ 対策: CSRFトークン
# サーバーが発行したランダムトークンをフォームに埋め込む
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
...
</form>
# サーバーはリクエスト受信時にトークンを検証
# 攻撃者のサイトはこのトークンを知ることができないため偽造不可
# ✅ 対策2: SameSite Cookie
Set-Cookie: session=xxx; SameSite=Strict; HttpOnly; Secure
# SameSite=Strict: クロスサイトリクエストではCookieを送らない
# HttpOnly: JavaScriptからCookieにアクセスできない
# Secure: HTTPS のみで送信
4. SSRF(サーバーサイドリクエストフォージェリ)
攻撃原理: サーバーが任意のURLにリクエストを送る機能を悪用し、内部ネットワークにアクセスされる
# ❌ 脆弱なコード: ユーザー指定URLをそのままフェッチ
import requests
def fetch_url(url):
return requests.get(url).text
# 攻撃例: url = "http://169.254.169.254/latest/meta-data/"
# → AWSのインスタンスメタデータにアクセス → 認証情報漏洩
# url = "http://internal-db.company.local:5432"
# → 社内DBへの不正アクセス
# ✅ 対策: 許可リスト(ホワイトリスト)による検証
from urllib.parse import urlparse
ALLOWED_DOMAINS = {"api.example.com", "cdn.example.com"}
def safe_fetch(url: str):
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
raise ValueError("Invalid scheme")
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError("Domain not allowed")
return requests.get(url, timeout=5).text
5. セキュリティヘッダ
適切な HTTP レスポンスヘッダはブラウザ側の防御を強化します。
# XSS対策
Content-Security-Policy: default-src 'self'
# クリックジャッキング対策(iframe埋め込みを禁止)
X-Frame-Options: DENY
# MIMEタイプスニッフィング防止
X-Content-Type-Options: nosniff
# HTTPS強制(HSTS)
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Referrerポリシー
Referrer-Policy: strict-origin-when-cross-origin
6. 認証・セッション管理の注意点
# ❌ パスワードを平文や可逆暗号で保存
user.password = password # 絶対NG
user.password = base64.b64encode(password) # 復号できるのでNG
# ✅ ハッシュ+ソルト(bcrypt推奨)
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# 検証
bcrypt.checkpw(input_password.encode(), hashed)
# セッションIDの扱い
# - ログイン成功後にセッションIDを再生成する(セッション固定攻撃対策)
# - セッションIDは十分なエントロピーを持つランダム値
# - セッションタイムアウトを設定する
脆弱性チェックリスト
入力値処理
□ SQLはプリペアドステートメントを使っているか
□ HTML出力時にエスケープしているか
□ ファイルアップロードはMIMEタイプと拡張子を検証しているか
認証・認可
□ パスワードはbcrypt/Argon2でハッシュしているか
□ ログイン試行回数制限(レートリミット)はあるか
□ 認可チェックは全エンドポイントに実装されているか
Cookie・セッション
□ HttpOnly・Secure・SameSite は設定されているか
□ CSRFトークンは実装されているか
通信
□ 全通信がHTTPSか
□ TLS 1.2以上を使用しているか
□ HSTSヘッダを返しているか
まとめ
セキュリティは「後から追加するもの」ではなく、設計段階から組み込むものです。
- 入力は常に不信任(Trust No Input)
- 出力時は常にエスケープ
- 最小権限の原則(必要な権限のみ付与)
- セキュリティヘッダを設定する
- 依存パッケージを定期的に更新する