こんにちは。コロンビア大学の博士課程でシステムセキュリティを研究しているKoukyosyumeiです。
ここ数日、セキュリティインシデントに関するニュースを目にする機会が増えています。こうしたニュースを見ると、「自分のWebサービスは大丈夫だろうか?」と考える方も多いのではないでしょうか。
Webアプリケーションへの攻撃というと、高度な技術を持ったハッカーが、複雑なプログラムを使って侵入したり、ソーシャルエンジニアリングを駆使するイメージがあるかもしれません。しかし、実際のセキュリティ診断やバグバウンティでは、もっと単純なところから調査を始めることも少なくありません。
例えば、
- URLの末尾に
.envや.git/configを付けてみる - GitHubの公開リポジトリからAPIキーを探す
- 検索欄に特殊な文字列を入力してみる
- APIのユーザーIDを他人のものに変更してみる
- 同じ操作を短時間に繰り返してみる
一見すると単純な操作ですが、これだけでも重大な脆弱性を発見できる場合があります。
もちろん、Webアプリケーションの脆弱性はこれだけではありません。XSS、SQL Injection、SSRF、認可の不備、ビジネスロジックの問題など、攻撃者が注目するポイントは数多くあります。
この記事では、攻撃者がWebアプリケーションのどこを調べるのかという視点から、初心者でも簡単に、自分のサービスで実践できるセキュリティチェックリストを紹介します。
なるべく専門的な知識がなくても実践できるように、具体的な確認方法と、問題が見つかった際の対処方法もまとめました。
なお、この記事で紹介するのは、主にWebアプリケーション自体の脆弱性を探すための方法です。MFA、バックアップ、ログ監視など、運用面でのセキュリティ対策も重要ですが、今回は扱いません。
注意:以下のテストは、自分が所有するWebサービスや、明示的にテストの許可を受けた環境でのみ実施してください。可能な限りローカル環境やステージング環境を利用し、テスト用アカウント・テストデータを使いましょう。
1. まず、外部からどんな情報が見えているか?
攻撃者がWebアプリケーションを調べる際、最初に行うことの一つが偵察・情報収集(Reconnaissance)です。どのようなAPIが存在するか、どんなフレームワークを使っているか、内部情報が漏れていないかなどを調べます。
1.1 設定ファイルやバックアップが公開されていないか
例えば、Webサーバーの設定を間違えると、開発時に利用していた設定ファイルがそのまま公開される場合があります。自分のWebサービスで、次のようなURLを確認してみましょう。
https://example.com/.env
https://example.com/.git/config
https://example.com/backup.zip
https://example.com/.env.bak
.envにはデータベースのパスワードやAPIキーが、.gitにはソースコードの履歴が含まれている可能性があります。
ただし、HTTP 200が返っただけではファイルが公開されているとは限りません。SPAでは存在しないURLにも同じHTMLを返す場合があります。レスポンスの内容まで確認しましょう。
1.2 GitHubの公開リポジトリに秘密情報がないか
さらに、GitHubでコードを公開している場合も注意が必要です。便利なのが、OSSのGitleaksで、Gitのコミット履歴からAPIキーやパスワードなどの秘密情報を検出できます。
macOSなら次のようにインストールできます。
brew install gitleaks
自分のリポジトリをcloneし、次のコマンドを実行します。
gitleaks git . --redact
現在のコードだけでなく、過去のコミットも調査対象にできます。秘密情報が検出された場合、Gitから文字列を削除するだけでなく、流出した可能性のあるキーを失効・再発行しましょう。
1.3 APIやJavaScriptから内部情報が漏れていないか
次に、Chromeの開発者ツールを開き、NetworkやSourcesタブを確認してみましょう。WebサービスがどのAPIを呼び出しているか、どのようなJavaScriptが配信されているかを観察できます。
例えば、次のようなものが見つかるかもしれません。
- SwaggerやOpenAPIのドキュメント
- 管理者向けAPIのエンドポイント
- ソースマップ(
.js.map) - ステージング環境のURL
- 開発中に残したコメントやデバッグ情報
これらは公開されているだけで直ちに脆弱性になるわけではありませんが、意図しない情報が含まれていないか確認する価値があります。
チェックリスト
-
.env、.git、バックアップなどが意図せず公開されていない - GitHubの公開リポジトリをGitleaksでスキャンした
- JavaScriptやHTMLに秘密情報が含まれていない
- Swagger/OpenAPIなどで内部専用の情報を公開していない
- 本番環境で不要なデバッグ機能が動作していない
2. ユーザー入力を悪用できないか?
Webアプリケーションには、検索欄、ログインフォーム、コメント、ファイルアップロードなど、外部からデータを受け取る場所が数多く存在します。攻撃者は、こうした入力を通じて、本来はデータとして扱われるべき文字列を、プログラムやコマンドとして解釈させられないか調べます。
2.1 XSS:入力した文字列がJavaScriptとして実行されないか?
この種の攻撃としてもっとも有名なもののひとつであるXSS(Cross-Site Scripting)は、Webページに意図しないスクリプトを実行させる脆弱性です。
例えば、ユーザーがコメントを投稿できるWebサービスを考えてみましょう。通常は入力した文字列がテキストとして表示されます。しかし、HTMLの扱い方が不適切な場合、入力内容がHTMLとして解釈されてしまう可能性があります。
まずは、自分のテスト環境の検索欄やコメント欄に、次のような文字列を入力してみましょう。
<b>security-test</b>
入力した場所や別のページで、文字列がそのまま表示されるでしょうか。それとも、意図せず太字のHTMLとして解釈されるでしょうか。
HTMLとして解釈された場合、XSSにつながる可能性があるため、さらに確認が必要です。ただし、Markdownエディタなど、意図的にHTMLをレンダリングする機能もあります。HTMLが解釈されること自体が、必ずしもXSSを意味するわけではありません。より厳密には、ユーザーが制御できる文字列によって、意図しないJavaScript実行が発生するかが重要です。
チェックリスト
- 検索欄やコメント欄でHTMLの特殊文字が安全に扱われる
- 保存した入力内容を別ページで表示しても危険なスクリプトが実行されない
- URLのクエリパラメータが安全に表示される
- MarkdownやリッチテキストのHTMLが適切にサニタイズされる
-
JavaScriptの
innerHTMLなどに信頼できない文字列を直接渡していない
対策の基本は、HTML、JavaScript、URLなど、出力先の文脈に応じた適切なエスケープやサニタイズを行うことです。
参考:OWASP Cross Site Scripting Prevention Cheat Sheet
2.2 SQL Injection:入力がデータベースのクエリを変えてしまわないか?
SQL Injectionは、ユーザー入力がSQL文の構造に影響してしまう脆弱性です。
例えば、ユーザー名を検索する機能があったとします。
SELECT * FROM users WHERE name = 'alice';
このSQLを、文字列の連結で組み立てていたらどうでしょうか。
query = "SELECT * FROM users WHERE name = '" + name + "'"
nameの内容によって、意図しないSQLが生成される可能性があります。
自分のサービスでは、検索欄やフィルターに、まずシングルクォート(')などの特殊文字を入力し、異常なエラーが発生しないか確認できます。
ただし、エラーが出ないことはSQL Injectionが存在しない証明にはなりません。より重要なのは、実装がパラメータ化クエリを使っているか確認することです。例えばPythonなら、次のように書けます。
cursor.execute(
"SELECT * FROM users WHERE name = ?",
(name,)
)
SQLの構造と入力値を分離することで、ユーザー入力がSQLの構文として解釈されることを防ぎます。
チェックリスト
- 検索・ログイン・フィルター機能でSQLを文字列連結していない
- パラメータ化クエリを使用している
- 並び替えの列名など、パラメータ化できない識別子は許可リストで制限している
- 不正な入力でSQLのエラーやスタックトレースが外部に表示されない
- データベースの接続アカウントに不要な権限を与えていない
NoSQLデータベースでも、外部入力をクエリ演算子として解釈してしまう問題があるため、同様に注意が必要です。
参考:OWASP SQL Injection Prevention Cheat Sheet
2.3 Command Injection:入力をシェルコマンドに渡していないか?
画像変換、PDF生成、ファイル圧縮などの機能では、バックエンドから外部コマンドを呼び出すことがあります。例えば、ファイル名を文字列連結してシェルコマンドを組み立てる実装は、Command Injectionの原因になり得ます。
攻撃者は、このようなファイル名やパラメータを操作することで、意図しないコマンドを実行させられないかを確認します。
例えば、以下のように、Webアプリが画像を変換するときに、Pythonで外部コマンドを実行しているとしましょう。
import os
filename = request.args["filename"]
os.system(f"convert {filename} output.png")
本来は、image.jpgのようなファイル名が入力されることを想定しているのでしょうが、攻撃者がファイル名としてimage.jpg; touch /tmp/injection.txtのような文字列を指定した場合、;がシェルコマンドの区切りとして解釈され、無関係なtouchコマンドが実行されてしまう可能性があります。
チェックリスト
- 外部入力をシェルコマンドの文字列に連結していない
-
shell=Trueなどを安易に使用していない - 外部プログラムを呼ぶ場合は引数を構造化して渡している
- ファイル名やオプションを適切に検証している
可能ならシェルを経由せず、ライブラリのAPIを直接利用する方が安全です。
3. 本来できない操作ができないか?
また、Webアプリケーションのセキュリティで特に重要なのは、アクセス制御とビジネスロジックの検証です。
XSSやSQL Injectionのように特殊な文字列を入力しなくても、普通のHTTPリクエストを少し変更するだけで、重大な問題が見つかる場合があります。
3.1 IDOR:他人のデータを取得・変更できないか?
例えば、ログインしたユーザーが次のAPIで自分のプロフィールを取得できるとします。
GET /api/users/123
ここで、123は自分のユーザーIDです。
攻撃者は、このIDを他人のものに変更してもデータを取得できないかを確認します。
GET /api/users/124
もし別ユーザーの非公開情報が取得できるなら、IDOR(Insecure Direct Object Reference)などの認可不備が存在する可能性があります。
実際に確認する方法
自分のテスト環境で、AliceとBobという2つのユーザーを作成してみましょう。
- Aliceとしてログインする
- Aliceのデータを取得するAPIを確認する
- リクエストのIDをBobのものに変更する
- Aliceの権限ではBobの非公開データが取得できないことを確認する
同じことを、更新や削除のAPIでも試してみましょう。
特にSaaSでは、別の組織やテナントのデータにアクセスできないことも重要です。
チェックリスト
- 他人のユーザーIDを指定しても非公開データを取得できない
- 他人のファイルや注文を変更・削除できない
- 別の組織・テナントのデータにアクセスできない
- 一般ユーザーが管理者APIを呼び出せない
- 認可をフロントエンドだけでなくバックエンドで検証している
3.2 Mass Assignment:想定外のパラメータを受け付けないか?
次に、APIが受け取るJSONを考えてみましょう。例えば、ユーザーのプロフィール更新APIが次のようなリクエストを受け取るとします。
{
"display_name": "Alice"
}
ここで、クライアントから渡されたJSONを、サーバー側でそのままユーザーモデルに反映していたらどうでしょうか。
{
"display_name": "Alice",
"role": "admin"
}
本来変更できないはずのroleまで書き換えられる設計になっているかもしれません。これがMass Assignmentの典型的な問題です。
チェックリスト
- APIが更新可能なフィールドを明示的に制限している
-
role、is_admin、owner_idなどを一般ユーザーが変更できない - クライアントが渡したJSONを無条件でDBモデルに反映していない
- 管理者のみ変更可能なフィールドにはサーバー側の認可チェックがある
実際の確認では、テスト環境で想定外のフィールドを追加し、その値がデータベースに反映されないか確かめましょう。
3.3 ビジネスロジック:正しいAPIを組み合わせると不正なことができないか?
ここまでは、主に単一のAPIの問題でした。しかし、各APIが個別には正しく動作していても、組み合わせると問題が発生する場合があります。
例えば、ECサイトのクーポン処理を考えてみましょう。
- クーポンは1人1回まで利用可能
- 決済APIは正常に動作する
- クーポン利用APIも正常に動作する
しかし、処理の順序や競合状態に問題があると、同じクーポンを複数回利用できてしまう可能性があります。攻撃者は、こうした「本来想定されていない操作の順序」に注目します。
チェックリスト
- クーポンやポイントを不正に繰り返し使用できない
- 決済せずに商品や有料機能を取得できない
- ワークフローの途中のステップを省略できない
- 同じ操作を短時間に複数回実行しても整合性が保たれる
- 招待、紹介特典、無料トライアルなどを不正利用できない。
この種のバグは、単純な脆弱性スキャナーでは発見しにくいことが多いため、特に時間をかけて調査する価値があります。
4. サーバーの裏側を悪用できないか?
次に、攻撃者がWebアプリケーションを経由して、内部ネットワークやファイルシステムなどへアクセスできないかを確認します。
4.1 SSRF:サーバーに意図しないURLへアクセスさせられないか?
最近のWebサービスには、ユーザーがURLを入力すると、サーバーがそのURLにアクセスする機能がよくあります。
例えば、
- URLのプレビューを生成する
- URLから画像をダウンロードする
- Webhookの接続テストを行う
- 外部のWebページをPDFに変換する
といった機能です。
こうした機能でURLを適切に制限していないと、攻撃者がサーバーに対して、本来アクセスさせるべきではない宛先へのリクエストを送らせる可能性があります。これがSSRF(Server-Side Request Forgery)です。
例えば、ユーザーが指定したURLから画像をダウンロードするWebアプリケーションがあるとします。
import requests
url = request.args["url"]
response = requests.get(url)
通常は、https://example.com/image.pngのようなURLが指定されることを想定しています。しかし、攻撃者がhttp://127.0.0.1:8080/adminを指定すると、Webアプリケーションを動かしているサーバーの情報が読み取れてしまうかもしれません。
実際にSSRFの有無をテストする場合、自分のサービスにURLを入力する機能があれば、テスト用のHTTPサーバーを用意し、そのURLへのアクセスがどのように処理されるか確認しましょう。例えば、ローカルのテスト環境で、許可されていない内部宛てURLが拒否されるかを確認できます。
チェックリスト
- 内部ネットワークやループバックアドレスへの接続を適切に制限している
- 外部URLを取得する機能を把握している
- URLのリダイレクト先も検証している
- DNS解決結果を考慮して接続先を制限している
- サーバーから外部へ接続できる範囲を必要最小限にしている
参考:OWASP SSRF Prevention Cheat Sheet
4.2 ファイルアップロード・Path Traversal
ファイルアップロード機能やダウンロード機能も、攻撃者が注目するポイントです。
例えば、
GET /download?file=report.pdf
というAPIが、リクエストのfileパラメータをそのままファイルパスとして使用していたらどうでしょうか。
入力値を工夫することで、本来公開されていないファイルを参照できてしまう場合があります。これがPath Traversalの代表的な問題です。
また、画像アップロード機能が実際には任意のファイルを受け付けてしまい、アップロードされたファイルをサーバー上で実行可能な状態にしてしまうケースも考えられます。
チェックリスト
- ファイル名から意図しないディレクトリを参照できない
- ファイルアップロードで許可する形式を制限している
- 拡張子だけでなく、ファイル内容も適切に検証している
- アップロードしたファイルが実行可能な場所に配置されない
- 非公開ファイルのダウンロード時にも認可を確認している
- ファイルサイズや処理量に適切な制限がある
ファイルパスを外部入力から直接組み立てない設計や、ファイルIDを介して安全に管理する設計が有効です。
4.3 依存ライブラリに既知の脆弱性がないか?
自分で実装したコードに脆弱性がなくても、依存ライブラリに問題が存在する場合があります。まずは、現在利用しているライブラリの既知の脆弱性を確認しましょう。
Node.jsの場合:
npm audit
Pythonの場合:
pip-audit
Rustの場合:
cargo audit
GitHub Dependabotを使えば、依存関係の脆弱性について継続的に通知を受けることもできます。
チェックリスト
- 依存ライブラリの既知の脆弱性をスキャンした
- 重大な脆弱性について影響の有無を確認した
- サポート終了したランタイムやフレームワークを使用していない
- 不要な依存関係や古い機能を削除している
- セキュリティ修正を継続的に取り込める
5. モダンなWebアプリ特有の攻撃も確認する
ここまで紹介したのは、昔から知られている脆弱性が中心です。しかし、現在のWebサービスは、CDN、OAuth、GraphQL、WebSocket、生成AIなど、多くの技術を組み合わせて動作しており、こうした構成ならではの攻撃もあります。
5.1 OAuth / SSOの設定不備
GoogleログインなどのOAuth/OIDCを利用している場合、認証のフロー自体が正しく設計されているか確認する必要があります。
例えば、ログイン完了後のリダイレクト先を自由に指定できたり、認証レスポンスと元のログイン要求が適切に紐付けられていなかったりすると、問題につながる可能性があります。
チェックリスト
- リダイレクトURIを厳格に制限している
-
stateなどによるリクエストの紐付けを適切に検証している -
OIDCでは
nonceなど、利用するフローに必要な検証を行っている - 認可コードフローでPKCEを適切に利用している
- メールアドレスだけで安易に異なるアカウントを統合していない
5.2 Web Cache Poisoning / Cache Deception
CDNやリバースプロキシを利用している場合、キャッシュの設定にも注意が必要です。
Web Cache Poisoningは、攻撃者がキャッシュされるレスポンスに影響を与え、他の利用者に意図しない内容を配信させる攻撃です。
また、Web Cache Deceptionでは、本来キャッシュしてはいけない個人向けのレスポンスが、共有キャッシュに保存されてしまう問題が発生します。
チェックリスト
- 個人情報を含むページを共有キャッシュに保存していない
- Cookieや認証情報に依存するレスポンスのキャッシュ方針が適切
- CDNとバックエンドでURLやパスの解釈に不一致がない
- キャッシュキーに影響するHTTPヘッダーやパラメータを把握している
テスト環境では、ユーザーAの非公開レスポンスがユーザーBに返らないことを確認しましょう。
5.3 GraphQL / WebSocket APIの認可不備
REST API以外を使っている場合も、アクセス制御の検証が重要です。例えばGraphQLでは、単一のエンドポイントを通じて多くの種類のデータを取得できます。そのため、各フィールドやオブジェクトに対して適切な認可が必要です。
また、WebSocketでは、接続時に認証していても、個々のメッセージや操作について権限が検証されていないケースがあります。
チェックリスト
- GraphQLの各操作・フィールドで適切な認可を行っている
- GraphQLのクエリの深さや実行コストを制限している
- WebSocketの接続時だけでなく、各操作でも認可している
- 他人のイベントや非公開メッセージを購読できない
- 不要なデバッグ情報やスキーマを公開していない
まとめ:自分のWebアプリを攻撃者目線でチェックしてみよう
ここまで、Webアプリケーションを調べる際に注目される代表的なポイントを紹介してきました。最後に、チェックリストをまとめます。
まず確認したい10項目
-
.env、.git、バックアップなどが公開されていない - GitHubの公開リポジトリをGitleaksで確認した
- XSSにつながる危険なHTML・JavaScriptの扱いがない
- SQL InjectionやCommand Injectionが発生しない
- 他人のデータを取得・変更できない
- APIに想定外のパラメータを渡しても権限昇格しない
- クーポンや決済などの処理を不正に繰り返せない
- SSRFやPath Traversalが発生しない
- 依存ライブラリの既知の脆弱性を確認した
- OAuth、キャッシュ、GraphQL、WebSocket、使用している機能に固有のリスクを確認した
もちろん、これらを確認しただけで、Webアプリケーションの安全性が保証されるわけではありません。しかし、攻撃者がどのようにWebアプリケーションを調べるのかを知り、自分でも同じ視点で確認することは、セキュリティを向上させる第一歩になります。
また、テストは一度きりで終わらせず、新しい機能の追加やコードの変更に合わせて継続することが重要です。
さらに一歩進んで:AIを使った自動Red-Teamingと形式検証
ここまで紹介した検査の中には、比較的簡単に手動で確認できるものもあります。しかし、Webアプリケーションが複雑になると、すべてのAPIや画面、ユーザー権限の組み合わせを手動で調べるのは極めて大変ですし、専門的な知識を求められます。
そこで役に立つのが、セキュリティテストの自動化であり、例えば、OWASP ZAPやBurp Suiteといったツールがプロの間では利用されています。
また、最近ではAIエージェントにWebアプリケーションを操作させ、脆弱性の発見を支援させるというアプローチもあります。
h5i:AIエージェントを使ってWebアプリケーションをテストする
最後に、私が開発しているh5iというOSSを少し紹介させてください。h5iは、Webアプリケーションのセキュリティを向上させるための、AIエージェント向けツールです。
AIエージェントからブラウザを操作したり、HTTPリクエストを記録・変更・再送信したりできます。例えば、次のような検査をAIエージェントに任せるためのツールとして利用できます。
- Webアプリケーション内のAPIを探索する
- HTTPリクエストを変更して認可不備を調べる
- 入力フォームやAPIの異常な動作を調べる
- 発見した問題と、その再現手順を記録する
h5i自体は無料のOSSで、外部のAIモデルを利用する場合などを除き、ソフトウェアの利用料金はかかりません。興味があれば、ぜひ試してみてください。h5iのバイナリをインストールし、Claude CodeやCodexなどに、h5iを使って、このWebアプリを監査してと頼むだけで、本格的な攻撃テストを実行することができます。また、h5i uiというコマンドによって、直感的な―ボードを確認することもできます。
バグを探すだけでなく、バグがないことを証明する
また、h5iはバグを探すだけでなく、元からバグがないことを証明しながらWebアプリケーションを開発するための枠組みも提供しています。
通常のセキュリティテストは、実際にリクエストを送ったり、入力を変更したりして、バグが存在することを発見するアプローチです。
一方、形式検証では、「特定の種類のバグが発生しないこと」を、数学的に証明することを目指します。例えば、複数の組織が利用するWebアプリケーションについて、「組織Aのユーザーは組織Bの非公開データにアクセスできない」という性質を証明する、といったことが考えられます。
h5iでは、RustのWebアプリケーションフレームワークとLean 4という定理証明支援系を組み合わせて、こうした性質を検証する仕組みも開発しています。
もちろん、証明には前提条件や適用範囲があり、Webアプリケーション全体が無条件に安全になるわけではありません。それでも、「攻撃してバグを見つける」ことと「バグが存在しないことを証明する」ことを組み合わせることで、より信頼できるWebサービスの開発につなげられると考えています。
