---
title: "Qoo10の不正アクセス：認証トークン不正発行の経緯と、発行監査・一括失効の実装手順"
url: "https://www.issoh.co.jp/tech/details/19729/"
published: 2026-11-13
updated: 2026-11-13
categories: ["セキュリティ"]
publisher: "株式会社一創"
---

# Qoo10の不正アクセス：認証トークン不正発行の経緯と、発行監査・一括失効の実装手順

ECモール「Qoo10」を運営するeBay Japan合同会社は、2026年11月12日、6日に受けた不正アクセスで一部の顧客の個人情報が閲覧された可能性があると公表しました。同社は検知後すぐにアクセスを遮断し、不正に発行・利用された可能性のある認証トークンをすべて無効化して、影響の可能性があるアカウントを強制ログアウトさせています。この記事では、公表で確定している事実と未公表の項目を分けて整理し、利用者が今日やることを短く示す構成です。後半は開発者向けに、「トークンの不正発行」を自社サービスで見つける発行ログの監査と、JWTを一括で失効させる検証側の設計を、実行できるPythonのコード付きで解説します。

## まとめ：Qoo10の不正アクセスで確定した事実と、自社サービスで確かめる作業

確定しているのは、11月6日の不正アクセス、検知後の遮断、認証トークンの全無効化と強制ログアウト、氏名・住所・住所の地理座標など16項目が閲覧された可能性、の4点です。決済情報とパスワードについては、漏えいを示す証拠を現時点で確認していないとしています。件数と侵入経路は公表されていません。

Qoo10の利用者は、パスワードの再設定より先に、再ログインや返金を装うメール・SMSのリンクを開かないことを優先します。

会員制のサービスを持つ開発者が自社で確かめる作業は、優先度の高い順に次の3つです。

1. トークンの発行ログに、ログイン成功と結び付かない発行が混ざっていないかを突き合わせる
2. JWTを使っている場合、全員分・特定の利用者分・特定のトークンを、検証側で拒否できる仕組みがあるかを確かめる
3. リフレッシュトークンのローテーションと再利用の検知が入っているかを確かめる

「全部無効化する」と言える状態を、事故の前に作っておくことが本題です。

## 11月12日の公表で確定している事実と、件数・侵入経路など未公表の項目

### 11月6日の不正アクセスから12日の公表までの遮断・トークン無効化・強制ログアウト

