security-logging-alerting-failures について
security-logging-alerting-failuresは、OWASP A09:2025に基づく監査スキルで、FastAPIとFlaskアプリのログおよびアラート運用を点検します。チェックリスト、構造化ログの指針、アラート閾値、自動スキャン用コマンドを提供します。
主な機能
- 01ログインジェクションと機密情報漏えいの確認
- 02認証・認可イベントログの点検
- 03構造化ログとSIEM互換性の評価
- 04アラートルールとインシデント対応の確認
- 05CI/CD向け自動スキャンコマンド
活用シーン
- 01FastAPIまたはFlaskのセキュリティログ監査
- 02ログ内のユーザー入力や秘密情報の検出
- 03認証失敗や管理操作の記録確認
- 04本番環境のアラート対象範囲の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill security-logging-alerting-failuresインストール後、Claude Code で/security-logging-alerting-failuresを入力して読み込まれたか確認します。
同梱ファイルreferences/alerting-platforms.mdreferences/remediation-patterns.md
詳細
A09:2025 — セキュリティログ記録とアラートの不備
OWASP ランク: #9(3 回連続でコミュニティ投票により選出)
2025 年に名称を変更:「アラート」を追加しました。アラートのないログ記録では、セキュリティ上の価値はほとんどありません。
統計: 5 CWEs、723 CVEs、平均発生率 3.91%
対象となる CWE: CWE-778、CWE-117、CWE-532、CWE-223、CWE-390
このカテゴリの対象
| 不備の種類 | 説明 |
|---|---|
| ログ記録の不足 | 監査対象となるイベント(ログイン、アクセス制御の失敗、検証エラー)が記録されない |
| ログインジェクション | ユーザー入力がエスケープされずにログメッセージに渡され、ログの偽造が可能になる |
| ログ内の機密データ | パスワード、トークン、PII、カード番号がログ出力に書き込まれる |
| セキュリティ関連情報の欠落 | エラーや警告が、コンテキスト(IP、ユーザー、タイムスタンプ、アクション)なしで記録される |
| アラートなし | ログは存在するものの、インシデント対応に通知するしきい値、ルール、プラットフォームがない |
監査チェックリスト — FastAPI と Flask
各セクションを順に確認してください。各項目について、PASS、FAIL、または NOT APPLICABLE を記録してください。
1. ログインジェクション(CWE-117)
ユーザーが制御する入力を、生のままログ文に渡してはいけません。
確認する点:
# VULNERABLE — newline injection, ANSI escape injection
logger.info(f"User logged in: {request.form['username']}")
logger.warning("Failed login for: " + username)
# SAFE — sanitize before logging
import re
safe_username = re.sub(r'[\r\n\x1b]', '_', username)
logger.info("Failed login for: %s", safe_username)
チェックリスト:
- ログ呼び出しで、生のリクエストデータを f 文字列や
+連結で扱っていない - ユーザーが提供するすべてのログ値から改行(
\r、\n)を除去している - ANSI エスケープコード(
\x1b[)を除去している(ターミナルインジェクションを防止) - 構造化ログ(JSON 形式)を使用している — フィールドが分離されるため、設計上インジェクションを防止できる
2. ログ内の機密データ(CWE-532)
DEBUG レベルであっても、シークレットをログ出力に含めてはいけません。
フラグを立てるパターン:
# VULNERABLE
logger.debug(f"Auth token: {token}")
logger.info(f"Processing payment for card: {card_number}")
app.logger.debug(request.json) # May contain password field
print(f"Password reset for {email}, token={reset_token}")
# SAFE
logger.info("Auth token validated for user_id=%s", user_id)
logger.info("Payment processed for user_id=%s, last4=%s", user_id, card_number[-4:])
スキャン対象:
-
password、passwd、secret、token、api_key、authorizationヘッダー値をログに記録していない - クレジットカード番号、SSN、メールアドレスとパスワードの組み合わせ全体をログに記録していない
-
request.json/request.formを、フィールドを絞り込まずに丸ごとログに記録していない - 本番コードに、機密変数を含む
print()文がない - Bandit ルール
B506(および類似ルール)が CI/CD でパスしている
3. ログ記録の不足(CWE-778)
以下のイベントは、完全なコンテキストを含むログエントリを必ず生成してください。
必須のログイベント:
| イベント | 最低限必要なフィールド |
|---|---|
| 認証の失敗 | タイムスタンプ、IP、試行されたユーザー名、ユーザーエージェント |
| 認証の成功 | タイムスタンプ、IP、user_id、方式(パスワード/OAuth/MFA) |
| 認可の失敗(403) | タイムスタンプ、IP、user_id、試行されたリソース、HTTP メソッド |
| 入力検証の失敗 | タイムスタンプ、IP、user_id、フィールド名(機密情報の場合は値を含めない)、エラー |
| 価値の高いトランザクション | タイムスタンプ、user_id、アクション、リソース ID、変更前後の状態 |
| アカウントの変更(パスワードリセット、ロール変更) | タイムスタンプ、actor_user_id、target_user_id、変更の種類 |
| 管理者の操作 | タイムスタンプ、admin_user_id、アクション、影響を受けたリソース |
FastAPI の実装パターン:
import logging
import structlog # recommended for structured logs
log = structlog.get_logger()
# In auth endpoint
@router.post("/login")
async def login(credentials: LoginSchema, request: Request):
user = await authenticate(credentials)
if not user:
log.warning(
"auth.failed",
ip=request.client.host,
username=credentials.username, # OK — username is not a secret
user_agent=request.headers.get("user-agent"),
)
raise HTTPException(status_code=401, detail="Invalid credentials")
log.info("auth.success", user_id=user.id, ip=request.client.host)
Flask の実装パターン:
from flask import request
import logging
@app.route("/login", methods=["POST"])
def login():
user = authenticate(request.form["username"], request.form["password"])
if not user:
app.logger.warning(
"auth.failed ip=%s username=%s ua=%s",
request.remote_addr,
request.form.get("username"),
request.user_agent.string,
)
return {"error": "Invalid credentials"}, 401
チェックリスト:
- すべての 401/403 レスポンスに、対応する
logger.warning()またはlogger.error()の呼び出しがある - ログエントリにタイムスタンプ(
loggingによる自動付与)、送信元 IP、ユーザー識別子が含まれている - パスワードリセットとアカウント変更の処理をログに記録している
- 管理パネルでの操作を、実行者の識別情報とともにログに記録している
- 価値の高い業務トランザクションの監査ログエントリを生成している
4. 構造化ログと SIEM との互換性
ログ形式は、可観測性プラットフォーム(ELK、Splunk、Datadog、Sentry)で解析できる必要があります。
FastAPI — structlog + JSON:
# main.py
import structlog
import logging
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.processors.JSONRenderer(), # machine-parseable
],
wrapper_class=structlog.BoundLogger,
logger_factory=structlog.PrintLoggerFactory(),
)
Flask — python-json-logger:
from pythonjsonlogger import jsonlogger
handler = logging.StreamHandler()
handler.setFormatter(jsonlogger.JsonFormatter(
fmt="%(asctime)s %(levelname)s %(name)s %(message)s"
))
app.logger.addHandler(handler)
チェックリスト:
- 本番環境のログ出力がプレーンテキストではなく JSON 形式になっている
- 本番環境でログレベルが
DEBUGになっていない(詳細情報が漏えいするため) - ログを一元管理プラットフォーム(Sentry、ELK、Splunk、Datadog、CloudWatch)に送信している
- ログの保持ポリシーがあり、コンプライアンス要件(例:最低 90 日)を満たしている
5. アラート連携
アラート機能のないログ記録は、障害を見逃すことにつながります。有効な監視が行われていることを確認してください。
Sentry(例外アラート):
# FastAPI
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
sentry_sdk.init(dsn=settings.SENTRY_DSN, integrations=[FastApiIntegration()])
# Flask
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(dsn=settings.SENTRY_DSN, integrations=[FlaskIntegration()])
推奨アラートしきい値:
| シグナル | アラートの条件 |
|---|---|
| ログイン失敗 | 5 分間に同じユーザー名で 10 回超の失敗 |
| ログイン失敗 | 5 分間に同じ IP から 100 回超の失敗 |
| 403 レスポンス | 1 分間に同じ user_id から 50 件超 |
| 500 エラー | 任意の 1 分間のリクエストの 5% 超 |
| 新しい管理者アカウント | 発生するたびに即時アラート |
| 権限昇格 | 発生するたびに即時アラート |
チェックリスト:
- Sentry または同等のサービスが初期化され、本番環境でイベントを受信している
- レート制限違反がアラートイベントを発生させる(HTTP 429 レスポンスを返すだけではない)
- 認証失敗が繰り返された場合にアラートが発生する(ブルートフォース攻撃の検出)
- 各アラートタイプに対応するアラート対応手順書/プレイブックがある
- 重要度の高い領域にハニートークンが配置されている(誤検知をほぼゼロに抑えた侵入検知)
自動スキャン
これらのツールを CI/CD の一部として実行してください:
# Static analysis — catches log injection patterns, sensitive data in logs
bandit -r . -t B106,B107,B110 --severity-level medium
# Search for raw request data in log calls
grep -rn "logger\.\(info\|warning\|error\|debug\)" . \
| grep -E "request\.(form|json|args|data|headers)\[" \
| grep -v "\.get("
# Search for potential sensitive field logging
grep -rn "logger\|logging\|print" . \
| grep -iE "password|passwd|secret|token|api_key|card|ssn|cvv"
主要な CWE リファレンス
| CWE | 名前 | Python におけるリスク |
|---|---|---|
| CWE-778 | ログ記録の不足 | 認証/認可の失敗がログに記録されない |
| CWE-117 | ログインジェクション | ロガー呼び出しでユーザー入力を f-string に使用する |
| CWE-532 | ログ内の機密情報 | デバッグ出力にパスワード/トークンが含まれる |
| CWE-223 | セキュリティ関連情報の欠落 | ログに IP/ユーザーのコンテキストがない |
| CWE-390 | エラー状態を検出しても対処しない | 例外を捕捉して握りつぶし、ログにも記録しない |
リファレンスファイル
references/remediation-patterns.md— 各 CWE の修正コードの完全な修正前/修正後の例references/alerting-platforms.md— Sentry、ELK、Splunk、Datadog の連携ガイド
チェックリストの範囲を超えた詳細な実装支援が必要な場合は、これらを参照してください。