マッチングサイト「Lili」の運営事務局は2026年10月14日、約3か月にわたるサーバーへの不正侵入で、会員と退会者の最大約98,256名の情報が流出した可能性があると公表しました。22日の第2報では、本人確認書類の縮小画像が残っていたとして対象を広げています。会員・退会者に向けて、パスワードの再設定、ほかのサービスでの使い回し、偽のCloudflare画面への対応を公式の文言に沿って先に回答する構成です。後半では、パスワードを暗号化して保存していた設計の何が問題だったか、原本を消しても残る縮小画像をどう管理するか、改ざんで仕込まれた偽画面をどう見つけるかを、Pythonの実行例つきで解説します。
まとめ:Lili不正アクセスで公表された事実と会員・運営者が取る行動
Liliのパスワードは10月14日にすべて無効化されています。ログイン画面から以前と違うパスワードを設定し直してください。急ぐべきは、Liliと同じパスワードを使っているメール・SNS・ネットバンキングなどの変更です。運営事務局は、パスワードが平文で流出した可能性が高いと説明しています。退会した人も、登録を途中でやめた人も対象です。
本人確認書類を出した人は、提出時期にかかわらず対象になりえます。10月6日と11日から12日の深夜にパソコンでLiliを開き、偽の確認画面の指示でコマンドを実行した人は、そのパソコンをネットから切り離してください。
会員情報を預かる運営者にとっての教訓は3つです。パスワードは元に戻せる暗号ではなくハッシュで保存すること。審査用に作った縮小画像まで含めて削除を管理すること。公開ファイルの改ざんを監視することです。
会員・退会者が最初に確かめる4点|パスワード・使い回し・書類・偽画面
全会員のパスワードは10月14日に無効化され以前と別の文字列で再設定
10月14日の第1報によると、Liliは同日、すべての会員のパスワードを一度無効にしました。ログイン画面の「パスワードをお忘れですか?」から、以前とは異なるパスワードを設定し直します。再設定後のパスワードは、元に戻せない方式で保存されると説明されています。
流出した可能性がある項目は、メールアドレス、パスワード、生年月日や都道府県などのプロフィール、サイト内のメッセージ、ログイン時のIPアドレスです。クレジットカード番号は決済代行会社が管理しており、対象外とされています。10月12日午前3時20分頃より後に登録した会員の情報も対象外です。
退会者と登録を途中でやめた人も対象で使い回し先の変更を最優先
対象の内訳は、利用中の会員約71,456名、退会者約26,800名、会員登録を途中でやめた人約10,223名です。問い合わせフォームの利用者1,364件も含まれます。退会者の情報は、再登録の防止などのために残していたと説明されています。
Liliにはもうログインしない退会者でも、当時のパスワードを他のサービスで使っているなら、そちらを変えてください。漏れたメールアドレスとパスワードの組は、別のサイトへのログインに試されます。手口と見分け方はパスワードリスト攻撃の検知と対策、新しいパスワードの決め方はパスワード管理のベストプラクティスにまとめています。
本人確認書類は2020年9月以降の提出分が縮小画像として対象になりうる
第1報の時点では、本人確認書類の画像の対象は2025年12月1日以降の提出分、最大7,261名でした。10月22日の第2報は、それ以前の提出分にも確認用の縮小画像が残っていたと訂正し、最大32,916名を追加しています。縮小画像の対象は2020年9月以降の提出分です。
縮小画像は横320×縦240ピクセル程度で、中央を切り抜いたものです。文字がつぶれて読めない画像もある一方、多くは記載事項を読み取れる状態だったとされています。塗りつぶして出した部分は写っていません。自分の画像に何が写っていたかは、本人確認のうえで窓口が回答します。身に覚えのない請求や申し込みが届いたら、警察署か窓口へ相談してください。
偽のCloudflare画面でコマンドを実行してしまった場合の4つの対応
パソコンでLiliを開いた人のうち、次の時間帯に「Cloudflare」を装う確認画面が出たケースがありました。10月6日午前3時30分頃から13時頃までと、11日23時50分頃から12日午前2時頃までです。画面はターミナルを開いてコマンドを貼り付けるよう求めるもので、操作していなければ影響はありません。スマートフォンやタブレットで見た人は対応不要です。
指示どおりに実行した場合、Liliが求める対応は次の順です。
- そのパソコンをインターネットから切断する
- セキュリティソフトでフルスキャンを行う
- 別の端末から、メール・Apple ID・Microsoftアカウント・Googleアカウント・ネットバンキングのパスワードを変える
- 暗号資産を持っている場合はウォレットの状態を確かめる
Liliは、メールでパスワードや支払いを求めることも、ターミナルやPowerShellでコマンドを実行させることもないと明言しています。問い合わせは第1報に載っている運営事務局の専用窓口か、サイトのお問い合わせフォームで受け付けており、受付時間は9時から19時です。
公式発表2本で追う時系列|7月11日の侵入開始から10月22日の第2報まで
侵入・発見・再侵入・遮断・公表・訂正の日時と公表文書の対応表
サービスを運営する特定商取引法に基づく表記上の事業者は株式会社キャリーで、発表はLili運営事務局の名義です。2本の発表を突き合わせると、次の流れになります。
| 日時 | 出来事 | 出典 |
|---|---|---|
| 7月11日 | 第三者の侵入が始まる | 第1報 |
| 10月6日 | 不審なプログラムに気付く | 第1報 |
| 10月11日 23時45分頃 | 別の経路から再侵入 | 第1報 |
| 10月12日 3時20分頃 | 経路をふさぎ不正なプログラムを削除 | 第1報 |
| 10月14日 | 公表と全会員のパスワード無効化 | 第1報 |
| 10月19日 | 登録を途中でやめた人を対象に追加 | 第1報の更新 |
| 10月20日 | 縮小画像を作らないよう修正 | 第2報 |
| 10月22日 | 第2報で縮小画像の対象を追加 | 第2報 |
侵入から発見までは87日です。発見の端緒はサーバー上の不審なプログラムで、利用者の申告ではありません。発見から再侵入の遮断までにさらに6日かかっています。
攻撃者がパスワードを元に戻すプログラムを置いていたことと非公表の点
第1報は、パスワードを暗号化して保存していたものの、攻撃者がパスワードを元に戻すためのプログラムをサーバーに設置していたと説明しています。侵入の原因は「プログラムの不備」とだけ書かれ、どの機能のどんな不備かは公表されていません。
悪用の報告は受けていないとされています。個人情報保護委員会への報告と警察への相談は済んでおり、第2報の内容も追加で報告するとしています。手口を断定する材料はまだ出ていません。
暗号化して保存したパスワードが平文で漏れる理由とハッシュへの移行
復号の鍵が同じサーバーにある暗号化は侵入されると平文と同じ扱い
暗号化は、鍵があれば元に戻せる処理です。ログイン時に照合するため、鍵はアプリケーションが読める場所に置くことになります。サーバーを乗っ取った攻撃者は、アプリと同じ権限で鍵を読み、データベースの値を復号できます。Liliの第1報が平文での流出可能性を高いと判断したのは、この構造によるものです。
OWASPのPassword Storage Cheat Sheetは、パスワードは暗号化ではなくハッシュで保存すべきだとし、推奨順にArgon2id、scrypt、bcrypt、PBKDF2を挙げています。NIST SP 800-63B-4の3.1.1.2も、32ビット以上のソルトとパスワードハッシュ方式での保存を求めています。暗号とハッシュの違いはハッシュ化とはで整理しました。
hashlib.scryptでOWASPの最小値を満たすハッシュを保存・照合するコード
Argon2idは外部ライブラリが要るため、ここでは標準ライブラリだけで動くPythonのhashlib.scryptを使います。パラメータはOWASPが示すscryptの最小値、N=2^17、r=8、p=1です。保存値にパラメータを含めておくと、後から強度を上げたときに古い値を見分けられます。
import hashlib, hmac, os, time, base64
N, R, P = 2**17, 8, 1 # OWASPのscrypt最小値
MAXMEM = 256 * 1024 * 1024
def b64(b):
return base64.b64encode(b).decode()
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}${b64(salt)}${b64(dk)}"
def verify(pw, stored):
_, n, r, p, salt, dk = stored.split("$")
test = hashlib.scrypt(pw.encode(), salt=base64.b64decode(salt), n=int(n), r=int(r), p=int(p),
maxmem=MAXMEM, dklen=32)
return hmac.compare_digest(test, base64.b64decode(dk))
def needs_rehash(stored):
_, n, r, p, *_ = stored.split("$")
return (int(n), int(r), int(p)) != (N, R, P)
t = time.perf_counter()
h = hash_pw("correct horse 2026")
print("保存値:", h[:40] + "...")
print("1回のハッシュ(ms):", round((time.perf_counter() - t) * 1000))
print("同じPWでも別の値:", hash_pw("correct horse 2026") != h)
print("正しいPW:", verify("correct horse 2026", h))
print("誤ったPW:", verify("correct horse 2025", h))
print("旧パラメータの再ハッシュ要否:", needs_rehash("scrypt$16384$8$1$AAAA$BBBB"))
2026年10月23日にPython 3.14.7で、架空のパスワードに対して実行した結果です。
保存値: scrypt$131072$8$1$Zfg+ljh2gaftMw0zyDlQ4g...
1回のハッシュ(ms): 601
同じPWでも別の値: True
正しいPW: True
誤ったPW: False
旧パラメータの再ハッシュ要否: True
1回の計算に約0.6秒かかり、128MiBのメモリを使います。ログイン1回なら問題になりませんが、漏れたハッシュを総当たりする側には重い負担です。同じパスワードでもソルトが違えば保存値は別になります。照合には、比較時間から一致した桁数を推測されないようhmac.compare_digestを使います。
暗号化済みデータの移行方法とハッシュでも守れない侵入中の入力
既存の暗号化データを移す方法は2つあります。鍵で一度復号してすぐハッシュに置き換え、鍵を破棄する方法と、Liliのように全件を無効化して再設定してもらう方法です。漏えいの疑いがあるなら後者を選びます。旧パスワードはすでに攻撃者の手元にある前提で動くべきだからです。一括無効化とセッション失効の実装はSONICWIREの事案で解説した全会員パスワード無効化にあります。
ハッシュにも限界があります。攻撃者がサーバーに居座っている間は、ログイン時に送られてきたパスワードをそのまま記録できます。ハッシュ化で守れるのは、侵入期間中にログインしなかった退会者や休眠会員の分です。Liliでいえば、7月11日の侵入開始より前に退会した人がこれにあたります。侵入を早く見つける仕組みと組み合わせて、初めて被害を小さくできます。
縮小画像が残った原因|派生ファイルを台帳で管理し原本と一緒に消す設計
320×240の確認用縮小画像が原本の削除後もサーバーに残った仕組み
第2報によると、本人確認書類を運営が確認する際、縮小画像が自動で作られ、サーバーに保存されていました。元の画像を削除しても、縮小画像は消されていませんでした。第1報の「2025年11月30日以前の画像はサーバーに残っていない」という説明は、原本だけを見て判断したものだったことになります。
画像の一覧表示や審査画面のために縮小版を作る処理は、多くのサービスにあります。作る処理と消す処理を別の人が別の時期に書くと、削除側が派生ファイルの存在を知らないまま運用が続きます。Liliは10月20日に縮小画像を作らないよう修正し、22日に残っていた画像をネットにつながるサーバーから移しました。
派生ファイルを台帳に登録して一括削除し台帳外のファイルを洗い出すコード
対策は、原本も派生ファイルも作った時点で台帳に登録し、削除は台帳を引いてまとめて行うことです。台帳に載っていないファイルを定期的に探せば、台帳導入前に作られた派生ファイルも見つかります。次の例は標準ライブラリのsqlite3でこれを再現したものです。
import sqlite3, tempfile
from pathlib import Path
root = Path(tempfile.mkdtemp())
db = sqlite3.connect(":memory:")
db.execute("create table files(path text primary key, doc_id int, kind text)")
def save(doc_id, kind, data):
# 原本も縮小画像も、作った時点で台帳に登録する
p = root / f"{doc_id}_{kind}.jpg"
p.write_bytes(data)
db.execute("insert into files values(?,?,?)", (str(p), doc_id, kind))
def delete_doc(doc_id):
# 原本だけでなく、台帳にある派生ファイルをすべて消す
rows = db.execute("select path from files where doc_id=?", (doc_id,)).fetchall()
for (path,) in rows:
Path(path).unlink(missing_ok=True)
db.execute("delete from files where doc_id=?", (doc_id,))
return len(rows)
def orphans():
# 台帳に無いファイル=削除漏れの候補
known = {r[0] for r in db.execute("select path from files")}
return sorted(p.name for p in root.iterdir() if str(p) not in known)
for d in (101, 102, 103):
save(d, "original", b"dummy")
save(d, "thumb_320x240", b"dummy")
(root / "99_thumb_320x240.jpg").write_bytes(b"dummy") # 台帳導入前に作られた縮小画像
print("審査後に消したファイル数:", delete_doc(101))
print("残っているファイル:", sorted(p.name for p in root.iterdir()))
print("台帳に無いファイル:", orphans())
審査後に消したファイル数: 2
残っているファイル: ['102_original.jpg', '102_thumb_320x240.jpg', '103_original.jpg', '103_thumb_320x240.jpg', '99_thumb_320x240.jpg']
台帳に無いファイル: ['99_thumb_320x240.jpg']
101番の書類は原本と縮小画像の2ファイルが同時に消え、台帳導入前の99番の縮小画像は「台帳に無いファイル」として浮かび上がります。オブジェクトストレージに置く場合も考え方は同じで、キーの一覧と台帳の差分を取ります。保管先そのものの分け方とアクセス制御はココナラの事案で扱った書類保管の設計を参照してください。
審査後に画像を消す運用へ切り替えるときの保持項目と保持期間の決め方
Liliは再発防止策として、本人確認書類の画像を審査後に削除する運用への見直しを挙げています。年齢確認が目的なら、審査後に残す必要があるのは「確認した日」「確認した書類の種類」「確認結果」「担当者」の記録で、画像そのものではありません。画像を残さなければ、侵入されても書類は出ていきません。
法令で書類の写しの保存が求められる業種は事情が違います。その場合は保存期間を決め、期間を過ぎた原本と派生ファイルを台帳から一括で消す運用です。本人確認の方式ごとに何を残すかはeKYCとはで比べています。
偽Cloudflare画面の注入を見つける|公開ファイルの許可外scriptを検出
サイトの改ざんで閲覧者のパソコンに攻撃が及ぶClickFix型の流れ
偽の確認画面は、人間かどうかを確かめるふりをして、クリップボードにコマンドを入れ、ターミナルへの貼り付けを促す手口です。ClickFixと呼ばれ、実行すると情報を盗むプログラムが入ることがあります。閲覧者側の見分け方と企業端末での防ぎ方はClickFixの手口と検知・防御で詳しく扱いました。
Liliの件で運営者が押さえるべき点は、表示期間が10月6日の発見当日と11日の再侵入時に重なることです。サーバーに入られると、会員データだけでなく、訪問者に配るページそのものが攻撃の道具に変わります。Liliは対策として、不正な画面のもとになったプログラムの削除と、ファイル改ざんを検知する監視の導入を挙げています。
許可外ドメインのscriptとクリップボード書き込みを検出するコード
ファイルのハッシュを記録して差分を見る改ざん監視は、Webシェルの検知と除去の手順で紹介しました。ここでは補助として、公開ファイルの中身から偽画面の典型を拾う検査を示します。読み込むscriptのドメインを許可リストと照らし、クリップボードへの書き込みやPowerShellの文字列を探します。
import re, tempfile
from pathlib import Path
from urllib.parse import urlparse
ALLOW = {"www.googletagmanager.com", "challenges.cloudflare.com"}
SUSPECT = re.compile(r"clipboard\.writeText|powershell|mshta|curl\s+-s", re.I)
def scan(docroot):
hits = []
for f in Path(docroot).rglob("*"):
if f.suffix not in {".html", ".php", ".js"}:
continue
s = f.read_text(encoding="utf-8", errors="ignore")
for src in re.findall(r"<script[^>]+src=[\"']([^\"']+)", s, re.I):
host = urlparse(src).hostname
if host and host not in ALLOW:
hits.append((f.name, "許可外のscript", host))
for m in SUSPECT.finditer(s):
hits.append((f.name, "疑わしい文字列", m.group(0)))
return hits
root = Path(tempfile.mkdtemp())
(root / "index.php").write_text(
'<script src="https://www.googletagmanager.com/gtag/js"></script>', encoding="utf-8")
(root / "footer.php").write_text(
'<script src="https://cdn-check.example.net/v.js"></script>', encoding="utf-8")
(root / "v.js").write_text(
'navigator.clipboard.writeText("powershell -w hidden ...")', encoding="utf-8")
for h in scan(root):
print(*h, sep=" | ")
footer.php | 許可外のscript | cdn-check.example.net
v.js | 疑わしい文字列 | clipboard.writeText
v.js | 疑わしい文字列 | powershell
ダミーの3ファイルに対して、許可外ドメインのscriptと、クリップボード書き込み・PowerShellの文字列が検出されました。この検査は文字列の照合なので、難読化されたコードは素通りします。ハッシュによる改ざん監視を主にし、これは見逃しを拾う網として毎時回す位置づけです。ブラウザ側では、Content-Security-Policyで読み込めるscriptのドメインを絞ると、注入されたscriptが動かなくなります。
同じ事故を防ぐために作り直す事業者と設定変更で足りる事業者の判断
パスワードを暗号化保存し本人確認書類を消していない事業者は作り直す
パスワードを元に戻せる形で保存しているなら、保存方式の変更は先送りできません。「管理画面でパスワードを表示できる」「問い合わせに旧パスワードを答えられる」といった機能があれば、可逆で保存している証拠です。本人確認書類の原本や縮小画像を期限なく残している場合も、台帳と削除処理を作り直す対象です。どちらも設定変更では直らず、データ移行と運用の変更を伴います。
漏えいのおそれが不正アクセスによるものなら、件数にかかわらず個人情報保護委員会への報告対象です。段取りは個人情報保護委員会への報告義務、様式は個人情報保護委員会の「漏えい等の対応とお役立ち資料」にあります。自社サービスの保存方式や画像の残り方を第三者の目で点検したいときは、脆弱性診断・セキュリティ診断で、ソースコードとサーバー上のファイルの両面から確かめられます。
ハッシュ保存済みで書類を審査後に消している事業者は監視の追加で足りる
パスワードをscryptやbcryptなどで保存し、書類も審査後に消しているなら、作り直しは過剰です。足すべきは、公開ファイルの改ざん監視と、ハッシュのパラメータが古くないかの確認です。bcryptならOWASPはコスト10以上を求めています。ログイン時に古いパラメータの値を見つけたら、その場で新しい値に置き換えれば移行できます。
見落としやすいのは、退会者のデータです。Liliは退会者の情報を残していたことを見直すとしています。不正の再登録を防ぐためなら、メールアドレスをハッシュにして照合用に残すだけで足り、パスワードやメッセージは不要です。外部に公開しているAPIの点検手順はAPI不正アクセス対策の実装手順にまとめています。
よくある質問
Liliの不正アクセスについて、会員・退会者と、会員情報を扱う運営者から出やすい質問をまとめました。
Liliに再ログインできないのは乗っ取られたからですか?
乗っ取りとは限りません。Liliは10月14日に、安全のためすべての会員のパスワードを一度無効にしています。以前のパスワードではログインできないため、ログイン画面の「パスワードをお忘れですか?」から以前と違うパスワードを設定し直してください。再設定後のパスワードは、元に戻せない方式で保存されると説明されています。
すでに退会していても情報は流出した可能性がありますか?
あります。対象には退会者約26,800名が含まれ、メールアドレス、利用当時のパスワード、プロフィール、サイト内のメッセージ、ログイン時のIPアドレスが流出した可能性があるとされています。Liliにログインする必要はありませんが、当時のパスワードを他のサービスで使っている場合は、そちらを必ず変えてください。
本人確認書類を出したかどうか覚えていない場合はどうすればよいですか?
第2報は、10月12日午前3時20分頃までに書類を出した人は、提出時期にかかわらず対象になる可能性があるとしています。提出したかどうかが分からない場合や、自分の縮小画像に何が写っていたかを知りたい場合は、運営事務局の窓口に問い合わせれば、本人確認のうえで回答を受けられる仕組みです。混雑時は回答まで1週間程度かかるとされています。
偽のCloudflare画面を見ただけでも被害はありますか?
画面が表示されただけで、コマンドを貼り付けて実行していなければ影響はないと公表されています。対象はパソコンで閲覧した人で、スマートフォンやタブレットは対応不要です。実行してしまった場合は、パソコンをネットから切り離し、フルスキャンをかけ、別の端末から主要なアカウントのパスワードを変えてください。
自社サービスのパスワードが暗号化保存かハッシュ保存かはどう確かめますか?
データベースの値に「$2b$」「scrypt$」「$argon2id$」のような方式名とパラメータが付いていればハッシュです。管理画面や問い合わせ対応で元のパスワードを確認できる、ソースコードに復号の処理と鍵がある、という場合は可逆の保存です。可逆なら、全件の再設定を伴う移行計画を立ててください。
関連記事
- SONICWIRE不正アクセスの確定値と対応:全会員パスワード無効化と再設定の実装:一括無効化とセッション失効のコード
- ClickFixとは?偽CAPTCHAでコマンドを実行させる手口と実装者向けの検知・防御【2026年時点】:偽確認画面の手口と端末側の防御
- ココナラ不正アクセス:履歴書・職務経歴書2,294件の対象確認と書類保管の設計:アップロード書類の保管先とアクセス制御
- ハッシュ化とは?暗号化との違いとパスワード保管・改ざん検知の仕組みを解説:暗号とハッシュの違いの基礎
- 情報漏洩対策とは:原因別4類型の防ぎ方と発生時の報告義務【2026年10月版】:情報漏洩を原因別に防ぐ全体像