応答したのはサーバーでした。 2026年8月から10月にかけて、少なくとも10社が、自社のアプリやAPI、照会ページから顧客情報が流出したと公表しました。会員に本人の情報を表示する仕組み、ログインなしで見られた予約ページ、会員情報取得API、荷物の問い合わせ。10月8日にはJPCERT/CCが、この秋の大規模な漏えいの背景にあるAPIの悪用に注意を呼びかけ、10月13日には情報通信研究機構(NICT)がその型に名前を付けました。企業のアプリを解析し、その裏にあるサーバーを攻撃する、というものです。
本ガイドは、同じことが自社に起きる前にやっておくべき作業を、効果の大きい順にまとめたものです。APIをすべて見つけ、公表に出てくる弱点を1つずつテストし、不合格を直し、悪用を監視します。アプリや会員サイトを公開している企業と、それを構築するSIerを想定しています。すでにデータが流出した場合は、本ガイドではなくAPIからのデータ流出対応プレイブックを使ってください。封じ込め、範囲の特定、個人情報保護委員会への報告を扱っています。
何が起きたのか:各社の公表の文言から
多くの公表は、経路を一文で説明しています。並べて読むと、弱点は少数に絞られます。右の2列の対応づけは当サイトによるもので、以下のどの企業も、引用した文言以上の原因は公表していません。
| 企業(公表日) | 公表の内容 | 件数 | 合致する弱点(OWASP API Top 10 2023) | 下のテスト |
|---|---|---|---|---|
| ローソンID(10月8日) | アプリで利用者本人の情報を表示する仕組みを通じて情報が流出。不正アクセスは9月12〜14日と17日 | 2,155,345アカウント | API1 オブジェクトレベルの認可の不備 | 2、3 |
| skyticket、3つの経路の1つ(10月9日) | バス予約の完了ページがログインなしで閲覧でき、8月3日から10月1日まで機械的に読み取られた | 約1万2,000件の予約 | API2 認証の不備、API1 | 1、3 |
| ソースネクスト(10月15日) | 会員情報取得APIが10月9日10時頃から不正に利用され、10月13日13時36分に遮断 | 最大22,519,960件 | 公表なし。4日間の読み取りは、API4 リソース消費の制限なしに合致 | 7 |
| 佐川急便(10月1日) | 9月30日、ウェブサイトの荷物問い合わせサービスへの第三者による不正アクセス。約100日分の送り主・届け先の情報が流出した可能性 | 件数の公表なし | API6 重要な業務フローへの無制限なアクセス | 3、7 |
| Unni | 相談履歴を返すAPIが悪用された | 国内の利用者約4万8,000人 | API1 | 2 |
| セイコーマートのアプリ | アプリのサーバーを経由して会員サーバーに到達された | 572,022人 | 公表なし。アプリのサーバーから会員サーバーへの経路は、NICTが説明する型 | 6、11、12 |
| スタディサプリ | 仕様上の不備により、第三者がメールアドレスの登録有無を確認できた | 3,687件のアドレス | API2(アカウントの列挙) | 8 |
| JPCERT/CC、ケースB(組織名なし) | 公開アプリから取得したエンドポイントと鍵、アプリの画面からは呼ばれない内部APIによる権限変更やアカウント作成、ヘッダーの有無や不正な形式のトークンでの応答の比較、NoSQLインジェクション、他の侵害で盗まれたAPIキー | — | API5 機能レベルの認可の不備、API9 不適切なインベントリ管理、API2、API8 | 6、9、10、11、13 |
| NICT、A2B攻撃(10月13日) | 公開アプリを解析してサーバーとAPIを把握し、そのサーバーを攻撃する。SSRF、リモートコード実行、SQLインジェクション、IDOR | — | API7 サーバーサイドリクエストフォージェリ、API8 セキュリティの設定ミス、API1 | 2、11、12 |
各行の出典は、当サイトのローソン、skyticketとA2B攻撃の注意喚起、ソースネクスト、佐川急便の分析と、すべての公表を一覧にした60日間のまとめにあります。JPCERT/CC:JPCERT-AT-2026-0030 · NICT · NICTの注意喚起(PDF) · OWASP API Security Top 10 2023(英語)
規模の大きいアプリの公表の中には、経路に触れていないものもあります。焼肉きんぐ(10,788,963件)とバーガーキング・ジャパン(約1,118万件、当サイトの分析)です。これらをAPIの事案と読まないでください。一方、上の表は、自社のAPIが合格すべきテストの一覧として読んでください。

