セキュリティ

OfferBoxの情報漏洩と最大31万人の氏名露出|API応答の過剰なデータ露出とリリース前の通信確認

OfferBoxの情報漏洩と最大31万人の氏名露出|API応答の過剰なデータ露出とリリース前の通信確認

新卒オファー型就活サービス「OfferBox」を運営する株式会社i-plugは2026年10月5日、利用企業向けの特定のページで、学生の氏名とメールアドレスが通信データに含まれていたと公表しました。画面には表示されていなかったものの、ブラウザの開発者ツールを開くと読み取れる状態で、対象は理論上最大314,009人です。本記事では公式発表に沿って経緯と影響を整理し、学生と利用企業がやることをまとめます。後半では、同じ型の不備を自社のWebサービスで起こさないために、API応答の組み立て方とリリース前の通信検査をコードで示します。

まとめ:OfferBoxの情報漏洩で確定した事実と学生・開発チームが今やること

確定しているのは、2026年5月12日から8月28日まで、利用企業向けの特定のページの通信データに学生の姓・名・メールアドレスが含まれていたこと、8月27日に利用企業からの問い合わせで発覚し、翌28日に設定不備を解消したこと、9月11日に個人情報保護委員会へ速報したことです。原因はサイバー攻撃ではなく、リリース時の確認が画面表示にとどまり、通信データの中身まで見ていなかったことだと同社は説明しています。

学生が今やることは、同社からの通知の確認と、氏名とメールアドレスを知っている相手からの不審な連絡への警戒です。Webサービスを開発するチームは、画面に出していない項目がAPIの応答に載っていないかを、リリース前の検査項目に加えてください。

項目 公表されている内容
不備の期間 2026年5月12日〜8月28日
発覚 8月27日・利用企業の問い合わせ
解消 8月28日
委員会への速報 9月11日
含まれた情報 姓・名・メールアドレス
対象 最大314,009人(理論値)
原因 システム設定不備

公表内容を時系列で確認する|5月12日の設定不備から10月5日の公表まで

利用企業からの問い合わせで発覚し翌日に設定不備を解消した経緯

i-plugの公式発表(2026年10月5日)に載っている経緯の表によると、8月27日に利用企業から「特定環境下で、オファーを承諾していない学生の氏名と思われるものが表示されている」との問い合わせを受け、同日中に社内調査を始めました。原因となった設定不備は翌8月28日に解消しています。

不備が生じていたのは5月12日からで、発覚までに約3か月半が経っていました。社内の監視ではなく、利用企業の指摘で見つかった点が本件の特徴です。通信データの中身は画面のテストでは目に入らないため、外部の誰かが開発者ツールを開くまで誰も気付かなかったことになります。

9月11日の速報と10月5日の公表までの同社による漏えいの判断

同社は規約類や表示を精査した結果、本件は意図しない漏えいに当たると判断し、9月11日に個人情報保護委員会へ速報しました。公表はその後の10月5日で、対象となる可能性があり連絡先を把握している学生には、個別に通知文を送っています。

再発防止策として挙げられたのは、新機能などのリリース判定基準の定期的な見直し、リリース判定のガイドラインの再整備、外部専門機関による脆弱性診断の実施です。あわせて、サービス内の全通信内容を確認し、同様の事象が起きていないことを確かめたとしています。

何が見えていたのか|画面に出ない氏名とメールが通信データに載る仕組み

開発者ツールのネットワーク画面で応答の中身がそのまま読める理由

公式発表によれば、氏名とメールアドレスは通常の閲覧画面には出ておらず、ブラウザの開発者ツールで開発者向け画面を表示したときに見える状態でした。含まれていた項目名は LASTNAME(姓)、FIRSTNAME(名)、LOGINID(メールアドレス)です。

いまのWebアプリは、画面を組み立てる前にサーバーからJSONなどのデータを受け取り、その一部だけを画面に描画します。Chrome DevToolsのNetworkパネルでは、受け取った応答の本文をResponseタブやPreviewタブでそのまま確かめられます。画面に出していない項目でも、ブラウザに届いた時点で利用者の手元にあるので、「表示していないから見えない」は成り立ちません。DevToolsで通信を再現する操作はCopy as cURLの使い方で扱っています。

文字コードで表示された姓名も一行の変換で読み戻せる仕組みと実例

同社は、氏名がそのままではなく「A4A2」のような文字コードで表示されており、変換ツールを使って初めて姓名が分かる状態だったと説明しています。ただし、文字コードは暗号ではありません。例として挙げられたA4A2は、日本語の文字コードEUC-JPで「あ」に当たる値で、次の一行で元に戻ります。

python3 -c "print(bytes.fromhex('A4A2').decode('euc_jp'))"
# 出力: あ
python3 -c "print(bytes.fromhex('BBB3C5C4').decode('euc_jp'))"
# 出力: 山田

