本文へ移動
🔓

情報流出を招く API の欠陥とは?認証と認可の違いをコードで解説

26分
もくじ

2026年の夏から、有名な会社が「会員の個人情報が流出しました」と発表するニュースが続いている。ただし、会社ごとの原因は、会社の外からは分からないことが多い。

国の機関が公表した事例の1つに、ログインした人が会員番号を書き換えて送ると、他人の情報が返ってくる API がある。自分の書く API で同じことが起きないように、短いコードで確かめる。

何が流出しているのか

Web サービスに会員登録するとき、利用者は名前、メールアドレス、住所、電話番号などを入力する。会社は、その情報を、自社のサーバーの中にあるデータベースに保存する。利用者がアプリやブラウザーを開くと、アプリやブラウザーはインターネットを通って会社のサーバーに情報を求め、サーバーはデータベースから情報を取り出して返す。

インターネット 利用者 利用者のアプリやブラウザー 会社のサーバー データベース(会員の名前、住所、電話番号)

サーバーは、インターネットにつながっていて、アプリや Web サイトから求められたデータを返すコンピューターだ。データベースは、大量の情報を整理して保存するためのソフトウェアで、サーバーの中で動いている。

ニュースになっている情報流出は、会社がこうして保管している会員の情報を、権限のない第三者が取得した、というものだ。

何らかの方法で情報を求める 会員の情報を取り出す 会員の名前、住所、電話番号 会員の情報が渡ってしまう 権限のない第三者 会社のサーバー データベース

2026年に発表された例を3つ挙げる。

サービス発表した会社と日付件数流出した情報
タイムズカーパーク24、2026年9月28日(公表文)約660万件氏名、住所、生年月日、電話番号、メールアドレス、運転免許証の情報など
焼肉きんぐの公式アプリ物語コーポレーション、2026年10月5日(ITmedia NEWS の記事)1078万8963件会員番号、氏名、メールアドレス、電話番号
infoQGMOリサーチ&AI、2026年10月5日(公表文)最大94万8,498件氏名、性別、生年月日、メールアドレス、住所、電話番号など

流出した情報は、詐欺に悪用されるおそれがある。情報を手に入れた第三者は、利用者の名前とメールアドレスに加えて、その利用者がどのサービスの会員なのかを知っている。だから第三者は、その会社を名乗る偽のメールを、本物らしく作れる。利用者が偽のメールのリンクを開いて、パスワードやクレジットカードの番号を入力すると、第三者はその情報も手に入れる。

実際にパーク24は、公表文の中で、同社を装ったメールやショートメッセージ、電話に注意するよう会員に呼びかけている。セキュリティの調査を公開している会社のマクニカも、氏名や連絡先に加えて利用しているサービスが分かれば、詐欺で相手を信用させる材料になると書いている。

会社ごとの原因は、外からは分からない

上の3社の発表を読むと、原因は会社ごとに違う。GMOリサーチ&AI は、サイトで使っていたソフトウェアの脆弱性(プログラムにある安全上の欠陥)を第三者に悪用された、と発表した。パーク24と物語コーポレーションは、発表の時点では、原因を調査中としている。

原因が詳しく発表されない例も多い。マクニカが数えたところ、2026年7月以降に国内で公表された81件のうち65件は、公表文に原因を分類できるだけの説明が無かった。会社が原因を詳しく書くと、同じ欠陥をまだ直していないほかのサービスが次に攻撃される。だから、書かない会社があるのは当然だと僕は考えている。

そのため、それぞれの会社で何が起きたのかは、会社の外からは分からない。分かるのは、国の機関や専門の団体が「こういう原因の事例があった」と公表している内容である。

個人情報の扱いを監督する国の機関である個人情報保護委員会は、2026年10月7日に注意喚起を出した。その資料に「API が悪用される事例」が載っている。セキュリティの事故の報告を受け付けている団体の JPCERT コーディネーションセンターも、2026年10月8日の注意喚起で、API を経由した不正な操作を攻撃の手口の1つに挙げている。

この種類の欠陥は、プログラムを書く人が自分のコードで再現でき、自分のコードで防げる。

API とは何か

API(Application Programming Interface)は、サーバーのプログラムのうち、アプリや Web サイトからの求めを受け取って、データを返す部分だ。アプリがサーバーに送る求めを、リクエストと呼ぶ。

たとえば、会員番号1番の山田さんが、アプリで自分の会員ページを開く。このときアプリは「会員番号1番の情報をください」というリクエストをサーバーに送る。サーバーの API は、データベースから1番の会員の情報を取り出して、アプリに返す。アプリは、返ってきた名前や住所を画面に表示する。

