【保存版】Webアプリのセキュリティ脅威と対策まとめ|代表的な15項目を一気に整理

Web アプリを動かしている限り、セキュリティの脅威は避けて通れません。一度でも侵害されると、サービス停止・データ漏えい・信用失墜と被害が一気に広がります。

古い金庫扉の閂とヒンジを間近で撮った写真
守るものの前に、いくつ鍵をかけるか

本記事では、Web アプリ開発で 必ず押さえておきたい代表的なセキュリティ脅威 を15件ピックアップし、それぞれの仕組みと対策をまとめます。チェックリストとしても使えるようにできるだけ網羅的に解説しているので、設計・実装・レビューの場面で振り返ってもらえると嬉しいです。

まず全体を一覧にしておきます。確認方法は、コードを読むもの・外から1コマンドで見られるものが混ざっています。

脅威確認方法主な対策
1. SQL インジェクションSQL を文字列連結で組み立てている箇所がないか、コードを検索するプリペアドステートメント、DB ユーザーの権限を最小に
2. XSSdangerouslySetInnerHTML や v-html など、エスケープを外している箇所を探す自動エスケープ、サニタイザ、CSP、Cookie に HttpOnly
3. CSRF状態を変える操作が GET で動かないか、トークンを検証しているかCSRF トークン、SameSite Cookie
4. CORS の設定ミス別オリジンからのリクエストに、Access-Control-Allow-Origin が何を返すか許可するオリジンを固定のリストに。Origin をそのまま返さない
5. SSRF利用者が指定した URL を、サーバーが取りに行く処理がないか許可リスト、プライベート IP とメタデータの拒否、IMDSv2
6. パストラバーサルファイルパスに入力値をそのまま使っている箇所がないかID から内部でパスを決める。realpath で正規化して検証
7. コマンドインジェクションsystem() や exec() など、シェルを呼ぶ箇所を探す引数を配列で渡す。そもそもシェルを使わない
8. オープンリダイレクトリダイレクト先を URL パラメータで受け取っていないか自サイト内・相対パスに限定
9. 認証・セッションの不備パスワードの保存方式、ログイン試行の制限、セッション ID の再発行bcrypt / argon2、レート制限、MFA
10. 認可の不備(IDOR)URL の ID を他人のものに書き換えて、見えてしまわないか所有者の確認をサーバー側で必ず行う
11. ファイルアップロード保存先が Web から直接実行できる場所になっていないか3点で検証、配信ディレクトリの外に保存、名前をランダムに
12. 機密情報の露出エラー画面・API レスポンス・公開パスを外から見る本番で詳細なエラーを出さない。必要な項目だけ返す
13. クリックジャッキングレスポンスに X-Frame-Options か frame-ancestors があるかX-Frame-Options、CSP の frame-ancestors
14. 依存ライブラリの脆弱性npm audit などで既知の脆弱性を確認するDependabot / Renovate、CI に SCA ツール
15. セキュリティヘッダの不足curl -I でレスポンスヘッダを見るHSTS・CSP・nosniff などを前段でまとめて付ける
項目名を押すと、それぞれの解説へ移動します
目次

1. SQL インジェクション

ユーザー入力を SQL に直接連結することで、攻撃者が任意のクエリを実行できてしまう脆弱性。最悪の場合 全テーブルの中身が抜かれる / 改ざんされる という致命的被害につながります。

# 危険: 入力を文字列連結している
sql = "SELECT * FROM users WHERE name = '" + name + "'"

# 安全: プレースホルダ + バインド変数
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
  • プリペアドステートメント / バインドパラメータを必ず使う
  • 動的に SQL を組み立てる必要があるときは 許可リスト方式(カラム名・テーブル名を定数照合)
  • DB ユーザーの権限を 必要最小限 に絞る(参照系と更新系のロール分離)

2. XSS(クロスサイトスクリプティング)

ユーザー入力が HTML/JS としてそのまま表示され、攻撃者の用意したスクリプトがブラウザで実行されてしまう脆弱性。セッションクッキーの窃取や 画面の改ざん に直結します。

  • テンプレートエンジンの 自動エスケープ を必ず有効にする(React/Vue は基本的にデフォルトで安全だが、dangerouslySetInnerHTML や v-html は要注意)
  • HTML を許容する場合は DOMPurify などのサニタイザ を必須化
  • Content-Security-Policy (CSP) ヘッダを設定して、許可していないインラインスクリプトの実行をブロック
  • Cookie は HttpOnly + Secure + SameSite=Lax/Strict を必ず付与

