オンライン麻雀「Maru-Jan」を運営するシグナルトークは2026年11月7日、外部からの不正アクセスで会員185万5,578人分のメールアドレスとパスワードが流出したと発表しました。同社の管理システムの機能が悪用され、11月4日15時頃に会員情報のデータが外部へダウンロードされています。本記事の前半は、自分は対象か、旧パスワードのままログインできるのに変えるべきか、お詫びの3,000ポイントはいつまでに受け取るか、という会員の疑問への公式発表に沿った回答です。後半では、会員サイトを運営する開発者向けに、管理機能の悪用を防ぐ認可と、漏えい後にパスワード変更を強制する実装をPythonのコードで示します。
まとめ:Maru-Jan不正アクセスで流出した情報と会員・運営者が取る対応
流出が確認されたのはメールアドレスとパスワードで、対象はMaru-Janの全会員です。メールアドレスは登録している会員だけが対象になります。クレジットカード番号は同社が保有していないため、流出していません。侵入経路は特定済みで、悪用された箇所に対策を施したうえで、11月7日16時にサービスが再開されました。
会員がやることは3つです。公式サイトの会員ページからMaru-Janのパスワードを変え、同じパスワードを使っている他のサービスも変えます。お詫びの3,000ポイントは11月20日深夜24時までに自分で受け取る仕組みです。運営者が持ち帰る教訓は2つあります。管理機能から会員全件を書き出せる経路は、それ自体が攻撃面になること。もう1つは、パスワードが漏れた後に旧パスワードのログインを残すかどうかを、事前に決めておくことです。
| 項目 | 公表されている内容(2026年11月7日時点) |
|---|---|
| 対象者数 | 185万5,578人(全会員) |
| 流出した情報 | メールアドレス・パスワード |
| 流出の時期 | 11月4日(水)15時頃 |
| 原因 | 管理システムの機能の悪用 |
| 対象外 | クレジットカード番号 |
| サービス再開 | 11月7日(土)16時 |
| お詫び | 1人3,000ポイント(11月20日まで) |
| 未公表 | 悪用された機能・保存方式 |
Maru-Jan会員が最初に確かめること:対象の範囲とパスワード変更・ポイント受取
対象は全会員:メールアドレス未登録の会員とクレジットカード情報の扱い
シグナルトークの発表文は、対象となる会員を「Maru-Janの全会員」と書いています。プレイ頻度や登録時期による線引きはありません。長く遊んでいない会員も、自分は含まれていると考えて動くのが安全です。
例外はメールアドレスです。発表文は、メールアドレスは登録している場合に対象となると注記しています。メールアドレスを登録していない会員でも、パスワードは流出の対象に入ります。
カード情報は流出していません。Maru-Janはクレジットカード情報のお取り扱いのページで、ポイント購入時のカード情報は決済代行会社が扱い、同社では保存も管理もしていないと説明しています。ただしカード会社を名乗って番号を聞く連絡は、今回の流出とは関係なく詐欺として扱います。
旧パスワードでもログインできる状態で変更すべき理由と変える順番
同社は、これまでのパスワードでもログインできると案内しています。変更は強制されていません。それでも同社自身が、流出したパスワードを使い続けると不正利用の危険があるとして、速やかな変更を求めています。
変える順番を決めておきます。最初に、Maru-Janと同じパスワードを使っているメールや通販サイトです。流出したのはメールアドレスとパスワードの組なので、同じ組で他のサイトへのログインを試される危険が一番大きいからです。この手口と運営側の防ぎ方はパスワードリスト攻撃の検知とレート制限の実装で解説しています。その次にMaru-Janです。どちらも、他のサービスで使っていない新しいパスワードにします。
Maru-Janの変更は、公式サイトの会員ページにある登録情報変更から行います。メールに書かれたリンクからは入らず、ブックマークや検索から公式サイトを開いて操作します。
お詫びの3,000ポイントは自分で受け取る:期限は11月20日深夜24時
お詫びの3,000ポイント(3,000円分)は、自動では付与されません。公式のお詫びとご説明の下部にあるポイント受け取りボタンか、会員ページの「ポイント受け取り」から受け取る仕組みです。受け取れるのは11月20日(金)深夜24時までです。
受け取り操作も公式サイトから行います。「ポイントを受け取るにはこちら」とログインを促すメールやSMSが届いても、そのリンクは使いません。偽のログイン画面にメールアドレスとパスワードを入力させる手口は、流出直後に増えるからです。
Maru-Janを名乗る不審なメールの見分け方と問い合わせフォーム
同社は、流出したメールアドレスにMaru-Janからの案内を装った不審なメールが届く可能性があると注意を促しています。見分ける基準は1つで足ります。パスワードの変更やポイントの受け取りを、メール内のリンクから求めるものは開きません。同社自身が、操作はメール内のリンクを経由せず普段使っている公式サイトから行うよう呼びかけているためです。手口の全体像はフィッシング詐欺の仕組みと技術的な対策にまとめています。
問い合わせは公式サイトのお問い合わせから送ります。11月7日16時37分の追記で、同社は届いたメールに全スタッフが目を通し、必ず返信しているとしています。電話窓口の案内は、11月7日時点ではありません。
公式発表で整理する経緯:11月4日の流出から11月7日のサービス再開まで
11月4日15時頃のダウンロードから再開後の11月11日延期まで6つの出来事
- 11月4日(水)15時頃:管理システムの機能が悪用され、会員情報のデータが外部へダウンロードされる
- 11月6日:公式サイトに「サイバー攻撃による臨時メンテナンスについて」を掲載。緊急メンテナンスの間、ゲームへのログインとWebの会員ページは停止
- 11月7日(土)16時:悪用された箇所への対策を施し、サービスを再開
- 11月7日:お詫びとご説明を公表し、185万5,578人分の流出を発表
- 11月7日16時37分:お詫びページに追記
- 11月11日(水)予定のメンテナンス:障害情報のページで、情報漏えい対策の影響により延期と告知
流出から公表までは3日です。会員情報のデータは、利用が止まる前に管理システムの機能を通じて外へ書き出されていました。どの時点で気づいたかは公表されていませんが、書き出しそのものを機能の側で止める設計は、後半で扱います。
お詫びページと発表文の違い:件数185万5,578人は発表文にだけ記載
情報源は2つあります。1つは公式サイトのお詫びとご説明で、代表取締役で開発責任者の栢孝文氏の名前で書かれています。もう1つは報道各社へ配信された発表文で、4Gamerが全文を掲載しました。
件数の185万5,578人は発表文にだけ書かれており、公式サイトのお詫びページの本文には件数がありません。「全会員が対象」という整理も発表文の項目です。お詫びページは経緯と会員へのお願いが中心で、両方を読んで初めて全体がそろいます。同社は、調査で判明した事実と対策の進捗を公式サイトで随時知らせるとしています。
11月7日時点で公表されていない悪用された機能・保存方式・報告状況
公表されていない項目は3つあります。悪用された管理システムの機能が何か、パスワードがどの形式で保存されていたか、個人情報保護委員会へ報告したかです。発表文は「メールアドレスおよびパスワード」と書き、ハッシュ値とは書いていません。保存方式が分からない以上、本記事は平文だった、あるいはハッシュ化されていたとは書きません。
会員にとっての結論は保存方式に左右されません。ハッシュ化されていても、弱いパスワードは総当たりで割り出されます。保存方式と割り出しの関係はハッシュ化とは:暗号化との違いとパスワード保管の仕組みで整理しています。以降の技術章で扱う範囲は、形が確定している「管理システムの機能が悪用された」「パスワードが流出した」の2点です。
管理システムの機能悪用で会員全件を持ち出させない機能レベル認可の実装
OWASP API5の export_all の例に見る管理機能自体が攻撃面になる構造
OWASP API Security Top 10 2023のAPI5(機能レベル認可の不備)は、点検すべき問いとして、一般利用者が管理用のエンドポイントに届かないか、URLとパラメータを推測するだけで別の役割の機能を呼べないかを挙げています。例に出てくるのは /api/v1/users/export_all です。利用者全件を書き出す機能は、管理画面では便利な一方、認可が1か所でも抜けると、そのまま一括取得の道具になります。
Maru-Janでどの機能が悪用されたかは公表されていません。ただ「管理システムの機能」で「会員情報データがダウンロードされた」という事実は、侵入者が既存の機能を呼んで書き出した形を示しています。管理者アカウントそのものの乗っ取りを防ぐMFAとIP制限はFaveConnect不正アクセスと管理画面の不正ログイン対策で扱いました。本記事は、仮に入られても全件を持ち出させない機能側の制限を扱います。
役割・出力列・件数上限・承認者の4段で絞る会員エクスポートのPythonコード
次の例は、会員の書き出し機能に4つの関門を置きます。呼べる役割を限り、出力できる列を許可リストで決め、承認なしで出せる件数に上限を設け、上限を超える分は本人以外の承認を求めます。結果はすべて監査ログへの記録対象です。データベースはPython標準のsqlite3のメモリ上に作ります。
# 管理機能の会員エクスポートを守る最小例(Python 3.9以上・標準ライブラリのみ)
import sqlite3
from datetime import datetime
ROLE_CAN_EXPORT = {"support-lead", "security"} # エクスポートを呼べる役割(機能レベル認可)
EXPORTABLE = {"member_no", "nickname", "joined_at"} # 出力してよい列。メールとパスワード列は入れない
LIMIT_WITHOUT_APPROVAL = 1000 # 承認なしで出せる件数の上限
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE members (member_no TEXT, nickname TEXT, email TEXT, password_hash TEXT, joined_at TEXT)")
con.executemany("INSERT INTO members VALUES (?,?,?,?,?)",
[(f"M{i:07d}", f"user{i}", f"mail-{i}", "scrypt$...", "2026-01-01") for i in range(5000)])
con.execute("CREATE TABLE audit_log (at TEXT, actor TEXT, action TEXT, columns TEXT, rows INTEGER, result TEXT)")
def export_members(actor, role, columns, limit, approved_by=None):
def log(result, rows=0):
con.execute("INSERT INTO audit_log VALUES (?,?,?,?,?,?)",
(datetime(2026, 11, 7, 10, 0).isoformat(), actor, "member.export", ",".join(columns), rows, result))
if role not in ROLE_CAN_EXPORT:
log("拒否:権限なし"); return "拒否:権限なし"
bad = set(columns) - EXPORTABLE
if bad:
log("拒否:出力できない列 " + ",".join(sorted(bad))); return "拒否:出力できない列 " + ",".join(sorted(bad))
if limit > LIMIT_WITHOUT_APPROVAL and (approved_by is None or approved_by == actor):
log("保留:承認待ち"); return "保留:承認待ち"
rows = con.execute(f"SELECT {','.join(columns)} FROM members LIMIT ?", (limit,)).fetchall()
log("許可", len(rows)); return f"許可:{len(rows)}件"
print(export_members("op-01", "operator", ["member_no"], 10))
print(export_members("lead-02", "support-lead", ["member_no", "email", "password_hash"], 10))
print(export_members("lead-02", "support-lead", ["member_no", "nickname"], 5000))
print(export_members("lead-02", "support-lead", ["member_no", "nickname"], 5000, approved_by="lead-02"))
print(export_members("lead-02", "support-lead", ["member_no", "nickname"], 5000, approved_by="sec-03"))
for r in con.execute("SELECT actor, columns, rows, result FROM audit_log"):
print(r)
Python 3.14.10(SQLite 3.50.4・Windows)で実行した結果です。
拒否:権限なし
拒否:出力できない列 email,password_hash
保留:承認待ち
保留:承認待ち
許可:5000件
('op-01', 'member_no', 0, '拒否:権限なし')
('lead-02', 'member_no,email,password_hash', 0, '拒否:出力できない列 email,password_hash')
('lead-02', 'member_no,nickname', 0, '保留:承認待ち')
('lead-02', 'member_no,nickname', 0, '保留:承認待ち')
('lead-02', 'member_no,nickname', 5000, '許可')
一般の運用担当は役割の段階で止まり、メールアドレスとパスワードの列は誰が呼んでも出ません。5,000件の書き出しは、自分で自分を承認した4行目が保留になり、別の担当者が承認した5行目だけが通りました。SQLに列名を埋め込んでいますが、許可リストで検査した後なので、利用者の入力がそのまま入ることはありません。
管理機能の件数上限を置く場面と、常時の承認フローが過剰になる規模
判断は言い切れます。会員のメールアドレスとパスワードの列を、管理画面の書き出し機能から出せる設計なら、今すぐ外すべきです。パスワードは本人確認のために照合するだけの値で、運用担当が一覧で見る理由がありません。メールアドレスの一括書き出しが要るのは配信業務くらいで、その業務も配信サービスへの連携で置き換えられます。
件数上限と承認は規模で決めます。会員が数十万人を超え、運用担当が複数の部署にいるなら、上限と承認は外せません。逆に、会員数千人で管理者が2人しかいない小さなサービスでは、毎回の承認は業務を止めるだけです。その規模なら、列の許可リストと監査ログだけを入れ、週1回ログを見返せば足ります。APIとして公開している管理機能の認可を全エンドポイントで点検する手順はAPI不正アクセス対策の実装手順で示しました。
パスワードが漏れた会員サイトが決める強制変更とセッション失効の実装手順
NIST SP 800-63B-4の侵害の証拠に基づく強制変更と旧パスワードの扱い
NIST SP 800-63B-4は、パスワードの定期変更を利用者に求めてはならないとする一方で、認証情報が侵害された証拠がある場合は変更を強制しなければならない(SHALL)と定めています。流出を確認した時点は、この「証拠がある場合」に当たります。
Maru-Janは、旧パスワードでのログインを残し、変更をお願いする形を取りました。185万人に一斉に変更を強いれば、問い合わせが窓口に集中し、パスワードを忘れた会員はログインできなくなるという負担は確かにあります。それでも、自分のサービスを設計する側としては、次回ログイン時に変更画面だけを通す強制変更を用意しておくほうを選びます。流出したパスワードで第三者がログインしても、変更画面から先に進めないからです。
全会員の強制変更・既存セッション失効のhashlib.scryptによるPython実装
次の例は、漏えいを確認した時点で全会員に変更必須の印を付け、既存のログイン状態をまとめて無効にします。パスワードはPython標準のhashlibのscryptで保存します。変更時に旧パスワードと同じ値を指定した場合は、拒否の対象です。
# 漏えい後に全会員へパスワード変更を強制する最小例(Python 3.9以上・標準ライブラリのみ)
import hashlib, hmac, os
N, R, P = 2**17, 8, 1 # OWASPが示すscryptの最小構成(128MiB)
MAXMEM = 256 * 1024 * 1024 # hashlib.scryptの既定上限を超えるため明示する
def hash_pw(pw):
salt = os.urandom(16)
dk = hashlib.scrypt(pw.encode(), salt=salt, n=N, r=R, p=P, maxmem=MAXMEM, dklen=32)
return f"scrypt${N}${R}${P}${salt.hex()}${dk.hex()}"
def verify_pw(pw, stored):
_, n, r, p, salt, dk = stored.split("$")
calc = hashlib.scrypt(pw.encode(), salt=bytes.fromhex(salt), n=int(n), r=int(r), p=int(p),
maxmem=MAXMEM, dklen=32)
return hmac.compare_digest(calc.hex(), dk)
users = {"alice": {"hash": hash_pw("old-pass-1"), "must_change": False, "session_epoch": 1}}
sessions = {"s-123": ("alice", 1)} # 漏えい前から残っているログイン状態
def breach_response():
"""漏えいを確認した時点で、全会員に変更を必須化し、既存のログイン状態を無効にする"""
for u in users.values():
u["must_change"] = True
u["session_epoch"] += 1
def session_valid(sid):
user, epoch = sessions[sid]
return epoch == users[user]["session_epoch"]
def login(user, pw):
u = users[user]
if not verify_pw(pw, u["hash"]):
return "失敗"
return "変更画面のみ許可" if u["must_change"] else "ログイン成功"
def change_password(user, old, new):
u = users[user]
if not verify_pw(old, u["hash"]) or verify_pw(new, u["hash"]):
return "変更拒否"
u["hash"], u["must_change"] = hash_pw(new), False
return "変更完了"
print("漏えい前のセッション:", session_valid("s-123"))
breach_response()
print("対応後のセッション:", session_valid("s-123"))
print("旧パスワードでログイン:", login("alice", "old-pass-1"))
print("同じパスワードへ変更:", change_password("alice", "old-pass-1", "old-pass-1"))
print("別のパスワードへ変更:", change_password("alice", "old-pass-1", "new-pass-9"))
print("新パスワードでログイン:", login("alice", "new-pass-9"))
実行結果は次のとおりです。
漏えい前のセッション: True
対応後のセッション: False
旧パスワードでログイン: 変更画面のみ許可
同じパスワードへ変更: 変更拒否
別のパスワードへ変更: 変更完了
新パスワードでログイン: ログイン成功
漏えい前から残っていたセッションは、世代番号を1つ進めただけで無効になりました。会員ごとのセッションを探して消す処理が要らないため、185万人規模でも更新は1回で済みます。旧パスワードでのログインは通りますが、行き先は変更画面だけです。同じ値への変更は拒否され、別の値に変えた後は通常どおりログインできました。
OWASPが示すscrypt・Argon2idの最小構成と保存方式を移行する順番
OWASPのパスワード保存チートシートは、第一候補にArgon2id(メモリ19MiB・反復2・並列1が最小構成)、使えない場合にscrypt(N=2^17・r=8・p=1)を挙げています。旧システム向けのbcryptは作業係数10以上、FIPS 140への準拠が必要な場合のPBKDF2-HMAC-SHA256は60万回以上です。上のコードのscryptは、このN=2^17の構成に合わせています。hashlib.scryptは既定のメモリ上限が32MiBのため、maxmemを明示しないとエラーになります。
自社の会員サイトが平文や単純なSHA-256で保存しているなら、強制変更と保存方式の移行は同じ機会にまとめます。新しいパスワードを受け取った時点で推奨方式で保存し直せば、別途の移行処理は要りません。ブックオフの事案で取り上げた退会会員データの消去と大量取得の検知と合わせて、会員データを持つ運営者が点検する範囲になります。
個人情報保護委員会への速報3〜5日・確報60日と運営者が点検を頼む範囲
メールアドレスとパスワードの組は、不正アクセスで漏えいした場合、個人情報保護法26条の報告と本人通知の対象になりえます。個人情報保護委員会の案内では、速報は事態を知った日から3〜5日以内、不正の目的によるおそれがある事態の確報は60日以内です。シグナルトークの発表文には、11月7日時点で委員会への報告に関する記載はありません。報告の段取りは個人情報保護委員会への報告義務の実務にまとめています。
会員制のゲームやアプリを運営する立場なら、確かめる順番は、管理機能から書き出せる列と件数、パスワードの保存方式、漏えい時の強制変更とセッション失効の手順です。管理画面やAPIの認可を第三者の目で確かめたい場合は脆弱性診断・セキュリティ診断で相談を受け付けています。
よくある質問
Maru-Janの不正アクセスと会員情報の流出について、会員から出やすい質問をまとめました。
Maru-Janをしばらく遊んでいませんが対象ですか?
対象です。シグナルトークの発表文は、対象を「Maru-Janの全会員」としています。最後にプレイした時期による線引きは示されていません。長く遊んでいない会員も、Maru-Janと同じパスワードを使っている他のサービスから順に変更してください。メールアドレスを登録していない会員は、メールアドレスは対象外ですが、パスワードは対象に含まれます。
旧パスワードのままログインできますが変更は必要ですか?
必要です。同社はこれまでのパスワードでもログインできると案内したうえで、流出したパスワードを使い続けることには不正利用の危険があるとして、速やかな変更を求めています。変更は公式サイトの会員ページの登録情報変更から行い、他のサービスで使っていない新しいパスワードにします。同じパスワードを使っている他のサービスも、変更が必要です。
お詫びの3,000ポイントはどうやって受け取りますか?
自動では付与されないため、自分で受け取ります。公式サイトのお詫びとご説明の下部にあるポイント受け取りボタンか、会員ページの「ポイント受け取り」から操作します。期限は11月20日(金)深夜24時です。ポイントの受け取りを案内するメールやSMSのリンクからはログインせず、公式サイトを直接開いてください。
Maru-Janからのメールは本物ですか?
メール内のリンクからパスワード変更やポイント受け取りを求めるものは、開かずに削除するのが安全です。同社は流出したアドレスに案内を装った不審なメールが届く可能性があるとし、操作はメール内のリンクを使わず公式サイトから行うよう呼びかけています。判断に迷う場合は、公式サイトのお問い合わせから確認できます。
クレジットカードの情報は流出していませんか?
流出していません。Maru-Janはポイント購入時のカード情報を保存しておらず、決済代行会社が扱う仕組みだと説明しています。発表文も、クレジットカード番号は同社で保有していないため対象に含まれないとしています。カード会社やMaru-Janを名乗ってカード番号を聞く連絡には応じないでください。
関連記事
- ブックオフ不正アクセス:約638万人の対象確認と本物メールの見分け方:同じく会員情報が一括で取得された事例と、退会者データの消去
- FaveConnect不正アクセス:11万7,392件の対象確認と管理画面の不正ログイン対策:管理画面の乗っ取りを止めるMFAとIP制限
- 花キューピット情報漏洩:423万件の対象確認とパスワード変更、使い回しの点検手順:パスワードの使い回しを点検する手順
- パスワードリスト攻撃とは?検知の指標と漏えい照合・レート制限の実装:流出したパスワードが他サービスで試される手口と防ぎ方
- API不正アクセス対策の実装手順:JPCERT注意喚起に沿うレート制限・認可・トークン失効:管理APIを含む全エンドポイントの認可