同社のシステムがどの文字コードで値を持っていたかは公表されていません。ここで示したいのは、16進数で書かれていることは見えにくさにはなっても保護にはならない点です。応答に載せてはいけない項目は、符号化ではなく、応答から外すことで守ります。

オファー承諾までは氏名を企業に開示しないという約束との食い違い

OfferBoxでは、企業が学生のプロフィールを見てオファーを送り、学生が承諾するまで、氏名など個人を特定できる情報は企業に開示しない設計になっています。同社はこの旨を学生の新規登録画面に記載していました。今回の不備は、この約束を通信データの段階で破っていたことになります。

最大314,009人という数字は、不備のあった期間に検索対象になっていた2027年卒と2028年卒の学生の総数です。通信データにしか出ていなかったため、実際にどの企業が誰の情報を見たかは特定できず、この数は閲覧された人数と一致しないと同社は注記しています。

学生と利用企業がやること|通知の確認と不審な連絡への備え・データの扱い

学生は氏名とメールアドレスを知る相手からの不審な連絡に注意する

同社は、情報を見られた可能性があるのは利用企業に限られ、第三者への流出は現段階で確認されておらず、二次被害のおそれは極めて低いとしています。含まれていたのは氏名とメールアドレスで、パスワードや住所は対象に挙がっていません。パスワードの変更を急ぐ事案ではありません。

それでも、オファーを承諾していない企業や、心当たりのない採用担当者を名乗るメールが氏名入りで届いた場合は、リンクを開く前に送信元を確かめてください。通知が届いていない場合でも、登録時と連絡先が変わっていると届かない可能性があると同社は案内しています。問い合わせ窓口は公式発表に記載されています。

利用企業が開発者ツールで見た学生情報の採用活動への使用を避ける理由

今回の発覚は、利用企業の担当者からの問い合わせがきっかけでした。逆に言えば、同じ画面を見た企業の中に、承諾前の学生の氏名を目にした担当者がいた可能性は否定できません。そうした情報を記録したり、オファー承諾前の連絡に使ったりすることは、サービスの約束を企業側から破ることになります。社内で扱いを確認し、手元に控えがあれば削除しておくのが無難です。

APIの過剰なデータ露出を防ぐ実装|応答を許可リストで組み立てる

OWASP API3:2023が定める過剰なデータ露出と今回の事象の対応

OWASP API Security Top 10 2023のAPI3:2023は、利用者が読むべきでないオブジェクトのプロパティをAPIが返してしまう問題を扱っています。2019年版の「Excessive Data Exposure(過剰なデータ露出)」と「Mass Assignment」を統合した項目です。対策としては、to_json() のような汎用のメソッドでオブジェクトを丸ごと返さず、返したいプロパティだけを明示的に選ぶこと、応答のスキーマを定義して検証することが挙げられています。

i-plugは原因を「システム設定不備」とだけ説明しており、どの仕組みで項目が載ったかは公表していません。ただ、画面には出さない項目が通信には出ていたという現象は、API3:2023が想定する典型と同じ形です。10項目の全体像はOWASP API Security Top 10 2023の全10項目、実装対策の考え方はAPIセキュリティとはで整理しています。

Django REST frameworkで承諾前と承諾後のシリアライザを分ける

Django REST frameworkの公式ドキュメントは、ModelSerializerで返す項目を fields で明示的に列挙することを強く勧めています。モデルに列が増えたときに意図せずデータを出してしまう危険を減らせるからです。バージョン3.3.0以降は fields か exclude の指定が必須ですが、fields = “__all__” と書けば全列が出るので、必須化だけでは防げません。

次の例は、オファー承諾前の検索結果と、承諾後の連絡用で、返す項目そのものを分けています。承諾前の応答には氏名とメールアドレスの列が最初から存在しないため、画面の作りに関係なく通信にも載りません。

from rest_framework import generics, serializers
from .models import Offer, Student

class StudentSearchSerializer(serializers.ModelSerializer):
    # オファー承諾前:個人を特定できる列を含めない
    class Meta:
        model = Student
        fields = ["id", "university", "faculty", "graduation_year", "self_pr"]

class StudentContactSerializer(serializers.ModelSerializer):
    # オファー承諾後:連絡に必要な列だけを足す
    class Meta:
        model = Student
        fields = ["id", "last_name", "first_name", "email"]

class StudentDetailView(generics.RetrieveAPIView):
    queryset = Student.objects.all()

    def get_serializer_class(self):
        accepted = Offer.objects.filter(
            company=self.request.user.company,
            student_id=self.kwargs["pk"],
            status=Offer.ACCEPTED,
        ).exists()
        return StudentContactSerializer if accepted else StudentSearchSerializer

