vibe-security について
vibe-securityは、Webアプリを開発・レビューする人向けのセキュアコーディングガイドです。アクセス制御、XSS、CSRF、SSRFなどの攻撃面ごとに確認項目を整理し、検証手順と防御策を示します。
主な機能
- 01関連するWeb攻撃面の特定
- 02アクセス制御と認可のレビュー
- 03XSSとCSRF対策の確認
- 04安全なリダイレクトとパスワード対策
- 05レスポンスヘッダーの検証と修正テスト
活用シーン
- 01ユーザー入力を扱うAPIルートのレビュー
- 02認証とセッション制御の実装
- 03ファイルアップロードとリダイレクトの評価
- 04セキュリティヘッダーとCSPポリシーの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nickcrew/ claude-cortex --skill vibe-securityインストール後、Claude Code で/vibe-securityを入力して読み込まれたか確認します。
詳細
Web アプリケーションのセキュアコーディングガイド
Web アプリケーション向けの包括的なセキュアコーディングの実践方法です。バグハンターの視点でコードに取り組み、機能を損なうことなく、アプリケーションを可能な限り安全にしてください。
このスキルを使用するタイミング
- 新しい Web アプリケーションのエンドポイントや API ルートを作成するとき
- ユーザー入力、認証、ファイルアップロードを扱う PR をレビューするとき
- 認証、認可、セッション管理を実装するとき
- ファイルアップロード、リダイレクト、URL ベースの機能を扱うとき
- セキュリティヘッダーや CSP ポリシーを追加するとき
- インフラストラクチャ/ネットワークセキュリティには使用しないでください。代わりに
defense-in-depthを使用してください
ワークフロー
ステップ 1:攻撃対象領域を特定する
レビュー対象のコードに適用されるセキュリティ領域を特定します。
| 領域 | 該当条件 |
|---|---|
| アクセス制御 | 認証が必要なエンドポイント、マルチテナントデータ |
| XSS | HTML、JavaScript、CSS に出力されるユーザー入力 |
| CSRF | 状態を変更するエンドポイント(POST、PUT、DELETE) |
| SSRF | サーバーがユーザー指定の URL にリクエストを送信する |
| SQL インジェクション | 動的なデータベースクエリ |
| ファイルアップロード | ファイルアップロード機能全般 |
| パストラバーサル | ファイルパスに含まれるユーザー入力 |
ステップ 2:領域固有のチェックを適用する
特定した領域ごとに、以下の該当セクションをチェックリストとして使用します。
ステップ 3:セキュリティヘッダーを確認する
すべてのレスポンスに必要なヘッダーが含まれていることを確認します(セキュリティヘッダーのチェックリストを参照)。
ステップ 4:レビューとテストを行う
- 修正によって機能が損なわれないことを確認する
- 各セクションに記載された回避手法を使ってテストする
- 利用可能な場合は、自動セキュリティスキャンを実行する
基本原則:
- 多層防御:単一のセキュリティ制御に依存しない
- 安全なフェイル:何らかの障害が発生した場合は、アクセスを拒否して安全側に倒す
- 最小権限:必要最小限の権限を付与する
- 入力検証:ユーザー入力を決して信用せず、すべてサーバー側で検証する
- 出力エンコーディング:データを出力先のコンテキストに適した形式でエンコードする
アクセス制御の問題
アクセス制御の脆弱性は、ユーザーが本来の権限を超えてリソースにアクセスしたり、操作を実行したりできる場合に発生します。
基本要件
認証が必要なすべてのデータ項目と操作について、次の事項を守ってください。
-
ユーザーレベルの認可
- 各ユーザーは自分のデータにのみアクセス/変更できるようにする
- 他のユーザーや組織のデータにアクセスできないようにする
- 所有権はルートレベルだけでなく、必ずデータ層で検証する
-
連番 ID ではなく UUID を使用する
- UUIDv4 など、推測できない識別子を使用する
- 例外:ユーザーから明示的に求められた場合に限り、連番 ID を使用する
-
アカウントのライフサイクル管理
- ユーザーが組織から削除された場合:すべてのアクセストークンとセッションを直ちに無効化する
- アカウントが削除/無効化された場合:すべてのアクティブなセッションと API キーを無効化する
- トークン失効リスト、または更新の仕組みを備えた短命トークンを実装する
認可チェックのチェックリスト
- すべてのリクエストで、ユーザーがそのリソースを所有していることを検証する(クライアント側のデータを信用しない)
- マルチテナントアプリでは、組織への所属を確認する
- ロールベースの操作について、ロールの権限を検証する
- 権限の変更後に、権限を再検証する
- 親リソースの所有権を確認する(例:コメントにアクセスする場合、親投稿をユーザーが所有していることを確認する)
避けるべき一般的な落とし穴
- IDOR(安全でない直接オブジェクト参照):リクエスト元のユーザーが、要求されたリソース ID にアクセスする権限を持つことを必ず検証する
- 権限昇格:ロールの変更はサーバー側で検証し、クライアントから送られたロール情報を決して信用しない
- 水平アクセス:同じ権限レベルのユーザー A がユーザー B のリソースにアクセスする
- 垂直アクセス:一般ユーザーが管理者機能にアクセスする
- マスアサインメント:ユーザーが更新できるフィールドを絞り込み、リクエストボディのすべてのフィールドを無条件に受け入れない
実装パターン
# Pseudocode for secure resource access
function getResource(resourceId, currentUser):
resource = database.find(resourceId)
if resource is null:
return 404 # Don't reveal if resource exists
if resource.ownerId != currentUser.id:
if not currentUser.hasOrgAccess(resource.orgId):
return 404 # Return 404, not 403, to prevent enumeration
return resource
クライアント側のバグ
クロスサイトスクリプティング(XSS)
ユーザーが直接または間接的に制御できるすべての入力を、XSS に対してサニタイズする必要があります。
保護対象の入力元
直接的な入力:
- フォームフィールド(メールアドレス、名前、自己紹介、コメントなど)
- 検索クエリ
- アップロード時のファイル名
- リッチテキストエディター/WYSIWYG コンテンツ
間接的な入力:
- URL パラメーターとクエリ文字列
- URL フラグメント(ハッシュ値)
- アプリケーションで使用される HTTP ヘッダー(表示する場合の Referer、User-Agent)
- ユーザーに表示するサードパーティ API のデータ
- WebSocket メッセージ
- iframe からの postMessage データ
- レンダリングする場合の LocalStorage/SessionStorage の値
見落とされやすいもの:
- ユーザー入力を反映するエラーメッセージ
- HTML を受け付ける PDF/ドキュメント生成ツール
- ユーザーデータを含むメールテンプレート
- 管理者パネルのログビューアー
- HTML としてレンダリングされる JSON レスポンス
- JavaScript を含む可能性がある SVG ファイルのアップロード
- HTML を許可している場合の Markdown レンダリング
保護戦略
-
出力エンコーディング(コンテキストに応じて使い分ける)
- HTML コンテキスト:HTML エンティティにエンコードする(
<→<) - JavaScript コンテキスト:JavaScript エスケープを行う
- URL コンテキスト:URL エンコードを行う
- CSS コンテキスト:CSS エスケープを行う
- フレームワークの組み込みエスケープ機能を使用する(React の JSX、Vue の {{ }} など)
- HTML コンテキスト:HTML エンティティにエンコードする(
-
コンテンツセキュリティポリシー(CSP)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';- スクリプトでは
'unsafe-inline'と'unsafe-eval'を避ける - 必要に応じて、インラインスクリプトに nonce またはハッシュを使用する
- 違反を報告する:
report-uri /csp-report
- スクリプトでは
-
入力のサニタイズ
- 実績のあるライブラリを使用する(HTML には DOMPurify)
- リッチテキストで許可するタグ/属性をホワイトリスト化する
- 危険なパターンを削除またはエンコードする
-
追加のヘッダー
X-Content-Type-Options: nosniffX-Frame-Options: DENY(または CSP の frame-ancestors を使用する)
クロスサイトリクエストフォージェリ(CSRF)
状態を変更するすべてのエンドポイントを、CSRF 攻撃から保護する必要があります。
CSRF 保護が必要なエンドポイント
認証済みアクション:
- すべての POST、PUT、PATCH、DELETE リクエスト
- 状態を変更する GET リクエスト(適切な HTTP メソッドを使うよう修正する)
- ファイルのアップロード
- 設定の変更
- 決済/トランザクションのエンドポイント
認証前のアクション:
- ログインエンドポイント(ログイン CSRF を防止する)
- サインアップエンドポイント
- パスワードリセット要求のエンドポイント
- パスワード変更エンドポイント
- メールアドレス/電話番号の確認エンドポイント
- OAuth コールバックエンドポイント
保護メカニズム
-
CSRF トークン
- 暗号学的に安全な乱数トークンを生成する
- トークンをユーザーセッションにひも付ける
- 状態を変更するすべてのリクエストで検証する
- ログイン後に再生成する(セッション固定との組み合わせを防止する)
-
SameSite Cookie
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnlyStrict:クロスサイトでは Cookie を送信しない(最も安全)Lax:トップレベルのナビゲーションでは Cookie を送信する(バランスが良い)- 多層防御のため、必ず CSRF トークンと併用する
-
二重送信 Cookie パターン
- CSRF トークンを Cookie とリクエスト本文/ヘッダーの両方で送信する
- サーバーで両者が一致することを検証する
エッジケースとよくある誤り
- トークンの存在確認:CSRF 検証をトークンの有無に依存させてはいけません。常にトークンを必須にしてください
- フォームごとのトークン:機密性の高い操作では、フォームごとに異なるトークンの使用を検討してください
- JSON API:JSON の Content-Type によって CSRF が防げると考えてはいけません。Origin/Referer ヘッダーを検証し、トークンも使用してください
- CORS の設定ミス:過度に許可的な CORS 設定は SameSite Cookie を迂回される可能性があります
- サブドメイン:サブドメインの乗っ取りが CSRF につながる可能性があるため、CSRF トークンのスコープを適切に設定してください
- Flash/PDF のアップロード:レガシーブラウザのプラグインによって SameSite が迂回される可能性があります
- 副作用のある GET リクエスト:GET で状態を変更してはいけません
- トークンの漏えい:CSRF トークンを URL に含めてはいけません
- URL 内のトークンとヘッダー内のトークン:URL パラメーターよりカスタムヘッダー(X-CSRF-Token)を優先してください
検証チェックリスト
- トークンは暗号学的に安全な乱数である(セキュアな乱数生成器を使用する)
- トークンはユーザーセッションに紐付けられている
- 状態を変更するすべてのリクエストで、サーバー側でトークンを検証する
- トークンがない場合は、リクエストを拒否する
- 認証状態が変化したときにトークンを再生成する
- SameSite Cookie 属性が設定されている
- セッション Cookie に Secure フラグと HttpOnly フラグが設定されている
シークレットキーと機密データの漏えい
シークレットや機密情報をクライアント側のコードから参照できないようにしてください。
クライアント側のコードで決して公開しないもの
API キーとシークレット:
- サードパーティ API キー(Stripe、AWS など)
- データベース接続文字列
- JWT 署名用シークレット
- 暗号化キー
- OAuth クライアントシークレット
- 内部サービスの URL/認証情報
機密性の高いユーザーデータ:
- クレジットカード番号全体
- 社会保障番号
- パスワード(ハッシュ化されていても)
- セキュリティの質問と回答
- 電話番号全体(マスキングする:--1234)
- 表示に不要な機密性の高い個人識別情報(PII)
インフラストラクチャの詳細:
- 内部 IP アドレス
- データベーススキーマ
- デバッグ情報
- 本番環境でのスタックトレース
- サーバーソフトウェアのバージョン
シークレットが隠れている場所(確認してください!)
- JavaScript バンドル(ソースマップを含む)
- HTML コメント
- 非表示のフォームフィールド
- データ属性
- LocalStorage/SessionStorage
- SSR アプリの初期状態/ハイドレーションデータ
- ビルドツールを通じて公開される環境変数(NEXT_PUBLIC_、REACT_APP_)
ベストプラクティス
- 環境変数:シークレットは
.envファイルに保存します - サーバー側のみ:シークレットを必要とする API 呼び出しはバックエンドからのみ行います
オープンリダイレクト
リダイレクト先の URL を受け付けるエンドポイントは、すべてオープンリダイレクト攻撃から保護する必要があります。
保護戦略
-
許可リストによる検証
allowed_domains = ['yourdomain.com', 'app.yourdomain.com'] function isValidRedirect(url): parsed = parseUrl(url) return parsed.hostname in allowed_domains -
相対 URL のみ
- 完全な URL ではなく、パス(例:
/dashboard)のみを受け付けます - パスが
/で始まり、//を含まないことを検証します
- 完全な URL ではなく、パス(例:
-
間接参照
- 生の URL の代わりにマッピングを使用します:
?redirect=dashboard→/dashboardを検索
- 生の URL の代わりにマッピングを使用します:
阻止すべき回避手法
| 手法 | 例 | 有効な理由 |
|---|---|---|
| @ 記号 | https://[email protected] | ブラウザーは legit.com をユーザー名として、evil.com に移動します |
| サブドメインの悪用 | https://legit.com.evil.com | evil.com がそのサブドメインを所有しています |
| プロトコルの細工 | javascript:alert(1) | リダイレクトを介した XSS |
| URL の二重エンコード | %252f%252fevil.com | 二重デコード後に //evil.com になります |
| バックスラッシュ | https://legit.com\@evil.com | 一部のパーサーは \ を / に正規化します |
| NULL バイト | https://legit.com%00.evil.com | 一部のパーサーは NULL で切り詰めます |
| タブ/改行 | https://legit.com%09.evil.com | 空白文字の解釈の違いを悪用します |
| Unicode 正規化 | https://legіt.com(キリル文字の і) | IDN ホモグラフ攻撃 |
| Data URL | data:text/html,<script>... | ペイロードを直接実行します |
| プロトコル相対 URL | //evil.com | 現在のページのプロトコルを使用します |
| フラグメントの悪用 | https://legit.com#@evil.com | ライブラリによって解析結果が異なります |
IDN ホモグラフ攻撃への対策
- 検証前に URL を Punycode に変換します
- 機密性の高いリダイレクトでは、ASCII 以外のドメインを全面的にブロックすることを検討します
パスワードのセキュリティ
パスワード要件
- 最低 8 文字(12 文字以上を推奨)
- 最大長を設けない(または、128 文字など十分に大きくする)
- 特殊文字を含むすべての文字を許可する
- 特定の文字種を必須にしない(ユーザーが強力なパスワードを選べるようにする)
保存
- Argon2id、bcrypt、または scrypt を使用します
- MD5、SHA1、またはプレーンな SHA256 は決して使用しないでください
サーバー側のバグ
サーバーサイドリクエストフォージェリ(SSRF)
サーバーがユーザーから提供された、またはユーザーの影響を受けた URL にリクエストを送信する機能は、すべて保護する必要があります。
脆弱性を招く可能性のある機能
- Webhook(ユーザーがコールバック URL を指定する)
- URL プレビュー
- URL から PDF を生成する機能
- URL から画像/ファイルを取得する機能
- URL からのインポート機能
- RSS/フィードリーダー
- ユーザー指定のエンドポイントとの API 連携
- プロキシ機能
- HTML から PDF/画像への変換機能
保護戦略
-
許可リスト方式(推奨)
- 事前に承認されたドメインへのリクエストのみを許可する
- 連携機能に対する厳格な許可リストを維持する
-
ネットワークの分離
- URL 取得サービスを分離されたネットワークで実行する
- 内部ネットワークおよびクラウドメタデータへのアクセスをブロックする
ブロックすべき IP および DNS のバイパス手法
| 手法 | 例 | 説明 |
|---|---|---|
| 10 進数 IP | http://2130706433 | 127.0.0.1 の 10 進数表記 |
| 8 進数 IP | http://0177.0.0.1 | 8 進数表記 |
| 16 進数 IP | http://0x7f.0x0.0x0.0x1 | 16 進数表記 |
| IPv6 localhost | http://[::1] | IPv6 ループバック |
| IPv4 マップ IPv6 | http://[::ffff:127.0.0.1] | IPv4 マップ IPv6 |
| 短縮 IPv6 | http://[::] | すべてゼロ |
| DNS リバインディング | 攻撃者の DNS が内部 IP を返す | 最初のリクエストは外部 IP に解決され、2 回目は内部 IP に解決される |
| 内部への CNAME | 攻撃者のドメインが内部を指す CNAME を設定する | DNS が内部ホスト名を指す |
| URL パーサーの混乱 | http://attacker.com#@internal | パーサーによって動作が異なる |
| リダイレクトチェーン | 外部 URL が内部へリダイレクトする | リダイレクトを慎重に追跡する |
| IPv6 スコープ ID | http://[fe80::1%25eth0] | インターフェーススコープ付き IPv6 |
| まれな IP 形式 | http://127.1 | 短縮 IP 表記 |
DNS リバインディングの防止
- リクエストを行う前に DNS を解決する
- 解決された IP が内部 IP でないことを検証する
- リクエスト中は解決済みの IP を固定する(再解決しない)
- または、間隔をあけて 2 回解決し、どちらも同じ外部 IP に解決されることを確認する
クラウドメタデータの保護
クラウドメタデータのエンドポイントへのアクセスをブロックします。
- AWS:
169.254.169.254 - GCP:
metadata.google.internal、169.254.169.254、http://metadata - Azure:
169.254.169.254 - DigitalOcean:
169.254.169.254
実装チェックリスト
- URL スキームが HTTP/HTTPS のみであることを検証する
- DNS を解決し、IP がプライベート/内部 IP でないことを検証する
- クラウドメタデータ IP を明示的にブロックする
- リダイレクトの追跡を制限または無効にする
- リダイレクトを追跡する場合は、各ホップを検証する
- リクエストにタイムアウトを設定する
- レスポンスサイズを制限する
- 可能な場合はネットワークを分離する
安全でないファイルアップロード
さまざまな攻撃を防ぐため、ファイルの種類、内容、サイズを検証する必要があります。
検証要件
1. ファイルの種類の検証
- ファイル拡張子を許可リストと照合する
- マジックバイト/ファイルシグネチャが想定される種類と一致することを検証する
- 1 つのチェックだけに依存しない
2. ファイル内容の検証
- マジックバイトを読み取り、検証する
- 画像の場合:画像ライブラリで処理を試みる(不正なファイルを検出する)
- ドキュメントの場合:マクロや埋め込みオブジェクトをスキャンする
- ポリグロットファイル(複数の種類として有効なファイル)をチェックする
3. ファイルサイズの制限
- サーバー側でファイルサイズの上限を設定する
- Web サーバー/プロキシの制限も設定する
- ファイルの種類ごとの制限を検討する(画像は動画より小さくする)
よくある回避手法と攻撃
| 攻撃 | 説明 | 防止策 |
|---|---|---|
| 拡張子の回避 | shell.php.jpg | 拡張子全体を確認し、許可リストを使用する |
| ヌルバイト | shell.php%00.jpg | ファイル名をサニタイズし、ヌルバイトを確認する |
| 二重拡張子 | shell.jpg.php | 拡張子は1つだけ許可する |
| MIME タイプの偽装 | Content-Type を image/jpeg に設定する | マジックバイトを検証する |
| マジックバイトの埋め込み | 悪意あるファイルの先頭に有効なマジックバイトを付加する | ヘッダーだけでなく、ファイル構造全体を確認する |
| ポリグロットファイル | JPEG と JavaScript の両方として有効なファイル | 想定する種類としてファイルを解析し、無効な場合は拒否する |
| JavaScript を含む SVG | <svg onload="alert(1)"> | SVG をサニタイズするか、全面的に禁止する |
| ファイルアップロード経由の XXE | 悪意ある DOCX、XLSX(いずれも XML) | パーサーで外部エンティティを無効にする |
| ZIP スリップ | アーカイブ内の ../../../etc/passwd | 展開したパスを検証する |
| ImageMagick のエクスプロイト | 特別に細工された画像 | ImageMagick を最新の状態に保ち、policy.xml を使用する |
| ファイル名へのインジェクション | ファイル名に含まれる ; rm -rf / | ファイル名をサニタイズし、ランダムな名前を使用する |
| Content-Type の混同 | ブラウザーによる MIME スニッフィング | X-Content-Type-Options: nosniff を設定する |
マジックバイトの参照表
| 種類 | マジックバイト(16進数) |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 |
25 50 44 46 | |
| ZIP | 50 4B 03 04 |
| DOCX/XLSX | 50 4B 03 04(ZIP ベース) |
安全なアップロード処理
- ファイル名を変更する:ランダムな UUID 名を使用し、元の名前は破棄します
- Web ルートの外に保存する:またはアップロード用に別のドメインを使用します
- 適切なヘッダーを付けて配信する:
Content-Disposition: attachment(ダウンロードを強制)X-Content-Type-Options: nosniff- 実際のファイル形式に一致する
Content-Type
- CDN/別のドメインを使用する:アップロードされたコンテンツをメインアプリから隔離します
- 制限的な権限を設定する:アップロードされたファイルは実行可能にしないでください
SQL インジェクション
SQL インジェクションは、適切な処理をせずにユーザー入力を SQL クエリに組み込むと発生します。
防止方法
1. パラメーター化クエリ(プリペアドステートメント) — 主な防御策
-- VULNERABLE
query = "SELECT * FROM users WHERE id = " + userId
-- SECURE
query = "SELECT * FROM users WHERE id = ?"
execute(query, [userId])
2. ORM の使用
- 自動的にパラメーター化する ORM メソッドを使用する
- ORM の生クエリメソッドには注意する
- ORM 固有のインジェクションポイントに注意する
3. 入力検証
- データ型を検証する(整数は整数である必要があります)
- 該当する場合は、許可する値をホワイトリストに登録する
- これは多層防御であり、主な防御策ではありません
注意すべきインジェクションポイント
- WHERE 句
- ORDER BY 句(見落とされがちです。パラメーターは使えないため、ホワイトリストが必要です)
- LIMIT/OFFSET 値
- テーブル名とカラム名(パラメーター化できないため、ホワイトリストが必要です)
- INSERT 値
- UPDATE SET 値
- 動的リストを含む IN 句
- LIKE パターン(ワイルドカードの %, _ もエスケープする)
その他の防御策
- 最小権限:データベースユーザーには、必要最小限の権限のみを付与します
- 危険な関数を無効にする:SQL Server の
xp_cmdshellなど - エラー処理:SQL エラーをユーザーに決して公開しないでください
XML 外部エンティティ(XXE)
XXE の脆弱性は、XML パーサーがユーザー提供の XML に含まれる外部エンティティ参照を処理すると発生します。
脆弱性があるシナリオ
直接的な XML 入力:
- SOAP API
- XML-RPC
- XML ファイルのアップロード
- 構成ファイルの解析
- RSS/Atom フィードの処理
間接的な XML:
- JSON/その他の形式をサーバー側で XML に変換する
- Office 文書(DOCX、XLSX、PPTX は XML を含む ZIP ファイルです)
- SVG ファイル(XML ベース)
- SAML アサーション
- XFA フォームを含む PDF
言語/パーサー別の防止策
Java:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
Python (lxml):
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
# Or use defusedxml library
PHP:
libxml_disable_entity_loader(true);
// Or use XMLReader with proper settings
Node.js:
// Use libraries that disable DTD processing by default
// If using libxmljs, set { noent: false, dtdload: false }
.NET:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XXE 防止チェックリスト
- 可能であれば、DTD 処理を完全に無効にする
- 外部エンティティの解決を無効にする
- 外部 DTD の読み込みを無効にする
- XInclude 処理を無効にする
- 最新のパッチが適用された XML パーサーのバージョンを使用する
- DTD が必要な場合は、解析前に XML を検証/サニタイズする
- 可能であれば、XML の代わりに JSON の使用を検討する
パストラバーサル
パストラバーサルの脆弱性は、ユーザー入力によってファイルパスが制御され、意図したディレクトリの外にあるファイルへアクセスできる場合に発生します。
脆弱なパターン
# VULNERABLE
file_path = "/uploads/" + user_input
file_path = base_dir + request.params['file']
template = "templates/" + user_provided_template
防止策
1. パスにユーザー入力を使用しない
# Instead of using user input directly
# Use indirect references
files = {'report': '/reports/q1.pdf', 'invoice': '/invoices/2024.pdf'}
file_path = files.get(user_input) # Returns None if invalid
2. 正規化と検証
import os
def safe_join(base_directory, user_path):
# Ensure base is absolute and normalized
base = os.path.abspath(os.path.realpath(base_directory))
# Join and then resolve the result
target = os.path.abspath(os.path.realpath(os.path.join(base, user_path)))
# Ensure the commonpath is the base directory
if os.path.commonpath([base, target]) != base:
raise ValueError("Error!")
return target
3. 入力のサニタイズ
..の並びを削除または拒否する- 絶対パスを示す記号(
/、C:)を削除または拒否する - 使用可能な文字を許可リストで指定する(英数字、ハイフン、アンダースコア)
- 該当する場合はファイル拡張子を検証する
パストラバーサルのチェックリスト
- ファイルパスにユーザー入力を直接使用しない
- パスを正規化し、ベースディレクトリと照合して検証する
- 該当する場合はファイル拡張子を制限する
- さまざまなエンコードや回避手法を使ってテストする
セキュリティヘッダーのチェックリスト
すべてのレスポンスに以下のヘッダーを含めます。
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: [see XSS section]
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store (for sensitive pages)
JWT のセキュリティ
JWT の設定ミスは、認証の完全な回避やトークンの偽造につながる可能性があります。
脆弱性
| 脆弱性 | 防止策 |
|---|---|
alg: none 攻撃 | サーバー側で必ずアルゴリズムを検証し、none を拒否する |
| アルゴリズムの混同 | 期待するアルゴリズムを明示的に指定し、トークンから決して導出しない |
| 弱い HMAC シークレット | 256 ビット以上の暗号学的に安全な乱数シークレットを使用する |
| 有効期限の未設定 | 必ず exp クレームを設定する |
| localStorage へのトークン保存 | httpOnly、Secure、SameSite=Strict の Cookie に保存し、localStorage には決して保存しない |
安全な実装
// 1. SIGNING
// Always use environment variables for secrets
const secret = process.env.JWT_SECRET;
const token = jwt.sign({
sub: userId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15 mins (Short-lived)
jti: crypto.randomUUID() // Unique ID for revocation/blacklisting
}, secret, {
algorithm: 'HS256'
});
// 2. SENDING (Cookie Best Practices)
// Protect against XSS and CSRF
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
// 3. VERIFYING
// CRITICAL: Whitelist the allowed algorithm
jwt.verify(token, secret, { algorithms: ['HS256'] }, (err, decoded) => {
if (err) {
// Handle invalid token
}
// Trust the payload
});
JWT のチェックリスト
- 検証時にアルゴリズムを明示的に指定する(トークンヘッダーを決して信頼しない)
-
alg: noneを拒否する - シークレットには 256 ビット以上のランダムデータを使用する(パスワードやフレーズは使用しない)
-
expクレームを必ず設定し、検証する - トークンは httpOnly Cookie に保存する(localStorage/sessionStorage には保存しない)
- リフレッシュトークンのローテーションを実装する(使用時に古いリフレッシュトークンを無効化する)
API のセキュリティ
マスアサインメント
フィルタリングされていないリクエストボディを受け入れると、権限昇格につながる可能性があります。
// VULNERABLE — user can set { role: "admin" } in request body
User.update(req.body)
// SECURE — whitelist allowed fields
const allowed = ['name', 'email', 'avatar']
const updates = pick(req.body, allowed)
User.update(updates)
これはあらゆる ORM/フレームワークに当てはまります。リクエストで変更できるフィールドは、必ず明示的に定義してください。
GraphQL
| 脆弱性 | 防止策 |
|---|---|
| 本番環境でのイントロスペクション | 本番環境ではイントロスペクションを無効にします。 |
| クエリ深度攻撃 | クエリ深度の制限を実装します(例:最大 10 レベル)。 |
| クエリ複雑度攻撃 | クエリのコストを計算し、厳格な上限を適用します。 |
| バッチ処理攻撃 | 1 回のリクエストで許可するオペレーション数を制限します。 |
const server = new ApolloServer({
introspection: process.env.NODE_ENV !== 'production',
validationRules: [
depthLimit(10),
costAnalysis({ maximumCost: 1000 })
]
})
一般的なセキュリティ原則
コードを生成する際は、必ず次のことを行ってください。
- すべての入力をサーバー側で検証する — クライアント側の検証だけを決して信頼しない
- パラメーター化クエリを使用する — クエリにユーザー入力を決して連結しない
- 出力をコンテキストに応じてエンコードする — HTML、JS、URL、CSS の各コンテキストでは異なるエンコードが必要
- 認証チェックを適用する — ルーティング時だけでなく、すべてのエンドポイントで行う
- 認可チェックを適用する — ユーザーが特定のリソースにアクセスできることを検証する
- 安全なデフォルト設定を使用する
- エラーを安全に処理する — スタックトレースや内部情報をユーザーに漏らさない
- 依存関係を最新の状態に保つ — 脆弱な依存関係を追跡するツールを使用する
判断に迷った場合は、より制限的で安全な選択肢を選び、セキュリティ上の考慮事項をコメントに記録してください。