自分のコードの「危ないサイン」を見抜く ― Web脆弱性5種の勘所
Webアプリのセキュリティを、代表的な攻撃手口を一通り手を動かして学んだ。普段アプリの開発や運用に関わってはいるが、セキュリティが専門というわけではない。そんな自分にとって、これは「自分の書いたコードを別の目線で見直す」いい機会になった。
学んでみて一番効いたのは、攻撃テクニックそのものより 「どこを見て、何を試したら『これは危ない』と気づけるのか」 という勘所だった。脆弱性は、まず気づかないとレビューでも拾えない。
この記事では、SQLi・XSS・CSRF・SSRF・IDORの5つを 図で 整理する。それぞれ「誰が・何を・どこで」やっているのかを一枚で見られるようにした。専門のペンテスターでなくても、自分のアプリのレビューやちょっとした動作確認に使えるレベルを目指す。
全部に共通する一本の原則
細かい話に入る前に、5種類を貫く軸を先に書いておく。
本来「データ」として扱うべき入力が、間違って「コード・命令」として解釈・実行されてしまう
レストランの注文票で、「ステーキ」と書く欄にこっそり「金庫を開けろ」と書いたら、店員がそれを命令だと思って実行してしまう ―― そんな状況を作れてしまうのが脆弱性の正体だ。「お客さんが書いた文字」と「お店への命令」をちゃんと区別できているか。レビュー時にこの一点を意識するだけで、当たりがつけられるようになる。
では、5つを順番に見ていく。
1. SQLインジェクション ― データベースをのぞかれる
サービスの裏側には、会員情報や注文履歴をしまっている「データベース」という金庫がある。利用者の入力(検索ワードやログイン情報)を、この金庫への問い合わせにそのまま混ぜてしまうと、利用者が金庫への命令を書き換えられてしまう。
入力した記号がSQL文の引用符を途中で閉じ、その後ろが「命令」として実行されてしまうのがポイント。過去の大規模な個人情報流出の多くが、この手口だった。古い手口なのに、いまだに現役で被害が出続けている。

確認の一手 はシンプルで、入力欄にシングルクォート ' を1つ入れること。DBの構文エラーが返れば、入力が命令として解釈されている合図になる。
本質的な対策は プレースホルダ。入力を「ただのデータ」として扱い、命令と物理的に混ざらない仕組みにする。「危険な文字を弾く」発想はいたちごっこになりやすい。
2. XSS ― 画面に仕掛けを埋め込まれる
利用者が書いた文字(コメント、プロフィール名、検索ワード)を、サイトがそのまま画面に表示するとき、その文字の中に「ちょっとした仕掛け」を紛れ込ませると、それを見た人のブラウザの中で勝手に動き出す。

とくに怖いのが「保存型」。掲示板やコメント欄に仕掛けが保存されると、そのページを見た人全員が次々と被害に遭う。もし管理者がその画面を見れば、最も強い権限が盗まれることになる。
確認の一手 は、入力した内容がそのまま画面に出る箇所に、アラートを出す短い仕掛けを入れて反応を見ること。
対策は出力時の エスケープ(文字を「ただの文字」として表示し、仕掛けとして動かないようにする)。<script> という単語だけを弾く対策では、別のタグの属性経由などで簡単に回避されてしまう。
3. CSRF ― 知らないうちに操作させられる
ログイン中の利用者をだまして、別の悪意あるページを開かせる。すると、利用者のブラウザが本人に代わって、勝手に「メールアドレスの変更」などの操作を送ってしまう。
この攻撃がややこしいのは、「誰が何をしているか」が3者に分かれている こと。図で見ると一気に分かりやすくなる。

攻撃者は罠を仕掛けて誘導するだけ。被害者はリンクを開くだけ。実際にリクエストを送るのは 被害者のブラウザ で、そこにログイン情報(クッキー)が自動で付くから、サーバーは本人の操作だと信じてしまう。攻撃者はクッキーの中身を一切知らないのに攻撃が成立する、という点が肝だ。
確認の一手 は、対象の操作を外部の自作ページから再現してみて、通ってしまうか試すこと。
なお「POSTにすれば安全」は誤解。GETもPOSTも両方狙えるので、メソッドの違いは対策にならない。本当の対策は、本人が意図した操作かを確認する仕組み(CSRFトークン)のほう。
4. SSRF ― サーバー自身を使い走りにされる
「このURLの内容を取ってきて」とサービスに頼める機能(リンクプレビュー、URLからのファイル取り込みなど)を悪用し、サービスのサーバー自身に、本来見えてはいけない内部の情報を取りに行かせる。

肝は「外からは届かないが、サーバー経由なら届く」という非対称性。社内だけの管理画面やクラウドの「鍵」にあたる情報は、ふつう外部からは絶対に届かない場所に置かれている。ところがサーバー自身はその内側の住人。だから、サーバーを使い走りにすることで、その内側に手を伸ばせてしまう。
確認の一手 は、url= path= server= のような「取得先を指定できそうな」パラメータを疑い、内部アドレスを入れて試すこと。
対策は、文字列の見た目でチェックするのではなく、URLをきちんと解析してから判定すること。そして、サーバーがどこへ通信できるか(egress)も絞っておくこと。
5. IDOR ― 番号を変えるだけで他人の情報が見える
5つの中で一番シンプルで、一番ぞっとする。URLやリクエストの中の 番号を1つ変えるだけで、他人の情報が見えてしまう。

問題の本質は、サービスが 「あなたは誰か(ログインしているか)」は確認するのに、「あなたにこの情報を見る権利があるか」を確認していない こと。ホテルのカードキーで「宿泊客かどうか」はチェックされるのに、どの部屋番号を押しても全部ドアが開いてしまう ―― そんな状態だ。
確認の一手 は、IDを自分以外の値に変えて送り、他人のデータが返るか見ること(アカウントを2つ作って試すと確実)。アドレスバーに見えていなくても、裏で飛ぶAPIリクエストのIDも要チェック。
対策は、サーバー側で毎回「このユーザーがこのデータの持ち主か」を検証すること。IDを複雑な文字列にするだけでは本質的な対策にならない。
レビュー時に効く、共通の観点
実際にコードや動作を見るときに効いたのは、この3つ。
パラメータ名で当たりをつける(url=→SSRF、id=→IDOR、検索/ログイン→SQLi)
特殊文字を1つ入れて反応を見る('→SQLi、<script>→XSS)
機能の性質から疑う(状態変化→CSRF、外部取得→SSRF)
そして全脆弱性に共通する落とし穴として、
見た目の難読化を「対策」と勘違いしない(複雑そうな文字列でも、中身がただの変換にすぎないことは多い)
「画面に見えていない=存在しない」ではない(隠しフィールド、裏で飛ぶリクエスト、隠しパラメータ)
「対策コードは書いたが実際には使われていない」が頻出(無害化の関数を用意したのに、出力側で使っていない、など)
自分のコードを「これは入力をデータとして扱っているか、命令として扱ってしまっているか」という一点で見直すと、レビューの解像度が上がる。
知れば知るほど、ふだん何気なく使っているサービスの裏側で、見えないところで多くの工夫がなされているのが分かっておもしろい。次はもう少し踏み込んだ、複数を組み合わせる攻撃も整理してみたい。