出し分けの判定はサーバー側で行い、画面側の条件分岐に頼らないことがこの書き方の要点です。Django以外でも、応答専用の型を作ってそこへ詰め替える方法で同じ効果が得られます。考え方はDTOとは、シリアライザの書き方はDjango REST FrameworkのSerializerとはで詳しく扱っています。

リリース前に通信データを検査する手順|HAR走査とPlaywrightの自動テスト

開発者ツールで保存したHARから個人情報の項目名を探すスクリプト

i-plugの確認工程は画面表示の確認にとどまっていました。手作業の受け入れ確認に通信の検査を足す最も手軽な方法は、対象画面を操作した後、DevToolsのNetworkパネルから通信をHARファイルとして保存し、その中のJSON応答を機械的に走査することです。次のスクリプトは、応答の中に氏名やメールアドレスを示す項目名、メールアドレスの形をした値があれば一覧を出し、終了コード1で終わります。

#!/usr/bin/env python3
# 使い方: python har_pii_scan.py capture.har
import base64, json, re, sys

# 応答に出てはいけないキー名(自社の命名に合わせて足す)
SENSITIVE = re.compile(r"(?i)^(last_?name|first_?name|full_?name|login_?id|e_?mail|mail_?address|tel|phone|birth)")

def walk(obj, path):
    if isinstance(obj, dict):
        for k, v in obj.items():
            p = f"{path}.{k}"
            if SENSITIVE.match(k):
                yield p, "key"
            yield from walk(v, p)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            yield from walk(v, f"{path}[{i}]")
    elif isinstance(obj, str) and "@" in obj and "." in obj.rsplit("@", 1)[-1]:
        yield path, "email-like value"

with open(sys.argv[1], encoding="utf-8") as f:
    har = json.load(f)

hits = 0
for e in har["log"]["entries"]:
    c = e["response"].get("content", {})
    if "json" not in c.get("mimeType", "") or not c.get("text"):
        continue
    text = c["text"]
    if c.get("encoding") == "base64":
        text = base64.b64decode(text).decode("utf-8", "replace")
    try:
        body = json.loads(text)
    except ValueError:
        continue
    for p, why in walk(body, "$"):
        print(f'{e["request"]["url"]}\t{p}\t{why}')
        hits += 1

print(f"hits={hits}")
sys.exit(1 if hits else 0)

模擬のHARで動かすと、検索APIの応答に入れた LASTNAME・FIRSTNAME・LOGINID と、base64で格納された応答の email を検出し、個人情報を含まない応答とHTMLは素通りしました。項目名の一覧は自社のモデル定義から作り、新しい列を足したら一覧も更新します。HARには認証用のCookieも入るので、保存したファイルは検査後に削除してください。

Playwrightで承諾前の画面の応答に氏名が無いことを毎回確かめる

手作業のHAR保存は漏れが出るため、CIで毎回回すテストに組み込みます。Playwrightのネットワーク機能では、page.on(“response”) でページが受け取ったすべての応答を拾えます。次のテストで確かめるのは、オファー承諾前の検索画面を開いたときのAPI応答に、禁止した項目名が一つも含まれないことです。

import { test, expect } from "@playwright/test";

const FORBIDDEN = [/^last_?name$/i, /^first_?name$/i, /^login_?id$/i, /^e_?mail$/i];

function findKeys(obj: unknown, path = "$", out: string[] = []): string[] {
  if (Array.isArray(obj)) obj.forEach((v, i) => findKeys(v, `${path}[${i}]`, out));
  else if (obj && typeof obj === "object") {
    for (const [k, v] of Object.entries(obj)) {
      if (FORBIDDEN.some((re) => re.test(k))) out.push(`${path}.${k}`);
      findKeys(v, `${path}.${k}`, out);
    }
  }
  return out;
}

test("承諾前の検索画面は氏名とメールアドレスを受け取らない", async ({ page }) => {
  const leaks: string[] = [];
  page.on("response", async (res) => {
    if (!res.url().includes("/api/")) return;
    if (!(res.headers()["content-type"] ?? "").includes("json")) return;
    leaks.push(...findKeys(await res.json()).map((p) => `${res.url()} ${p}`));
  });
  await page.goto("/company/students/search");
  await page.waitForLoadState("networkidle");
  expect(leaks).toEqual([]);
});

ログイン処理は既存のテストの共通設定に任せ、ここで記述している範囲は応答の検査だけです。このテストは画面の見た目を一切見ないので、画面には何も出ていないのに通信に載っているという今回の型をそのまま捕まえられます。Playwrightの導入と全体像はPlaywrightとはで解説しています。

設定不備型の漏えいと報告義務・外部診断|社内確認と第三者点検の分担

攻撃がなくても設定ミスで閲覧可能になれば漏えいとして扱われる