3. CSRF(クロスサイトリクエストフォージェリ)

ログイン済みユーザーのブラウザを利用して、攻撃者が意図しないリクエストを送信させる攻撃。送金や設定変更など 状態変更を伴う操作 が標的になります。

  • CSRF トークン をフォーム・POSTリクエストに含めて検証する
  • SameSite Cookie(Lax 以上)で、クロスサイトでクッキーが自動送信されない設定にする
  • 状態変更は POST / PUT / DELETE を使い、GET ではやらない

4. CORS の設定ミス

CORS(Cross-Origin Resource Sharing)はブラウザの同一オリジンポリシーを緩和する仕組みですが、緩めすぎると認証付き API が他オリジンから叩き放題 になります。

  • Access-Control-Allow-Origin: * と Access-Control-Allow-Credentials: true を 同時に使わない(ブラウザが拒否するが、認証情報を許可しているつもりで誤設定するケースが多い)
  • 許可オリジンは ホワイトリスト方式 でハードコード or 設定ファイル管理
  • 動的にエコーバックする実装(リクエストの Origin をそのまま返す)は絶対にしない

5. SSRF(サーバーサイドリクエストフォージェリ)

ユーザーから受け取った URL をサーバ側でそのままリクエストすることで、内部ネットワークやクラウドのメタデータエンドポイント(例: 169.254.169.254)にアクセスされてしまう攻撃。AWS の IAM 認証情報を抜かれた事例で有名になりました。

  • 外部 URL を fetch する処理は ホワイトリスト方式 で制限
  • プライベート IP / 169.254.x.x / メタデータエンドポイントは 明示的に拒否
  • EC2 なら IMDSv2 を強制(トークン必須にしてメタデータ盗難リスクを下げる)

6. パストラバーサル

ファイル名にユーザー入力を含めると、../../etc/passwd のような相対パスを使ってサーバ内の任意ファイルが読まれる可能性があります。

  • ファイル名・パスをそのまま入力に使わず、ID から内部マッピングでパスを解決する
  • どうしても文字列を受け取るなら、realpath() で ベースディレクトリ配下に正規化 してから検証
  • ../ や URL エンコードされた %2e%2e%2f も含めて検査

7. コマンドインジェクション

サーバ上で system() や exec() 系を使うとき、ユーザー入力をシェル経由で渡すと 任意コマンドを実行 できてしまう脆弱性です。

  • シェル経由ではなく 引数配列形式 で実行(subprocess.run([cmd, arg1, arg2]))
  • そもそもシェル呼び出しを避け、ライブラリで実現できないか を先に検討
  • 必要なら入力を 許可リストで厳格にバリデーション(英数のみ、長さ制限)

8. オープンリダイレクト

リダイレクト先 URL をパラメータで受け取る実装で、攻撃者の用意した外部サイトに飛ばされてしまう脆弱性。フィッシングと組み合わせて悪用されます。

  • リダイレクト先は 自サイトのホワイトリスト or 相対パス限定
  • 外部ドメインを許可せざるを得ないときは、確認画面を挟む

9. 認証・セッションの不備

ログイン処理やセッション管理の作りが甘いと、ブルートフォース・セッション固定・トークン漏えい など多面的な攻撃に晒されます。

  • パスワードは bcrypt / argon2 / scrypt でハッシュ化(MD5/SHA1 はNG)
  • ログイン試行に レート制限 + 連続失敗時のアカウントロック
  • セッション ID は ログイン成功時に再発行(セッション固定対策)
  • 多要素認証(MFA)を可能な限り導入
  • JWT を使うなら 有効期限を短く + 署名検証を必ず行う(alg: none 受け入れは厳禁)

10. 認可の不備(IDOR)

「ログインさえしていれば誰でも他人のデータを参照できる」というパターン。URL の ID を書き換えるだけで他人の請求書が見えてしまうような事例は枚挙にいとまがありません。

  • すべてのリソース取得・更新で 「リクエスト元ユーザーがそのリソースの所有者か」 をサーバ側で必ず検証
  • UUID にすれば防げるという誤解は捨てる(推測不可能なだけで認可ではない)
  • 権限制御は 共通モジュール(ポリシー / ガード) に集約し、コードレビューで漏れを検知

