Webセキュリティ対策ガイド:6つの層で守るベストプラクティス(2026年版)

Advertisement
Webセキュリティ対策のベストプラクティスとは
Webセキュリティ対策のベストプラクティスとは、Webサイトやwebアプリケーションが乗っ取られたり、改ざんされたり、訪問者への攻撃に悪用されたり、気づかないうちにデータを抜き取られたりするのを防ぐための一連の対策です。1つの製品や1つの設定で完結するものではありません。ドメインレジストラから始まり、DNS、TLS、サーバーが送るHTTPヘッダー、ログインや入力を処理するコード、土台となるサードパーティのパッケージ、サーバーそのもの、そして問題の発生を知らせてくれるログまでを貫く、判断の積み重ねです。
層で考えるべき理由は、攻撃者がそう考えているからです。Verizonの2026年版データ侵害調査報告書(DBIR)によると、侵害の31%はソフトウェアの脆弱性の悪用から始まっており、初めて盗まれた認証情報を抜いて最も多い侵入経路になりました。また、全侵害の48%にランサムウェアが関与しています。パッチの適用漏れが1つ、APIキーの流出が1つ、レート制限のない管理画面が1つあれば、それで十分なのです。このガイドで紹介する対策に特殊なものはありません。侵害されるサイトの多くは、ある層を磨き上げる一方で、別の層の基本を飛ばしてしまったサイトです。
このガイドは外側から内側へと順に構成しています。各対策では、なぜ重要なのか、具体的に何を設定するのか、そしてどう確認するのかを、コマンドや無料ツールとあわせて説明します。HTTPヘッダーチェックやSSLチェックで最もよく見かける失敗は、判断の誤りではなく、誰かが有効だと思い込んだまま一度も確認していなかった設定だからです。
Webセキュリティの全体像:守るべき6つの層
Webサイトへの攻撃は、必ず6つの層のどれかを狙います。以下の表は、このガイド全体の地図です。各層に何があり、典型的にどう攻撃され、どの無料チェックで防御が実際に機能しているかを確認できるかをまとめました。
| 層 | 攻撃者がここで行うこと | 主な対策 | 確認方法 |
|---|---|---|---|
| 1. ドメインとDNS | ドメインの乗っ取り、ネームサーバーの書き換え、放置されたサブドメインの乗っ取り、不正な証明書の取得 | レジストラロック、MFA、DNSSEC、CAA、古いレコードの棚卸し | WHOIS検索、DNSルックアップ、サブドメイン検索 |
| 2. 通信(TLS) | HTTPへのダウングレード、暗号化の除去、古い暗号スイートの悪用、期限切れの証明書 | TLS 1.2以上、preload付きのHSTS、更新の自動化 | SSLチェッカー |
| 3. HTTPヘッダー | スクリプトの注入(XSS)、クリックジャッキングのためのフレーム埋め込み、コンテンツタイプの推測、リファラーの漏えい | CSP、frame-ancestors、nosniff、Referrer-Policy、Permissions-Policy | HTTPヘッダーチェッカー |
| 4. アプリケーション | クレデンシャルスタッフィング、インジェクション、アクセス制御の不備、CSRF、安全でないアップロード | Argon2id+MFA+レート制限、パラメータ化クエリ、サーバー側での認可、SameSite Cookie | コードレビュー、OWASP ZAP、パスワード強度テスト |
| 5. 依存関係 | 悪意あるパッケージ、ライブラリの既知のCVE、CIパイプラインの侵害 | ロックファイル、監査、バージョン固定、SBOM、最小権限のトークン | npm audit、Dependabot、Trivy |
| 6. サーバーと運用 | 開いたままのポート、初期パスワード、未適用のパッチ、バックアップなし、ログなし | ファイアウォール、鍵認証のみのSSH、パッチ適用、3-2-1バックアップ、ログの集中管理 | ポートチェッカー、IPブラックリストチェッカー、ドメインヘルスチェック |
Advertisement
レイヤー1:ドメインとDNSを守る
このガイドのほかのすべての対策は、あなたがまだ自分のドメインを管理できていることを前提にしています。攻撃者がレジストラにログインしたりネームサーバーを書き換えたりできれば、トラフィックを好きな場所へ向け、あなたのドメイン名で有効な証明書を取得し、あなた宛てのメールをすべて読めてしまいます。その間、あなたのアプリケーションのコードは一度も実行されません。だからこそドメインとDNSのセキュリティはWebセキュリティ対策の第一歩であり、その大部分は設定だけで完了します。
まず自分のドメインでWHOIS検索を行い、3つの点を確認してください。ステータスコードにclientTransferProhibitedが含まれていること、有効期限が1年以上先であること、そしてレジストラが実際に自分でアカウントを持っているところであることです。企業のドメインが、以前の外注業者のレジストラアカウントに残ったままになっているケースは意外なほどよくあります。表示されるステータスコードの意味は、WHOIS検索ガイドですべて解説しています。
レジストラロック、MFA、有効期限アラート
レジストラロック(トランスファーロックとも呼ばれます)を有効にして、明示的に解除しない限りドメインをほかのレジストラへ移管できないようにし、レジストラのアカウント自体でも多要素認証(MFA)をオンにしましょう。1つのログインで下流のすべてを支配できるため、レジストラのアカウントはフィッシングの格好の標的です。レジストラが対応していれば、SMSではなくハードウェアキーや認証アプリを使ってください。
ドメインは期限切れにならない支払い方法で自動更新に設定し、そのうえで有効期限の60日前にカレンダーのアラートも入れておきましょう。失効したドメインはドロップキャッチ業者に数時間で取得され、購入者はあなたのメール、トラフィック、そしてそのドメインを信頼しているOAuthログインをすべて引き継ぎます。
レジストリロック(レジストリ側で手動かつ帯域外で行うロック)は、事業が依存しているドメインなら追加料金を払う価値があります。レジストラのアカウントが侵害されても、ネームサーバーの変更を防げます。
役割を分けます。 ドメインの費用を払う人、ドメインを所有するアカウント、DNSプロバイダーをそれぞれ文書化し、特定の社員のメールボックスに紐づけないようにします。
WHOISプライバシーを有効にして、連絡先情報がフィッシングの地図にならないようにします。アカウントには
domains@のような、監視されている役割アドレスを登録しておきましょう。
DNSSECでゾーンに署名する
DNSSECはDNSゾーンに署名し、リゾルバーが偽造された応答を検知できるようにします。DNSSECがなければ、リゾルバーのキャッシュを汚染できる攻撃者は、ブラウザにあなたのドメイン名を表示させたまま訪問者を偽のサーバーへ誘導できます。多くのマネージドDNSプロバイダー(Cloudflare、Route 53、Google Cloud DNS、そして多くのレジストラ)では、スイッチ1つで有効にできます。手作業が必要なのは、レジストラでDSレコードを公開する手順だけです。確認はdigか、ドメインヘルスチェックに含まれるDNSSECチェックで行えます。
dig +dnssec +short yourdomain.com A
# A signed zone returns the record AND an RRSIG line:
# 203.0.113.10
# A 13 2 3600 20261015000000 20260924000000 34505 yourdomain.com. Kx3f...==
dig +short yourdomain.com DS
# A DS record at the parent zone proves the chain of trust is complete
# 34505 13 2 6A1B...C9CAAレコードで証明書の発行元を制限する
CAAレコード(RFC 8659)は、どの認証局(CA)があなたのドメインの証明書を発行してよいかを指定します。すべての公的CAは発行前にこれを確認する義務があるため、CAAレコードがあれば、別のCAのドメイン認証をだました攻撃者を締め出せます。一般的なケースは次の3つのレコードでカバーできます。証明書を発行できるCA、ワイルドカード証明書を発行できるCA、そして拒否された発行リクエストの報告先です。
yourdomain.com. CAA 0 issue "letsencrypt.org"
yourdomain.com. CAA 0 issuewild ";"
yourdomain.com. CAA 0 iodef "mailto:security@yourdomain.com"古いレコードを棚卸しする:サブドメイン乗っ取り
サブドメイン乗っ取りは、利用をやめたサービスをDNSレコードがまだ指しているときに起こります。削除済みのGitHub Pagesサイト、Azureのアプリ、S3バケット、SaaSベンダーのホスト名へのCNAMEなどです。放置されたその宛先は誰でも登録でき、そうなるとold-app.yourdomain.com上で、あなたの名前のまま、有効な証明書付きでコンテンツを配信できてしまいます。レコードの存在を誰も覚えていないため、バグバウンティプログラムで最もよく報告される問題の1つです。
証明書の透明性(CT)ログを読み取るサブドメイン検索にドメインをかけると、これまでに証明書が発行されたすべてのホスト名がわかります。次に、それぞれをDNSルックアップで名前解決し、宛先がNXDOMAINやプロバイダーの「no such app」ページを返すレコードは削除します。これを四半期ごとに繰り返し、サービスを廃止する際の手順にDNSレコードの削除を含めておきましょう。
レイヤー2:HTTPSとTLSを正しく設定する
HTTPSはもはやベストプラクティスではなく、最低限の前提です。ブラウザは素のHTTPのページを「保護されていない通信」と表示します。安全なサイトと単に暗号化されているだけのサイトを今も分けているのは、受け入れるTLSのバージョン、そもそもHTTPで到達できる経路が残っているかどうか、そして2026年3月に始まった証明書有効期間の短縮に耐えられるほど更新が自動化されているかどうかです。
Advertisement
TLS 1.2を最低ラインに、TLS 1.3を優先
TLS 1.0と1.1は2021年にRFC 8996で正式に非推奨となり、現行のブラウザはどれもこれらをネゴシエートしません。サーバーでは無効にし、TLS 1.3(既知の弱い暗号スイートをすべて排除し、1往復でハンドシェイクを完了します)を優先してください。TLS 1.2は、ECDHE-ECDSA-AES128-GCM-SHA256やECDHE-RSA-CHACHA20-POLY1305のようなAEAD暗号スイートに限って残します。
外部からテストするときに重要なのは2行です。ネゴシエートされたプロトコルと、証明書チェーン全体が検証されたことを示すVerify return code: 0です。中間証明書の欠落は、「Chromeでは動くのにcurlやAndroidでは失敗する」という報告の最も一般的な原因です。修正方法はSSL証明書チェーンのガイドで解説しており、SSLチェッカーは不完全なチェーンを直接指摘します。以下はdnsrobot.netに対して実行した例です。
echo | openssl s_client -connect dnsrobot.net:443 -servername dnsrobot.net 2>/dev/null \
| grep -E 'Protocol|Cipher|Verify return'
# Protocol : TLSv1.3
# Cipher : TLS_AES_256_GCM_SHA384
# Verify return code: 0 (ok)
# Confirm the old versions are refused:
openssl s_client -connect dnsrobot.net:443 -tls1_1 2>&1 | grep -iE 'alert|no protocols'HSTS:ブラウザに二度とHTTPを使わせない
HTTPからHTTPSへのリダイレクトだけでは不十分です。訪問者がhttp://yourdomain.comに送る最初のリクエストは依然として平文で送られ、同じネットワーク上の攻撃者はリダイレクトより先に応答できてしまいます。これを解決するのがHTTP Strict Transport Security(HSTS)(RFC 6797)です。一度このヘッダーを受け取ったブラウザは、以後あなたのドメインへのhttp://のURLを、何かを送信する前にすべてhttps://に書き換えます。
max-ageは最低1年(31536000秒)に設定し、すべてのサブドメインがHTTPSで配信できるようになったらincludeSubDomainsを追加します。そのうえでhstspreload.orgにドメインを登録すれば、Chrome、Firefox、Safari、Edgeに最初から組み込まれて配布されます。プリロードによって、初回アクセス時のすき間は完全になくなります。ヘッダーはHTTPヘッダーチェッカーで確認できます。HSTSはA〜Fのセキュリティスコアの評価項目に含まれています。
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload有効期間47日の証明書へ:今すぐ更新を自動化する
CA/Browser Forumの投票SC-081により、すべての公的TLS証明書の最大有効期間は段階的に短縮されています。最初の短縮は2026年3月15日に発効したため、今日購入・更新する証明書はすでに200日に制限されており、2029年にはおよそ6週間ごとに証明書を差し替える必要が出てきます。
| 発効日 | 証明書の最大有効期間 | ドメイン認証の再利用期間 |
|---|---|---|
| 2026年3月15日より前 | 398日 | 398日 |
| 2026年3月15日 | 200日 | 200日 |
| 2027年3月15日 | 100日 | 100日 |
| 2029年3月15日 | 47日 | 10日 |
手動の更新ではこのスケジュールに耐えられません。ベストプラクティスはACMEによる完全自動の発行です。certbot、acme.sh、Caddyを通じたLet's EncryptやGoogle Trust Services、あるいはCloudflare、Vercel、Netlify、多くのホスティングパネルに組み込まれた自動証明書を使いましょう。更新の経路は必要になる前にテストしてください(certbotならcertbot renew --dry-run)。注意すべき失敗は、前回の更新以降に追加されたCAAレコードやファイアウォールのルールが検証をブロックしているケースです。何を使うにしても、有効期限は外部からも監視しましょう。SSLチェッカーは残り日数を表示します。証明書が期限切れになると、すべての訪問が全画面のブラウザ警告に変わってしまいます。
レイヤー3:HTTPセキュリティヘッダー
セキュリティヘッダーは、サーバーがブラウザに対して、ページで何をしてよく、何をしてはいけないかを伝える指示です。どのスクリプトを実行できるか、ページをフレームに埋め込めるか、ブラウザがコンテンツタイプを推測してよいか、訪問者がどこから来たかをほかのサイトにどこまで伝えるか、といった内容です。コストはかからず、すべてのページに一度に適用され、攻撃の種類ごとまとめてブロックできます。一方で、多くのサイトが部分的に間違えている層でもあり、だからこそHTTPヘッダーチェッカーはこれを採点しています。以下は、dnsrobot.netが送信しているヘッダーをセキュリティ関連に絞ったものです。
curl -sI https://dnsrobot.net/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'
strict-transport-security: max-age=31536000; includeSubDomains; preload
content-security-policy: default-src 'self'; script-src 'self' ... https://*.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(self), microphone=(), geolocation=(), payment=(), usb=()すべてのサイトが送るべき7つのヘッダー
一般的なサイトで目指すべき値は次のとおりです。最初の5つが評価を左右するヘッダーで、残りの2つはほかが揃ったあとに手軽に追加できるものです。
| ヘッダー | 推奨値 | 防げる攻撃 |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | プロトコルのダウングレード、HTTP経由でのCookie窃取 |
Content-Security-Policy | nonceベースのscript-src('strict-dynamic'付き)、object-src 'none'、base-uri 'none'、frame-ancestors 'none' | クロスサイトスクリプティング(XSS)、データインジェクション、クリックジャッキング |
X-Content-Type-Options | nosniff | アップロードされたファイルを実行可能なスクリプトに変えてしまうMIMEスニッフィング |
Referrer-Policy | strict-origin-when-cross-origin | 完全なURL(トークンや検索語)の第三者への漏えい |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | サードパーティのスクリプトによる強力なブラウザAPIの無断使用 |
X-Frame-Options | DENY(frame-ancestorsのレガシーな代替) | CSP Level 2に対応していないブラウザでのクリックジャッキング |
Cross-Origin-Opener-Policy | same-origin | window.openerを介したクロスウィンドウ攻撃 |
サイトを壊さずにContent Security Policyを導入する
Content Security Policy(CSP)は、インジェクションのバグが存在していても注入されたスクリプトの実行を止められるため、XSSに対して最も効果的な防御策です。難点は、厳格なポリシーを適用すると、忘れていたインラインスクリプトやサードパーティのタグがすべて動かなくなることです。Googleが推奨し、MDNでも解説されている現在のアプローチはnonceベースのポリシーです。サーバーがレスポンスごとにランダムなnonceを生成して、意図して出力するすべてのscriptタグに付与し、ブラウザはそれ以外のスクリプトをすべて拒否します。
このポリシーを実用的にしているのが'strict-dynamic'です。nonce付きのスクリプトは、個別に許可リストへ載せなくても追加のスクリプト(アナリティクス、タグマネージャー、ウィジェット)を読み込めます。一方、https:と'unsafe-inline'のトークンは最新のブラウザでは無視され、古いブラウザ向けのフォールバックとしてのみ機能します。まずContent-Security-Policy-Report-Onlyで展開し、1週間ほど違反レポートを確認してから強制モードに切り替えましょう。Next.js、Rails、Djangoなどのフレームワークは公式にnonceをサポートしています。静的サイトでは、代わりにハッシュベースのscript-srcを使えます。
Content-Security-Policy:
script-src 'nonce-r4nd0m1z3dV4lu3' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
report-to csp-endpoint
<!-- Only scripts carrying the nonce run; strict-dynamic lets them load their own dependencies -->
<script nonce="r4nd0m1z3dV4lu3" src="/app.js"></script>クリックジャッキング対策:frame-ancestorsとX-Frame-Options
クリックジャッキングは、攻撃者のページの中にあなたのサイトを見えない状態で読み込み、訪問者に見えないボタンをクリックさせる攻撃です。CSPのframe-ancestors 'none'(自サイトのページを埋め込む場合は'self')で現行のすべてのブラウザで防げ、X-Frame-Options: DENYで古いブラウザもカバーできます。埋め込み可能なウィジェットを意図的に提供する場合は、そのパスだけを、必要なオリジンに対してだけ除外してください。X-Frame-Optionsガイドでは、具体的なサーバーのルールと、テスト中に遭遇する「refused to connect」エラーを詳しく解説しています。
nosniff、Referrer-Policy、Permissions-Policy、COOP
X-Content-Type-Options: nosniffは、ブラウザがContent-Typeを勝手に推測し直すのを防ぎます。この推測こそが、HTMLを含むユーザーアップロードの「画像」を実行可能なページに変えてしまう原因です。Referrer-Policy: strict-origin-when-cross-origin(現行ブラウザのデフォルトですが、明示的に設定しましょう)は、ほかのサイトにはオリジンだけを送るため、URLに含まれるパスワードリセット用のトークンや検索クエリが外部に漏れません。Permissions-Policyは使っていないブラウザ機能を無効にするので、侵害されたサードパーティのスクリプトがカメラを起動したり位置情報を読み取ったりできなくなります。Cross-Origin-Opener-Policy: same-originは、開いたページとのwindow.openerのつながりを切断し、クロスウィンドウ攻撃の一群をブロックします。
削除すべきヘッダーも重要です。ServerとX-Powered-Byは正確なソフトウェアのバージョンを脆弱性スキャナーに知らせてしまいます。X-XSS-Protectionは非推奨で、古いブラウザではバグの原因になりうるため、0に設定するか削除してください。CMS検出ツールを使えば、スキャナーがヘッダーだけからあなたのスタックについて何を把握できるかがわかります。
レイヤー4:クレデンシャルスタッフィングに耐える認証
攻撃者がパスワードを1つずつ推測することはほとんどありません。ほかのサイトから流出した何十億ものメールアドレスとパスワードの組み合わせを使い回し(クレデンシャルスタッフィング)、残りはフィッシングで盗みます。そのため認証のベストプラクティスは3つの要素からなります。データベースが流出してもパスワードは流出しないように保存すること、盗まれたパスワード単体では役に立たないようにすること、そしてブルートフォースを意味がないほど遅くすることです。OWASP Top 10:2025では、Authentication Failures(認証の失敗)がA07に位置づけられています。
Advertisement
パスワードはArgon2idまたはbcryptでハッシュ化する
パスワードをそのまま保存してはいけませんし、高速なハッシュで保存してもいけません。MD5、SHA-1、さらにはSHA-256も高速に計算できるよう設計されているため、流出したハッシュの一覧は1枚のGPUで毎秒数十億回の推測にかけられます。パスワードごとに固有のソルトを付けたうえで、低速でメモリ負荷の高いパスワードハッシュ関数を使いましょう。OWASP Password Storage Cheat Sheetは、次の順で推奨しています。
Argon2id:メモリ最低19 MiB、反復回数2、並列度1(または46 MiBで反復回数1)。
scrypt:Argon2が使えない場合に、N = 2^17、r = 8、p = 1で使用。
bcrypt:ワークファクター10以上(入力が72バイトまでという制限に注意。長いパスワードを事前ハッシュする場合は慎重な実装が必要です)。
PBKDF2-HMAC-SHA256:FIPS準拠が必須の場合に限り、反復回数600,000回で使用。
// Node.js with the argon2 package
import argon2 from "argon2";
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // KiB = 19 MiB
timeCost: 2,
parallelism: 1,
});
// Later, on login:
const ok = await argon2.verify(hash, submittedPassword);本当に役立つパスワードルール(NIST SP 800-63B)
多くのサイトがいまだに強制している文字種の組み合わせルール(大文字を1つ、記号を1つ、90日ごとに変更)は、Summer2026!のような予測しやすいパスワードを生み、使い回しを促してしまいます。現在のNIST SP 800-63Bのガイダンスは、これらを本当に重要な点を測るルールに置き換えています。
MFAも必須にする場合は最低8文字、パスワード単体で使う場合は最低15文字とし、少なくとも64文字まで入力できるようにします。
文字種の組み合わせルールは設けず、定期的な変更も強制しません。変更を求めるのは、侵害の証拠がある場合だけです。
新しいパスワードはすべて流出リストと照合し(Have I Been Pwnedのrange APIを使えば、パスワード自体を送信せずに照合できます)、既知の流出パスワードは拒否します。
貼り付けやパスワードマネージャーを許可し、スペースやUnicodeも受け付け、ルールではなくエントロピーにもとづく強度メーターを表示します。
パスワード強度テストを使うと、これらの指標が古いルールとどう違うかがわかります。エントロピーとパターン検出から解読にかかる時間を推定し、流出データとも照合するため、赤字の「記号が必要です」というメッセージよりはるかに役立つフィードバックが得られます。
MFAとパスキー
多要素認証を導入すれば、盗まれたパスワードは行き止まりになります。すべてのユーザーに提供し、管理者、経理、サポートの役割では必須にしましょう。選択肢はフィッシング耐性で順位づけします。パスキーとFIDO2セキュリティキーは、認証情報が正規のオリジンに結びついているためフィッシングされません。認証アプリ(TOTP)は良い選択肢です。SMSコードはないよりはましですが、SIMスワップによって傍受されるおそれがあります。パスキーは現行のすべてのブラウザとOSでサポートされており、パスワードそのものをなくせるため、クレデンシャルスタッフィングという攻撃カテゴリー自体も消えます。
アカウント復旧の経路もログインと同じくらい慎重に守りましょう。リカバリーコードはハッシュ化して保存し、メールによるリセットには15分以内に失効する使い捨てトークンを使い、秘密の質問は使わないようにします。
レート制限、アカウントロック、ボット対策
ログイン、登録、パスワードリセット、MFAの各エンドポイントには、IPごとかつアカウントごとにレート制限をかけ、上限に達したら429 Too Many RequestsをRetry-Afterヘッダー付きで返します(クライアント側の対応はHTTPエラー429のガイドで解説しています)。これに加えて、1つのアカウントで失敗が続いた場合は段階的に遅延を増やすか一時的にロックし、分散型の攻撃を受けるエンドポイントにはCAPTCHAやプルーフ・オブ・ワークのチャレンジを導入します。失敗したログインはすべて送信元IPとともに記録しておけば、クレデンシャルスタッフィングのパターンを数か月後ではなく数分で見つけられます。
# nginx: 5 login attempts per minute per client IP
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /api/login {
limit_req zone=login burst=4 nodelay;
limit_req_status 429;
proxy_pass http://app;
}セッション、Cookie、CSRF
ログイン後は、セッションCookieがユーザーそのものです。したがって、パスワードと同等の保護が必要です。3つのCookie属性と1つの名前プレフィックスで、対策のほとんどをまかなえます。
Secureを付けると、Cookieは平文のHTTPで送信されなくなるため、公衆Wi-Fiで盗聴されません。HttpOnlyを付けるとJavaScriptから見えなくなるため、XSSのバグがあってもCookieを読み取られません。SameSite=Lax(管理画面ならStrict)を付けると、クロスサイトのPOSTリクエストでCookieが送られなくなり、ほとんどのCSRF攻撃を無力化できます。__Host-プレフィックスを付けると、Secureがあり、Path=/で、Domain属性がない場合を除いてブラウザがCookieを拒否するため、サブドメインから上書きされなくなります。
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800CSRF:SameSiteの背後にもう1つの防御線を
クロスサイトリクエストフォージェリ(CSRF)は、ログイン中のブラウザをだまし、別のページからあなたのサイトへ状態を変更するリクエストを送信させる攻撃です。SameSite Cookieで一般的なケースは防げますが、GET以外のすべてのリクエストにはもう1つ防御を残しておきましょう。hiddenフィールドやカスタムヘッダーに入れるセッションごとのCSRF対策トークンか、Origin(またはSec-Fetch-Site)ヘッダーが自サイトのオリジンと一致するかのチェックです。ほとんどのフレームワーク(Django、Rails、Laravel、Next.jsのServer Actions)はデフォルトでこれを行います。よくある失敗は、APIエンドポイントでこれを無効にしたうえで、そのエンドポイントをブラウザから呼び出すことです。
セッションID、ローテーション、JWT
セッションIDは少なくとも128ビットのランダム性をもって生成し、セッション固定攻撃を防ぐためにログイン時と権限が変わるたびにIDをローテーションし、一定時間操作がなければセッションを失効させ、サーバー側でセッションを無効化する「すべてのデバイスからログアウト」ボタンを用意しましょう。JWTは有効期間を短くし(数分程度にして、失効させられるリフレッシュトークンと組み合わせます)、どのスクリプトからも読めるlocalStorageには決して保存しないでください。署名鍵が漏れたら完全な侵害として扱います。その鍵でこれまでに署名されたすべてのトークンが偽造可能になるからです。
インジェクションとXSS:入力を検証し、出力をエンコードする
インジェクション(Injection、A05:2025)とクロスサイトスクリプティングは、場所が違うだけで同じ間違いです。ユーザーからのデータを、まるでコードであるかのようにインタープリター(SQL、シェル、LDAPクエリ、HTMLページ)に渡してしまうことです。対策もどこでも同じで、データとコードを分離し、文字列の連結でコマンドを組み立てないことです。
Advertisement
文字列を組み立てず、パラメータ化クエリを使う
SQLでは、プリペアドステートメントか、それを生成するORMを使いましょう。そうすればデータベースは入力を値として扱い、クエリの構造が変わることはありません。同じルールはシェルコマンド(文字列ではなく引数の配列で渡す)、NoSQLのフィルター(ユーザー入力に含まれる{"$gt": ""}のような演算子オブジェクトを拒否する)、ファイルパス(パスを解決し、想定したディレクトリの配下に収まっていることを確認する)にも当てはまります。
// Vulnerable: the input becomes part of the query text
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Safe: the driver sends the value separately from the query
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);
// Python / psycopg: same idea
cur.execute("SELECT * FROM users WHERE email = %s", (email,))出力エンコードでXSSを防ぐ
クロスサイトスクリプティングは、ユーザーが制御できるテキストを、出力先のコンテキストに合わせてエンコードせずにページへ書き込んだときに発生します。最近のテンプレートエンジン(ReactとJSX、Vue、Jinja2、Blade、ERB)はデフォルトでエンコードするため、現在のXSSの多くは抜け道から生まれます。dangerouslySetInnerHTML、v-html、|safe、innerHTML、そしてユーザーデータからURLやインラインのイベントハンドラーを組み立てる処理です。出力先のコンテキストに正確に合わせてエンコードし、リッチなHTMLは正規表現ではなくDOMPurifyのような専用ライブラリでサニタイズしましょう。
| データの出力先 | エンコード方法 | 例 |
|---|---|---|
| HTML本文 | HTMLエンティティ | <は<に、"は"に変換 |
| HTML属性 | 属性値を引用符で囲んだうえでのエンティティ化 | 属性値は必ず引用符で囲み、"と'をエンコード |
| JavaScript | スクリプトのテキストに直接埋め込まない。data-属性かJSONの<script type="application/json">ブロック経由でデータを渡す | 文字列内の</script>は\u003c/script\u003eに変換 |
| URLパラメータ | パーセントエンコーディング | encodeURIComponent(value) |
| CSSの値 | 一切使わない。クラス名を許可リストから選ぶ | ユーザー入力をstyleに埋め込まない |
SSRF、ファイルアップロード、デシリアライズ
サーバーサイドリクエストフォージェリ(SSRF):サーバーがユーザーから指定されたURLを取得する場合(Webhook、画像プロキシ、リンクプレビューなど)、攻撃者はその宛先を内部サービスやクラウドのメタデータエンドポイント169.254.169.254に向け、認証情報を読み取れます。まずホスト名を名前解決し、プライベートおよびリンクローカルの範囲を拒否し、リダイレクトを無効にし、取得処理は内部の何にも到達できないネットワークセグメントに置きましょう。
ファイルアップロード:ファイルの種類は、拡張子やクライアントが送るContent-Typeではなく、中身を調べて検証します。サイズの上限を設け、ファイルはWebルートの外かオブジェクトストレージに、自分で生成したランダムな名前で保存し、可能であればnosniffとContent-Disposition: attachmentを付けて別のオリジンから配信します。アップロードされたファイルが、Webサーバーに実行される場所に置かれることがあってはなりません。
デシリアライズとマスアサインメント:信頼できないデータを、任意のクラスをインスタンス化できる形式(Javaのネイティブシリアライズ、Pythonのpickle、PHPのunserialize、カスタムタグ付きのYAML)でデシリアライズしてはいけません。スキーマ付きのJSONを使いましょう。リクエストボディは明示的な許可リストのフィールドにだけバインドし、たまたまそのカラムを持つモデルにユーザーが"role": "admin"をPOSTできないようにします。
アクセス制御の不備(Broken Access Control):第1位のリスク
Broken Access Control(アクセス制御の不備)は2021年からOWASP Top 10の首位を守り続け、A01:2025でもその座にあります。これは単一のバグではなく、習慣の問題です。リクエストのたびに、相手が誰か(認証)は確認しても、その人が何に触れてよいか(認可)の確認を忘れてしまうのです。典型的な形が安全でない直接オブジェクト参照(IDOR)です。GET /api/invoices/1042は請求書の所有者には機能しますが、番号を書き換えた誰にでも機能してしまいます。
デフォルトで拒否します。 すべてのルートでアクセスを許可する明示的なルールを必須にし、ルールがなければ200ではなく403を返します。
サーバー側で、オブジェクトごとに強制します。 UIでボタンを隠すのはアクセス制御ではありません。一括処理のエンドポイント、エクスポート、バックグラウンドジョブも含め、すべての読み書きで、現在のユーザーがその特定のレコードを所有しているか、アクセスを許可されているかを確認します。
役立つ場面では推測困難なID(UUID)を使いますが、それに頼ってはいけません。隠すことは認可ではありません。
CORSを厳格にします。 認証情報付きの
Access-Control-Allow-Origin: *や、リクエストが送ってきたOriginをそのまま返す設定は、ユーザーが訪れるあらゆるWebサイトにAPIを明け渡すことになります。ディレクトリ一覧表示を無効にし、
.git、.env、バックアップファイル、設定ファイルへのアクセスをWebサーバーのレベルでブロックします。管理画面は公開インターネットから切り離すか、MFAとIP許可リストで保護します。認可の失敗にもレート制限をかけ、ログに記録します。 1つのセッションから403が連続するのは、列挙攻撃が進行中のサインです。
// Express: ownership check on every object access
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
if (!invoice || invoice.ownerId !== req.user.id) {
return res.status(404).end(); // 404, not 403: don't confirm the record exists
}
res.json(invoice);
});レイヤー5:依存関係とソフトウェアサプライチェーン
一般的なwebアプリケーションで、自分で書いたコードはおそらく5%程度で、残りの95%はダウンロードしたコードです。だからこそ2025年版のOWASP Top 10ではSoftware Supply Chain Failures(ソフトウェアサプライチェーンの失敗)がA03に初登場し、2026年のDBIRでは脆弱性の悪用が盗まれた認証情報を上回りました。2025年9月には、フィッシングの被害にあった1人のメンテナーを通じて、chalk、debug、そしてほかの16のnpmパッケージ(合計で週20億回以上ダウンロードされています)に暗号資産を盗むマルウェアが仕込まれました。その期間中にバージョンを固定せずにインストールしたプロジェクトは、すべて自動的にそれを取り込んでしまいました。
ロックファイルをコミットし(
package-lock.json、yarn.lock、pnpm-lock.yaml、poetry.lock、go.sum)、CIではnpm ciでインストールします。これでビルドの再現性が保たれ、上流の新しいリリースがレビューなしに紛れ込むことを防げます。継続的にスキャンします:
npm audit、pip-audit、bundler-audit、更新にはGitHub DependabotやRenovate、OSレイヤーにはTrivyやGrypeのようなコンテナスキャナーを使います。セキュリティ以外のアップグレードは数日遅らせます(Renovateの
minimumReleaseAge)。そうすれば、悪意あるリリースは多くの場合、取り込む前に取り下げられます。ただし、セキュリティパッチはすぐに適用してください。CIではサードパーティのアクションやスクリプトをコミットSHAに固定し、公開CDNから読み込むスクリプトにはすべてSubresource Integrity(
integrity="sha384-...")を付けます。CIには最小権限を与えます:可能な限り読み取り専用のトークンを使い、長期間有効なシークレットの代わりに短命のOIDC認証情報を使い、公開(publish)の権限はリリース用のジョブだけに限定します。
SBOMを生成します(CycloneDXまたはSPDX)。ビルド時に生成しておけば、次にLog4Shell級のCVEが公表されたとき、「自分たちは影響を受けるのか」に数分で答えられます。
# Reproducible install + audit in CI
npm ci --ignore-scripts
npm audit --audit-level=high
# Pin a GitHub Action to a commit, not a floating tag
# uses: actions/checkout@v4 <- can change underneath you
# uses: actions/checkout@<full-commit-sha> # v4.2.2
# Subresource Integrity for a CDN script (hash from: openssl dgst -sha384 -binary lib.min.js | openssl base64 -A)
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-<base64-hash>"
crossorigin="anonymous"></script>WordPressなどのCMSを運用している場合も、同じルールがプラグインとテーマに当てはまります。CMSの侵害の多くはそこから始まるからです。コアとすべてのプラグインを最新に保ち、使っていないものは削除し、スキャナーがバージョンについて何を把握できるかをCMS検出ツールで確認しましょう。
レイヤー6:サーバーのハードニング(ポート、パッチ、シークレット、バックアップ)
その下のサーバーがSSHでパスワードログインを受け付けていたり、データベースを公開ポートで動かしていたり、構築以来一度もパッチを当てていなかったりすれば、アプリケーション層の対策は意味をなしません。サーバーのベストプラクティスは地味ですが、ランサムウェアが侵入してくるのはまさにここです。
提供していないポートはすべて閉じる
公開Webサーバーで開けておく必要があるのは、80番と443番のポート、そして自社のIP範囲からのSSHだけです。データベース(3306、5432、27017)、Redis(6379)、Elasticsearch(9200)、管理画面、メトリクスのエンドポイントは、localhostかプライベートネットワークにのみバインドしましょう。変更のたびにポートチェッカーで外部から確認してください。それが攻撃者から見える姿であり、クラウドのセキュリティグループは読み違えやすいからです。
# Ubuntu: default-deny firewall, allow only web + SSH from your office range
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable
# SSH: keys only, no root password login
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sudo systemctl reload sshパッチは定期的に適用し、シークレットはコードに入れない
OSのセキュリティ更新を自動適用するよう設定し、ランタイムとフレームワークのセキュリティメーリングリストを購読し、インターネットに公開しているものの重大なCVEは当日中に対応すべき仕事として扱いましょう。サービスは非特権ユーザーとして、コンテナ内またはsystemdのサンドボックス機能を使って実行し、侵害されたプロセスがマシン全体を読み取れないようにします。
シークレット(データベースのパスワード、APIキー、署名鍵)は、リポジトリにも、Dockerイメージのレイヤーにも、クライアント側のJavaScriptにも置いてはいけません。デプロイ時に注入される環境変数かシークレットマネージャーから読み込み、アクセス権を持つ人が退職したらローテーションし、gitleaksのようなツールでリポジトリの履歴をスキャンしてください。一度コミットされた鍵は、コミットを削除したあとでも永久に漏えいしたものと見なすべきだからです。
ランサムウェアに耐えるバックアップと、前段のCDN
3-2-1ルールに従いましょう。コピーを3つ、2種類の異なるメディアに、1つはオフサイトに保管し、少なくとも1つはイミュータブル(変更不可)またはオフラインにして、サーバーに到達したランサムウェアがバックアップまで暗号化できないようにします。バックアップは暗号化し、データベースとアップロードディレクトリを含め、そして何より、定期的にリストアをテストしてください。誰も復元を試したことのないバックアップは、計画ではなく願望にすぎません。インシデントが一時的な障害で済むか、会社の存続にかかわる事態になるかを決めるのは復旧時間です。
オリジンの前段にCDNまたはWAF(Cloudflare、Fastly、WAF付きのAWS CloudFront、またはホスティング会社の同等サービス)を置きましょう。大量のトラフィックによるDDoSを吸収し、一般的なインジェクションのペイロードや悪質なボットに対するマネージドルールを適用し、オリジンのIPを隠し、アプリケーションに手を加えずにレート制限や地域ルールを適用する場所になります。そのうえで、CDNのIP範囲からしかポート443に直接到達できないようにオリジンのファイアウォールを制限します。そうしないと、攻撃者は単にCDNを迂回するだけです。ホスティングチェッカーを使えば、CDNの背後にあるサイトの実際のオリジンが露出しているかどうかがわかります。
メール認証:SPF、DKIM、DMARC
Webセキュリティには、パスワードリセット、請求書、サポートの返信を運ぶメールも含まれます。SPF、DKIM、DMARCがなければ、誰でもbilling@yourdomain.comとしてメールを送れてしまいます。また2024年2月以降、GoogleとYahooは大量送信者にこの3つすべてを必須としています。必要なのは次の3つのDNS TXTレコードです。
yourdomain.com. TXT "v=spf1 include:_spf.google.com -all"
google._domainkey.yourdomain.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
_dmarc.yourdomain.com. TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s"DMARCはまずp=noneで始めてレポートを収集し、正規の送信元(マーケティングプラットフォーム、ヘルプデスク、CRM)がすべてアライメントに合格したら、p=quarantine、さらにp=rejectへと移行します。MTA-STSとTLS-RPTのレコードを追加してメールサーバーへの暗号化された配送を強制し、メールを一切送信しないドメインにはnull MX(MX 0 .)を公開して、なりすましを完全に防ぎましょう。各レコードはSPFチェッカー、DKIMチェッカー、DMARCチェッカーで確認し、到達率が落ちたらIPブラックリストチェッカーで送信IPがブロックリストに載っていないか確認してください。
ログを取り、監視し、侵害に備える
OWASP 2025の10カテゴリーのうち2つは、ミスが起きたあとの対応に関するものです。Security Logging and Alerting Failures(セキュリティログとアラートの失敗、A09)と、新たに加わったMishandling of Exceptional Conditions(例外的な状況の不適切な処理、A10)です。典型的な侵害がいまだに何か月も発見されないのは、証拠がそもそも記録されていないか、記録されていても誰も見ていないからです。
セキュリティイベントを文脈付きで記録します:ログインの成功と失敗、MFAの変更、パスワードリセット、権限の変更、アクセス制御による拒否、入力検証の失敗、管理者の操作をすべて、タイムスタンプ、ユーザーID、送信元IP、ユーザーエージェントとともに記録します。
シークレットは決してログに残しません:パスワード、セッショントークン、カード番号全体、APIキーなどです。呼び出し箇所ごとではなく、ロギング層でマスクしましょう。
ログはサーバーの外に送ります:集中管理用のストア(CloudWatch、Loki、Elastic、SIEM)に少なくとも90日間保持し、root権限を得た攻撃者でも痕跡を消せないようにします。
単発のイベントではなくパターンでアラートを出します:1分間に50回のログイン失敗、1つのセッションからの403の急増、営業時間外に作成された新しい管理者、自分で申請していないドメインの証明書の発行などです。
フェイルクローズにします:下流のチェック(認証サービス、レート制限、WAF)がエラーになったら、リクエストを拒否します。A10が存在するのは、本来ブロックするはずのチェックが例外を投げたときにアクセスを許可してしまうシステムが非常に多いからです。
外部から監視します:稼働状況、証明書の有効期限、DNSの変更、ブロックリストへの掲載、ヘッダーの後退(リグレッション)を監視します。ドメインヘルスチェックは、DNS、SSL、メール認証、ヘッダーのチェックを1つのスコアにまとめており、デプロイのたびに再実行できます。
インシデント対応計画は必要になる前に書く
誰がオンコールを担当するか、すべての認証情報をどうローテーションするか、サイトをどう読み取り専用にするか、クリーンなバックアップはどこにあるか、どの規制当局や顧客に何時間以内に通知しなければならないか(GDPRでは72時間)を、事前に決めておきましょう。年に1回は机上演習(テーブルトップ演習)を実施してください。計画をリハーサルしたチームは数日で復旧し、そうでないチームは何週間も場当たり的な対応を続けることになります。
テストする:スキャナー、ペネトレーションテスト、継続的なチェック
ここまでの対策はすべて検証でき、その検証は形骸化しないよう可能な限り自動化すべきです。無料ですぐにできるものから、定期的に行う有料のものまで、現実的な段階は次のとおりです。
| チェック項目 | ツール | 頻度 |
|---|---|---|
| TLSの設定、チェーン、有効期限 | SSLチェッカー、SSL Labs、testssl.sh | 証明書やサーバーを変更するたび。有効期限は毎日 |
| セキュリティヘッダーとCSP | HTTPヘッダーチェッカー、MDN HTTP Observatory | デプロイのたび(CIに組み込む) |
| 開いているポート | ポートチェッカー、nmap | ファイアウォールやクラウドの設定を変更するたび |
| DNS、DNSSEC、メール認証 | ドメインヘルスチェック、DMARCチェッカー | 毎月、およびDNSを編集するたび |
| 放置されたサブドメイン | サブドメイン検索 | 四半期ごと、およびサービスを廃止するたび |
| 既知の脆弱性がある依存関係 | npm audit、Dependabot、Trivy | ビルドのたび |
| コードレベルの欠陥(SAST) | Semgrep、CodeQL | プルリクエストのたび |
| 実行時のWeb脆弱性(DAST) | OWASP ZAP、Nuclei | ステージング環境に対して毎週 |
| 認可とビジネスロジック | 手動のペネトレーションテストまたはバグバウンティプログラム | 年1回、および大きな機能の追加後 |
スキャナーはインジェクション、ヘッダー、古いコンポーネントの検出は得意ですが、認可やビジネスロジックは苦手です。お金や個人データを扱うものについては、少なくとも年1回は人による検査の予算を確保しましょう。修正は露出度の高い順に行います。インターネットから認証なしで悪用できるものが最優先です。
Webセキュリティ対策チェックリスト
これをプロジェクトのREADMEやチケット管理ツールにコピーしてください。各行は上で紹介した対策の1つで、完了か未完了かを判断できる形で書いています。最後の列がその証拠です。
| 層 | 対策 | 完了の基準 |
|---|---|---|
| ドメイン | レジストラロックとレジストラアカウントのMFA、自動更新をオン | WHOISにclientTransferProhibitedが表示され、有効期限が12か月以上先 |
| DNS | DNSSECで署名済み、自分のCAだけを記載したCAAレコード | dig +dnssecがRRSIGを返し、親ゾーンにDSレコードがある |
| DNS | 放置されたCNAMEレコードやAレコードがない | サブドメイン検索で見つかったすべてのホスト名が、自分で管理しているものに解決される |
| TLS | TLS 1.2以上のみ、TLS 1.3を優先、完全なチェーンを配信 | SSLチェッカーでチェーンが完全、TLS 1.0/1.1が拒否される |
| TLS | max-age 1年、includeSubDomains、プリロード済みのHSTS | hstspreload.orgに登録されている |
| TLS | ACMEで更新を自動化、有効期限を監視 | certbot renew --dry-runが成功し、期限14日前のアラートを設定済み |
| ヘッダー | CSP(nonceベース)、frame-ancestors、nosniff、Referrer-Policy、Permissions-Policy | HTTPヘッダーチェッカーの評価がA |
| 認証 | Argon2idまたはbcryptでのハッシュ化、NISTのパスワードルール、流出リストとの照合 | ハッシュ1回に100〜250ミリ秒かかり、文字種の組み合わせルールがない |
| 認証 | MFAを全員に提供し管理者には必須、パスキーに対応 | 第2要素なしでは管理者としてログインできない |
| 認証 | ログイン、リセット、MFAのエンドポイントにIPごと・アカウントごとのレート制限 | 1分以内の6回目の試行で429が返る |
| セッション | Secure・HttpOnly・SameSite付きの__Host- Cookie、ログイン時にIDをローテーション | DevToolsでCookie属性が確認でき、ログイン後は古いセッションが無効になる |
| 入力 | すべてのクエリをパラメータ化、コンテキストごとに出力をエンコード、アップロードは中身で検証 | コード検索で文字列連結のクエリが見つからず、XSSのペイロードがテキストとして表示される |
| アクセス制御 | デフォルト拒否のルーティング、オブジェクトごとの所有者チェック、厳格なCORS | 別のユーザーのIDで再送すると404または403が返る |
| 依存関係 | ロックファイルをコミット、npm ci、CIで監査、アクションをSHAで固定、CDNスクリプトにSRI | 深刻度の高いCVEがあるとビルドが失敗する |
| サーバー | 公開は80番と443番のみ、SSHは鍵認証のみ、セキュリティ更新の自動適用、シークレットは環境変数かVaultに保管 | ポートチェッカーで22、3306、6379番がインターネットから閉じている |
| バックアップ | イミュータブルなコピー1つを含む3-2-1、リストアをテスト済み | 最後に成功したリストアテストが90日以内 |
| メール | SPFは-all、DKIM、DMARCはp=reject、MTA-STS | DMARCチェッカーで合格し、集計レポートが届いている |
| 監視 | 認証・認可のイベントを集中的に記録、パターンでアラート、稼働状況・証明書・DNSを外部から監視 | テスト用のアラートが発報され、受信できた |
| 対応 | インシデント対応計画を文書化、security.txtを公開、机上演習を実施 | 直近12か月以内に計画を見直している |
OWASP Top 10:2025との対応表
OWASP Top 10は、webアプリケーションのリスクを示すリストとして最も多く引用されており、2025年版では順位が入れ替わり、2つの新しいカテゴリーが加わりました。顧客や監査人、コンプライアンスのフレームワークからどう対処しているかを聞かれた場合は、このガイドの対策との対応表として次を使ってください。
| OWASP Top 10:2025 | このガイドでの対策 |
|---|---|
| A01 Broken Access Control(アクセス制御の不備) | デフォルト拒否、オブジェクトごとのチェック、厳格なCORS、管理画面の保護 |
| A02 Security Misconfiguration(セキュリティ設定のミス) | セキュリティヘッダー、HSTS、ポートの閉鎖、バージョンヘッダーの削除、ディレクトリ一覧表示の無効化 |
| A03 Software Supply Chain Failures(ソフトウェアサプライチェーンの失敗、新規) | ロックファイル、監査、アクションの固定、SRI、SBOM、最小権限のCI |
| A04 Cryptographic Failures(暗号化の失敗) | TLS 1.2以上と1.3、HSTS、Argon2id、シークレット管理、バックアップの暗号化 |
| A05 Injection(インジェクション) | パラメータ化クエリ、出力エンコード、CSP、アップロードの検証 |
| A06 Insecure Design(安全でない設計) | 層ごとの脅威モデル、フェイルクローズのデフォルト設定、設計段階からのレート制限 |
| A07 Authentication Failures(認証の失敗) | NISTのパスワードルール、流出リストとの照合、MFAとパスキー、セッションのローテーション |
| A08 Software or Data Integrity Failures(ソフトウェアまたはデータの整合性の失敗) | SRI、署名・固定された依存関係、安全なデシリアライズ |
| A09 Security Logging and Alerting Failures(セキュリティログとアラートの失敗) | ログの集中管理、パターンによるアラート、外部からの監視 |
| A10 Mishandling of Exceptional Conditions(例外的な状況の不適切な処理、新規) | フェイルクローズ、統一されたエラー応答、ユーザーにスタックトレースを見せない |
サイトのセキュリティヘッダーを数秒でチェック
DNS Robotの無料HTTPヘッダーチェッカーは、任意のURLを取得してセキュリティヘッダーをA〜Fで評価します。HSTS、Content-Security-Policy、X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Permissions-Policyについて、実際に見つかった値と不足しているものを表示します。登録は不要です。
試す HTTPヘッダーチェッカーAdvertisement
Webセキュリティ対策のよくある質問
最新のTLSとHSTSでHTTPSを強制し、厳格なセキュリティヘッダー(特にContent Security Policy)を送信し、パスワードはArgon2idでハッシュ化したうえでMFAとレート制限で守り、パラメータ化クエリと出力エンコードを使い、すべてのオブジェクトについてサーバー側でアクセス制御を行い、依存関係をパッチ適用済みかつバージョン固定の状態に保ち、使っていないポートを閉じ、セキュリティイベントを集中的に記録することです。ただし最初に取り組むべきはドメインとDNSの保護です。ほかのすべては、自分のドメイン名を管理し続けられていることが前提だからです。