結論(未認証APIの情報露出兆候)
未認証APIの情報露出兆候とは、公開ページから参照されているAPI(/api/ や .json)の応答に、機微な項目名が含まれている状態です。対象となるのは、ログインせずに取得した応答に含まれるパスワード・トークン・セッションID・メール・権限・利用者IDといった項目名です。APIは画面より多くの項目をそのまま返しがちで、画面に表示しなくても応答した時点で誰でも見られます。認証必須APIは全エンドポイントでサーバー側の認証を確認し、応答は必要な項目だけで組み立てます。
この記事のポイント
- JSONの応答は開発者ツールやcurlで誰でも見られるため、「画面に表示しなければ良い」は誤りです。
- 典型的なパターンは、認証の欠落・過剰なデータ返却・オブジェクト単位の認可欠落・デバッグ用APIの残存です。
- セキュリティAIチェッカーは、公開ページから見つけたAPI候補を最大2件、Cookieなしで取得し、JSONのキー名だけを調べます。値は記録・表示しません。
- password・secret・access_token・session_id などのキーがあれば×、email・role・user_id だけなら△(要確認)です。
- 防災情報やオープンデータなど、誰でも取得してよいAPIは認証不要で正しく、問題になるのは応答の中身です。
| 診断項目名 | 未認証APIの情報露出兆候 |
|---|---|
| 項目ID | auth.anonymous-api-exposure |
| 分類 | 認証・アクセス制御 |
| 重大度・配点の重み | 重要(high) 重み 4 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V8.2.1(機能レベル)、V8.2.2(データレベル)、V8.2.3(フィールドレベルのアクセス制限)、V8.3.1/OWASP API Security Top 10 2023 API1(Broken Object Level Authorization)、API2(Broken Authentication)、API3(Broken Object Property Level Authorization)、API5(Broken Function Level Authorization)/OWASP WSTG-APIT-02(API Broken Object Level Authorization)、WSTG-APIT-03(Excessive Data Exposure)、WSTG-APIT-04(API Broken Function Level Authorization)、WSTG-ATHZ-02/CWE-200(Exposure of Sensitive Information to an Unauthorized Actor)、CWE-306、CWE-862(Missing Authorization)、CWE-213 |
未認証APIの情報露出兆候とは
未認証APIの情報露出兆候とは、公開ページから参照されている API(/api/... や .json のデータ)をログインせずに取得したとき、その応答にパスワード・トークン・セッション ID・メール・権限・利用者 ID といった機微な項目名(キー名)が含まれている状態のことです。
Web ページの裏側では、JavaScript が /api/... や .json のデータを読み込んで画面を組み立てていることが増えています。API は画面よりも多くの項目をそのまま返しがちで、「画面には表示していないが、JSON にはメールアドレスや権限、トークンが入っている」という露出が起こります。また、ログインが必要な画面から呼ばれる API なのに、API 側では認証を確認していない、というケースもあります。
{ "user_id": 1024, "name": "...", "email": "...", "role": "admin", "password": "..." }
セキュリティAIチェッカーのこの項目は、公開ページから参照されている API 候補をログインせずに取得し、応答に機微なキー名が含まれていないかを確認します。値そのものは記録も表示もしません。
APIで情報露出が起きやすいパターン
4つの典型パターン
| パターン | 例 | 対応する OWASP API Security Top 10 |
|---|---|---|
| 認証の欠落 | ログイン後の画面用の /api/user/list が未ログインでも 200 | API2 / API5 |
| 過剰なデータ返却 | 職員一覧 API が氏名だけでなく email、role、password_hash も返す | API3 |
| オブジェクト単位の認可欠落 | /api/application?id=123 の id を変えると他人の申請が見える | API1 |
| デバッグ用 API の残存 | /api/debug/session が session_id を返す | API8 / API9 |
OWASP API Security Top 10 2023 の API3 は、2019 年版の Excessive Data Exposure と Mass Assignment を統合し、オブジェクトのプロパティ(フィールド)単位の認可不備を根本原因として扱っています。OWASP ASVS 5.0.0 V8.2.3 も、フィールドレベルのアクセス制限を求めています。
「フロントで表示しなければ良い」は誤り
JSON の応答は、ブラウザの開発者ツールや curl で誰でもそのまま見られます。画面に表示するかどうかは関係なく、API が返した時点で露出 です。モデルをそのまま返すのではなく、必要な項目だけを返す設計(レスポンスの明示的な組み立て、DTO)が必要です。
公開してよい API との区別
自治体サイトには、防災情報・オープンデータ・イベント一覧など、誰でも取得してよい API や JSON が多数あります。これらは認証不要で正しく、問題になるのは「中身」です。
たとえば、担当課の代表メールアドレス("email": "bousai@city...")は、公開情報として問題ない場合もあります。そのため、この項目は次のように段階を分けています。
- メール・権限・利用者 ID のキー → 「要確認(warn)」
- パスワード・秘密値・トークン・セッション ID のキー → 「要改善(fail)」
URL のクエリ文字列やGETフォームに機密情報を載せていないかは「機密情報をURL・GETフォームへ含めない」、API 仕様書や GraphQL の開発画面が公開されていないかは「API仕様・GraphQL開発画面の公開」で確認します。このページの項目は、API の応答本文に含まれる機微なキー名に集中しています。
未認証APIの情報露出兆候の診断基準(セキュリティAIチェッカー)
取得対象
- トップページと巡回した最大2ページの HTML(
a・link・script・img・iframe要素の href/src、form 要素の action、location.href =・fetch(・axios.get(/axios.post(の文字列)、および複数ページ巡回の結果から抽出した同一オリジンの URL のうち、パスに/api/を含むもの、または拡張子が.jsonのものを最大2件。 - 状態変更を連想させる
logout・signout・delete・remove・destroy・unsubscribeをパスに含む URL は候補から除き、GET を送りません。 - 各候補へ Cookie・認証ヘッダーなしで GET を1回送ります(タイムアウト5秒、リダイレクトは2回まで追跡)。180KB を超える応答は先頭 180KB だけで判定し、リダイレクト先が別オリジンになった応答は機密キーの検査に使いません。
判定に使うキー名
応答本文に "キー名": または 'キー名': の形(JSON のキー)が現れるかを、大文字・小文字を区別せずに調べます。
| 区分 | キー名 |
|---|---|
| 重大(fail 対象) | password、passwd、secret、access_token/accesstoken、refresh_token/refreshtoken、session_id/sessionid |
| 要確認(warn 対象) | 上記に加え email、role、user_id/userid |
キーは完全一致のみで数えます("user_email" は email として数えません)。値の有無や中身は判定しません。
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| × 要改善(fail) | 同一オリジンの 2xx 応答に重大キーが1つ以上ある | 重み4 × 0 |
| △ 要確認(warn) | 重大キーは無いが、要確認キー(email・role・user_id)がある | 重み4 × 0.45 |
| ○ 問題なし(pass) | 取得した応答に該当キーが無い、または 401/403/404 等で内容を返さない | 重み4 × 1.00 |
| 対象外(na) | API 候補が見つからない、または全候補の取得に失敗した | 計算対象外 |
証拠欄には「n/m 件で利用者・権限・認証情報に関係するキー名を確認しました(値は非表示)」のように件数のみを表示します。
自治体サイトでの判定例
| サイトの状態 | 判定 |
|---|---|
manifest.json だけが候補で、name・icons 等のみ | ○ |
防災メール登録案内の /api/contact.json に "email": "bousai@…"(課の代表アドレス) | △(公開を意図した情報であれば説明可能) |
施設予約の /api/users が未ログインで "user_id"、"email"、"password" を返す | × |
判定の限界
- 公開ページから参照されない API、POST でしか呼べない API、id 等のパラメーターを変えた場合の認可(API1)は確認しません。
- 180KB より後ろにだけ現れるキーは検出できません。
- キー名による推定のため、
"pwd"のような別名や、ネストした独自形式は検出できないことがあります。
API の一覧(OpenAPI 定義等)を使った認証・認可テストを別途実施してください。
未認証のAPIから情報が露出するとどんなリスクがあるか
- 個人情報の大量取得:利用者一覧 API が未認証で取得できれば、ページングを回すだけで全件を取得されます。
- アカウント乗っ取り:アクセストークン・セッション ID・パスワード(ハッシュを含む)が返れば、なりすましやオフラインでの解析につながります。
- 権限情報の悪用:
roleや内部 ID の構造が分かると、権限昇格や他人のデータ参照(id の書き換え)を試みる手がかりになります。 - 発見の容易さ:API の URL はページの JavaScript から簡単に抽出でき、自動化ツールで探索されます。
- OWASP は、API の認可不備を API Security Top 10 の上位リスクとして挙げています。
未認証APIの情報露出を防ぐ対策(PHP・Apache・nginx・Next.js・CMS)
基本方針
- API を「公開 API」と「認証必須 API」に分け、認証必須 API は全エンドポイントでサーバー側のセッション/トークンを検証する。
- 応答は必要な項目だけを明示的に組み立てる(モデルをそのままシリアライズしない)。
- パスワード(ハッシュを含む)・トークン・セッション ID は、どの API の応答にも含めない。
PHP
<?php
session_start();
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
if (empty($_SESSION['user_id'])) {
http_response_code(401);
echo json_encode(['error' => 'unauthorized']);
exit;
}
$row = fetchUser($_SESSION['user_id']);
// 必要な項目だけを詰め直す(password_hash 等は含めない)
echo json_encode([
'name' => $row['display_name'],
'reservations' => $row['reservations'],
], JSON_UNESCAPED_UNICODE);
Apache(暫定措置)
開発・デバッグ用の API や使っていない JSON を、外部から遮断します。LocationMatch は .htaccess では使えないため、httpd.conf または VirtualHost の中に記述します。
<LocationMatch "^/api/(debug|internal|admin)/">
Require all denied
</LocationMatch>
庁内からだけ使う API は、Require ip 192.0.2.0/24 のように接続元を限定します(ただし IP 制限は認証の代わりではありません)。
nginx
location /api/internal/ { deny all; }
location /api/ {
auth_request /auth/check; # 認証必須 API だけを分けて適用
proxy_pass http://app;
}
Next.js(Route Handler)
export async function GET(req: Request) {
const session = await getServerSession(req);
if (!session) return Response.json({ error: "unauthorized" }, { status: 401 });
const user = await db.user.findUnique({
where: { id: session.userId },
select: { displayName: true }, // 返す列を明示
});
return Response.json(user, { headers: { "Cache-Control": "no-store" } });
}
CMS・静的 JSON
WordPress は、標準の REST API /wp-json/wp/v2/users で、投稿を公開しているユーザーの表示名・スラッグ(多くの場合ログイン名と同じ)等を未ログインでも返します。不要な REST エンドポイントは無効化または認証必須にし、公開ディレクトリに置いた JSON に職員情報が含まれていないか確認してください。
設定後の確認方法
curl -s https://www.city.example.lg.jp/api/xxx | head -c 2000
curl -s https://www.city.example.lg.jp/api/xxx \
| grep -oiE '"(password|passwd|secret|access_?token|refresh_?token|session_?id|email|role|user_?id)"\s*:' | sort | uniq -c
ログイン後の画面で開発者ツールのネットワークタブを開き、呼ばれている API を一覧化して、同じ URL を未ログインで要求したときに 401/403 になるかも確認します。
サイトを壊さないための注意点(ガードレール)
- API の応答項目を減らすと、画面側が壊れることがあります。フロントエンドで使っている項目を確認してから削ってください。
- 公開 API にも
Cache-Controlやレート制限を設定し、大量取得を抑えます。 - 既にトークン等が露出していた場合は、該当するトークン・パスワードを失効・再発行してください。
よくある質問(FAQ)
画面に表示していない項目なら、APIの応答に含まれていても問題ありませんか?
問題があります。JSONの応答はブラウザの開発者ツールやcurlでそのまま見られるため、APIが返した時点で露出しています。必要な項目だけを明示的に組み立てて返す設計(DTO)が必要です。
公開されているAPIはすべて危険ですか?
危険ではありません。防災情報・オープンデータ・イベント一覧など、誰でも取得してよいAPIは認証不要で正しい設計です。問題になるのは応答の中身で、パスワードやトークン、不要な個人情報を含めないことが重要です。
課の代表メールアドレスがJSONに入っていて△になりました。直す必要がありますか?
公開を意図した情報であれば説明可能です。email・role・user_id のキーは公開情報の場合もあるため、×ではなく△(要確認)にしています。個人のアドレスなど公開を意図しない値でないかを確認してください。
WordPressのREST APIで注意すべき点はありますか?
標準の /wp-json/wp/v2/users は、投稿を公開しているユーザーの表示名やスラッグ(多くの場合ログイン名と同じ)などを未ログインでも返します。不要なRESTエンドポイントは無効化するか認証必須にしてください。
未認証APIの情報露出兆候の診断で問題なしなら、APIの認可は十分ですか?
十分とは限りません。公開ページから参照されないAPI、POSTでしか呼べないAPI、idなどのパラメーターを変えたときの認可(API1)は確認していません。API一覧(OpenAPI定義等)を使った認証・認可テストを別途実施してください。
トークンやパスワードが露出していた場合はどうすればよいですか?
応答から該当項目を除くだけでなく、露出していたトークン・パスワードを失効・再発行してください。あわせて公開APIにもレート制限を設定し、大量取得を抑えます。
この項目を含む127項目を、URLを入れるだけで無料診断します。
参考資料
- OWASP ASVS 5.0.0 V8 Authorization
- OWASP API Security Top 10 2023
- OWASP WSTG-APIT-02 API Broken Object Level Authorization
- OWASP WSTG-APIT-03 Excessive Data Exposure
- OWASP WSTG-APIT-04 API Broken Function Level Authorization
- WordPress REST API Handbook「Users」
- CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
- CWE-862 Missing Authorization
- IPA「安全なウェブサイトの作り方」