OWASP Web Application Top 10とは?

OWASP Top 10は、Webアプリケーションセキュリティにおいて最も広く認知されている啓発文書です。2025年版では最新の脅威状況を反映し、ソフトウェアサプライチェーンの障害や例外条件の不適切な処理などの新カテゴリが導入されています。

1️⃣ A01 - アクセス制御の不備

Critical

概要

アクセス制御は、ユーザーが意図された権限の範囲外で行動できないようにポリシーを強制します。不備があると、不正な情報開示、データの変更や破壊、ユーザーの権限を超えたビジネス機能の実行につながります。

リスク

攻撃者はアクセス制御の欠陥を悪用して、他のユーザーのアカウントへのアクセス、機密ファイルの閲覧、他のユーザーのデータの変更、またはアクセス権の変更が可能になります。

脆弱なコード例

Python ❌ Bad
# User ID taken directly from request without authorization check
@app.route('/api/user/<user_id>/profile')
def get_profile(user_id):
    user = db.get_user(user_id)  # No ownership check!
    return jsonify(user.to_dict())

安全なコード例

Python ✅ Good
@app.route('/api/user/<user_id>/profile')
@login_required
def get_profile(user_id):
    # Verify the requesting user owns this resource
    if current_user.id != user_id and not current_user.is_admin:
        abort(403)

    user = db.get_user(user_id)
    if not user:
        abort(404)
    return jsonify(user.to_dict())

対策チェックリスト

2️⃣ A02 - セキュリティ設定の不備

High

概要

セキュリティ設定の不備は、セキュリティ設定が不正確に定義、実装、または維持された場合に発生します。セキュリティ強化の欠如、不要な機能の有効化、デフォルトアカウントの未変更パスワード、詳細すぎるエラーメッセージ、HTTPセキュリティヘッダーの設定ミスなどが含まれます。

リスク

設定ミスのあるサーバー、フレームワーク、クラウドサービスは、機密データの漏洩、不正アクセスの許可、さらなる攻撃計画のための情報提供につながる可能性があります。

脆弱なコード例

Python ❌ Bad
# Debug mode enabled in production, verbose errors exposed
app = Flask(__name__)
app.config['DEBUG'] = True  # Exposes stack traces!
app.config['SECRET_KEY'] = 'default-secret'  # Default key!

安全なコード例

Python ✅ Good
import os

app = Flask(__name__)
app.config['DEBUG'] = False
app.config['SECRET_KEY'] = os.environ['SECRET_KEY']

@app.after_request
def set_security_headers(response):
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    return response

対策チェックリスト

3️⃣ A03 - ソフトウェアサプライチェーンの障害

High

概要

2025年新設。サードパーティコンポーネント、依存関係、CI/CDパイプラインに関連するリスクに焦点を当てています。攻撃者はパッケージの侵害、オープンソースライブラリへの悪意のあるコード注入、ビルドパイプラインの脆弱性の悪用により、ソフトウェアサプライチェーンを標的にします。

リスク

単一の侵害された依存関係が数千のアプリケーションに影響を与える可能性があります。サプライチェーン攻撃は最小限の検知でデータ窃取、バックドアのインストール、またはシステム全体の侵害につながる可能性があります。

脆弱なコード例

JavaScript ❌ Bad
// package.json with unpinned dependencies
{
  "dependencies": {
    "express": "*",           // Any version!
    "lodash": "^4.0.0",       // Wide range
    "unknown-pkg": "^1.0.0"   // Unvetted package
  }
}

安全なコード例

JavaScript ✅ Good
// package.json with pinned versions + lockfile + audit
{
  "dependencies": {
    "express": "4.21.2",
    "lodash": "4.17.21"
  },
  "scripts": {
    "preinstall": "npx npm-audit-resolver",
    "integrity-check": "npm audit signatures"
  }
}
// Also: use package-lock.json, enable Dependabot/Renovate,
// generate SBOM, verify package provenance

対策チェックリスト

4️⃣ A04 - 暗号化の失敗

High

概要

暗号化に関連する失敗で、機密データの漏洩につながることが多いです。非推奨アルゴリズムの使用、脆弱な鍵生成、転送中または保存中のデータの暗号化欠如、不適切な証明書検証などが含まれます。

リスク

脆弱または欠如した暗号化は、パスワード、クレジットカード番号、医療記録、個人情報、ビジネスシークレットを漏洩させ、規制違反(GDPR、PCI DSS)につながる可能性があります。

脆弱なコード例

Python ❌ Bad
import hashlib

# Storing passwords with weak hashing
def store_password(password: str) -> str:
    return hashlib.md5(password.encode()).hexdigest()  # MD5 is broken!

安全なコード例

Python ✅ Good
import bcrypt

def store_password(password: str) -> bytes:
    # Use bcrypt with automatic salting
    salt = bcrypt.gensalt(rounds=12)
    return bcrypt.hashpw(password.encode(), salt)

def verify_password(password: str, hashed: bytes) -> bool:
    return bcrypt.checkpw(password.encode(), hashed)