11. ファイルアップロードの脆弱性

アップロードされたファイルがそのまま実行可能な場所に保存されると、Web シェルを設置されてサーバを乗っ取られる 危険があります。

  • 拡張子・MIME タイプ・実際のファイルヘッダ(マジックバイト)の3点で検証
  • 保存先は Web 配信ディレクトリ外 または S3 などの別ストレージ
  • ファイル名はサーバ側で ランダム名に書き換え(パストラバーサル防止も兼ねる)
  • 画像なら 再エンコード して保存すれば、埋め込みスクリプトも無効化できる

12. 機密情報の露出

エラーメッセージにスタックトレースや内部 SQL を出す、デバッグモードで本番運用する、API のレスポンスに不要なフィールドを混ぜる…。地味ですが現場でいちばん多い穴です。

  • 本番では 詳細なエラー出力を OFF
    ユーザー向けには汎用メッセージを返す
  • シークレットは 環境変数 or シークレットマネージャ
    リポジトリ・ログに残さない
  • API レスポンスは 明示的に必要なフィールドだけ 返す(モデルをそのままシリアライズしない)
  • S3 バケットや GitHub リポジトリの 公開設定 を定期的に棚卸し

自分のサイトで確かめる

WordPress なら、狙われやすいパスをまとめて叩くと状態が分かります。

$ for p in "wp-json/wp/v2/users" "?author=1" "readme.html" "xmlrpc.php" \
           "wp-content/uploads/" "wp-login.php" "wp-content/debug.log" ".env"; do
    printf "%-28s %s\n" "/$p" \
      "$(curl -s -o /dev/null -w '%{http_code}' -L "https://example.com/$p")"
  done

次の2つが 200 やリダイレクトで返ってくるなら、ユーザー名(ログインIDの候補)が外から取れます。

  • /wp-json/wp/v2/users
    id / name / slug / link / description を JSON で返す。slug がそのままログイン名になっていることが多い
  • /?author=1
    /author/<slug>/ にリダイレクトされる。REST API を塞いでもこちらが残っていると意味がない

ユーザー名が分かること自体は脆弱性ではありません。ただ総当たりの手間が半分になるので、塞げるなら塞いだほうがいい類のものです。9番の「認証・セッションの不備」と組み合わさると効いてきます。

/readme.html は残さない

WordPress を展開したときに付いてくる案内ファイルです。設置したことを忘れがちですが、消しても何も壊れません。 バージョン情報の手がかりになるので、消すか 404 にしておきます。本体を更新すると戻ってくることがあるので、.htaccess などで 404 にするほうが確実です。

直し方:ユーザー名の露出は2か所とも塞ぐ

片方だけ塞いでも意味がないので、REST API とリダイレクトの両方を止めます。

// functions.php:ユーザー一覧のREST APIを未ログインには返さない
add_filter( 'rest_endpoints', function ( $endpoints ) {
    if ( is_user_logged_in() ) {
        return $endpoints;
    }
    unset( $endpoints['/wp/v2/users'] );
    unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
    return $endpoints;
} );

// ?author=N によるユーザー名の割り出しを止める
add_action( 'template_redirect', function () {
    if ( is_author() || isset( $_GET['author'] ) ) {
        wp_safe_redirect( home_url( '/' ), 301 );
        exit;
    }
} );

投稿者アーカイブを使っているサイトでは、後半のリダイレクトは入れられません。その場合は slug をログイン名と別の値にしておく(user_nicename を変える)ほうが現実的です。

13. クリックジャッキング

透明な iframe で自サイトを攻撃者ページ上に重ね、ユーザーに意図しないボタンを押させる攻撃。

  • X-Frame-Options: DENY(または SAMEORIGIN)を設定
  • あわせて Content-Security-Policy の frame-ancestors ディレクティブで防御

14. 依存ライブラリの脆弱性(サプライチェーン)

自前コードよりも、利用している npm / pip / Maven などのライブラリ経由で侵害される事例が増えています。

  • Dependabot / Renovate などで脆弱性アラートを自動化
  • CI に SCA ツール(Snyk / GitHub Advanced Security / Trivy など)を組み込む
  • 未知の作者・ダウンロード数が極端に少ないライブラリは利用前にレビュー

