メインコンテンツへ移動
SEMentor
セキュリティ 約12分

Webセキュリティ基礎

SQLインジェクション・XSS・CSRF・SSRF など実務で遭遇する主要な脆弱性を理解し対策を学ぶ

なぜセキュリティを学ぶか

セキュリティインシデントの多くは「知っていれば防げた」脆弱性です。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)
# < → &lt;  > → &gt;  & → &amp;  " → &quot;

# ✅ 対策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)
  • 出力時は常にエスケープ
  • 最小権限の原則(必要な権限のみ付与)
  • セキュリティヘッダを設定する
  • 依存パッケージを定期的に更新する

このレッスンは未完了です。

次のレッスンへ