対策チェックリスト

5️⃣ A05 - インジェクション

Critical

概要

インジェクションの欠陥は、信頼できないデータがコマンドやクエリの一部としてインタープリターに送信されるときに発生します。SQLインジェクション、NoSQLインジェクション、OSコマンドインジェクション、クロスサイトスクリプティング(XSS)が最も一般的な形態です。悪意のあるデータにより、インタープリターが意図しないコマンドを実行する可能性があります。

リスク

インジェクションはデータの損失、破壊、不正アクセス、ホストの完全な乗っ取り、またはサービス拒否につながる可能性があります。SQLインジェクションだけでも、最も危険で蔓延している攻撃ベクトルの一つです。

脆弱なコード例

Python ❌ Bad
# SQL injection via string concatenation
def get_user(username: str):
    query = f"SELECT * FROM users WHERE name = '{username}'"
    return db.execute(query)  # username = "' OR '1'='1"

安全なコード例

Python ✅ Good
# Parameterized queries prevent SQL injection
def get_user(username: str):
    query = "SELECT * FROM users WHERE name = %s"
    return db.execute(query, (username,))

# For XSS prevention, escape output
from markupsafe import escape

def render_comment(comment: str) -> str:
    return f"<p>{escape(comment)}</p>"

対策チェックリスト

6️⃣ A06 - 安全でない設計

High

概要

安全でない設計は、設計やアーキテクチャの欠陥に関連するリスクを指します。脅威モデリング、セキュアデザインパターン、リファレンスアーキテクチャの使用を求めます。安全でない設計は完璧な実装では修正できません。特定の攻撃に対する防御のためのセキュリティ制御が最初から作成されていないためです。

リスク

設計の欠陥は、自動化ツールで検出が困難なビジネスロジックの脆弱性につながる可能性があります。機密操作のレート制限の欠如、重要なアクションの多要素認証の欠如、不十分な不正検出はすべて設計レベルの障害です。

脆弱なコード例

Python ❌ Bad
# Password reset with no rate limit or verification
@app.route('/reset-password', methods=['POST'])
def reset_password():
    email = request.form['email']
    new_pass = request.form['new_password']
    user = db.find_by_email(email)
    user.password = hash_password(new_pass)  # No token verification!
    db.save(user)

安全なコード例

Python ✅ Good
from datetime import datetime, timedelta

@app.route('/reset-password', methods=['POST'])
@rate_limit("3/hour")
def reset_password():
    token = request.form['token']
    new_pass = request.form['new_password']

    # Verify time-limited, single-use token
    reset_req = db.find_reset_token(token)
    if not reset_req or reset_req.used or reset_req.expires < datetime.utcnow():
        abort(400, "Invalid or expired token")

    # Enforce password complexity
    if not meets_password_policy(new_pass):
        abort(400, "Password does not meet requirements")

    reset_req.user.password = hash_password(new_pass)
    reset_req.used = True
    db.save_all([reset_req.user, reset_req])

対策チェックリスト

7️⃣ A07 - 認証の失敗

High

概要

ユーザーIDの確認、認証、セッション管理は重要です。認証の失敗は、アプリケーションがクレデンシャルスタッフィング、ブルートフォース、脆弱なパスワードを許可したり、ログイン後にセッションIDをローテーションしないなどのセッション管理の欠陥がある場合に発生します。

リスク

攻撃者は自動化されたクレデンシャルスタッフィング、ブルートフォース、またはセッションハイジャックによりユーザーアカウントにアクセスできます。侵害されたアカウントは個人情報の窃取、詐欺、データ侵害につながる可能性があります。

脆弱なコード例

Python ❌ Bad
# No brute force protection, weak session handling
@app.route('/login', methods=['POST'])
def login():
    user = db.find_by_email(request.form['email'])
    if user and user.password == request.form['password']:  # Plain comparison!
        session['user'] = user.id  # Session ID not rotated
        return redirect('/dashboard')

安全なコード例

Python ✅ Good
from flask_limiter import Limiter

limiter = Limiter(app, default_limits=["100/hour"])

@app.route('/login', methods=['POST'])
@limiter.limit("5/minute")
def login():
    user = db.find_by_email(request.form['email'])
    if not user or not bcrypt.checkpw(
        request.form['password'].encode(), user.password_hash
    ):
        # Generic error to prevent user enumeration
        return "Invalid credentials", 401

    # Rotate session ID after authentication
    session.regenerate()
    session['user'] = user.id

    # Check for MFA requirement
    if user.mfa_enabled:
        return redirect('/mfa-verify')
    return redirect('/dashboard')

対策チェックリスト

8️⃣ A08 - ソフトウェアとデータの整合性障害

High

概要

ソフトウェアとデータの整合性障害は、整合性違反から保護されていないコードとインフラストラクチャに関連します。安全でないデシリアライゼーション、整合性検証なしの信頼できないCDNやプラグインの使用、署名付き更新なしの自動更新メカニズムなどが含まれます。