リクエスト「会員番号1番の情報をください」 1番の会員の情報を取り出す 山田さんの名前、住所、電話番号 山田さんの名前、住所、電話番号 山田さんのアプリ サーバーの API データベース

リクエストに書く会員番号を決めるのは、サーバーではなく、リクエストを送る側だ。アプリは利用者のスマートフォンの中で動いている。だから利用者は、アプリが送るリクエストの中身を調べて、会員番号を「2」に書き換えたリクエストを自分で送れる。

個人情報保護委員会の資料にある事例は、この書き換えを使ったものだ。攻撃者がサービスにログインした後、リクエストの中の会員番号などを書き換えることで、本来は取得できないほかの利用者の情報を取得できた。

認証と認可の違い

API は、情報を返す前に、2つのことを調べる必要がある。1つめは認証で、リクエストを送ってきたのは誰かを調べる。2つめは認可で、その人に、求められた情報を渡してよいかを調べる。2つは名前が似ているが、調べる内容が違う。

認証認可
API が調べることリクエストを送ってきたのは誰かその人に、求められた情報を渡してよいか
この処理が無い場合API は、ログインしていない人にも情報を返すAPI は、ログインした人になら他人の情報も返す

API は、認証、認可の順に調べて、両方を通ったリクエストにだけ情報を返す。

していない している 渡してはいけない 渡してよい リクエストが届く 認証: 送ってきた人はログインしているか 情報を返さない(401) 認可: その人に、求められた情報を渡してよいか 情報を返さない(403) 情報を返す(200)

認証だけを行う API は、受付で宿泊客かどうかを確かめた後、どの部屋の鍵でも渡してしまうホテルに似ている。個人情報保護委員会の資料にある事例は、認証は行っているが、認可を行っていない API に当たる。

悪い例と良い例をコードで確かめる

実際の API はインターネット越しにリクエストを受け取るが、API が行う処理は「リクエストに書かれた会員番号の情報を探して返す」である。この処理は、関数1つで再現できる。コードは TypeScript で書き、Node.js 24.13.1 で実行する。

TypeScript は、JavaScript に、値の種類(数なのか文字なのか)を書き添えられるようにしたプログラミング言語だ。Node.js は、JavaScript と TypeScript のコードをパソコンの上で実行する道具で、node というコマンドで使う。

悪い例1: 認証も認可も行わない API

// 会員1人ぶんの情報の形を決める
type Member = {
  id: number; // 会員番号
  name: string; // 名前
  address: string; // 住所
  phone: string; // 電話番号
};

// 会員の一覧(実際のサービスでは、この情報はデータベースに保存されている)
const members: Member[] = [
  { id: 1, name: "山田 太郎", address: "東京都千代田区 1-1-1", phone: "090-0000-0001" },
  { id: 2, name: "佐藤 花子", address: "大阪府大阪市 2-2-2", phone: "090-0000-0002" },
];

// 会員の情報を返す API
//   memberId: リクエストに書かれていた会員番号
function getMember(memberId: number) {
  // 会員の一覧から、会員番号が memberId と同じ会員を探す
  const member = members.find((m) => m.id === memberId);

  // 見つけた会員の情報を、そのまま返す
  // status は処理の結果を表す番号で、200 は「成功」を表す
  return { status: 200, body: member };
}

// ログインしていない人が、会員番号 2 を書いたリクエストを送る
console.log(getMember(2));
{
  status: 200,
  body: {
    id: 2,
    name: '佐藤 花子',
    address: '大阪府大阪市 2-2-2',
    phone: '090-0000-0002'
  }
}

この API は、リクエストを送ってきた人が誰なのかを一度も調べていない。そのため、会員番号を書いて送れば、会員でない人にも佐藤さんの住所と電話番号を返す。

リクエスト「会員番号2番の情報をください」 2番の会員の情報を取り出す 佐藤さんの名前、住所、電話番号 佐藤さんの名前、住所、電話番号 何も調べない ログインしていない人 サーバーの API データベース

悪い例2: 認証だけを行う API

悪い例1に、ログインしていない人には情報を返さない処理(認証)を足す。

サーバーは、利用者がログインしたときに、その利用者が誰なのかを記録する。その後にリクエストが届くと、サーバーは記録を見て、リクエストを送ってきた人の会員番号を調べる。下のコードの loginUserId には、サーバーが調べたその会員番号が入る。リクエストに書かれていた会員番号の memberId とは別のものだ。利用者は、リクエストに書く memberId を書き換えられる。一方、loginUserId はサーバーが自分の記録から調べた値なので、利用者には書き換えられない。実際の API でも、リクエストに書かれた値を loginUserId に入れてはいけない。

Member と members は悪い例1と同じなので、下のコードでは省いている。

