secure-code-review について
OWASP Top 10(2021)に基づく、Web アプリ向けのコードセキュリティレビューチェックリストです。セキュリティ担当者向けに、カテゴリ別の確認項目、脆弱なコード例、修正方法を示します。
主な機能
- 01OWASP Top 10(2021)を網羅
- 02カテゴリごとの確認項目
- 03脆弱なコード例の提示
- 04修正方法とコード例
- 05リスクレベル別の分類
活用シーン
- 01Web アプリの SQL インジェクション確認
- 02API エンドポイントの SSRF レビュー
- 03OWASP Top 10 全項目のコードレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zebbern/ claude-code-guide --skill secure-code-reviewインストール後、Claude Code で/secure-code-reviewを入力して読み込まれたか確認します。
同梱ファイルLICENSE
詳細
OWASP Top 10 コードセキュリティレビューのチェックリスト
OWASP Top 10(2021)標準に基づく、体系的なセキュリティレビューです。各項目には、脆弱性の説明、典型的な脆弱コード、確認ポイント、修正例が含まれています。Web アプリケーションのセキュリティに重点を置いたコードレビュー向けに設計されています。
使い方
レビュー対象のコードファイルまたはコードスニペットを提示し、確認する OWASP カテゴリー(または全カテゴリーのレビュー)を指定すると、項目ごとの監査レポートが得られます。
プロンプトの例:
- 「このコードに SQL インジェクションのリスクがないか確認してください」
- 「このプロジェクトの OWASP Top 10 セキュリティレビューをすべて実施してください」
- 「この API エンドポイントに SSRF の脆弱性はありますか?」
クイックリファレンス
| ID | カテゴリー | 主な確認事項 |
|---|---|---|
| A01 | アクセス制御の不備 | すべてのエンドポイントで現在のユーザーの本人確認をしていますか?ID を変更すると、他のユーザーのデータにアクセスできませんか? |
| A02 | 暗号化の失敗 | パスワードは bcrypt/argon2 でハッシュ化されていますか?シークレットがハードコードされていませんか? |
| A03 | インジェクション | 文字列連結による SQL はありませんか?shell=True はありませんか?テンプレート出力がエスケープされていませんか? |
| A04 | 安全でない設計 | レート制限は実施されていますか?重要なワークフローを回避できませんか? |
| A05 | セキュリティ設定ミス | DEBUG が有効になっていませんか?エラーページにスタックトレースが表示されていませんか?デフォルトの認証情報が使われていませんか? |
| A06 | 脆弱なコンポーネント | pip audit / npm audit で CVE が見つかりませんか? |
| A07 | 認証の失敗 | JWT の署名は検証されていますか?トークンを失効させられますか?MFA を利用できますか? |
| A08 | 完全性の不備 | 信頼できないデータをデシリアライズする pickle.loads はありませんか? |
| A09 | ログ記録とモニタリングの失敗 | ログに平文のパスワードが含まれていませんか?ログイン失敗は記録されていますか? |
| A10 | SSRF | ユーザーが指定した URL は、内部 IP アドレスへのアクセスを防ぐようにフィルタリングされていますか? |
レビュープロセス SOP
基本原則:真陽性の見落としより、偽陽性を優先してください。
- スコープを定義する — レビューするファイル、モジュール、またはコードスニペットを特定します
- 網羅性を確認する — A01〜A10 を順に確認します。すべての項目をレポートに含めてください(指摘事項がない項目は合格と記載します)。通常は見つかった問題のみを報告しますが、このプロセスでは見落としを防ぐため、すべてを網羅する必要があります
- リスクを分類する — 各指摘事項にラベルを付けます:
- RED 高:直接悪用可能(RCE、SQL インジェクション、内部ネットワークに到達する SSRF、平文でのパスワード保存)
- YELLOW 中:特定の条件下で悪用可能(レート制限の欠如、弱いパスワードポリシー、静的トークン)
- GREEN 低:直接的な悪用経路はないが、多層防御に不足がある(セキュリティヘッダーの欠如、不十分なログ記録)
- すべての指摘事項に、すぐに使える修正コードを含めてください(説明だけでなく、実際のコードを記載します)。具体的な
file:line_numberを参照します - レビュー報告書を出力する — 以下のテンプレートを使用し、指摘事項を重大度の高い順に並べ、最後に優先順位を付けた修正リストを記載します
A01:2021 — アクセス制御の不備
リスク: 他のユーザーのデータにアクセスしたり、許可されていない操作を実行したりする可能性があります。
確認ポイント:
- すべての API エンドポイントで認可が実施されていますか?
- IDOR(安全でない直接オブジェクト参照)の脆弱性はありませんか。ID パラメーターを変更すると、他のユーザーのデータにアクセスできませんか?
- 管理者インターフェースでロールを確認していますか?
- アクセス制御はサーバー側で実施されていますか(UI 要素を隠すだけではありませんか)?
- CORS ポリシーが過度に寛容ではありませんか?
脆弱なコードの例
# ❌ Vulnerable: No authorization check — any user can view others' orders by changing user_id
@app.route("/api/orders/<user_id>")
def get_orders(user_id):
orders = db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")
return jsonify(orders)
修正例
# ✅ Fixed: Verify the authenticated user can only access their own data
@app.route("/api/orders")
@login_required
def get_orders():
current_user_id = get_current_user().id
orders = db.query("SELECT * FROM orders WHERE user_id = %s", (current_user_id,))
return jsonify(orders)
A02:2021 — 暗号化の失敗
リスク: 機密データ(パスワード、クレジットカード番号、個人情報)が暗号化されていないか、脆弱な暗号アルゴリズムが使用されています。
確認ポイント:
- パスワードは MD5/SHA1 ではなく、安全なハッシュ化方式(bcrypt/scrypt/argon2)を使用して保存されていますか?
- 転送中の機密データには HTTPS が強制されていますか?
- 暗号化キーがソースコードにハードコードされていませんか?
- 非推奨の暗号アルゴリズム(DES、RC4、MD5)が使用されていませんか?
- 機密性の高いデータベースフィールドは保存時に暗号化されていますか?
脆弱なコードの例
# ❌ Vulnerable: MD5 for password storage, hardcoded secret key
import hashlib
SECRET_KEY = "my-secret-key-123"
def save_password(password):
hashed = hashlib.md5(password.encode()).hexdigest()
db.save(hashed)
修正例
# ✅ Fixed: bcrypt for password hashing, secret key from environment variable
import bcrypt
import os
SECRET_KEY = os.environ["SECRET_KEY"]
def save_password(password):
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode(), salt)
db.save(hashed)
A03:2021 — インジェクション
リスク: ユーザー入力が SQL、OS コマンド、LDAP クエリなどに直接連結され、攻撃者が任意のクエリやコマンドを実行できるようになります。
チェックポイント:
- SQL クエリでは、文字列の連結ではなく、パラメーター化クエリや ORM を使用していますか?
- ユーザー入力を連結する
os.system()またはsubprocess.call(shell=True)の呼び出しはありますか? - テンプレートのレンダリング時にユーザー入力を適切にエスケープし、XSS を防いでいますか?
- LDAP/XPath/NoSQL クエリで特殊文字をフィルタリングしていますか?
- フィルタリングされていないユーザー入力をそのままログに記録していませんか(ログインジェクション)?
SQL インジェクション — 脆弱なコード
# ❌ Vulnerable: String-concatenated SQL — attacker can input ' OR 1=1 --
@app.route("/api/user")
def get_user():
username = request.args.get("username")
query = f"SELECT * FROM users WHERE username = '{username}'"
result = db.execute(query)
return jsonify(result)
SQL インジェクション — 修正方法
# ✅ Fixed: Parameterized query
@app.route("/api/user")
def get_user():
username = request.args.get("username")
result = db.execute(
"SELECT * FROM users WHERE username = %s",
(username,)
)
return jsonify(result)
コマンドインジェクション — 脆弱なコード
# ❌ Vulnerable: User input concatenated directly into shell command
import os
def ping_host(host):
os.system(f"ping -c 4 {host}")
コマンドインジェクション — 修正方法
# ✅ Fixed: Use subprocess with list arguments, shell disabled
import subprocess
import re
def ping_host(host):
if not re.match(r'^[a-zA-Z0-9.\-]+$', host):
raise ValueError("Invalid hostname")
subprocess.run(["ping", "-c", "4", host], check=True)
A04:2021 — 安全でない設計
リスク: 完璧に実装しても修正できない、ビジネスロジックの設計上の欠陥です。
チェックポイント:
- 重要な操作にレート制限を設けていますか?
- パスワードリセットのフローが悪用される可能性はありませんか(ユーザー名の列挙、確認コードの総当たりなど)?
- 支払い、送金などの機密性の高い操作に二次確認を要求していますか?
- 上限のない一括操作エンドポイントはありませんか?
- ビジネスワークフローを順序どおりに実行しなくてもよい状態になっていませんか(例:支払いをスキップして注文を完了できる)?
脆弱なコードの例
# ❌ Vulnerable: No attempt limit on verification code — can be brute-forced
@app.route("/api/verify-code", methods=["POST"])
def verify_code():
code = request.json["code"]
stored_code = session.get("verification_code")
if code == stored_code:
return jsonify({"status": "verified"})
return jsonify({"status": "invalid"}), 400
修正例
# ✅ Fixed: Added attempt limit and expiration
@app.route("/api/verify-code", methods=["POST"])
def verify_code():
attempts = session.get("verify_attempts", 0)
if attempts >= 5:
return jsonify({"error": "Too many attempts, please request a new code"}), 429
code = request.json["code"]
stored = session.get("verification_code")
expire_at = session.get("code_expire_at", 0)
if time.time() > expire_at:
return jsonify({"error": "Verification code has expired"}), 400
session["verify_attempts"] = attempts + 1
if code == stored:
session.pop("verify_attempts", None)
return jsonify({"status": "verified"})
return jsonify({"status": "invalid"}), 400
A05:2021 — セキュリティの設定ミス
リスク: アプリケーションやサーバーがデフォルト設定を使用していたり、不要な機能を有効にしていたり、エラーメッセージで機密情報を公開していたりします。
チェックポイント:
- 本番環境で DEBUG モードを無効にしていますか?
- エラーページでスタックトレース、データベースのバージョンなどが漏えいしていませんか?
- デフォルトの認証情報を使い続けていませんか?
- HTTP レスポンスにセキュリティヘッダー(X-Frame-Options、Content-Security-Policy など)が含まれていますか?
- 不要な HTTP メソッド(PUT、DELETE、TRACE)を無効にしていますか?
- ディレクトリ一覧表示を無効にしていますか?
脆弱なコードの例
# ❌ Vulnerable: DEBUG enabled in production, leaking sensitive information
app = Flask(__name__)
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "default-secret"
@app.errorhandler(500)
def error_handler(e):
return jsonify({"error": str(e), "traceback": traceback.format_exc()}), 500
修正例
# ✅ Fixed: Configuration from environment variables, DEBUG off in production
import os
app = Flask(__name__)
app.config["DEBUG"] = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]
@app.errorhandler(500)
def error_handler(e):
app.logger.error(f"Internal error: {e}")
return jsonify({"error": "Internal server error, please try again later"}), 500
A06:2021 — 脆弱で古いコンポーネント
リスク: 既知の脆弱性があるサードパーティーライブラリやフレームワークのバージョンを使用しています。
チェックポイント:
- 依存関係に既知の CVE はありませんか(
pip audit、npm audit、snykなどでスキャン)? - 長期間更新されていない依存関係はありませんか?
- メンテナンスされていないライブラリを使用していませんか?
- ロックファイル(package-lock.json/requirements.txt)をバージョン管理していますか?
- 依存関係を自動更新する仕組み(Dependabot など)はありますか?
スキャンコマンド
# Python projects
pip audit
# Node.js projects
npm audit
# General scanning
# Use open-source tools like trivy or grype to scan container/project dependencies
修正のガイダンス
# Update vulnerable packages
pip install --upgrade package_name
# Auto-fix npm vulnerabilities
npm audit fix
# Pin dependency versions to prevent implicit upgrades
pip freeze > requirements.txt
A07:2021 — 識別と認証の失敗
リスク: 認証メカニズムに欠陥があり、総当たり攻撃、クレデンシャルスタッフィング、セッションハイジャックを許してしまいます。
チェックポイント:
- ログイン失敗に対するレート制限(アカウントロック/遅延)はありますか?
- パスワードポリシーは適切ですか(最小文字数、複雑さの要件)?
- ログアウト時にセッショントークンを無効化していますか?
- セッション ID は十分にランダムで予測困難ですか?
- 機密性の高い操作で MFA をサポートしていますか?
- JWT トークンの署名と有効期限を検証していますか?
脆弱なコードの例
# ❌ Vulnerable: JWT signature not verified, accepts alg=none
import jwt
def verify_token(token):
payload = jwt.decode(token, options={"verify_signature": False})
return payload
修正例
# ✅ Fixed: Enforce signature and expiration verification, specify algorithm
import jwt
import os
JWT_SECRET = os.environ["JWT_SECRET"]
def verify_token(token):
try:
payload = jwt.decode(
token,
JWT_SECRET,
algorithms=["HS256"],
options={"require": ["exp", "iat", "sub"]}
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError("Token has expired")
except jwt.InvalidTokenError:
raise AuthError("Invalid token")
A08:2021 — ソフトウェアとデータの完全性の不備
リスク: ソフトウェア更新、重要なデータ、CI/CD パイプラインの完全性を検証できていないため、サプライチェーン攻撃やデータ改ざんを許してしまいます。
チェックポイント:
- デシリアライズに安全でないメソッドを使用していませんか(例:信頼できないデータに対する Python の
pickle.loads)? - CI/CD パイプラインで完全性を検証していますか?
- サードパーティーの CDN リソースで SRI(Subresource Integrity)を使用していますか?
- 重要な設定ファイルの変更を監査ログに記録していますか?
- 自動更新の仕組みで署名を検証していますか?
脆弱なコードの例
# ❌ Vulnerable: Deserializing untrusted data — can lead to remote code execution
import pickle
@app.route("/api/import", methods=["POST"])
def import_data():
data = pickle.loads(request.data)
process(data)
return "OK"
修正例
# ✅ Fixed: Use a safe data format (JSON), refuse to deserialize arbitrary objects
import json
@app.route("/api/import", methods=["POST"])
def import_data():
try:
data = json.loads(request.data)
except json.JSONDecodeError:
return jsonify({"error": "Invalid JSON format"}), 400
process(data)
return "OK"
A09:2021 — セキュリティログと監視の不備
リスク: セキュリティイベントのログ記録と監視が不十分なため、攻撃の検知と対応が遅れるおそれがあります。
チェックポイント:
- ログインの成功と失敗が記録されていますか?
- 権限の変更やデータの削除など、機密性の高い操作が監査ログに記録されていますか?
- パスワード、トークン、その他の機密データが誤ってログに含まれていませんか?
- ログは改ざん耐性を備えていますか(集中管理されたストレージ、追記専用の書き込みなど)?
- 異常な動作(ログイン失敗の急増など)に対するアラートが設定されていますか?
脆弱なコードの例
# ❌ Vulnerable: No logging on login failure, and plaintext password in logs
def login(username, password):
user = db.get_user(username)
if not user or not check_password(password, user.password_hash):
print(f"Login failed for {username} with password {password}")
return None
return create_session(user)
修正例
# ✅ Fixed: Log security events without logging sensitive data
import logging
security_logger = logging.getLogger("security")
def login(username, password):
user = db.get_user(username)
if not user or not check_password(password, user.password_hash):
security_logger.warning(
"Login failed",
extra={"username": username, "ip": request.remote_addr}
)
return None
security_logger.info(
"Login successful",
extra={"username": username, "ip": request.remote_addr}
)
return create_session(user)
A10:2021 — サーバーサイドリクエストフォージェリ(SSRF)
リスク: アプリケーションがユーザー提供の URL を受け付けてサーバー側からリクエストを送信することで、攻撃者が内部ネットワークのリソースやクラウドのメタデータにアクセスできるおそれがあります。
チェックポイント:
- ユーザーが指定した URL を受け付け、サーバー側からリクエストを送信する機能(画像の取得、URL プレビュー、Webhook コールバックなど)がありますか?
- URL のプロトコルは制限されていますか(http/https のみを許可していますか)?
- 内部 IP アドレスの範囲(127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)はフィルタリングされていますか?
- クラウドのメタデータアドレス(169.254.169.254)はブロックされていますか?
- DNS リバインディング攻撃への対策はありますか?
脆弱なコードの例
# ❌ Vulnerable: Directly requesting user-supplied URL — can access internal network and cloud metadata
import requests
@app.route("/api/fetch-url")
def fetch_url():
url = request.args.get("url")
response = requests.get(url)
return response.text
修正例
# ✅ Fixed: Validate URL protocol and target address, block internal network access
import requests
import ipaddress
from urllib.parse import urlparse
import socket
BLOCKED_NETWORKS = [
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("169.254.0.0/16"),
]
def is_safe_url(url):
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
return False
try:
ip = ipaddress.ip_address(socket.gethostbyname(parsed.hostname))
for network in BLOCKED_NETWORKS:
if ip in network:
return False
except (socket.gaierror, ValueError):
return False
return True
@app.route("/api/fetch-url")
def fetch_url():
url = request.args.get("url")
if not is_safe_url(url):
return jsonify({"error": "Access to this address is not allowed"}), 403
response = requests.get(url, timeout=10, allow_redirects=False)
return response.text
レビューレポートのテンプレート
レビューの完了後、次の形式でレポートを出力してください。10 項目すべてを記載してください。該当事項がない項目には「合格」と記入してください。
# OWASP Top 10 Security Review Report
## Review Summary
- Scope: [list of files/modules]
- Date: [date]
- Risk summary: RED High x | YELLOW Medium x | GREEN Low x | PASS No findings x
## Findings (sorted by severity, descending)
### [Severity] [OWASP ID] — [Issue Title]
- **Location:** [file:line_number]
- **Description:** [issue description]
- **Impact:** [potential consequences]
- **Fix:** (ready-to-use code, not just a description)
### PASS A0X — [Category] — No issues found
## Remediation Priority
1. [Most urgent fix — rationale]
2. [Next priority — rationale]
3. ...
推奨されるオープンソースのセキュリティツール
| ツール | 言語 | 目的 |
|---|---|---|
bandit | Python | Python コードのセキュリティスキャン |
semgrep | 複数言語 | ルールベースのコードスキャン |
eslint-plugin-security | JavaScript | JS のセキュリティルール |
npm audit / pip audit | JS / Python | 依存関係の脆弱性スキャン |
trivy | 複数言語 | コンテナと依存関係のスキャン |
sqlmap | — | SQL インジェクションの検出 |
OWASP ZAP | — | Web アプリケーションの動的スキャン |
注: このチェックリストは補助的なレビュー用ツールであり、専門家による侵入テストの代わりにはなりません。高いセキュリティが求められるシステムでは、自動スキャン+手動でのコード監査+侵入テストを組み合わせてください。