リクルートは2026年10月3日、オンライン学習サービス「スタディサプリ」で、システムの一部機能にあった仕様の不備を突かれ、メールアドレス3,687件について登録されているかどうかを第三者に特定された可能性があると公表しました。判明は9月30日で、同日中に機能を改修しています。氏名やパスワード、決済情報の流出は確認されていません。本記事では公表内容と自治体の発表を時系列で整理し、利用者と学校・自治体の担当者が取る行動をまとめます。後半では、報道で「アカウント列挙」と呼ばれたこの種の不備を自社サービスで防ぐため、ログイン・新規登録・パスワード再設定の応答を揃える実装を、動くコードと点検手順で示します。
まとめ:スタディサプリの不正アクセスで確定した事実と開発者が直す箇所
確定しているのは、9月30日に第三者の不適切な操作が判明したこと、対象が小学講座・中学講座・高校講座・大学受験講座の利用者であること、3,687件のメールアドレスが登録済みと特定された可能性があること、同日中に同じ手法で確認できない状態へ改修したことです。不備があった機能の名前と、操作が行われていた期間は公表されていません。
利用者がやることは一つです。スタディサプリやリクルートを名乗るメールのリンクを開かず、公式サイトか公式アプリからアクセスしてください。開発者の側では、登録済みと未登録で応答が変わる箇所を、文言・ステータスコード・処理時間の三点で揃えます。そのうえでアカウント単位とIP単位のレート制限をかけ、試行の量そのものを抑えます。
| 項目 | 公表されている内容 |
|---|---|
| 判明 | 2026年9月30日 |
| 公表 | 2026年10月3日 |
| 対象件数 | メールアドレス3,687件 |
| 対象講座 | 小学・中学・高校・大学受験 |
| 原因 | 一部機能の仕様の不備 |
| 他の情報の流出 | 確認されていない |
| 対象機能・期間 | 非公表 |
公表内容を時系列で確認する|9月30日の判明から自治体経由の連絡まで
9月30日の判明と同日の改修から10月3日の公表までの四日間
スタディサプリの重要なお知らせ(2026年10月3日掲載)によると、9月30日に第三者による不適切な操作が判明しました。リクルートは同日時点で、同じ手法では登録の有無を確認できないよう機能を修正しています。あわせて緊急のセキュリティ点検を行い、監視体制を強化しました。
公表文は、対象の可能性がある利用者へ順次個別に連絡し、問い合わせ窓口を設けたとしています。氏名・パスワード・決済情報など、メールアドレス以外の個人情報の外部への流出は確認されておらず、アカウントへの不正ログインや二次被害も公表時点では確認されていません。判明から改修までが同日、公表までが三日です。
千代田区の児童・生徒8名に見る学校・自治体経由の利用者への影響
学校や自治体経由で契約している利用者には、学校・自治体を通じた連絡も行われました。東京都千代田区は区のお知らせ(2026年10月5日更新)で、任意で登録されていた区内の児童・生徒8名分のメールアドレスが対象になった可能性があると公表しています。
区への一報は10月2日で、10月2日から5日にかけて対象者を確認し、各学校を通じて連絡しました。登録されていたのが学校配布のアカウントと一致する場合はアカウントの変更作業を行い、私用のメールアドレスだった場合は不審なメールへの注意を改めて案内するとしています。教育サービスでは、利用者本人・保護者・学校・自治体と連絡の経路が分かれる点が、一般のWebサービスと違います。
公表文に書かれていないことと報道が補ったアカウント列挙という見方
公表文が原因として示しているのは「システムの一部機能における仕様の不備」までです。ログイン画面なのか、新規登録なのか、パスワード再設定なのかは書かれていません。いつから操作が続いていたのかも分かりません。
ITmedia NEWS(2026年10月6日)は、登録済みと未登録のメールアドレスで異なる応答が返る仕組みを突いた「アカウント列挙」と呼ばれる攻撃の可能性を伝えています。これは報道による説明で、リクルートが手口の名称を示したわけではありません。本記事では特定の画面に結び付けた推測はせず、列挙が起きうる箇所を一通り点検する前提で話を進めます。
アカウント列挙の仕組み|登録済みと未登録で応答が変わる画面と差の種類
ログイン・新規登録・パスワード再設定で登録の有無が漏れる典型
アカウント列挙は、攻撃者が手元のメールアドレス一覧を順に送り、返ってくる応答の違いから登録済みのものを選り分ける手口です。MITREはCWE-204(Observable Response Discrepancy)として分類しており、利用者名が無い場合と、パスワードだけが違う場合とで別のエラー文言を出すログイン処理を例に挙げています。
| 画面 | 漏れる応答の例 |
|---|---|
| ログイン | 「このアドレスは登録されていません」 |
| 新規登録 | 「このアドレスは既に使われています」 |
| パスワード再設定 | 「該当するアカウントがありません」 |
| 入力補助のAPI | 重複チェックの結果をJSONで返す |
見落とされやすいのは最後の行です。画面の文言を直しても、入力中に重複を確かめるAPIが真偽値を返していれば、そこから一件ずつ確かめられます。
文言が同じでもステータスコードや応答時間の差で判別される理由
OWASPのAuthentication Cheat Sheetは、画面上の文言が共通でも、成功時に200、失敗時に403のようにHTTPステータスコードが分かれていれば有効なアカウントが漏れると指摘しています。リダイレクト先のURLやレスポンスの長さも同じ扱いです。
処理時間も差になります。利用者が存在しないときはすぐに失敗を返し、存在するときだけパスワードのハッシュ計算をする実装では、応答時間の差で登録の有無が分かります。同シートが推奨するのは、存在の有無にかかわらず同じ処理をたどる実装です。文言・ステータスコード・処理時間の三つを揃えて、初めて応答の統一と言えます。
登録の有無だけが分かった場合に起きる標的型フィッシングとの組み合わせ
登録の有無はパスワードほど重い情報に見えません。ただ、攻撃者にとっては「このアドレスの持ち主はスタディサプリを使っている」という確度の高い宛先一覧になります。サービス名や講座名を入れた偽の通知は、無差別のばらまきメールより開かれやすくなります。
教育サービスの場合、受け取るのが児童・生徒や保護者であることも条件を悪くします。リクルートが公表文で不審なメールへの注意を呼びかけているのは、この二次被害を想定したものです。偽の通知が認証情報を盗む流れはフィッシング詐欺の成立の仕組みと技術的対策で詳しく扱っています。
利用者と学校・自治体の担当者が今やる確認|不審メールと登録アドレス
スタディサプリを名乗るメールに利用者が取る行動と公式経路の確かめ方
リクルートは、スタディサプリやリクルートを装った不審なメールが届いても、記載されたURLにアクセスせず、パスワードや認証コードを入力しないよう求めています。利用時のアクセス先として案内しているのは、公式サイトか公式アプリです。メールのリンクを使わないと決めておけば、文面の真偽を見分ける必要がなくなります。
個別の連絡が届いた場合も、内容の確認は公式サイトの重要なお知らせや問い合わせ窓口で行います。窓口は、個人契約者向けと、学校・自治体経由の生徒・保護者・先生向けに分かれた構成です。同じパスワードを他のサービスで使っている人は、この機会に分けておくと被害の連鎖を止められます。
学校配布アカウントと私用アドレスで分かれる学校・自治体側の対処
学校や自治体の担当者が確かめるのは、登録されていたメールアドレスの種類です。千代田区の対応がそのまま手順の例になります。
- 事業者から対象者の連絡を受け、在籍校ごとに該当する児童・生徒を特定する
- 登録アドレスが学校配布のアカウントか、家庭の私用アドレスかを確かめる
- 学校配布のアカウントなら、アドレスの変更や再発行を検討する
- 私用アドレスなら、本人と保護者へ不審メールへの注意を案内する
学校配布のアカウントは、規則的な形式(学籍番号と学校ドメインの組み合わせなど)で作られていることが多く、一件が分かると同じ学校の別アドレスも推測されやすくなります。私用アドレスより変更の優先度を上げてください。
応答を揃える実装|ログイン・登録・再設定で同じ文言と同じ処理時間を返す
OWASPが示す三画面の推奨メッセージと既存の登録者へ通知を送る設計
Authentication Cheat Sheetは、三つの画面で使う共通メッセージの例を挙げています。ログインは「ユーザーIDまたはパスワードが無効です」、パスワード再設定は「そのメールアドレスが登録されていれば、再設定のメールを送ります」、新規登録は「入力されたアドレスに有効化のリンクを送りました」です。
難しいのは新規登録です。「既に使われています」と返さないなら、既存の登録者には別の文面のメールを送る形にします。本人には「このアドレスで登録の操作がありました。既にアカウントをお持ちです」と届き、画面上は未登録の場合と同じ表示になります。本人が登録済みを忘れていた場合も、メールで気付けるため使い勝手は大きく落ちません。
存在しない利用者でもハッシュ計算を走らせるPythonコードと実行結果
次のコードは、文言・ステータスコード・処理時間を揃えたログインと、レート制限の最小構成です。標準ライブラリだけで動きます。ハッシュの反復回数はOWASPのPassword Storage Cheat SheetがPBKDF2-HMAC-SHA256に示す60万回に合わせています。
import hashlib
import hmac
import os
import time
from collections import defaultdict, deque
ITERATIONS = 600_000
DUMMY_SALT = os.urandom(16)
DUMMY_HASH = hashlib.pbkdf2_hmac("sha256", b"dummy", DUMMY_SALT, ITERATIONS)
def make_user(password):
salt = os.urandom(16)
return {"salt": salt, "hash": hashlib.pbkdf2_hmac("sha256", password.encode(), salt, ITERATIONS)}
USERS = {"[email protected]": make_user("correct-horse")}
class SlidingWindow:
def __init__(self, limit, window_sec):
self.limit, self.window = limit, window_sec
self.hits = defaultdict(deque)
def allow(self, key):
now = time.monotonic()
q = self.hits[key]
while q and now - q[0] > self.window:
q.popleft()
if len(q) >= self.limit:
return False
q.append(now)
return True
per_ip = SlidingWindow(limit=20, window_sec=60)
per_account = SlidingWindow(limit=5, window_sec=900)
LOGIN_FAILED = (401, "メールアドレスまたはパスワードが正しくありません")
TOO_MANY = (429, "しばらく時間をおいて再度お試しください")
RESET_SENT = (200, "登録済みのアドレスであれば、再設定の案内をお送りしました")
def login(email, password, ip):
if not per_ip.allow(ip) or not per_account.allow(email.lower()):
return TOO_MANY
user = USERS.get(email.lower())
salt = user["salt"] if user else DUMMY_SALT
expected = user["hash"] if user else DUMMY_HASH
actual = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, ITERATIONS)
if user and hmac.compare_digest(actual, expected):
return (200, "ログインしました")
return LOGIN_FAILED
def request_reset(email, ip, enqueue):
if not per_ip.allow(ip):
return TOO_MANY
enqueue(email.lower())
return RESET_SENT
if __name__ == "__main__":
jobs = []
for email in ["[email protected]", "[email protected]"]:
t = time.perf_counter()
status, msg = login(email, "wrong-pass", ip=f"198.51.100.{len(email)}")
print(f"login {email:18} {status} {msg} {time.perf_counter() - t:.3f}s")
for email in ["[email protected]", "[email protected]"]:
print("reset", email, request_reset(email, "198.51.100.7", jobs.append))
print("queued:", jobs)
print("連続6回:", [login("[email protected]", "x", ip=f"203.0.113.{i}")[0] for i in range(6)])
Python 3.14.6で実行すると、登録済みの[email protected]と未登録の[email protected]はどちらも401と同じ文言を返し、処理時間も約2.7秒でほぼ同じになりました。要点は、利用者が見つからないときもダミーのソルトでハッシュ計算を一回行うことです。比較にはhmac.compare_digestを使い、文字列比較の途中終了による時間差も避けています。
パスワード再設定はメール送信をキューへ回して処理時間の差を消す
パスワード再設定では、登録済みのアドレスにだけメールを送るため、その送信処理が応答時間の差になります。OWASPのForgot Password Cheat Sheetは、存在するアカウントにもしないアカウントにも同じメッセージを、同じ程度の時間で返すよう求め、その手段として非同期の呼び出しを挙げています。
上のコードのrequest_resetは、登録の有無を確かめずにアドレスをキューへ積み、すぐ同じ応答を返します。登録の確認、トークンの発行、メール送信を担当するのは、裏側で処理するワーカーです。同シートは、トークンを暗号論的に安全な乱数で作ること、一度使ったら無効にすること、適切な期間で失効させることも求めています。再設定の要求が多いことを理由にアカウントをロックしない点も、利用者を締め出す嫌がらせを防ぐ設計として押さえておきます。
レート制限と監視で列挙を遅らせる|アカウント単位とIP単位の二重の上限
NIST SP 800-63Bの失敗100回上限とアカウント単位で数える理由
応答を揃えても、試行の回数に制限がなければ、攻撃者が時間をかけて差を探すことは可能です。NIST SP 800-63BのSec. 3.2.2は、一つのアカウントに対する連続した認証失敗を100回以下に制限するよう定め、100回は上限でありそれより低い値にしてよいとしています。失敗のたびに待機時間を延ばす方法やボット対策のチャレンジも、追加の手段として挙げています。
数える単位は二つ必要です。OWASPは失敗の回数をIPアドレスではなくアカウント単位で数えるよう求めています。多数のIPアドレスから同じアカウントを狙う攻撃に、IP単位の制限は効かないためです。一方、列挙は一つのIPアドレスから多数のアドレスを試すことが多く、こちらはIP単位で止めます。コードでper_ipとper_accountを分けているのはこのためで、実行結果では存在しないアドレスでも6回目に429が返り、制限のかかり方からも登録の有無は分かりません。具体的な方式の比較はAPIレート制限の5方式と429設計が参考になります。
自社サービスの画面が登録の有無を漏らしていないかcurlで確かめる手順
点検は、確実に登録されているアドレスと、確実に存在しないアドレスを一つずつ用意し、同じ画面に送って応答を並べるだけで始められます。本番ではなく検証環境で行ってください。
for e in [email protected] "nobody-$(date +%s)@example.jp"; do
curl -s -o /dev/null \
-w "$e status=%{http_code} bytes=%{size_download} time=%{time_total}s redirect=%{redirect_url}\n" \
-X POST https://staging.example.jp/password/reset \
-d "email=$e"
done
二行のstatus、bytes、redirectが一致し、timeの差が数回の計測で一定の傾向を示さなければ合格です。ログイン、新規登録、入力補助のAPIにも同じ形で送ります。bytesだけが違うときは、画面の一部に「登録済み」を示す文言や要素が紛れていないかを見てください。
応答統一を入れるべきサービスと手間に見合わない場面を分ける判断軸
入れるべきなのは、利用者がメールアドレスでログインし、登録していること自体が属性を表すサービスです。教育、医療、金融、求人、会員制の通販などが当たります。スタディサプリの件のように、利用者が子どもや患者であれば、登録の有無を知られる不利益は大きくなります。
反対に、社内の業務システムで利用者が全従業員と決まっている場合、登録の有無は秘密ではありません。この場合に新規登録の文面を作り分けるのは手間に見合わず、レート制限と多要素認証を先に固めるほうが効果があります。公開のSNSのように利用者名がもともと公開されているサービスも同様です。判断の軸は「登録しているという事実を知られて困る人がいるか」で、いるなら応答統一は必須、いないなら見送ってかまいません。認証の強化側は多要素認証の実装方式と耐フィッシングMFAで整理しています。
事故後の報告と再発防止の段取り|登録の有無が漏れたときに確かめること
個人情報保護委員会への報告要否を確かめる四類型と期限の考え方
自社サービスで同種の事案が起きた場合、最初に確かめるのは個人情報保護委員会への報告の要否です。対象は要配慮個人情報、財産的被害のおそれ、不正の目的をもって行われたおそれ、本人の数が1,000人超の四類型で、速報は発覚から3〜5日以内、確報は30日以内(不正目的の場合は60日以内)です。
登録の有無が特定されたことがどの類型に当たるかは、取り扱っている情報と事案の実態で変わります。本記事ではリクルートの件の該当性は判断しません。類型ごとの判断と報告書の準備は個人情報保護委員会への報告義務の実務にまとめています。
列挙の痕跡を認証ログから拾う観点と外部の脆弱性診断に任せる範囲
列挙の痕跡は、ログイン失敗や再設定要求のログに、短時間で多数の異なるアドレスが並ぶ形で残ります。一つのIPアドレスやUser-Agentから、存在しないアドレスへの要求が続いていないかを集計してください。ログの取り方と集計の手順は不正アクセスのログ確認・解析方法が使えます。
応答の差は、画面を一つずつ見ただけでは気付きにくい不備です。入力補助のAPIやスマートフォンアプリ向けのAPIのように、画面と別の入口があるサービスほど見落としが増えます。ログイン周りの入口をまとめて点検したい場合は、脆弱性診断・セキュリティ診断で、画面とAPIの両方の応答差を含めて確認できます。同じG30の事例では、Gyazoの不正アクセスがアップロード基盤、タイムズカーの不正アクセスが保管設計の問題でした。認証の入口から起きた今回の件と並べると、点検の範囲を決める材料になります。
よくある質問
スタディサプリの不正アクセスとアカウント列挙について、利用者と開発者から出やすい質問をまとめました。
スタディサプリのパスワードは変更したほうがよいですか?
公表時点で、パスワードの流出や不正ログインは確認されていません。変更は必須とされていませんが、同じパスワードを他のサービスでも使っている場合は分けておくと安心です。注意すべきは偽のメールで、届いたメールのリンクからパスワードを入力しないことが最も効く対策です。
自分のメールアドレスが対象かどうかはどう確かめますか?
リクルートは対象の可能性がある利用者へ個別に連絡するとしています。学校・自治体経由の利用者には、学校や自治体を通じた連絡も行われています。連絡を装ったメールもありうるため、確認は公式サイトの重要なお知らせや問い合わせ窓口から行ってください。
メールアドレスの登録の有無だけで何が問題になるのですか?
登録済みと分かったアドレスは、サービス名を出した偽の通知の宛先として精度が上がります。無差別のばらまきメールより信じられやすく、ログイン情報や決済情報を盗まれる入り口になります。利用者が子どもの場合は、利用している学習サービスという属性そのものも知られたくない情報です。
ログイン画面の文言を統一すればアカウント列挙は防げますか?
それだけでは防げません。HTTPステータスコード、レスポンスの長さ、リダイレクト先、処理時間が違えば、文言が同じでも判別されます。新規登録やパスワード再設定、入力中の重複チェックAPIにも同じ問題があります。応答を揃えたうえで、アカウント単位とIP単位のレート制限をかけてください。
新規登録で既に使われているアドレスだと伝えないと不便ではありませんか?
画面では未登録の場合と同じ表示にし、既存の登録者のメールアドレスへ「既にアカウントをお持ちです」と知らせれば、本人は気付けます。他人には登録の有無が分かりません。社内システムのように登録の有無が秘密でない場合は、従来どおり画面で伝えてもかまいません。
関連記事
- Gyazoの不正アクセスと2,362万件の流出|利用者の点検手順とアップロード基盤の見直し:同じG30の事例で、アップロード処理を起点にした侵害
- タイムズカーの不正アクセスと免許証画像160万件の流出|退会者まで残さない保管設計:保管しているデータの範囲を決める設計の比較に
- フィッシング詐欺とは?成立の仕組みと実装者向けの技術的対策を解説:列挙されたアドレスが使われる二次被害の仕組み
- APIレート制限(レートリミット)とは?5方式の比較と429設計・分散実装【2026年版】:列挙を遅らせるレート制限の方式選び
- 個人情報保護委員会への報告義務|対象4類型・速報3〜5日と確報30日の実務:事案が起きたときの報告判断