// 会員の情報を返す API
//   loginUserId: リクエストを送ってきた人の会員番号
//                (ログインしていない人の場合は null が入る)
//   memberId:    リクエストに書かれていた会員番号
function getMember(loginUserId: number | null, memberId: number) {
  // 【ここを足した】ログインしていない人には、情報を返さない
  // status の 401 は「ログインが必要」を表す
  if (loginUserId === null) {
    return { status: 401, body: "ログインしてください" };
  }

  // 会員の一覧から、会員番号が memberId と同じ会員を探して返す
  const member = members.find((m) => m.id === memberId);
  return { status: 200, body: member };
}

// ログインしていない人が、会員番号 2 を書いたリクエストを送る
console.log(getMember(null, 2));

// ログインした山田さん(会員番号 1)が、会員番号 2 を書いたリクエストを送る
console.log(getMember(1, 2));
{ status: 401, body: 'ログインしてください' }
{
  status: 200,
  body: {
    id: 2,
    name: '佐藤 花子',
    address: '大阪府大阪市 2-2-2',
    phone: '090-0000-0002'
  }
}

1つめの結果では、API はログインしていない人の求めを断っている。ところが2つめの結果では、API は山田さんに、佐藤さんの住所と電話番号を返している。山田さんは、リクエストの会員番号を1から2に書き換えただけだ。

リクエスト「会員番号2番の情報をください」 2番の会員の情報を取り出す 佐藤さんの名前、住所、電話番号 佐藤さんの名前、住所、電話番号 ログインしているかだけを調べる 山田さんのアプリ サーバーの API データベース

個人情報保護委員会の資料にある事例は、この悪い例2と同じ種類の欠陥である。この API は、認証を行っているが、認可を行っていない。リクエストに書かれた会員番号が、送ってきた本人の会員番号と同じかどうかを調べていない。

良い例: 認証と認可の両方を行う API

この例では、会員の情報を見てよいのは本人だけとする。そこで、リクエストに書かれた会員番号と、送ってきた本人の会員番号を比べる処理を足す。

function getMember(loginUserId: number | null, memberId: number) {
  // 認証: リクエストを送ってきた人が誰なのかを調べる
  if (loginUserId === null) {
    return { status: 401, body: "ログインしてください" };
  }

  // 【ここを足した】認可: その人に、求められた情報を渡してよいかを調べる
  // リクエストに書かれた会員番号が、送ってきた本人の番号と違う場合は、情報を返さない
  // status の 403 は「権限が無い」を表す
  if (loginUserId !== memberId) {
    return { status: 403, body: "この情報を見る権限がありません" };
  }

  const member = members.find((m) => m.id === memberId);
  return { status: 200, body: member };
}

// ログインした山田さん(会員番号 1)が、会員番号 2 を書いたリクエストを送る
console.log(getMember(1, 2));

// ログインした山田さん(会員番号 1)が、自分の会員番号 1 を書いたリクエストを送る
console.log(getMember(1, 1));
{ status: 403, body: 'この情報を見る権限がありません' }
{
  status: 200,
  body: {
    id: 1,
    name: '山田 太郎',
    address: '東京都千代田区 1-1-1',
    phone: '090-0000-0001'
  }
}

API は、山田さんが佐藤さんの情報を求めたリクエストを断り、山田さんが自分の情報を求めたリクエストには情報を返す。足したコードは、コメントを除くと3行である。

リクエスト「会員番号2番の情報をください」 「この情報を見る権限がありません」 認証: 山田さんはログインしている 認可: 2番は山田さん本人の番号ではない 山田さんのアプリ サーバーの API データベース

API は、データベースから佐藤さんの情報を取り出す前に、リクエストを断っている。

認可で調べる内容は、サービスの決まりによって変わる。上のコードは「本人だけが見てよい」という決まりの例だ。家族や担当者にも見せるサービスでは、API は、その人が見てよい相手に含まれるかを調べる。

認可を行わない欠陥は、よく見つかる

悪い例2の欠陥には名前が付いている。Web アプリケーションの安全性についての資料を公開している団体の OWASP(Open Worldwide Application Security Project)は、API の危険な欠陥を10個挙げた一覧の1番目に、この欠陥を「Broken Object Level Authorization」という名前で載せている。OWASP は、この欠陥が API を使うアプリケーションで非常によく見つかると説明している。

個人情報保護委員会の資料も、この事例の対策として、認証に加えて、認可を都度行う仕組みを実装することを挙げている。

API を作る人が確かめる5つのこと

サーバーの側で動くプログラムを初めて書く人は、API を1本書くたびに、次の5つを確かめるとよい。