図1:公開アプリから会員データベースへの4つの扉と、それぞれを見つけるテスト。この記事のために作成した図です。
攻撃者の視点
レッドチームが自社にこの型を仕掛けるとしたら。JPCERT/CCとNICTの説明をもとにMITRE ATT&CKに対応づけたモデルで、特定の企業の侵害を再現したものではありません。
| 段階 | 内容 | ATT&CK |
|---|---|---|
| アプリを読む | 公開されたAndroidまたはiOSアプリをダウンロードして逆コンパイルし、ホスト、パス、パラメーター、鍵らしき文字列を一覧にする。ウェブのフロントエンドのJavaScriptも読む | T1594 Search Victim-Owned Websites、T1592 Gather Victim Host Information |
| アプリが持つものを使う | アプリから見つけた鍵や、他の侵害で盗まれた鍵でAPIを呼び出す | T1552.001 Credentials In Files、T1078 Valid Accounts |
| 開いている扉を探す | ログインなし、攻撃者自身のアカウントと他人のID、ヘッダーを外したり不正な形式のトークンを使ったりして各エンドポイントを呼ぶ。画面からは使われない管理用・内部用のパスも呼ぶ | T1190 Exploit Public-Facing Application |
| 変えられるものを変える | 内部APIや管理APIで権限を上げる、アカウントを作る、データを書き換える(JPCERT/CCが一部の事例で報告) | T1136 Create Account、T1565.001 Stored Data Manipulation |
| すべて取る | 呼び出しを自動化してIDを順にたどり、警報の閾値を下回る速さで何日も続ける | T1119 Automated Collection、T1213 Data from Information Repositories |
| データを使う | 企業名をかたるフィッシング、メールアドレスとパスワードの組によるパスワードリスト型攻撃 | T1566 Phishing、T1110.004 Credential Stuffing |
攻撃者は、アプリ自体の欠陥を必要としません。アプリもそのコードも鍵も攻撃者の手元にあるので、誰が何を読めるかの判断は、すべてサーバー側で行う必要があります。
ステップ1:運用しているAPIをすべて見つける
一覧にないものはテストできません。攻撃者が出発点にするのは公開アプリで、自社も同じところから始めるべきです。6つの情報源をすべて当たってください。それぞれ、ほかでは見つからないAPIを見つけます。
| 情報源 | 方法 | 見つかるもの |
|---|---|---|
| 自社のリリース版アプリ | 自社のAndroidとiOSのリリースビルドをjadxやMobSFで逆コンパイルし、ホスト、パス、鍵らしき文字列を一覧にする | 裏側のホスト、隠れたパスや管理用パス、テスト用ホスト、すべての端末に配られた鍵 |
| ウェブのフロントエンド | ブラウザーの開発者ツールのネットワークタブを開いてすべての画面をたどり、JavaScriptのバンドルとソースマップを検索する | ウェブサイトが呼ぶルート、GraphQLのエンドポイントとスキーマ |
| ゲートウェイとクラウド | aws apigateway get-rest-apis、aws apigatewayv2 get-apis、az apim api list、ロードバランサーのリスナー |
意図して公開したAPI |
| 通信の記録 | 下のクエリ1:過去30日間に応答したすべてのルート | 誰も一覧にしていないルート:旧バージョン、デバッグ用、パートナー向け |
| 証明書とDNS | 証明書の透明性ログ(例:crt.sh)で、自社ドメインのapi.、stg.、dev.、admin.などの名前を調べる |
忘れられたホストや環境 |
| 契約 | SIerとアプリ開発会社に、OpenAPIの定義と、管理用、バッチ用、パートナー向けを含むすべてのエンドポイントの一覧を求める | 自社のために作られたのに、自社では文書化していないAPI |
成果物はAPI台帳です。 エンドポイントごとに1行で、ホスト、パス、メソッド、呼び出し元(アプリ、ウェブ、パートナー、社員)、認証方式、受け取るオブジェクトID、返す個人データ、担当者と委託先、レート制限、ログにオブジェクトIDが残るか、最終テスト日、状態を記録します。テンプレートは本ガイドの最後にあります。
判断基準: 担当者がいない、または存在理由がないエンドポイントは止めます。NICTの当面の対策も同じで、不要なAPI、管理機能、テスト環境を停止するよう求めています。
ステップ2:エンドポイントごとにテストする(13のテスト)
テストは、自社のシステムか、書面で許可を得たシステムだけに行ってください。許可なく他社のAPIを試すと、不正アクセス禁止法に触れるおそれがあります。本番と同じ設定のステージング環境、2つのテスト用顧客(AとB)、すべての要求と応答の記録を用意します。
| # | テスト | 方法 | 合格 | OWASP API 2023 | 事例 |
|---|---|---|---|---|---|
| 1 | ログインなし | トークンやCookieを外して各要求を繰り返す | 顧客データを返すすべてのエンドポイントが401 | API2 | skyticketのバス予約ページ |
| 2 | 他人の記録 | Aでログインし、BのIDでBの記録を求める | 403か404。Bのデータは決して返らない | API1 | ローソンに合致、Unni |
| 3 | 推測できるID | IDが連番、短い、意味を持つ(会員番号、予約番号、問い合わせ番号、電話番号)か | IDはランダムで、かつテスト2にも合格。ランダムさはテスト2の代わりにならない | API1、API6 | 佐川急便、skyticket |
| 4 | 余分な項目 | 各応答と、画面に表示される内容を比べる | 画面で使わない項目がない。パスワード、ハッシュ、トークン、内部フラグは決して含めない | API3 | ソースネクスト(パスワードが対象) |
| 5 | 書けないはずの項目 | 更新要求で権限、ポイント、メールアドレス、所有者の項目を送る | 無視されるか拒否される | API3 | JPCERT/CCのケースB(権限変更) |
| 6 | 隠れた機能 | Aのトークンで、ステップ1で見つけた管理用・内部用のパスを呼ぶ | 403。できればインターネットから到達できない | API5、API9 | JPCERT/CCのケースB |
| 7 | 量 | 1つのアカウントと1つのアドレスから、1分間に1,000回の照会を自動で送る | 1,000回よりずっと前に制限(429)がかかり、警報が上がる | API4、API6 | ソースネクスト、佐川急便 |
| 8 | 「このメールアドレスは登録済み?」 | ログイン、新規登録、パスワード再設定を、登録済みと未登録のアドレスで試す | 応答も応答時間も同じ | API2 | スタディサプリ |
| 9 | 壊れたトークン | 期限切れ、不正な形式、他人のもの、ヘッダーなしの各パターン | すべて同じように拒否され、エラーに余計な情報がない | API2 | JPCERT/CCのケースB(応答の比較) |
| 10 | 旧バージョンとテスト用ホスト | 同じ要求を/v1/、/old/、ステージング用ホストに送る |
停止されているか、同じ制御で本番データがない | API9 | NICT(古いAPI、テスト環境) |
| 11 | インジェクション | 検索や絞り込みのパラメーターにSQLやNoSQLの演算子を入れ、スキャナーで試す | 拒否される。パラメーター化されたクエリ | API8 | NICT(SQLインジェクション)、JPCERT/CC(NoSQLインジェクション) |
| 12 | URLの取得 | URLを受け取るパラメーター(画像の取り込み、Webhook、プレビュー) | 許可リストの宛先だけ。内部アドレスやクラウドのメタデータには到達しない | API7 | NICT(SSRF) |
| 13 | アプリ内の鍵 | アプリのパッケージ内の、鍵やトークンらしき文字列 | データにアクセスできるものはない。見つかったら交換する | API2、API8 | NICT、JPCERT/CC(アプリから取得された鍵) |
ツール。 OWASP ZAPはOpenAPIの定義を読み込んで、ステージングのAPIを検査できます。Burp SuiteのAutorize拡張は、顧客Aの要求を顧客Bのセッションで再送するため、アプリ全体でテスト2を自動化できます。テスト1、2、4、6はビルドパイプラインの自動テストとして残してください。エンドポイントごとに「AはBを読めない」テストを1つ置けば、新しいリリースが直した扉を開け直すことはありません。
ステップ3:不合格を直す
守れるデータの多い順に並べています。
- すべてのオブジェクトをサーバーで認可する(テスト1〜3)。呼び出し元は検証済みのトークンから取り、パラメーターからは決して取りません。記録は所有者を条件に含めて読み込みます。例:
WHERE id = :id AND owner_id = :caller。既定は拒否とし、確認は各処理ではなく1つの共通層に置き、エンドポイントごとに単体テストを用意します。 - 画面に必要なものだけを返す(テスト4、5)。応答は、明示した項目のリストから組み立て、データベースの行をそのまま返しません。パスワードやそのハッシュを返す必要のあるAPIはありません。呼び出し元が書き込めない項目は、無視するか拒否します。
- 照会ページと確認ページを固める(佐川急便、skyticket)。問い合わせ番号や予約番号に加えて、郵便番号や電話番号の下4桁など、顧客本人しか知らないものを求めます。確認用リンクには期限を付け、表示は一部にとどめ、照会は番号ごと・呼び出し元ごとに制限します。
- 機能ごとに別々に制限する(テスト7、8)。JPCERT/CCは、ログイン、パスワード再設定、SMS送信、検索について、それぞれ時間当たりの制限を設けるよう求めています。アカウント、端末、アドレスごとに制限し、1日の上限を設け、1回に返す件数に上限を置き、エラーは同じにします。Azure API Managementでは
rate-limit-by-keyでトークンのサブジェクトごとに数え、quota-by-keyで1日の上限を加えられます(いずれも従量課金プランでは使えません)。AWSでは、API Gatewayのスロットリングと使用量プラン、AWS WAFのレートベースのルールを使います。rate-limit-by-key(英語) · API Gatewayのスロットリング(英語) · AWS WAFのレートベースのルール(英語) - 管理用・内部用のAPIをインターネットから外す(テスト6)。別のホストに置き、管理用ネットワークか閉域接続からだけ到達でき、多要素認証を必須にします。API Managementの
ip-filterポリシーで呼び出し元を制限できます。使われていないものは削除します。ip-filter(英語) - トークンは短命に、アプリ内の鍵は無価値に(テスト9、13)。JPCERT/CCが勧めるとおり、トークンは範囲を絞り、有効期限を短くし、すぐに失効できるようにします。ゲートウェイ(API Managementの
validate-jwt)とサービスの両方で検証します。アプリに長期間有効な秘密を入れず、見つかった鍵は交換します。Google Play IntegrityやApple App Attestなどのアプリ認証は、攻撃者の手間を増やしますが、認可の代わりにはなりません。validate-jwt(英語) - 旧バージョンとテスト環境を廃止する(テスト10)。残すなら本番と同じ制御を適用し、テスト環境に本番の個人データを置きません。
- 入力を防御的に扱う(テスト11、12)。パラメーター化されたクエリ、入力からのNoSQL演算子の除去、サーバーが取得するURLの許可リスト、内部アドレスやクラウドのメタデータへの経路の遮断です。
- 持つ量を減らす。 JPCERT/CCは、保存期間や目的を過ぎたデータを持たないよう勧めています。ソースネクストで対象となった件数(2,252万件)は、のべ会員数(2,000万人超)を上回っていました。削除した記録は流出しません。
ステップ4:悪用を監視する
調査で必要になるものを記録します。 要求ごとに、時刻、呼び出し元(検証済みトークンのユーザーID)、接続元アドレス、アプリのバージョンやユーザーエージェント、ルートのテンプレート(例:/members/{id})、要求されたオブジェクトID、応答コード、応答サイズ、返した件数です。ゲートウェイのログは通常、ルートとサイズまでです。オブジェクトIDと件数はアプリケーション側で記録する必要があります。対応プレイブックで、そのためのオブジェクト単位のイベントを定義しています。
公表に出てくる形で警報を出します:
- 1つの呼び出し元が多数の異なる記録を読む。 対応プレイブックのクエリ2が、呼び出し元ごとの異なるオブジェクト数を数えます。必要になる前に定期実行してください。
- 探り。 多数のルートで拒否が続き、その後に成功する。下のクエリ2です。
- インターネットに応答する隠れたルート。 下のクエリ3。結果が空になるのが目標です。
- 誰も一覧にしていないルート。 下のクエリ1で、通信を台帳と照合します。
- 呼び出し元自身の平常値と比べた量。 当サイトのソースネクストの分析に、呼び出し元ごとの基準値を使うクエリがあります。毎秒約66件で4日間続く読み取りに、人が気づく必要があってはいけません。
- おとりの記録。 メールアドレスや電話番号に何かが送られると警報が鳴る、架空の会員を作ります。ログに現れなくても、名簿が他人の手に渡ったことが分かります。
ハンティングと監視のクエリ
ダウンロード:ルートの棚卸し(KQL) · 認証の探り(KQL) · 隠れたルート(KQL) · 認証の探り(SPL)
KQLのクエリは、Azure API Managementのリソース固有のApiManagementGatewayLogs表を使います。列名はMicrosoftの表のリファレンスによります。実行する前に、REPLACE_WITH_の値をすべて置き換えてください。
1. KQL:30日間に応答したすべてのルート
// Illustrative; not executed against a live workspace. Azure Monitor Logs, resource-specific ApiManagementGatewayLogs.
// Every route that answered in the last 30 days, to reconcile with your API register (step 1 of the guide).
// Numbers and long IDs in paths become {id}, so one route is one row. UserId and ApimSubscriptionId are
// API Management identities only; an empty value does not prove the application accepted an anonymous caller.
let Start = ago(30d);
let TargetResource = "REPLACE_WITH_APIM_RESOURCE_ID";
let RiskyRoute = @"(?i)/(v0|v1|old|legacy|test|stg|debug|internal|admin|mgmt|batch)(/|$)";
ApiManagementGatewayLogs
| where TimeGenerated >= Start
| where _ResourceId =~ TargetResource
| extend Host = tostring(parse_url(Url).Host), Path = tostring(parse_url(Url).Path)
| extend Route = replace_regex(Path, @"/[0-9A-Fa-f-]{8,}|/\d+", "/{id}")
| summarize Requests = count(), Succeeded = countif(ResponseCode between (200 .. 299)),
NoApimIdentity = countif(isempty(UserId) and isempty(ApimSubscriptionId)),
Callers = dcount(CallerIpAddress), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by Host, Method, Route, ApiId, OperationId
| extend MatchedNoApi = isempty(ApiId), RiskyName = Route matches regex RiskyRoute
| order by MatchedNoApi desc, RiskyName desc, Requests desc
2. KQL:探りと、その後の成功
// Illustrative; not executed against a live workspace. Azure Monitor Logs, resource-specific ApiManagementGatewayLogs.
// Callers that collect many denials across many routes in an hour, and whether they then succeed: the token,
// header and route probing JPCERT/CC describes in JPCERT-AT-2026-0030 (case B). Review candidates, not verdicts.
let Start = ago(7d);
let TargetResource = "REPLACE_WITH_APIM_RESOURCE_ID";
let TrustedEgress = dynamic(["REPLACE_WITH_CORPORATE_EGRESS_IP"]);
ApiManagementGatewayLogs
| where TimeGenerated >= Start
| where _ResourceId =~ TargetResource
| where not(CallerIpAddress in (TrustedEgress))
| extend Route = replace_regex(tostring(parse_url(Url).Path), @"/[0-9A-Fa-f-]{8,}|/\d+", "/{id}")
| summarize Denied = countif(ResponseCode in (401, 403)), NotFound = countif(ResponseCode == 404),
Succeeded = countif(ResponseCode between (200 .. 299)), Routes = dcount(Route),
DeniedRoutes = make_set_if(Route, ResponseCode in (401, 403), 20),
FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by CallerIpAddress, Hour = bin(TimeGenerated, 1h)
| where Denied + NotFound >= 50 and Routes >= 10
| extend ThenSucceeded = Succeeded > 0
| order by ThenSucceeded desc, Denied desc
3. KQL:インターネットに応答する隠れたルート
// Illustrative; not executed against a live workspace. Azure Monitor Logs, resource-specific ApiManagementGatewayLogs.
// Admin, internal and old-version routes that answered callers outside the networks meant to use them.
// Replace the pattern with the hidden routes in your register. An empty result is the goal.
let Start = ago(30d);
let TargetResource = "REPLACE_WITH_APIM_RESOURCE_ID";
let AdminEgress = dynamic(["REPLACE_WITH_ADMIN_EGRESS_IP"]);
let HiddenRoute = @"(?i)/(admin|internal|mgmt|manage|debug|batch|v0|v1)(/|$)";
ApiManagementGatewayLogs
| where TimeGenerated >= Start
| where _ResourceId =~ TargetResource
| extend Path = tostring(parse_url(Url).Path)
| where Path matches regex HiddenRoute
| where not(CallerIpAddress in (AdminEgress))
| summarize Requests = count(), Succeeded = countif(ResponseCode between (200 .. 299)),
Methods = make_set(Method, 10), Paths = make_set(Path, 20),
FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by CallerIpAddress, ApiId, OperationId
| order by Succeeded desc, Requests desc
4. Splunk:探りと、その後の成功
SplunkのCommon Information ModelのWebデータモデル(src、status、uri_path)を使うため、このモデルに対応づけたウェブやAPIのログならどれでも使えます。Splunk CIM:Web(英語)
| tstats summariesonly=true count FROM datamodel=Web WHERE earliest=-7d BY Web.src Web.status Web.uri_path _time span=1h
| rename Web.* AS *
| search NOT src IN ("REPLACE_WITH_CORPORATE_EGRESS_IP")
| eval denied=if(in(status, "401", "403"), count, 0), not_found=if(status="404", count, 0), succeeded=if(match(status, "^2"), count, 0)
| stats sum(denied) AS denied sum(not_found) AS not_found sum(succeeded) AS succeeded dc(uri_path) AS routes BY _time src
| where denied + not_found >= 50 AND routes >= 10
| eval then_succeeded=if(succeeded > 0, "yes", "no")
| sort 0 - denied
5. Sumo Logic:AWS API Gatewayでの探り
HTTP APIでは、アクセスログの形式を次のキーのJSONにします。変数はAWSのHTTP APIアクセスログ変数の一覧(英語)によります。subにはJWTオーソライザーが必要です。
{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","responseLength":"$context.responseLength","sub":"$context.authorizer.claims.sub"}
_sourceCategory=REPLACE_WITH_APIGW_ACCESS_LOG_CATEGORY
| json field=_raw "ip", "routeKey", "status" as ip, route, status
| if(status = "401" or status = "403", 1, 0) as denied
| if(status = "404", 1, 0) as not_found
| if(status matches "2*", 1, 0) as succeeded
| timeslice 1h
| sum(denied) as denied, sum(not_found) as not_found, sum(succeeded) as succeeded, count_distinct(route) as routes by _timeslice, ip
| where denied + not_found >= 50 and routes >= 10
| sort by denied
ステップ5:定着させる
日本のアプリや会員サイトの多くは、SIerが構築し運用しています。台帳もテストもログも、契約で求めなければ続きません。
- 契約に書く。 管理用、バッチ用、パートナー向けを含むすべてのエンドポイントのOpenAPI定義、受け入れ時とビルドパイプラインでの「AはBを読めない」テスト、呼び出し元とオブジェクトIDを含むログ、機能ごとのレート制限、アプリに長期間有効な秘密を入れないこと、不正アクセスの疑いを24時間以内に知らせること。最後の項目は、下の報告期限を守るためです。
- すべてのエンドポイントに担当者を置く。 台帳の各行に、チームではなく個人の名前を書き、四半期ごとと、アプリのリリースのたびに見直します。
- 年1回ではなく、APIが変わるリリースのたびにテストする。 skyticketの事案がその理由を示しています。3つの経路は、それぞれ別の修正を必要としました。
すでにデータが流出した場合
APIからのデータ流出対応プレイブックに切り替えてください。不正アクセスによる個人データの漏えいでは、個人情報保護委員会への報告期限が始まります。速報は速やかに行い、委員会のガイドラインはその目安を、事態を知った時点からおおむね3〜5日以内としています。確報は30日以内、不正の目的をもって行われたおそれがある場合は60日以内で、不正アクセスはこれに当たります。本人への通知も必要です。個人情報保護委員会:ガイドライン(通則編) · BUSINESS LAWYERS
30日・60日・90日の計画

図2:最初の90日間。この記事のために作成した図です。
| 期間 | やること | 完了の目安 |
|---|---|---|
| 1〜30日目 | 6つの情報源から台帳を作る。担当者のいないものを止める。個人データを返すすべてのエンドポイントでテスト1、2、6を実施する。ゲートウェイのログを有効にする。クエリ1を実行する | すべてのエンドポイントに担当者がいる。テスト1、2、6に不合格で修正日の決まっていないものがない |
| 31〜60日目 | ステップ3の順に不合格を直す。項目の許可リスト、機能ごとの制限、管理APIのインターネットからの切り離し、アプリ内の鍵の交換、クエリ2と3の定期実行、おとりの記録の設置 | クエリが担当者付きで定期実行されている。おとりが稼働している。どのアプリにも長期間有効な秘密がない |
| 61〜90日目 | すべてのエンドポイントで13のテスト。パイプラインに「AはBを読めない」テスト。契約更新時の条項追加。保存期間を過ぎたデータの削除。対応プレイブックを使った机上演習 | リリースのたびにテストが動く。契約が更新されている。演習を実施した |
防御側の視点:対応すべきチーム
| チーム | 範囲 | 次の一手 | 必要な証跡 |
|---|---|---|---|
| IT運用 | すべてのAPI、アプリの裏側、ゲートウェイ、テスト用ホスト | 台帳を作る。テスト1、2、6を実施する。担当者のいないものを止める | 台帳、テスト結果 |
| SIEM / SOC | ゲートウェイ、アプリケーション、WAFのログ | クエリ1〜3と呼び出し元ごとの基準値のクエリを定期実行する。おとりを置く | 実行計画、警報の担当者 |
| 法務・プライバシー | 各APIが返す個人データ | データとAPIを対応づける。事故の前に報告期限を把握する | データの対応表、報告手順 |
| 委託先リスク | SIerとアプリ開発会社 | 下の依頼文を送る。契約条項を加える | 書面の回答、契約の変更 |
コピーして使えるテンプレート
API台帳(エンドポイントごとに1行)
host,path,method,called_by,authentication,object_ids_taken,personal_data_returned,owner,vendor,rate_limit,logs_object_id,last_tested,tests_failed,status
api.example.co.jp,/v2/members/{id},GET,app,JWT (15分),member_id,"氏名, メールアドレス, 電話番号",[担当者],[委託先],1ユーザーあたり60回/分,yes,[日付],なし,稼働中
SIerやアプリ開発会社への依頼
件名:【[会社名]】2026年10月8日のJPCERT/CCの注意喚起と、10月13日のNICTのA2B攻撃の注意喚起を受けた、APIの棚卸しとアクセス制御の確認のお願い
JPCERT/CCとNICTは、攻撃者が公開アプリを解析し、その裏にあるAPIを見つけて悪用していると注意を呼びかけています。以下について、[期日]までに書面でご回答ください。
1. 当社のために構築または運用しているすべてのエンドポイントについて、OpenAPIの定義か完全な一覧を、稼働しているホストとともにお送りください。管理用、バッチ用、パートナー向け、旧バージョン、テスト用を含みます。
2. 個人データを返す各エンドポイントで、呼び出し元が要求された特定の記録を読めることを、サーバーはどのように確認していますか。顧客Aが顧客Bのデータを読めないことを示す自動テストをお送りください。
3. 各エンドポイントはどの項目を返しますか。画面に表示されない項目はありますか。パスワード、パスワードのハッシュ、トークンを含む応答はありますか。
4. ログイン、パスワード再設定、SMS送信、検索、照会に、アカウントごと・アドレスごとにどのようなレート制限がありますか。
5. 管理用・内部用のAPIはインターネットから到達できますか。できる場合、どこから、どのような認証で到達できますか。
6. アプリのリリース版にAPIキーや秘密が含まれていますか。含まれている場合、何にアクセスでき、いつ交換しますか。
7. ログには、要求ごとの呼び出し元とオブジェクトIDが記録されますか。保存期間はどれくらいですか。
8. 不正アクセスの疑いを、何時間以内に当社へ知らせていただけますか。
よろしくお願いいたします。
[氏名、役職、連絡先]
経営層向け5行報告
1. 他社で起きたこと:2026年8〜10月、少なくとも10社が、アプリやAPI、照会ページから顧客情報が流出したと公表。JPCERT/CC(10月8日)とNICT(10月13日)も同じ型の攻撃に注意を呼びかけた。
2. 当社の状況:個人データを返すAPIは[数]本、うち委託先が構築したものが[数]本。担当者がいるのは[数]本、「顧客Aは顧客Bを読めない」テストに合格したのは[数]本。
3. 実施済み:台帳の作成[済/日付]、担当者のいないエンドポイントの停止[数]、テスト1、2、6の実施[日付]、監視クエリの定期実行[済/日付]。
4. 残るリスク:[例:管理APIがインターネットから到達可能、アプリのバージョンXに鍵が含まれる、機能ごとのレート制限がない]。
5. 判断が必要な事項:[例:委託先Xによる修正の予算、契約更新時の条項追加、テスト2に合格するまでのリリース停止]。次回報告:[日付]。
本ガイドで決着しないこと
- 上の事案の原因。 多くの企業は、APIがどう悪用されたかを詳しく公表していません。各事案と弱点の対応づけは、各社の公表の文言を当サイトが読んだものです。
- 自社の閾値。 クエリの数値(拒否50回、ルート10、照会1,000回)は出発点です。自社の通信から決めてください。
- GraphQLとバッチのエクスポート。 1回の要求で数千件が返ることがあります。要求の数だけでなく、返した件数を数えてください。
- 自社が呼び出す他社のAPI(OWASP API10)。本ガイドは自社が公開するAPIを扱います。委託先については委託先の侵害対応プレイブックを参照してください。
出典と更新履歴
- JPCERT/CC:JPCERT-AT-2026-0030、2026年10月8日
- NICT:A2B攻撃に関する注意喚起、2026年10月13日
- NICT LETTICE:注意喚起(PDF)
- OWASP API Security Top 10 2023(英語)
- Microsoft Learn:ApiManagementGatewayLogs表(英語)
- Microsoft Learn:rate-limit-by-key(英語) · validate-jwt(英語) · ip-filter(英語)
- AWS:HTTP APIのアクセスログ変数(英語) · API Gatewayのスロットリング(英語) · AWS WAFのレートベースのルール(英語)
- Splunk CIM:Webデータモデル(英語)
- 個人情報保護委員会:ガイドライン(通則編)
当サイトの関連記事:APIからのデータ流出対応プレイブック · ソースネクスト · skyticketとNICTのA2B攻撃の注意喚起 · ローソンID · 佐川急便 · 60日間のまとめ · 漏えいしたクラウドの鍵の対応プレイブック
2026年10月18日: 初版を公開しました。事案の事実は、各社の公表とJPCERT/CC、NICTの注意喚起を、当サイトの過去の分析で引用したとおりに使っています。API Managementの列名はMicrosoftの表のリファレンスで確認しました。AWSとSplunkの項目名は、各社の文書ページの検索結果を通じて確認しました。調査環境からはそれらのページを直接開けませんでした。クエリは実環境では実行していません。