個人情報保護委員会のガイドライン(通則編)は、漏えいを「個人データが外部に流出すること」と定め、例の一つにシステムの設定ミス等によりインターネット上で個人データの閲覧が可能な状態となっていた場合を挙げています。i-plugが攻撃を受けていないのに速報を出したのは、この考え方に沿った判断と読めます。

本人の数が1,000人を超える漏えい等は、件数だけで報告の対象になります。速報と確報の期限、四つの類型の見分け方は委員会の漏えい等の対応ページと、当サイトの個人情報保護委員会への報告義務で確かめてください。

リリース判定に通信検査を足す条件と外部の脆弱性診断に任せる範囲

社内で持つべきなのは、新しい画面やAPIを出すたびに回る検査です。上のPlaywrightのテストのように、禁止項目の一覧と応答の走査をCIに組み込めば、リリース判定の項目として毎回確かめられます。項目名の一覧を誰が更新するかを決めておかないと、新しい列で同じ抜けが起きます。

一方、どの応答に何を載せてよいかという設計そのものの抜けは、作った本人には見えにくいものです。i-plugも外部専門機関による脆弱性診断を実施しています。会員の個人情報を段階的に開示するサービスや、企業と個人をつなぐマッチングサービスのように、相手によって見せてよい項目が変わる設計では、リリース前に一度、第三者の目で通信まで確かめることは有益です。外部診断の種類と費用感は脆弱性診断とはで整理しています。APIの応答や権限による出し分けまで含めて点検したい場合は、脆弱性診断・セキュリティ診断で画面と通信の両方から確認できます。

同じ時期の事業者側の事例として、LEAN BODYの不正アクセス、タイムズカーの不正アクセス、Gyazoの不正アクセスも比較の材料になります。いずれも外部からの攻撃が起点で、今回のように攻撃なしで起きた設定不備とは対策の入口が異なる事例です。クライアントに渡す鍵の扱いという点ではSupabase APIキーの管理も同じ論点を含みます。

よくある質問

OfferBoxの情報漏洩と、API応答の過剰なデータ露出について、学生と開発者から出やすい質問をまとめました。

OfferBoxの情報漏洩で自分のパスワードを変える必要はありますか?

公表された対象は氏名とメールアドレスで、パスワードは含まれていません。同社は第三者への流出は確認されておらず、二次被害のおそれは極めて低いとしています。変更を急ぐ必要はありませんが、同じパスワードを複数のサービスで使っている場合は、この機会に分けておくと他の事故にも備えられます。

自分が対象かどうかはどうすれば分かりますか?

同社は、対象となる可能性があり連絡先を把握している学生に個別の通知を送っています。対象は不備の期間に検索対象になっていた2027年卒と2028年卒の学生です。連絡先が変わっていると通知が届かないことがあるため、心配な場合は公式発表に載っている問い合わせ窓口に確認してください。

不正アクセスではないのに情報漏洩と呼ばれるのはなぜですか?

個人情報保護委員会のガイドラインは、システムの設定ミスで個人データが閲覧できる状態になっていた場合を漏えいの例に挙げています。判断の基準は、攻撃の有無ではなく、本来見せない相手に見える状態だったかどうかです。i-plugも本件を意図しない漏えいに当たると判断し、委員会へ速報しています。

画面に表示していない項目なら応答に含めても問題ありませんか?

問題があります。ブラウザに届いた応答は、開発者ツールを開けば誰でも読めます。画面に出すかどうかは見た目の制御であって、アクセス制御ではありません。相手に見せてはいけない項目は、サーバー側で応答から外すことが前提です。

自社のWebサービスで同じ不備がないか何から確認すればよいですか?

まず、相手によって見せてよい項目が変わる画面を洗い出し、その画面を開いたときのAPI応答をDevToolsで確かめます。次に、応答を組み立てる箇所が返す項目を明示的に列挙しているかをコードで確認し、最後にPlaywrightなどで応答の検査をCIに組み込みます。設計の抜けが心配な場合に選べる方法の一つは、外部の脆弱性診断で通信まで点検してもらうことです。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.06 テックブログ 大和証券の不正アクセスと約11万人分の口座番号:問い合わせ管理の委託先に残さない設計
  2. 2026.10.06 テックブログ 焼肉きんぐの不正アクセスと1,078万件の会員情報|全件規模の流出を防ぐAPIとログの点検
  3. 2026.10.06 テックブログ 原子力研究開発機構の不正アクセスと身分証画像の漏えい|研究支援サイトのファイル保管を点検する手順
  4. 2026.10.04 テックブログ デジタル庁GSSの不正アクセスと約24.6万件|CVSS中のVPN脆弱性を何で優先するか
  5. 2026.10.01 テックブログ AWS VPN Clientの使い方:6.x系のインストールとCLI・接続できない時の確認先

RELATED POSTS 関連記事

目次