Webアプリケーションの最も重大な10のセキュリティリスクとその対策。
OWASP Top 10は、Webアプリケーションセキュリティにおいて最も広く認知されている啓発文書です。2025年版では最新の脅威状況を反映し、ソフトウェアサプライチェーンの障害や例外条件の不適切な処理などの新カテゴリが導入されています。
アクセス制御は、ユーザーが意図された権限の範囲外で行動できないようにポリシーを強制します。不備があると、不正な情報開示、データの変更や破壊、ユーザーの権限を超えたビジネス機能の実行につながります。
攻撃者はアクセス制御の欠陥を悪用して、他のユーザーのアカウントへのアクセス、機密ファイルの閲覧、他のユーザーのデータの変更、またはアクセス権の変更が可能になります。
# 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())
@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())
セキュリティ設定の不備は、セキュリティ設定が不正確に定義、実装、または維持された場合に発生します。セキュリティ強化の欠如、不要な機能の有効化、デフォルトアカウントの未変更パスワード、詳細すぎるエラーメッセージ、HTTPセキュリティヘッダーの設定ミスなどが含まれます。
設定ミスのあるサーバー、フレームワーク、クラウドサービスは、機密データの漏洩、不正アクセスの許可、さらなる攻撃計画のための情報提供につながる可能性があります。
# 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!
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
2025年新設。サードパーティコンポーネント、依存関係、CI/CDパイプラインに関連するリスクに焦点を当てています。攻撃者はパッケージの侵害、オープンソースライブラリへの悪意のあるコード注入、ビルドパイプラインの脆弱性の悪用により、ソフトウェアサプライチェーンを標的にします。
単一の侵害された依存関係が数千のアプリケーションに影響を与える可能性があります。サプライチェーン攻撃は最小限の検知でデータ窃取、バックドアのインストール、またはシステム全体の侵害につながる可能性があります。
// package.json with unpinned dependencies { "dependencies": { "express": "*", // Any version! "lodash": "^4.0.0", // Wide range "unknown-pkg": "^1.0.0" // Unvetted package } }
// 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
暗号化に関連する失敗で、機密データの漏洩につながることが多いです。非推奨アルゴリズムの使用、脆弱な鍵生成、転送中または保存中のデータの暗号化欠如、不適切な証明書検証などが含まれます。
脆弱または欠如した暗号化は、パスワード、クレジットカード番号、医療記録、個人情報、ビジネスシークレットを漏洩させ、規制違反(GDPR、PCI DSS)につながる可能性があります。
import hashlib # Storing passwords with weak hashing def store_password(password: str) -> str: return hashlib.md5(password.encode()).hexdigest() # MD5 is broken!
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)
インジェクションの欠陥は、信頼できないデータがコマンドやクエリの一部としてインタープリターに送信されるときに発生します。SQLインジェクション、NoSQLインジェクション、OSコマンドインジェクション、クロスサイトスクリプティング(XSS)が最も一般的な形態です。悪意のあるデータにより、インタープリターが意図しないコマンドを実行する可能性があります。
インジェクションはデータの損失、破壊、不正アクセス、ホストの完全な乗っ取り、またはサービス拒否につながる可能性があります。SQLインジェクションだけでも、最も危険で蔓延している攻撃ベクトルの一つです。
# 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"
# 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>"
安全でない設計は、設計やアーキテクチャの欠陥に関連するリスクを指します。脅威モデリング、セキュアデザインパターン、リファレンスアーキテクチャの使用を求めます。安全でない設計は完璧な実装では修正できません。特定の攻撃に対する防御のためのセキュリティ制御が最初から作成されていないためです。
設計の欠陥は、自動化ツールで検出が困難なビジネスロジックの脆弱性につながる可能性があります。機密操作のレート制限の欠如、重要なアクションの多要素認証の欠如、不十分な不正検出はすべて設計レベルの障害です。
# 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)
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])
ユーザーIDの確認、認証、セッション管理は重要です。認証の失敗は、アプリケーションがクレデンシャルスタッフィング、ブルートフォース、脆弱なパスワードを許可したり、ログイン後にセッションIDをローテーションしないなどのセッション管理の欠陥がある場合に発生します。
攻撃者は自動化されたクレデンシャルスタッフィング、ブルートフォース、またはセッションハイジャックによりユーザーアカウントにアクセスできます。侵害されたアカウントは個人情報の窃取、詐欺、データ侵害につながる可能性があります。
# 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')
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')
ソフトウェアとデータの整合性障害は、整合性違反から保護されていないコードとインフラストラクチャに関連します。安全でないデシリアライゼーション、整合性検証なしの信頼できないCDNやプラグインの使用、署名付き更新なしの自動更新メカニズムなどが含まれます。
安全でないデシリアライゼーションはリモートコード実行につながる可能性があります。Subresource Integrity(SRI)なしの信頼できないCDNからのスクリプト読み込みは、攻撃者がアプリケーションに悪意のあるコードを注入することを可能にします。
<!-- 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!
<!-- 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
十分なログ記録、監視、アラートがなければ、侵害をタイムリーに検出することは不可能です。不十分なログ記録、インシデント対応システムとの非効果的な統合、リアルタイムアラートの欠如により、攻撃者はシステムへのさらなる攻撃、永続性の維持、データの改ざんまたは抽出が可能になります。
適切なログ記録がなければ、攻撃者は長期間にわたり検出されずに活動できます。多くの侵害調査では、侵害の検出にかかる平均時間が200日を超え、内部監視ではなく外部の当事者によって発見されることが多いことが示されています。
# 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')
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')
2025年新設。エラー、例外、エッジケースを不適切に処理するアプリケーションは、機密情報を漏洩させたり、矛盾した状態に入ったり、悪用可能な条件を生み出す可能性があります。本番環境での詳細なエラーメッセージ、セキュリティ制御を迂回するキャッチされない例外、レースコンディションなどが含まれます。
不適切なエラー処理はスタックトレース、データベースの詳細、内部パスを漏洩させる可能性があります。セキュリティチェックのレースコンディションにより、TOCTOU(Time-of-Check-to-Time-of-Use)攻撃で認可や決済検証を迂回される可能性があります。
# 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!
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 | 認証の失敗 | High | MFA、ブルートフォース保護、セッションローテーション |
| A08 | ソフトウェアとデータの整合性障害 | High | SRI、署名付き更新、安全なデシリアライゼーション |
| A09 | セキュリティログとアラートの障害 | Medium | 集中ログ管理、リアルタイムアラート、インシデント対応 |
| A10 | 例外条件の不適切な処理 | Medium | グローバルエラーハンドラー、汎用メッセージ、TOCTOU防止 |