結論(認証後コンテンツを静的ファイルで直接配信していない)
認証後コンテンツを静的ファイルで直接配信していないかとは、ログイン画面で照合したあと、JavaScriptやフォームでmenu.htmやdata.txtのような静的ファイルへ遷移させる構成になっていないかを確かめる診断項目です。静的ファイル自体には認証が掛からないため、URLを知れば誰でも直接取得できます。保護コンテンツはサーバー側でセッションを検証する経路からのみ返します。
この記事のポイント
- Webサーバーは.htm・.html・.txtなどの静的ファイルを、サーバー設定で認証を掛けない限り誰にでもそのまま返します。
- パスワード照合をサーバーで行っていても、照合後の遷移先が静的ファイルなら保護になりません(WSTG-ATHN-04のDirect Page Request)。
- 推測されにくいファイル名、robots.txtでの除外、noindexは、いずれもアクセスを防ぎません。
- セキュリティAIチェッカーは「認証の入口」と「スクリプトやフォームによる静的ファイルへの遷移」が同じトップページにあると△(要確認)と判定します。
- 対策は、実ファイルを公開領域の外へ移し、セッション検証後に配信するスクリプト経由で返し、旧静的URLは404か403にすることです。
| 診断項目名 | 認証後コンテンツを静的ファイルで直接配信していない |
|---|---|
| 項目ID | auth.static-content-signal |
| 分類 | 認証・アクセス制御 |
| 重大度・配点の重み | 最重要(critical) 重み 5 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V8.2.1(機能単位のアクセスを明示的な権限を持つ利用者に限定)、V8.3.1(信頼できるサービス層で認可を強制)/OWASP WSTG-ATHN-04(Bypassing Authentication Schema:Direct Page Request/Forced Browsing)、WSTG-ATHZ-02/CWE-425(Direct Request ('Forced Browsing'))、CWE-602/IPA「安全なウェブサイトの作り方」1.11 アクセス制御や認可制御の欠落 |
認証後コンテンツを静的ファイルで直接配信していないかとは
認証後コンテンツを静的ファイルで直接配信していないかとは、ログイン画面でパスワードを確認したあと、JavaScript で menu.htm や data.txt のような静的ファイルへ遷移させる構成になっていないかを確かめる診断項目です。
// 危険な例:照合後に静的ファイルへ遷移している
if (input === "…") { location.href = "staff/menu.htm"; }
このような構成では、静的ファイル自体には認証が掛かっていません。URL を知っていれば誰でも直接取得でき、OWASP WSTG-ATHN-04 が挙げる「ログインページだけでアクセス制御している場合、認証方式は迂回され得る(Direct Page Request)」状態です。セキュリティAIチェッカーは、トップページHTMLに「認証の入口」と「スクリプトやフォームによる静的ファイルへの遷移」が同時に存在する兆候を検出します。
なぜ静的ファイルは守れないのか
Webサーバーは .htm・.html・.txt などの静的ファイルを、アプリケーションの処理を通さずにそのまま返します。サーバー設定(Basic 認証、auth_request、IIS の認証設定など)を明示的に掛けていない限り、リクエストした者が誰であっても内容を返します。
ログイン画面での照合結果をブラウザ側に保存し(sessionStorage など)、遷移先ページの JavaScript がその値を確認する方式では、確認処理そのものが取得後のブラウザで動くため、取得自体は防げません。
静的ファイルの直接配信が「認証の迂回」になる仕組み
WSTG の「Direct Page Request」と CWE-425
WSTG-ATHN-04 は、認証回避手法の一つとして保護ページへの直接リクエスト(強制ブラウズ、Forced Browsing)を挙げ、ログインページでのみアクセス制御を実装しているアプリケーションでは認証方式を迂回できると説明しています。CWE-425(Direct Request ('Forced Browsing'))も、制限された URL やファイルへの直接リクエストに対して、アプリケーションが適切に認可を強制しない弱点として定義しています。
要件の根拠
| 出典 | 内容 |
|---|---|
| OWASP ASVS 5.0.0 V8.2.1 | 機能単位のアクセスを明示的な権限を持つ利用者に限定する |
| OWASP ASVS 5.0.0 V8.3.1 | 信頼できるサービス層で認可を強制する |
| OWASP WSTG-ATHN-04/WSTG-ATHZ-02 | 認証・認可方式の迂回テスト(Direct Page Request/Forced Browsing) |
| CWE-425/CWE-602 | 強制ブラウズ/サーバー側のセキュリティをクライアント側で強制する弱点 |
よくある誤解
- 「推測されにくいファイル名にしてある」:ファイル名は JavaScript のソース、ブラウザ履歴、アクセスログ、プロキシログ、検索エンジンのキャッシュから判明します。秘密のURLは認可の代わりになりません。
- 「robots.txt で除外しているので見つからない」:robots.txt は公開ファイルであり、逆に場所を教えることになります。
- 「noindex を付けている」:検索結果に出にくくなるだけで、アクセスは防げません。
認証の入口として使われる兆候そのものは、「クライアント保存値を認証判定に使っていない」と「Refererをアクセス制御に使っていない」でも個別に確認します。
認証後コンテンツを静的ファイルで直接配信していないの診断基準(セキュリティAIチェッカー)
取得対象
- 診断対象URL(最終URL)のトップページHTML 1件。追加通信はありません。
- 実際に静的ファイルへ未認証で到達できるかは、「認証後静的ファイルの未認証到達性」(
auth.static-target-reachability、外部診断)が候補URLを Cookie なしで取得して確認します。この項目はその前段の「構成上の兆候」を見るものです。
判定に使う2つの条件
次の (A) と (B) が両方成立した場合に△とします。
(A) 認証の入口がある(いずれか)
- パスワード入力欄:
type="password"(引用符なしも含む) - 「クライアント保存値を認証判定に使っていない」の兆候(Web Storage で認証・トークン系キーを読み書き)
- 「Refererをアクセス制御に使っていない」の兆候(参照元の比較で遷移・認証を制御)
(B) スクリプトまたはフォームから静的ファイルへ遷移・取得する記述がある
(?:location(?:\s*\.\s*href)?\s*=(?!=)|location\s*\.\s*(?:replace|assign)\s*\(|window\s*\.\s*open\s*\(|fetch\s*\(|\baction\s*=)\s*["'][^"']+\.(?:html?|txt)(?:[?#"'])
location.href = "menu.htm"、location.replace('member/index.html')、window.open("list.htm")、fetch("data/secret.txt")、<form action="top.html"> などが該当します。
通常の <a href="…htm"> リンクや src 属性は対象外です。自治体サイトはナビゲーションに .htm/.html へのリンクを大量に含むため、通常リンクを条件にすると、ログインと無関係なページまで警告してしまうからです。
判定条件
| 判定 | 条件 | 根拠表示 |
|---|---|---|
| ○ 問題なし(pass) | 上記以外 | 取得HTML内に認証後の静的ファイル直配信を示す組み合わせはありません。 |
| △ 要確認(warn) | (A) かつ (B) | 認証画面・認証処理から.htm/.txtへ直接遷移する兆候があります。 |
| × 要改善(fail) | この項目では使用しない | ― |
| 対象外(na) | この項目では使用しない | ― |
静的検査では、遷移先が本当に保護対象か、サーバー側で保護されているかを確定できないため、×ではなく△とします。確定判定は「認証後静的ファイルの未認証到達性」で行い、未認証で内容が返れば×になります。
得点への影響
重み5の critical 項目です。○は5点、△は2.25点、×は0点です。
自治体サイトでの判定例
| トップページの内容 | 判定 |
|---|---|
パスワード欄があり、照合後 location.href = "staff/menu.htm" | △ |
sessionStorage.setItem("auth","ok"); location.replace("member.html") | △ |
パスワード欄があり、<form action="/login.php" method="post"> でサーバー照合 | ○(遷移先が静的ファイルでない) |
ナビゲーションに <a href="kurashi/index.htm"> が多数あるが、パスワード欄なし | ○ |
検出の限界
- ログイン画面がトップページ以外にある場合、この項目は兆候を観測できません(「認証後静的ファイルの未認証到達性」は巡回したページから候補を探します)。
- 外部JSファイル内の遷移、
.pdf・.csv・.jsonの直接配信は、この項目の検出対象外です。
認証後コンテンツを静的ファイルで配信するとどんなリスクがあるか
攻撃シナリオ
- 攻撃者がログイン画面のソースを読み、成功時の遷移先
staff/menu.htmを知る。 curl https://www.example.lg.jp/staff/menu.htmで取得すると、本文がそのまま返る。menu.htmからリンクされた資料(.pdf、.txt、.xlsx)も同様に取得できる。
パスワード照合をサーバー側(PHP 等)で行っていても、照合後の遷移先が静的ファイルであれば同じ結果になります。
想定される被害
- 職員向け資料、内部規程案、個人情報を含む一覧の漏えい
- 検索エンジンや第三者によるキャッシュ・転載で、削除後も情報が残る
- 「認証付き」と説明していた場合の信用失墜
認証後コンテンツを安全に配信する方法(PHP・Apache・nginx・IIS・Next.js)
基本方針
- 保護したいコンテンツは、サーバー側でセッションを検証するプログラム(PHP、アプリサーバー、認証プロキシ)を経由してのみ返す。
- 実ファイルは公開ディレクトリの外へ移すか、公開ディレクトリ内ならサーバー設定で直接アクセスを拒否する。
- 旧静的URLは 404(または 403)を返すようにし、検索エンジン等に残ったURLからの到達も防ぐ。
PHP 配信スクリプト(共有ホスティング向け)
<?php
// staff/view.php?f=menu
require __DIR__ . '/guard.php'; // セッション未検証なら login へ
$name = $_GET['f'] ?? '';
if (!preg_match('/\A[a-z0-9_-]{1,40}\z/', $name)) { http_response_code(404); exit; }
$base = realpath(__DIR__ . '/../private_data'); // 公開領域外
$path = realpath("$base/$name.html");
if ($path === false || strpos($path, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(404); exit;
}
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store');
header('X-Content-Type-Options: nosniff');
readfile($path);
ファイル名はホワイトリスト形式で検証し、realpath で配下に限定します(ディレクトリ・トラバーサル対策)。
Apache(公開領域に残る旧ファイルの遮断)
# staff/.htaccess
<FilesMatch "\.(?:html?|txt|csv|pdf)$">
Require all denied
</FilesMatch>
Require all denied は 403 を返します。存在自体を隠したい場合は、RedirectMatch 404 ^/staff/.*\.(?:html?|txt)$ のように 404 を返す方法もあります。
nginx
location /staff/ {
auth_request /auth/check; # すべての静的ファイルにも認証を適用
}
location ^~ /private_data/ { internal; } # X-Accel-Redirect 経由でのみ配信
アプリ側でセッションを確認した後、X-Accel-Redirect: /private_data/menu.html を返すと、nginx が静的ファイルを効率よく配信できます。
IIS
対象フォルダーで匿名認証を無効化し、Windows 認証またはフォーム認証を有効化します。統合パイプラインで静的ファイルにも認証が適用されることを、未認証の curl で必ず確認します。
Next.js(Vercel)
public/ 配下のファイルは誰でも取得できるため、保護コンテンツを置かないでください。Route Handler でセッションを検証してから内容を返します。
// app/staff/doc/[name]/route.ts
import { cookies } from "next/headers";
import { verifySession } from "@/lib/session";
import { readProtected } from "@/lib/storage"; // 非公開ストレージから取得
export async function GET(_: Request, { params }: { params: Promise<{ name: string }> }) {
const sid = (await cookies()).get("__Host-sid")?.value;
if (!sid || !(await verifySession(sid))) return new Response("Not Found", { status: 404 });
const { name } = await params;
if (!/^[a-z0-9_-]{1,40}$/.test(name)) return new Response("Not Found", { status: 404 });
return new Response(await readProtected(name), {
headers: { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" },
});
}
サイトを壊さないためのガードレール
- 遮断を先に入れると業務が止まります。「新しい保護経路を用意 → 利用者に周知 → 旧URLを遮断」の順で切り替えます。
- 保護ページから参照される画像・PDF なども同じ保護配下へ移さないと、そこから漏えいします。
- ブラウザや共有キャッシュ(CDN)に保護ページが残らないよう
Cache-Control: no-storeを付けます(ASVS V14.3.2、V14.2.2)。
設定後の確認方法
# 旧静的URL:404 または 403 になること
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.lg.jp/staff/menu.htm
# 新経路:未認証では本文を返さないこと
curl -s -o /dev/null -w "%{http_code}\n" "https://www.example.lg.jp/staff/view.php?f=menu"
よくある質問(FAQ)
ログイン画面でパスワードを確認していれば、遷移先の.htmは守られますか?
守られません。静的ファイルはサーバー設定で認証を掛けない限り、誰がリクエストしても内容を返します。パスワード照合をサーバー側で行っていても、照合後の遷移先が静的ファイルであれば、URLを直接開くだけで閲覧できます。
推測されにくいファイル名にしておけば大丈夫ですか?
大丈夫ではありません。ファイル名はJavaScriptのソース、ブラウザ履歴、アクセスログ、プロキシログ、検索エンジンのキャッシュから判明します。秘密のURLは認可の代わりになりません。
robots.txtやnoindexで隠せば見つかりませんか?
robots.txtは公開ファイルであり、逆に場所を教えることになります。noindexは検索結果に出にくくなるだけで、アクセスは防げません。
通常の a href で.htmへリンクしているだけでも△になりますか?
なりません。判定対象は location.href・location.replace・location.assign・window.open・fetch・form の action による静的ファイルへの遷移・取得です。自治体サイトはナビゲーションに.htmへのリンクを大量に含むため、通常のリンクは条件にしていません。
「認証後コンテンツを静的ファイルで直接配信していない」と「認証後静的ファイルの未認証到達性」の違いは何ですか?
この項目はトップページHTMLから構成上の兆候を見る前段の確認で、△までの判定です。実際に未認証で静的ファイルに到達できるかは、外部診断の「認証後静的ファイルの未認証到達性」が候補URLをCookieなしで取得して確認し、内容が返れば×と確定させます。
旧静的URLは403と404のどちらを返すべきですか?
Apacheの Require all denied は403を返し、存在自体を隠したい場合は RedirectMatch 404 で404を返す方法もあります。どちらでも本文は返らなくなります。検索エンジン等に残ったURLからの到達も防げます。
この項目を含む127項目を、URLを入れるだけで無料診断します。