15. セキュリティヘッダの不足

ブラウザ側の防御機構を有効化する HTTP レスポンスヘッダ群。アプリ修正なしで効くものも多いので、最低限まとめて設定しておきます。

  • Strict-Transport-Security
    HTTPS を強制
  • Content-Security-Policy
    スクリプト実行元を制限
  • X-Content-Type-Options: nosniff
    MIME スニッフィング無効化
  • X-Frame-Options
    クリックジャッキング対策
  • Referrer-Policy
    他サイトへの URL 漏えい制御
  • Permissions-Policy
    カメラ・マイク等の権限制御

ヘッダを確かめる

確認は curl -I でヘッダを取って、代表的な9項目があるかを見るだけです。

$ curl -sI https://example.com/ | tr -d '\r' > h.txt
$ for k in strict-transport-security content-security-policy \
           x-frame-options x-content-type-options referrer-policy \
           permissions-policy cross-origin-opener-policy \
           cross-origin-resource-policy x-xss-protection; do
    grep -qi "^$k:" h.txt && echo "OK   $k" || echo "なし $k"
  done

この9項目は「よく確認されるヘッダ」を並べたもので、すべてが必須という意味ではありません。X-XSS-Protection は現在は非推奨で、最近のブラウザは対応していません(付ける場合も 0 を指定するのが一般的です)。Cross-Origin-Opener-Policy と Cross-Origin-Resource-Policy も、クロスオリジン分離が必要なサイト向けの設定です。まず入れるべきなのは、下の直し方に書いた5つです。

直し方:前段(CDN)で足すのが楽

Cloudflare を通しているなら、Transform Rules の「Modify Response Header」で足すのがいちばん手間が少ないです。WordPress 側を触らずに済み、テーマ更新でも消えません。

# Cloudflare の Transform Rules(Modify Response Header)で足す例
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

Content-Security-Policy だけは慎重に。 既存サイトにいきなり入れると広告・解析・埋め込みが動かなくなります。まず Content-Security-Policy-Report-Only で様子を見て、レポートを確認してから本適用するのが安全です。

横断的な備え

個別の脆弱性対策に加えて、運用面の備え も同じくらい重要です。

  • WAF(AWS WAF / Cloudflare WAF)でアプリ前段に共通防御を入れる
  • レート制限 / Bot 対策(API Gateway / Cloudflare Turnstile など)
  • 監査ログ + 異常検知(ログイン失敗、権限昇格、機密 API へのアクセスを記録・通知)
  • 脆弱性診断 / ペネトレーションテスト を定期的に実施
  • インシデント対応プレイブックを平時から準備(連絡フロー、ログ保全、影響範囲調査の手順)

まとめ

Web アプリのセキュリティは 「単一の銀の弾丸」では守れない 領域です。今回紹介した 15 の脅威に対して、対策が複数レイヤで効いている状態(多層防御)を目指すのが現実解です。

最後にチェックリストとして要点をまとめておきます。

  • 入力は 必ずバリデーション、出力は 必ずエスケープ
  • 認証は強く、認可は サーバ側で必ず再検証
  • シークレットは コード・ログに残さない
  • ヘッダ・CORS・CSP など ブラウザ側の防御も使い倒す
  • 依存ライブラリは 自動更新+脆弱性スキャン
  • WAF・ログ・監視で 気付ける仕組み を整える

15項目のうち、コードを書かないと確認できないものと、外から1コマンドで確認できるものが混ざっています。12番・13番・15番あたりは後者で、curl だけで確かめられます。

15項目の一覧を持っていても、自分の環境に当ててみるまでは、どこが抜けているかは分かりません。

チェックリストを作ったら、そのリストを自分の環境に一度当ててみるところまでやらないと意味がない、というのが今回の結論です。

本記事は一覧として継続的に更新していく予定です。「これも入れて」というネタがあればぜひ教えてください。

次に読む記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

わどこんのアバター わどこん

実務12年のバックエンド・インフラエンジニア。バックエンド開発からクラウド・インフラの設計・構築・運用まで担当しています。主要言語は Java・Kotlin・PHP・Python。運用の現場で拾った知見を、再現できる手順に落として残すのがこのブログのテーマです。

目次