実装由来の脆弱性レビュー観点(インジェクション・IDOR・DoS)
一括更新・並び替えAPIは所有者チェックが漏れやすい(IDOR検査観点)
約2か月前
問題
リソースの単体 Update / Delete には所有者チェック(WHERE id = ? AND user_id = ? 相当)があるのに、並び替えや一括更新のエンドポイントだけ抜けていることが多い。とくに CTE や VALUES を使った一括 UPDATE で WHERE 対象.id = 入力.id のように ID だけで突き合わせ、所有者条件を付け忘れると、認証済みの任意ユーザーが他人のリソースを書き換えられる(IDOR / Broken Access Control)。
影響が並び順や軽微なフィールドの改ざんに留まっても、テナント越えの書き込みであることに変わりはない。
判断基準
- 「読み取りはスコープしているか」だけでなく「すべての書き込み経路がスコープしているか」を観点にする。とくに bulk/sort/move/reorder/batch 系は単体操作とは別実装になりがちなので個別に確認する。
- 入力 ID 群が本当に当該ユーザーのものかを検証するか、UPDATE/DELETE の WHERE に常に所有者条件を含める。アプリ層の事前チェックだけに頼らず、データアクセス層のクエリ自体にも条件を残すと安全。
検証方法
ユーザーA でログインし、ユーザーB のリソース ID を指定して並び替え・一括更新を呼ぶ。所有者条件があれば B のレコードは変化せず、A の所有外 ID は更新対象にならないことを確認する。単体操作のテストが通っていても bulk 系は別途テストする。
react-markdownはデフォルトで危険URLと生HTMLを無害化する(XSS誤検知の判別)
約2か月前
仕様
react-markdown は v9 以降、リンクや画像の URL を既定の urlTransform(defaultUrlTransform)で検証し、javascript: data: などの危険スキームを取り除く。許可されるのは http / https / mailto / tel と相対 URL 程度。さらに生の HTML(<script> や <iframe> など)はデフォルトでは一切レンダリングされない。
そのため、ユーザー入力の Markdown を素の react-markdown で表示しているだけなら、javascript: リンク注入や HTML 埋め込みによる XSS は既定で防がれている。
監査時の判断基準
Markdown レンダリングを XSS として指摘する前に、次を確認する。これらが無ければ多くは誤検知。
rehype-rawを使っていないか(使うと生 HTML がそのまま描画され、別途サニタイズが必須になる)。urlTransform/transformLinkUriを独自実装で上書きし、無害化を外していないか。dangerouslySetInnerHTMLで別経路にレンダリングしていないか。
アプリ側コードに href?.startsWith('http') のようなスキーム判定ロジックがあっても、それは target=_blank 付与など表示制御目的であることが多く、サニタイズ責務はライブラリ側にある。判定ロジックのバグ(例: || のつもりで ?? を使う)は論理バグではあっても、URL 無害化が効いていれば XSS にはならない。
検証方法
[x](javascript:alert(1)) や生 <img onerror=...> を含む Markdown を実際に描画し、生成 DOM の href / 要素を確認する。href が除去・置換され、HTML がエスケープされていれば既定の保護が効いている。保護を意図的に外す(rehype-raw 導入など)場合のみ、DOMPurify 等での明示的サニタイズを必須とする。
クラウドストレージの削除APIはプレフィックス制限と所有者検証を必須にする
約2か月前
問題
オブジェクトストレージ(GCS/S3 等)の削除エンドポイントで、クライアントが送ってきたオブジェクト URL を正規表現などでパース対象キーに変換し、認証だけ通っていれば削除する実装は危険。https://storage.example.com/<bucket>/(.+) のように (.+) で任意パスを拾うと、他ユーザーの URL(公開プロフィール等から取得可能)を投げるだけでそのオブジェクトを削除でき、バケット内の任意オブジェクトに波及しうる(IDOR / Broken Access Control)。アップロード側は profiles/ などにプレフィックス制限しているのに、削除側だけ制限が抜けるパターンが典型。
判断基準
- 認証(誰がログインしているか)と認可(そのオブジェクトが本人のものか)は別物。削除経路では必ず所有者検証を入れる。
- 対策の柱は二つ。(1) 削除可能なキーを所定プレフィックスに限定する。(2) ファイル名やパスにユーザー識別子を含め、リクエスト者と一致するキーだけ削除許可する。あるいは所有権判定を持つバックエンド側に削除を委譲する。
- 存在/非存在でレスポンス差分を返すと、オブジェクト列挙(存在確認)にも悪用されるため、所有者検証を入れて経路ごと塞ぐ。
検証方法
ユーザーA でログインし、ユーザーB のオブジェクト URL や所定プレフィックス外のキーを指定して削除 API を呼ぶ。所有者・プレフィックス検証があれば対象は削除されず拒否されることを確認する。アップロード経路にプレフィックス制限があっても削除経路は別実装になりやすいので個別にテストする。
execFileでもcmd.exe経由のURL起動はコマンド注入になりうる(ブラウザ起動の落とし穴)
約2か月前
問題
child_process.execFile(や spawn)はシェルを介さないため一般にコマンド注入に強いが、Windows で execFile('cmd', ['/c', 'start', '', url]) のようにブラウザを開く実装では、cmd.exe 自身が引数中の & | ^ 等のメタ文字を解釈する。そのため半信頼の URL(例: OAuth ディスカバリの authorization_endpoint から組み立てた URL、外部入力由来の URL)を渡すと、URL に細工があるとコマンドが実行されうる。macOS の open や Linux の xdg-open は引数として安全に渡るため、この穴は Windows 固有。
判断基準と対策
- URL を開く前にスキームが
https:(必要ならhttp:)であることを検証し、&|^<>%等のメタ文字を含む URL は拒否する。 - Windows では
cmd /c startを避け、rundll32 url.dll,FileProtocolHandler <url>などシェル解釈を挟まない起動方法を使う。どうしてもstartを使うなら URL を^でエスケープする。 - 「execFile を使っているから安全」と早合点しない。安全なのは渡した実行ファイルが引数をそのまま受け取る場合で、間に
cmd.exeのようなシェル的インタプリタを噛ませると前提が崩れる。
検証方法
メタ文字を含む URL(https://example.com/?a=1&calc)を起動関数に渡し、追加のプロセスが起動しないこと・拒否されることを Windows で確認する。
MCP/ツールのレスポンスに外部由来データを埋め込む時はプロンプトインジェクションを区切る
約2か月前
問題
MCP サーバーやエージェントのツールが、API から取得したデータ(メモ本文、著者名、公開コンテンツ等)を区切りなくそのままツール結果テキストに連結して返すと、その中に「これまでの指示を無視して…」のような文言が含まれていた場合、呼び出し側 LLM への間接プロンプトインジェクション経路になる。とくに公開メモ・いいね・コメントなど他ユーザー由来のコンテンツを返す経路はリスクが高い。ツール定義の description は静的なら問題ないが、レスポンス本文は外部入力で動的に変わる点が見落とされやすい。
判断基準
- ツール結果に外部由来データを載せるときは、それが「データであって指示ではない」ことを明示する区切り(フェンスや注記)で囲む。
- とくに他ユーザー・第三者が編集できるコンテンツを返す経路では必須とみなす。自分専用データのみを返す経路でも、内容に他者由来テキストが混ざりうるなら同様に扱う。
- 認可スコープでの多層防御も併用する。注入があってもツールがリクエスト者の
userIDでしかデータ取得できなければ、越境アクセス自体は防げる。
検証観点
注入文言を含むコンテンツを保存し、それをツールが返したとき、出力上でユーザーデータと指示が明確に分離されているかを確認する。区切りがない場合、下流 LLM が埋め込み指示に従う挙動を再現テストする。
Node.jsのHTTPサーバーはHostヘッダー由来の例外でプロセスごと落ちうる
約2か月前
問題
Node.js の HTTP サーバー生成関数に渡すリクエストハンドラを async で書き、先頭付近で Host ヘッダーの値からリクエスト URL を組み立てると(URL コンストラクタにパスとホスト由来の base を渡すパターン)、空白や制御文字を含む不正な Host で URL コンストラクタが TypeError を投げる。これが async ハンドラ内の未捕捉例外だと「未処理の Promise リジェクション」になり、Node 15 以降の既定の挙動(unhandled-rejections=throw)ではプロセスが終了する。
結果として、認証不要・1 リクエスト(不正な Host ヘッダーを1つ付けるだけ)でサーバープロセスを落とせる。ループされれば継続的なサービス停止になる。
判断基準と対策
- リクエストハンドラ本体は必ず try/catch で包み、パース失敗は 400 等で返す。URL コンストラクタなど例外を投げうる処理を catch の外に置かない。
- 多層防御としてプロセスレベルの unhandledRejection / uncaughtException ハンドラを登録し、ログ出力のみでプロセスを落とさない。
- ユーザー制御の値(Host、パス、ヘッダー)を URL コンストラクタやパーサに渡す箇所は、事前に妥当性を検証するか、安全なデフォルトにフォールバックする。
適用場面
生の node の http / https モジュールでサーバーを書く場合や、フレームワークを介さずリクエストを処理する MCP サーバー・プロキシ・ヘルスチェック層などに当てはまる。Express 等の多くは内部で例外を捕捉するが、独自ハンドラを挟むと同じ穴が空きうる。
検証方法
サーバーを起動し、生ソケットで不正な Host(空白入り・空文字・制御文字入り)を送る。修正前は同入力でプロセスが終了し、修正後は 4xx などの応答を返しプロセスが生存し続けることを確認する。
URL入力の検証は url タグでなく http_url を使う(url は javascript: を通す)
約1か月前
go-playground/validator の url タグは URL 構文ベースの検証で、javascript:alert(1) や ftp://... など http(s) 以外のスキームも「妥当な URL」として通す。ユーザー入力の URL を後で <a href> 等に出力する場合、url 検証だけでは格納型 XSS(javascript: スキーム)を防げない。
判断基準
- 表示用に受け取る URL は http/https 限定にしたいことがほとんど。その場合は
http_urlタグを使う(http または https スキームと host を必須にする)。例:omitempty,http_url,max=100。 - クライアント側のスキーム検証(フォームの ^https?:// 等)だけに依存しない。API を直接叩けば回避されるため、サーバ側でも必須化する(多層防御)。
落とし穴
urlとhttp_urlは別物。urlは scheme を問わないため javascript:/data:/ftp: を許す。- エラーメッセージを独自に翻訳登録している場合、新しいタグ(http_url)の訳を追加しないと、そのフィールドだけ既定(英語)メッセージになる。
検証方法
javascript:alert(1) / ftp://example.com / スキーム無しの裸ドメインが弾かれ、http(s)://... が通り、空値(omitempty)が通ることをテストする。
認証と認可を分離し、所有者・スコープ条件は全ての書き込み経路でサーバ側のクエリに強制する
約1か月前
認証(誰がログインしているか)と認可(その対象が本人のものか)は別物として扱い、認可はフロントの暗黙ルールやログイン通過ではなく、サーバ側のデータアクセス層のクエリ条件として強制する。最適化するのは「経路ごとに別実装でも所有者・テナント境界を必ず守ること」、避けるのは「読み取りだけスコープして書き込み経路を信頼境界の外に置くこと」。
判断基準
- 単体の更新・削除に所有者条件があっても、一括更新・並び替え・バルク削除・リダイレクト戻り先など別実装になりがちな経路は個別に確認する。ID だけで対象を突き合わせる一括更新は越境書き込み(IDOR)になりやすい。
- 削除・参照を URL やキーのパースで受ける経路は、許可プレフィックスへの限定と所有者一致の双方を入れる。任意パスを拾う実装は他人の対象へ波及する。
- 公開範囲やスコープのような状態は、フロントの「空なら全体」式の暗黙ルールに任せず、サーバ側で正規化して中間状態(API 直叩き・別クライアント・既存不正値)にも耐えさせる。
- アプリ層の事前チェックだけに頼らず、データアクセス層のクエリ自体に所有者条件を残す多層防御にする。フロントが意図通り制御できていても信頼境界はサーバに置く。
例外・限界 真に公開で誰でも読める対象は所有者条件が不要なことがある。その場合も「公開だから書き込みも自由」と混同しない。
検証 別ユーザーでログインし、他人の対象 ID やプレフィックス外のキーを各書き込み経路へ渡して、対象が変化せず拒否されることを確認する。単体操作のテストが通っていても一括・並び替え・削除は個別に検証する。
根拠(synthesize 元)
- 482 一括更新・並び替えAPIは所有者チェックが漏れやすい(IDOR検査観点)
- 489 クラウドストレージの削除APIはプレフィックス制限と所有者検証を必須にする
- 347 公開範囲のような状態表現はフロントの暗黙ルールではなくバックで正規化する
- 409 OAuth consent の redirect URL は保存元が自前でも検証してから使う
- 371 NestJS Guard: canActivate の false は403、NotFoundExceptionで404隠蔽
セキュリティの深刻度は警告件数や規格違反でなく、攻撃者制御可能な到達経路と前提条件で評価する
約1か月前
脆弱性・暗号・秘密混入の深刻度は、自動スキャンの警告件数やベストプラクティス違反の有無でなく、「攻撃者が制御できる経路で実コードから到達するか」「その前提(鍵のエントロピー、入力の制御可否、ファイルの追跡状況)で実害が打ち消せるか」という一次情報で評価する。最適化するのは「実際に悪用可能かに基づく優先順位」、避けるのは「ツールの出力やルール違反をそのまま深刻度として鵜呑みにすること」。
判断基準
- 依存の audit に残る critical/high は、その CVE の攻撃経路に自コードが到達しうるかで実害を見る。到達不能や攻撃者制御不可なら、破壊的なダウングレードを急がず別対応に回す判断もある。
- 呼び出し到達解析(実コードからの到達有無を静的に分類するツール)が使えるなら、到達ありを最優先にし、import のみ・require のみは後回しにする。
- 暗号方式の理論的弱点は入力前提で打ち消せることがある。鍵が高エントロピー乱数ならオフライン総当たりは非現実的で即時脆弱性ではない。人手の低エントロピー値だと前提が崩れる、という分け方をする。
- 秘密情報の混入指摘は、対象がバージョン管理下にあるか・履歴に残るかを確認してから深刻度を決める。追跡対象外なら「ソースへの秘密混入」には当たらず過大評価になる。
- 自動スキャンやサブエージェントは深刻度を過大に出しがちなので、報告前に到達経路・スコープ・前提を一次情報で裏取りして補正する。
注意 実害が低くても、監査をクリーンに保つために対応する判断はありうる。その場合もリスクと必要性を分けて記録し、低リスクと無リスクを混同しない。
検証 指摘ごとに「攻撃者が制御できる入力からこの経路へ到達できるか」「前提を満たさないと成立しないか」を言語化し、裏取りできない深刻度は据え置きにする。
根拠(synthesize 元)
- 278 残存 CVE は攻撃者制御可能な経路かで実害を評価する
- 429 govulncheck の呼び出し到達解析で脆弱性をトリアージする
- 316 crypto-js の AES パスフレーズモードの弱点とリスク評価
- 430 秘密情報の所見は git 追跡状況を確認してから深刻度を判定する
守りたい条件は内側の機構に強制し、型宣言・フロント・プロンプト遵守を信頼境界にしない
約1か月前
不変条件・絞り込み・安全条件を「守られているはず」で済ませない。最適化するのは「呼び出し側が間違えても破れない強制」、避けるのは「型宣言・フロントの暗黙ルール・プロンプトや呼び出し規律の遵守を信頼境界に置くこと」。守りたい条件は、最も内側で確実に効く機構(サーバ側の検証・正規化、クエリ条件、出力の区切り、明示的な値の組み立て)に落とす。
判断基準
- 型や契約は宣言であって強制ではない。構造的部分型では宣言にないフィールドが素通りして実送信される、ゆるい入力境界が厳格な内部型へ不正値を流して汎用エラーに潰れる、といった乖離が起きる。送る内容は型に宣言し、必要な値だけを明示的に組み立て、境界で内部型に合わせて正規化・厳格化する。
- フロントの暗黙ルール(空なら全体、等)に状態の意味を委ねない。中間状態は API 直叩きや別クライアント、既存不正値から生じるので、サーバ側で正規化して不正値・中間状態にも耐えさせる。
- クライアント側の検証だけに頼らず、同じ条件をサーバ側にも置く多層防御にする。入力品質ゲートはクライアント差に左右されないようサーバ側へ寄せ、hard error・warning・正規化提案に段階を分ける。
- AI や LLM に渡すツール結果へ外部由来データを区切りなく連結しない。「データであって指示ではない」区切りで囲み、認可スコープでの多層防御も併用する。プロンプト遵守を安全境界にしない。
例外・限界
内側強制はコストを伴う(クエリ条件の追加、正規化処理)。真に公開で誰でも読める対象など、強制が不要な範囲は分けて考える。ただし「公開だから何でも自由」と混同しない。
検証
呼び出し側の遵守を外した状態(フィルタを渡さない、不正値・中間状態を直接送る、宣言にないフィールドを混ぜる、注入文言を含む外部データを通す)で、内側の機構が条件を維持し拒否・正規化するかを確認する。
根拠(synthesize 元)
- 392 MCP サーバーの品質改善は入力品質ゲートを先に固める
- 492 MCP/ツールのレスポンスに外部由来データを埋め込む時はプロンプトインジェクションを区切る
- 532 TS の余剰プロパティ検査は変数渡しでは効かず未宣言フィールドが暗黙送信される
- 547 ゆるい入力境界と厳格な内部型の不一致は unmarshal 失敗を汎用エラーに隠す