リスク

安全でないデシリアライゼーションはリモートコード実行につながる可能性があります。Subresource Integrity(SRI)なしの信頼できないCDNからのスクリプト読み込みは、攻撃者がアプリケーションに悪意のあるコードを注入することを可能にします。

脆弱なコード例

HTML ❌ Bad
<!-- Loading external scripts without integrity checks -->
<script src="https://cdn.example.com/lib.js"></script>

<!-- Insecure deserialization in Python -->
import pickle
data = pickle.loads(user_input)  # Arbitrary code execution!

安全なコード例

HTML ✅ Good
<!-- Use Subresource Integrity (SRI) for external scripts -->
<script src="https://cdn.example.com/lib.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K..."
  crossorigin="anonymous"></script>

# Use safe deserialization in Python
import json
data = json.loads(user_input)  # Safe: only parses JSON data

対策チェックリスト

9️⃣ A09 - セキュリティログとアラートの障害

Medium

概要

十分なログ記録、監視、アラートがなければ、侵害をタイムリーに検出することは不可能です。不十分なログ記録、インシデント対応システムとの非効果的な統合、リアルタイムアラートの欠如により、攻撃者はシステムへのさらなる攻撃、永続性の維持、データの改ざんまたは抽出が可能になります。

リスク

適切なログ記録がなければ、攻撃者は長期間にわたり検出されずに活動できます。多くの侵害調査では、侵害の検出にかかる平均時間が200日を超え、内部監視ではなく外部の当事者によって発見されることが多いことが示されています。

脆弱なコード例

Python ❌ Bad
# No logging of security-relevant events
@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)
    if not user:
        return "Login failed", 401  # No record of failure
    return redirect('/dashboard')

安全なコード例

Python ✅ Good
import logging
from datetime import datetime

security_log = logging.getLogger('security')

@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)
    if not user:
        security_log.warning(
            "LOGIN_FAILED | ip=%s | email=%s | time=%s",
            request.remote_addr,
            request.form.get('email', 'unknown'),
            datetime.utcnow().isoformat(),
        )
        check_brute_force(request.remote_addr)
        return "Invalid credentials", 401

    security_log.info(
        "LOGIN_SUCCESS | user_id=%s | ip=%s",
        user.id, request.remote_addr,
    )
    return redirect('/dashboard')

対策チェックリスト

🔟 A10 - 例外条件の不適切な処理

Medium

概要

2025年新設。エラー、例外、エッジケースを不適切に処理するアプリケーションは、機密情報を漏洩させたり、矛盾した状態に入ったり、悪用可能な条件を生み出す可能性があります。本番環境での詳細なエラーメッセージ、セキュリティ制御を迂回するキャッチされない例外、レースコンディションなどが含まれます。

リスク

不適切なエラー処理はスタックトレース、データベースの詳細、内部パスを漏洩させる可能性があります。セキュリティチェックのレースコンディションにより、TOCTOU(Time-of-Check-to-Time-of-Use)攻撃で認可や決済検証を迂回される可能性があります。

脆弱なコード例

Python ❌ Bad
# Exposing internal details in error messages
@app.route('/api/data')
def get_data():
    try:
        result = db.query(request.args['q'])
        return jsonify(result)
    except Exception as e:
        return jsonify({"error": str(e)}), 500  # Leaks DB details!

安全なコード例

Python ✅ Good
import uuid, logging

logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_exception(e):
    error_id = str(uuid.uuid4())
    logger.error("Unhandled exception [%s]: %s", error_id, e, exc_info=True)

    # Return generic error with reference ID for support
    return jsonify({
        "error": "An internal error occurred",
        "reference": error_id,
    }), 500

@app.route('/api/data')
def get_data():
    q = request.args.get('q')
    if not q or not is_valid_query(q):
        return jsonify({"error": "Invalid query parameter"}), 400

    result = db.query(q)
    return jsonify(result)

対策チェックリスト

📊 サマリーテーブル

ID 脆弱性 重要度 主な対策
A01アクセス制御の不備Criticalサーバー側のアクセスチェック、デフォルト拒否、レコード所有権検証
A02セキュリティ設定の不備Highハードニングプロセス、セキュリティヘッダー、デフォルトの無効化
A03ソフトウェアサプライチェーンの障害Highバージョン固定、SBOM、依存関係スキャン
A04暗号化の失敗High強力なアルゴリズム、TLS 1.2+、鍵管理
A05インジェクションCriticalパラメータ化クエリ、出力エンコーディング、入力検証
A06安全でない設計High脅威モデリング、セキュアパターン、悪用ケーステスト
A07認証の失敗HighMFA、ブルートフォース保護、セッションローテーション
A08ソフトウェアとデータの整合性障害HighSRI、署名付き更新、安全なデシリアライゼーション
A09セキュリティログとアラートの障害Medium集中ログ管理、リアルタイムアラート、インシデント対応
A10例外条件の不適切な処理Mediumグローバルエラーハンドラー、汎用メッセージ、TOCTOU防止