Qoo10は11月12日に[公式サイトのお知らせ](https://www.qoo10.jp/gmkt.inc/Main/PopupNotice.aspx?nid=6246)を掲載しました。ITmedia NEWSなどの報道によると、内容は次のとおりです。11月6日に外部の第三者からシステムへの不正アクセスを受け、検知後すぐに遮断しました。不正に発行・利用された可能性がある認証トークンはすべて無効化し、影響を受けた可能性のあるアカウントは強制的にログアウトさせています。

詳しい調査は現在も続いています。影響を受けた人数、侵入の経路、トークンがどの方法で不正に発行されたかは、公表時点で明らかにされていません。この記事でも推測では書きません。

### 閲覧の可能性がある16項目と、決済情報・パスワードを未確認とした範囲

閲覧された可能性のある情報として挙げられたのは16項目です。通常の取引で取得した情報と、利用者が自分で入力した情報の両方が含まれます。

| 区分                 | 項目                        |
| ------------------ | ------------------------- |
| 本人の属性（氏名・生年月日など）   | 氏名、ニックネーム、生年月日、性別         |
| 本人の属性（プロフィールなど）    | プロフィール情報、任意入力の身体的特徴       |
| 連絡先                | メールアドレス、電話番号              |
| 住所                 | 住所、配送先住所、住所の地理座標          |
| アカウント・利用状況（識別・接続元） | アカウント識別情報、IPアドレス          |
| アカウント・利用状況（顧客区分など） | 顧客セグメント情報、個人メモ、サイト内ポイント残高 |

決済情報とパスワードは、漏えいを示す証拠を「現時点で」確認していないという表現です。漏えいしなかったと断定したものではありません。住所の地理座標は、住所の文字列よりも正確に自宅の位置を示します。配送の精度を上げるために持つ値ですが、閲覧されたときの影響は住所より重いと見ておきます。

### 利用者が今日やることと、強制ログアウト後の再ログインを装う連絡への注意

Qoo10は、なりすましや不審な電話・SMS・メールが発生するおそれがあるとして、送信元が確認できないメッセージのリンクを押さないよう呼びかけています。強制ログアウトの直後は、「再ログインが必要です」「ポイントの確認」「返金の手続き」を口実にした偽の連絡が届きやすい時期です。再ログインは、公式アプリかブックマークから行います。

問い合わせ先は、利用者が「お客様相談窓口」、出店者が出店者向けの問い合わせフォームです。ほかのサービスとQoo10で同じパスワードを使っている場合は、ほかのサービス側を変更します。漏えいの証拠が無いとされていても、使い回しは別の経路で狙われます。

### 2025年9月の不正ログイン騒動と今回の公表を分けて読む理由

「Qoo10 不正アクセス」で検索すると、2025年9月の記事も出てきます。当時はSNSで不正ログインの報告が相次ぎましたが、eBay Japanは個人情報の流出や不正アクセスの事実は確認されていないと説明し、他サイトから漏れたIDとパスワードを使った試行の可能性を挙げていました。利用者のパスワードが入口になる型です。

今回はQoo10のシステム自体が不正アクセスを受け、認証トークンの発行・利用が問題になっています。対策の主体も中身も違います。利用者がパスワードを変えるだけでは閉じない型なので、運営側が発行済みのトークンを無効化する必要がありました。

## 「不正に発行された認証トークン」が示す侵害の型と、パスワード漏えいとの違い

### パスワードを知らなくても、発行済みのトークンだけでなりすましが成り立つ仕組み

ログインに成功すると、サーバーは認証トークンを発行し、アプリやブラウザは以後のリクエストにそれを付けて送ります。多くのAPIは「Authorization: Bearer」ヘッダーでトークンを受け取り、持っている者を本人として扱う仕組みです。つまりトークンを手に入れた者は、パスワードを知らなくても有効期限まで本人として振る舞えます。

このため、パスワードが漏れていなくてもトークンが不正に作られた時点で被害は起こり得ます。逆に、パスワードを変えただけでは発行済みのトークンは止まりません。Qoo10がパスワードの再設定より先にトークンの無効化と強制ログアウトを行ったのは、この順序に沿った対応です。トークンの持ち方と寿命の基本は[JWTの構造と署名検証の解説](https://www.issoh.co.jp/tech/details/13453/)で整理しています。

### トークンの発行経路が破られる型：署名鍵の漏えい・発行APIの認可不備・内部経路の悪用

ここからは一般論です。Qoo10でどの型が起きたかは公表されていません。認証トークンが「不正に発行される」経路は、実務では次の3つに分けて点検します。

- 署名鍵の漏えい：JWTの署名鍵が設定ファイルやリポジトリ、ログから漏れると、攻撃者は任意の利用者のトークンを自分で作れます。発行ログには何も残りません。
- 発行APIの認可不備：トークンを発行するAPIが、利用者IDを引数で受け取り、本人確認の結果と照合せずに発行してしまう型です。発行ログには残ります。
- 内部・管理用の発行経路の悪用：サポート担当の代理ログインやバッチ用の発行機能が、外から呼べる状態になっていた型です。

前の2つは対処が真逆です。鍵の漏えいなら鍵を替えるしかなく、発行APIの不備なら発行ログから被害者を特定できます。どちらか分からない段階では、両方を前提に動きます。

## 発行監査の実装：トークン発行ログとログイン成功ログを突き合わせる手順

### 発行ログの6項目でトークンの不正発行を追跡し、生値の保存を避ける理由

「不正に発行されたトークン」を後から特定するには、発行の時点で記録を残しておくことが必要です。[RFC 7519](https://www.rfc-editor.org/rfc/rfc7519.html)はトークンを一意に識別する jti クレームと発行時刻の iat クレームを定めています。発行ログには、jti、利用者ID（sub）、元になったセッションID、発行経路（パスワード・リフレッシュ・内部など）、接続元IP、時刻の6項目を残します。

トークン本体はログに書きません。[OWASPのSession Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session%5FManagement%5FCheat%5FSheet.html)は、生のセッションIDをログに残さず、ソルト付きのハッシュを記録するよう勧めています。ログが漏れたときに、そのままなりすましに使われるのを防ぐためです。同じ資料は、セッションIDの作成・更新・破棄をイベントとして記録することも推奨しています。

### 認証を経ずに発行されたトークンを洗い出すPythonの突き合わせ例

次のコードは、ログイン成功ログとトークン発行ログを突き合わせ、ログインと結び付かない発行に「要調査」を付けます。パスワードによる発行はログイン成功から60秒以内、リフレッシュによる発行は同じセッションが存在することを条件にしています。

```
# トークン発行ログとログイン成功ログを突き合わせ、認証を経ずに発行されたトークンを洗い出す（Python 3.9以上・標準ライブラリのみ）
import csv
import io
from datetime import datetime, timedelta

WINDOW = timedelta(seconds=60)  # ログイン成功から発行までに許す最大の間隔

# 認証サーバーのログイン成功ログ（user_id, session_id, 時刻）
LOGINS = """user_id,session_id,at
u100,s-01,2026-11-06T09:58:12+09:00
u200,s-02,2026-11-06T10:03:40+09:00
"""
# トークン発行ログ（jti, user_id, session_id, 発行経路, 時刻）
ISSUED = """jti,user_id,session_id,grant,at
t-001,u100,s-01,password,2026-11-06T09:58:13+09:00
t-002,u100,s-01,refresh,2026-11-06T10:28:13+09:00
t-003,u200,s-02,password,2026-11-06T10:03:41+09:00
t-004,u300,,internal,2026-11-06T10:05:02+09:00
t-005,u400,s-99,password,2026-11-06T10:05:09+09:00
"""

def rows(text):
    return list(csv.DictReader(io.StringIO(text)))

logins = {r["session_id"]: r for r in rows(LOGINS)}
issued = rows(ISSUED)

for t in issued:
    at = datetime.fromisoformat(t["at"])
    login = logins.get(t["session_id"])
    if t["grant"] == "refresh":
        # 更新は、同じセッションで正規に発行済みのトークンがあることを条件にする
        ok = login is not None and login["user_id"] == t["user_id"]
        reason = "" if ok else "元になるセッションが無い更新"
    elif login is None:
        ok, reason = False, "ログイン成功ログに無いセッション"
    elif login["user_id"] != t["user_id"]:
        ok, reason = False, "セッションと利用者が一致しない"
    else:
        gap = at - datetime.fromisoformat(login["at"])
        ok = timedelta(0) <= gap <= WINDOW
        reason = "" if ok else f"ログインから{gap}後の発行"
    print("OK  " if ok else "要調査", t["jti"], t["user_id"], t["grant"], reason)
```

Python 3.14.10（Windows）で2026年11月13日に実行した結果です。値は説明用に作ったもので、Qoo10のログを再現したものではありません。

```
OK   t-001 u100 password
OK   t-002 u100 refresh
OK   t-003 u200 password
要調査 t-004 u300 internal ログイン成功ログに無いセッション
要調査 t-005 u400 password ログイン成功ログに無いセッション
```

t-004 はセッションを持たない内部経路からの発行、t-005 は存在しないセッションIDを名乗る発行です。どちらも発行APIの認可不備か内部経路の悪用を疑う材料になります。署名鍵が漏れた場合は発行ログ自体に記録が残らないため、この突き合わせでは見つかりません。その場合は、APIのアクセスログに出てくる jti のうち発行ログに無いものを数えます。

## 「すべて無効化」の実装：JWTを一括失効させる失効境界・鍵の世代・jtiの使い分け

### RFC 7009の失効エンドポイントだけでは自己完結型トークンを即時に止められない理由

OAuth 2.0のトークン失効は[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html)で定められた仕様です。リフレッシュトークンを失効させたら、同じ認可グラントに基づくアクセストークンも無効化することが推奨（SHOULD）されています。ただし同じRFCの実装上の注記は、JWTのような自己完結型のアクセストークンを即時に止めるには、認可サーバーとリソースサーバーの間に標準化されていない連携が要り、代わりに寿命を短くする方法もある、と書いています。

JWTは、検証側が署名と期限だけを見て受理する設計が多いため、認可サーバーで「失効させた」と記録しても、検証側がその記録を見に行かなければ期限まで通ります。サーバー側で状態を持つセッション方式との違いは[セッションIDの発行から失効までの実装](https://www.issoh.co.jp/tech/details/16366/)、検証のたびに認可サーバーへ問い合わせる方式は[トークンイントロスペクションの解説](https://www.issoh.co.jp/tech/details/16417/)で扱いました。

### 鍵の世代・失効境界・jti拒否リストを検証側に組み込むPythonの一括失効例

検証側で拒否する粒度は3段階に分けます。全員分は署名鍵の世代（kid）を替えて旧世代を受け付けない方法、特定の利用者分は「この時刻より前に発行されたものは無効」という失効境界、特定のトークンは jti の拒否リストです。

```
# JWTを「失効時刻の境界」と「署名鍵の世代」で一括失効させる検証側の例（Python 3.9以上・標準ライブラリのみ）
import base64
import hashlib
import hmac
import json
from datetime import datetime

def ts(iso):
    return int(datetime.fromisoformat(iso).timestamp())

KEYS = {"k2026-10": b"old-secret", "k2026-11": b"new-secret"}  # 鍵の世代（kid）ごとの署名鍵
ACTIVE_KIDS = {"k2026-11"}           # 侵害後は旧世代の鍵で署名されたトークンを受け付けない
REVOKED_BEFORE = {"u100": ts("2026-11-06T11:00:00+09:00")} # 利用者ごとの失効境界（この時刻より前に発行されたものは無効）
DENY_JTI = {"t-004"}                 # 個別に失効させたトークンID

def b64(data):
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

def sign(claims, kid):
    head = b64(json.dumps({"alg": "HS256", "typ": "JWT", "kid": kid}).encode())
    body = b64(json.dumps(claims).encode())
    sig = hmac.new(KEYS[kid], f"{head}.{body}".encode(), hashlib.sha256).digest()
    return f"{head}.{body}.{b64(sig)}"

def unb64(s):
    return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

def verify(token, now):
    head_b, body_b, sig_b = token.split(".")
    head = json.loads(unb64(head_b))
    kid = head.get("kid")
    if head.get("alg") != "HS256" or kid not in ACTIVE_KIDS:
        return "拒否: 失効した鍵の世代"
    want = hmac.new(KEYS[kid], f"{head_b}.{body_b}".encode(), hashlib.sha256).digest()
    if not hmac.compare_digest(want, unb64(sig_b)):
        return "拒否: 署名不一致"
    c = json.loads(unb64(body_b))
    if c["exp"] <= now:
        return "拒否: 期限切れ"
    if c["jti"] in DENY_JTI:
        return "拒否: 個別失効"
    if c["iat"] < REVOKED_BEFORE.get(c["sub"], 0):
        return "拒否: 失効境界より前の発行"
    return "受理"

NOW = ts("2026-11-06T15:00:00+09:00")
cases = [
    ("旧鍵で署名", sign({"sub": "u200", "jti": "t-003", "iat": ts("2026-11-06T09:06:40+09:00"), "exp": NOW + 600}, "k2026-10")),
    ("境界より前", sign({"sub": "u100", "jti": "t-010", "iat": ts("2026-11-06T10:46:40+09:00"), "exp": NOW + 600}, "k2026-11")),
    ("個別失効",   sign({"sub": "u300", "jti": "t-004", "iat": ts("2026-11-06T11:36:40+09:00"), "exp": NOW + 600}, "k2026-11")),
    ("再ログイン後", sign({"sub": "u100", "jti": "t-011", "iat": ts("2026-11-06T11:53:20+09:00"), "exp": NOW + 600}, "k2026-11")),
]
for label, tok in cases:
    print(f"{label:6} {verify(tok, NOW)}")
```

同じ環境での実行結果です。

```
旧鍵で署名  拒否: 失効した鍵の世代
境界より前  拒否: 失効境界より前の発行
個別失効   拒否: 個別失効
再ログイン後 受理
```

本番では、ACTIVE\_KIDS・REVOKED\_BEFORE・DENY\_JTI をコードに書かず、全サーバーが参照できるRedisなどに置きます。拒否リストは exp を過ぎた jti から消せるので、アクセストークンの寿命を15分程度に抑えておけば数は増え続けません。検証のたびに外部ストアを引く遅延が気になる場合も、失効境界は利用者ごとに1値なので、短いキャッシュで足ります。

### リフレッシュトークンのローテーションと再利用検知で失効漏れを塞ぐ設計

アクセストークンを全部止めても、リフレッシュトークンが生きていれば新しいアクセストークンを取り直されます。[RFC 9700（OAuth 2.0 Security Best Current Practice）](https://www.rfc-editor.org/rfc/rfc9700.html)は、公開クライアントのリフレッシュトークンについて、送信者への拘束かローテーションのどちらかを必須（MUST）としています。

ローテーションでは、更新のたびに新しいリフレッシュトークンを発行し、前のものを無効にします。無効になったトークンがもう一度使われたら、正規の利用者と攻撃者のどちらかが古いものを出したということです。RFC 9700の4.14.2は、認可サーバーはどちらが出したかを判別できないため、有効なリフレッシュトークンを無効化するとしています。パスワード変更などのセキュリティイベントで自動的に失効させることも認めています（MAY）。寿命と更新フローの設計は[リフレッシュトークンのローテーションの解説](https://www.issoh.co.jp/tech/details/16409/)にまとめました。

## 判断：強制ログアウトを全利用者に掛ける場面と、対象アカウントに絞る場面

### 署名鍵の入れ替えで全員を落とす条件と、失効境界で対象者だけを落とす条件

全員を落とすのは、署名鍵の漏えいを否定できない場合です。鍵が漏れていれば、攻撃者はどの利用者のトークンでも作れるため、発行ログで被害者を絞る意味がありません。鍵を替えて旧世代を拒否し、全利用者に再ログインを求めます。利用者の不便は一度の再ログインで済みます。

対象者に絞ってよいのは、発行ログの突き合わせで不正な発行を特定でき、かつ署名鍵が無事だと確かめられた場合だけです。この条件が揃わないうちに「影響のあったアカウントだけ」に絞ると、記録に残らない発行を取りこぼします。迷ったら全員を落とします。

その後の再ログインは、偽メールが最も効く時期と重なります。通知メールには再ログイン用のリンクを入れず、「アプリかブックマークから開いてください」と書くのが安全です。多要素認証をまだ入れていないなら、この機会に検討します。方式の選び方は[多要素認証の実装方式の解説](https://www.issoh.co.jp/tech/details/13314/)で比較しています。

### 漏えいのおそれの段階で個人情報保護委員会への報告に備えて揃える情報

不正アクセスによる個人データの漏えいは、実際に持ち出されたと確定していなくても「おそれ」の段階で報告の対象になります。[個人情報保護法ガイドライン（通則編）](https://www.ppc.go.jp/personalinfo/legal/guidelines%5Ftsusoku/)は、不正の目的をもって行われたおそれがある漏えい等を、件数にかかわらず報告対象の類型の一つとしています。速報は把握から概ね3〜5日以内です。

速報の期限に間に合わせるには、どのトークンで、どのAPIから、どの項目が返ったかを示せる必要があります。発行ログとアクセスログが jti で結び付いていれば、閲覧の可能性がある利用者と項目を絞り込めます。報告の流れは[個人情報保護委員会への報告義務の実務](https://www.issoh.co.jp/column/details/17840/)、APIのトークン保管と失効の手順は[API不正アクセス対策の実装手順](https://www.issoh.co.jp/tech/details/18346/)で解説しました。自社の会員サービスで、トークンの発行経路や失効の仕組みに抜けが無いかを第三者の目で確かめたい場合は、[脆弱性診断・セキュリティ診断](https://www.issoh.co.jp/service/system/vulnerability/)で相談できます。

## よくある質問

Qoo10の不正アクセスについて、利用者と開発者から出やすい質問をまとめました。2026年11月13日時点の公表内容にもとづきます。

### Qoo10の不正アクセスで漏えいしたのは何人分ですか？

11月13日時点で、eBay Japanは影響を受けた人数を公表していません。お知らせに記載があるのは、一部の顧客の個人情報が閲覧された可能性があること、閲覧の可能性がある16項目、決済情報とパスワードの漏えいを示す証拠は現時点で確認していないことです。調査は続いているため、人数や対象者への個別連絡の有無は、続報で確かめてください。

### Qoo10のパスワードは変更したほうがいいですか？

Qoo10はパスワード漏えいの証拠を現時点で確認していないとしていますが、変更して損はありません。優先するのは、ほかのサービスと同じパスワードを使っている場合に、ほかのサービス側を変えることです。変更はメールやSMSのリンクからではなく、公式アプリかブックマークから行います。再ログインを促す連絡は偽物が混ざりやすい時期です。

### 強制ログアウトされたのは不正アクセスの影響ですか？

Qoo10は、影響を受けた可能性のあるアカウントを強制的にログアウトさせたと公表しています。不正に発行・利用された可能性がある認証トークンを無効化した結果、ログイン状態が切れた可能性があります。ただし、強制ログアウトされたことが、そのアカウントの情報が閲覧された証拠になるわけではありません。対象かどうかはお客様相談窓口で確かめます。

### 認証トークンを無効化するとは、具体的に何をすることですか？

ログイン後に発行された「本人である証明」を、サーバー側で受け付けなくすることです。サーバーにセッションを持つ方式なら、該当のセッションを削除します。JWTのように検証側が署名と期限だけを見る方式では、署名鍵を替えて旧鍵のトークンを拒否する、利用者ごとに失効時刻の境界を置く、トークンIDを拒否リストに入れる、のいずれかを検証の処理に組み込みます。

### JWTはサーバー側で失効させられないのですか？

JWTそのものには失効の仕組みがありませんが、検証側の処理で拒否することはできます。本文の例のように、鍵の世代（kid）、利用者ごとの失効境界（iat との比較）、jti の拒否リストを検証で確かめれば、有効期限を迎える前でも拒否できる仕組みです。そのぶん検証のたびに共有ストアを参照する必要があるため、アクセストークンの寿命を短くし、リフレッシュトークンをローテーションする設計と組み合わせます。

## 関連記事

- [API不正アクセス対策の実装手順：JPCERT注意喚起に沿うレート制限・認可・トークン失効とアプリ解析・SSRF対策](https://www.issoh.co.jp/tech/details/18346/)：APIのトークン保管と失効の手順
- [個人情報保護委員会への報告義務｜対象4類型・速報3〜5日と確報30日の実務](https://www.issoh.co.jp/column/details/17840/)：不正アクセスで漏えいのおそれが出たときの報告の流れ
- [リフレッシュトークンとは？トークンローテーションの仕組みと更新フロー・寿命設計を実装者向けに解説](https://www.issoh.co.jp/tech/details/16409/)：ローテーションと再利用検知の設計
- [トークンイントロスペクションとは？RFC 7662の検証とJWT方式の使い分けを解説](https://www.issoh.co.jp/tech/details/16417/)：検証のたびに失効状態を問い合わせる方式
- [トイポ不正アクセス：0120-546-030は本物か、対象確認と漏えい項目](https://www.issoh.co.jp/tech/details/18373/)：会員アプリの不正アクセスで利用者が確かめること

---

出典: [Qoo10の不正アクセス：認証トークン不正発行の経緯と、発行監査・一括失効の実装手順](<https://www.issoh.co.jp/tech/details/19729/>)（株式会社一創）