1. ログインが必要な API で、認証を行っているか

会員の情報を返す API のように、ログインした人だけに使わせる API では、認証の処理を必ず書く。1本でも書き忘れると、その API は悪い例1と同じになり、ログインしていない人にも情報を返す。

2. 返す情報が、リクエストを送ってきた本人のものかを調べているか

認可の処理が無い API は悪い例2と同じになり、ログインした人になら他人の情報も返す。

3. 認証と認可の処理を、サーバーのプログラムに書いているか

アプリの画面で他人の会員番号を入力できないようにしても、情報流出は防げない。攻撃者は、アプリの画面を使わずに、書き換えたリクエストを API へ直接送るからだ。

画面を使わず、書き換えたリクエストを直接送る 利用者 アプリの画面(他人の番号は入力できない) サーバーの API 攻撃者

マクニカも、点検する項目として、権限の強い機能でアクセス権をサーバーの側で確認しているか、を挙げている。

4. 画面が使う項目だけを返しているか

画面に「ようこそ、山田 太郎さん」と表示するための API を例にする。画面が使うのは名前だけだ。

// 画面に「ようこそ、〇〇さん」と表示するための API(直す前)
function getGreetingBefore(loginUserId: number) {
  const member = members.find((m) => m.id === loginUserId);

  // 画面が使うのは名前だけなのに、住所と電話番号も一緒に返している
  return { status: 200, body: member };
}

// 画面に「ようこそ、〇〇さん」と表示するための API(直した後)
function getGreetingAfter(loginUserId: number) {
  const member = members.find((m) => m.id === loginUserId);

  // 画面が使う名前だけを取り出して返す
  return { status: 200, body: { name: member?.name } };
}

console.log(getGreetingBefore(1));
console.log(getGreetingAfter(1));
{
  status: 200,
  body: {
    id: 1,
    name: '山田 太郎',
    address: '東京都千代田区 1-1-1',
    phone: '090-0000-0001'
  }
}
{ status: 200, body: { name: '山田 太郎' } }

アプリが画面に表示しない項目も、API が返せば、リクエストを送った人はすべて読める。直す前の API に認可の欠陥が重なると、攻撃者は名前だけでなく住所と電話番号も取得する。個人情報保護委員会の資料も、不要な情報を返さないように設計することを対策に挙げている。

5. 利用者に配るコードに、秘密の値を書いていないか

API キーは、API を使うアプリや利用者を見分けるために、リクエストと一緒に送る文字列だ。API キーの中には、外部に知られると、その API を他人に使われてしまうものがある。

// 利用者に配るアプリのコードに、API キーをそのまま書いている例
const API_KEY = "demo-secret-key-12345";

スマートフォンのアプリと、ブラウザーで動くコードは、利用者の端末に届く。利用者は、自分の端末に届いたコードの中身を調べられる。

API キーを書いたアプリを配る アプリの中身を調べる 取り出した API キーでリクエストを送る 会社 利用者の端末 API キーが取り出される サーバーの API

マクニカは、コードを読みにくく加工していても、コードに書いた値は解析で取り出される可能性があると書いている。JPCERT コーディネーションセンターも、公開されているスマートフォンのアプリを解析して API キーを特定する手口の報告を受けている。だから、外部に知られると困る値は、利用者に配るコードに書かず、サーバーに置く。

個人情報保護委員会の資料は、ほかに、会員番号を連番にしないことと、短い時間に大量に届くリクエストを制限することも対策に挙げている。ただし、会員番号を推測しにくい値にしても、認可の処理が無ければ、API は会員番号を知っている人に情報を返す。先に書くのは認可の処理だ。

利用者にできること

利用者は、API に認可の処理が無いことによる情報流出を、自分では防げない。利用者がどれだけ長いパスワードを設定していても、API が他人に情報を返せば、情報は流出する。

利用者にできるのは、流出した後の被害を増やさないことだ。流出を発表した会社は、利用者に次の2つを求めている。

  • 同じパスワードをほかのサービスでも使っている場合は、そのサービスのパスワードを変える(GMOリサーチ&AI の公表文)
  • その会社を装ったメールや電話に注意し、身に覚えのない連絡に書かれたリンクを開かない(パーク24の公表文)

まとめ

認証は「リクエストを送ってきたのは誰か」を調べる処理で、認可は「その人に、求められた情報を渡してよいか」を調べる処理だ。認証だけを行う API は、会員番号を書き換えたリクエストに対して、他人の情報を返す。

ニュースになった情報流出の原因は、会社の外からは分からないことが多い。一方で、自分が書いた API に認可の処理があるかどうかは、コードを開けば分かる。

おわり😊

RELATED

つくることで、見える景色がある。