リスト型攻撃は、他社で漏れたパスワードで自社のログインが破られる攻撃です。2026年10月は、パスワードを含む情報漏洩の発表が続きました。ソースネクストは10月15日の公表日に、最大22,519,960件の会員情報にパスワードが含まれるとしました。メルカリについては2,400万件を売るという主張が出ています(流出は未確認)。こうした発表のあとで本当に危ないのは、漏れた会社ではなく、同じパスワードを使い回している別のサービスです。漏れた組を他社のログイン画面で試すのがリスト型攻撃です。

本記事は連載「事故から逆算するWebセキュリティ」の第10回です。ログインを持つサービスの開発者に向けて、この攻撃が普通のログインに紛れる理由と、層ごとの対策を、手元で動かした結果つきで整理します。後半では利用者側のパスワード漏洩確認も扱います。セキュリティ全体の地図は「サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説」にまとめています。

FROM ANOTHER BREACH攻撃の流れ

他社で漏れた組が、自社のログイン画面で試される

  1. 他社で漏洩

    メールアドレスとパスワードの組

  2. 名簿にまとまる

    売買・公開されたダンプ

  3. ボットとプロキシ

    送信元のIPを散らす

  4. 自社のログイン

    1アカウントに1回ずつ

  5. 当たった組が残る

    使い回した人が乗っ取られる

出典: OWASP OAT-008 Credential Stuffing・Credential Stuffing Prevention Cheat Sheet(2026-10-17確認)

30秒でわかる

  • ・何が起きるか:他社で漏れた「メールアドレス+パスワード」を、自社のログインで1回ずつ試される。使い回していた人だけが入られる
  • ・なぜ気づきにくいか:正しい組での1回のログインは、本人のログインと区別がつかない。アカウントごとの回数制限にも届かない
  • ・効く対策:登録・変更時に漏洩パスワードを弾く(NIST SP 800-63B-4 の必須)、多要素認証、怪しいときだけの追加認証
  • ・見つけ方:1つの送信元が多数のアカウントに失敗している、存在しないIDへの試行が多い、新しい端末からの成功が増える

漏れた組がどれだけの規模で出回りうるかは、今年の KDDI の事案が示しています。ISP 向けメールシステムへの不正アクセスで、761万6,173人分のパスワードが平文のまま保存されていたと個人情報保護委員会が8月19日に公表し、メールサービスの利用者に向けた注意喚起も出しました。同じパスワードを他のサービスで使っていれば、そのサービスのログインがリスト型攻撃の的になります。

リスト型攻撃とは|漏れた組をそのまま試す攻撃

英語ではクレデンシャルスタッフィング(credential stuffing)と呼ばれます。OWASP は自動化された脅威の一覧で、これを OAT-008 Credential Stuffing と定義しています。要旨は「盗まれたユーザー名とパスワードの組が有効かを確かめる、大量のログイン試行」です。別名に Password list attack が並んでおり、日本語の「リスト型攻撃」はこれに当たります。

総当たり(OAT-007 Credential Cracking)との違いは、推測をしない点です。OWASP の定義は、組の出どころを次の3つとしています。

・他のアプリケーションから攻撃者自身が盗んだもの
・犯罪者の市場で買ったもの
・公開された漏洩データのダンプ

狙われるのはメールアドレスをログインIDにしているサービスです。OWASP のチートシートは、多くの人が1つのメールアドレスをすべてのサービスで使うため、メールアドレスとパスワードの組がそのまま別のサービスに通じやすいと指摘しています。

1件ごとの成功率は低くても、試す件数が多ければ当たる件数の絶対数は大きくなります。今回の発表に当てはめると、ソースネクストの件は「パスワードも対象」と公表されています(詳しくは「ソースネクスト情報漏洩|最大2,252万件・パスワードも対象、会員とAPIを持つ側が確認すること」)。同社は対象のパスワードをリセットしましたが、同じ組を使っていた他社のアカウントは守られません。メルカリの件は販売者の主張の段階で、流出は確認されていません(「メルカリ情報漏洩の主張|2,400万件のデータ販売は未確認、分かっていることと利用者の備え」)。一方、日経の件はシステム開発に利用するクラウドサービスの認証情報が入口で、利用者のパスワードの使い回しとは別の経路です(「日経不正アクセスで183万件漏えい|システム開発に利用するクラウドの認証情報が入口、対象者と開発チームの点検」)。

なぜリスト型攻撃は普通のログインに見えるのか

攻撃者の手元にあるのは「正しいかもしれない組」です。当たった組は1回目のログインで成功するので、失敗は1件も残りません。外れた組も、1アカウントにつき1回の失敗で終わります。

WHICH DEFENSE STOPS ITどこで止まるか

正しい組が1回ずつ来ると、回数の制限はすり抜ける

漏れた正しい組での1回のログイン

  • 回数上限IDごと すり抜ける1回目で正解するので数えられない
  • IP上限送信元ごと 分散されると通るプロキシでIPを散らされる
  • 漏洩照合登録・変更時 使い回しを残さないNIST SP 800-63B-4 の必須
  • MFAパスキーを含む 組だけでは入れないOWASP が最も有効とする
出典: OWASP Credential Stuffing Prevention Cheat Sheet・NIST SP 800-63B-4(2026-10-17確認)

ここで、よくある防御が2つともすり抜けられます。

・アカウントごとの回数制限:NIST SP 800-63B-4 は、1アカウントの連続失敗を100回以下で止めることを求めています。総当たりには効きますが、1回ずつ試すリスト型攻撃はこの上限に届きません
・IPごとの回数制限:OWASP のチートシートは、Sentry MBA などの攻撃ツールがプロキシ網を組み込み、大量のIPに試行を散らすと書いています。IPあたりの量が少ないままなので、ブロックリストもIP単位の制限も破られます

OWASP は CAPTCHA も万能ではないとしています。解く道具やサービスがあり、かなりの割合で突破されるからです。全員に出すと正規の利用者の手間ばかり増えるので、怪しいときだけ出す使い方を勧めています。

リスト型攻撃の対策|層ごとに止める場所を変える

対策は1つで足りません。OWASP は、多要素認証(MFA)が大半のパスワード攻撃に対する最良の防御だとしたうえで、MFA を全員に強制できない場合は複数の防御を重ねるよう書いています。どの層が何を止めるかを表にしました。

対策 止めるもの 効かない場面 利用者の手間 / 実装の手間
漏洩パスワードの照合(登録・変更時) 既に漏れているパスワードの新規利用 照合を入れる前から使われているパスワード 低 / 低(外部APIか、一覧の全件取得)
MFA・パスキー 組が正しくても、パスワードだけでは入れない 弱い回復手順(秘密の質問など)から入られる 中 / 中
怪しいときだけの追加認証 新しい端末・普段と違う場所・匿名化サービス経由 端末の特徴は偽装されうる 低 / 中〜高
アカウントごとの回数制限 総当たり・推測 1アカウント1回のリスト型攻撃 低 / 低
IP・回線事業者ごとの制限 1か所から大量に来る攻撃 プロキシで分散された攻撃 低 / 低〜中
CAPTCHA 単純なボット 解読ツール・サービス 全員に出すと高 / 低
通知とセッション一覧 乗っ取りに気づくまでの時間 通知が多すぎて無視される 低 / 低

パスワードの規則は NIST SP 800-63B-4 に合わせる

NIST SP 800-63B-4 は、パスワードの登録・変更のときに、漏洩したパスワード・辞書の単語・サービス名などを含む一覧と照合することを必須(SHALL)としています。一覧に載っていたら別のパスワードを選ばせ、理由も伝えます。同じ文書は、記号や大文字を混ぜさせる規則と、定期的な変更の強制を禁じています。代わりに、侵害の証拠があるときは変更させることを求めています。長さは、パスワードだけで認証するなら15文字以上、多要素認証の一部なら8文字以上が下限です。

追加認証は怪しいときだけ出す

OWASP は、次のようなときだけ2つ目の要素を求める方法を挙げています。

・新しいブラウザや端末、新しいIPアドレスからのログイン
・普段と違う国・場所、匿名化サービスやプロキシ経由のIP
・複数のアカウントにログインを試したIP
・ボットらしい操作(1つのIPやサブネットからの大量のログイン)

端末の特徴(User-Agent・画面サイズ)は偽装されやすく、接続の特徴(TLS の指紋 JA3 や HTTP/2 の特徴)のほうが当てになる場合がある、とも書かれています。パスキーへの移行は大きな話題なので次回に回し、ここでは「パスワードだけでは入れない状態を作る」ことを優先順位の最上位に置くにとどめます。

パスワード漏洩確認を実装する|k匿名性の照合を再現した

登録・変更時の照合には、Have I Been Pwned(HIBP)の Pwned Passwords がよく使われ、OWASP のチートシートも例に挙げています。HIBP の公開リポジトリ(PwnedPasswordsAzureFunction と PwnedPasswordsCloudflareWorker の README)によると、仕組みは次のとおりです。

・エンドポイントは GET /range/{先頭5文字}。SHA-1 ハッシュの先頭5文字(16進数)で問い合わせる
・ハッシュは先頭5文字ごとに16の5乗個の範囲に分けて保存され、各範囲には残り35文字と出現回数が並ぶ
・Add-Padding: true を付けると、応答に詰め物が足されて大きさから中身を推し量りにくくなる
・mode=ntlm で NTLM ハッシュの範囲も引ける

K-ANONYMITY照合の仕組み

送るのはハッシュの先頭5文字だけ。照合は手元で行う

  1. SHA-1にする

    password123 → CBFDAC6008…

  2. 先頭5文字を送る

    GET /range/CBFDA

  3. 候補の一覧が返る

    同じ先頭の「残り35文字:回数」

  4. 手元で照合

    一致すれば漏洩済み

出典: HaveIBeenPwned/PwnedPasswordsAzureFunction・PwnedPasswordsCloudflareWorker の README(2026-10-17確認)。ハッシュ値は当サイトの手元で計算

サーバーに届くのは先頭5文字だけで、同じ5文字で始まるハッシュは多数あるため、どのパスワードを調べたかは特定されません。これが k 匿名性です。照合の本体は次の数行で書けます。

import hashlib, os, sys, urllib.request
BASE = os.environ.get("PWNED_BASE", "https://api.pwnedpasswords.com")

def pwned_count(password: str) -> int:
    h = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
    prefix, suffix = h[:5], h[5:]          # 送るのは先頭5文字だけ
    req = urllib.request.Request(f"{BASE}/range/{prefix}",
                                 headers={"Add-Padding": "true"})
    body = urllib.request.urlopen(req, timeout=10).read().decode()
    for line in body.splitlines():          # 残り35文字の照合は手元で行う
        s, _, n = line.partition(":")
        if s == suffix:
            return int(n)
    return 0

for pw in sys.argv[1:]:
    h = hashlib.sha1(pw.encode()).hexdigest().upper()
    print(f"{h[:5]} | {h[5:]} -> {pwned_count(pw)}")

本記事の検証について:本物の api.pwnedpasswords.com ではなく、同じ形式(残り35文字:回数)を返す小さなサーバーを手元に立て、PWNED_BASE=http://127.0.0.1:8765 で上のコードを動かしました。中身は SecLists の rockyou-withcount(2009年の RockYou 漏洩の出現回数つき一覧)の上位20万件です。下の回数は HIBP の値ではなく、本物の API に対する動作は未検証です。

試したパスワード 送った先頭5文字 手元で照合した残り 結果(手元の一覧での回数)
password123(陽性) CBFDA C6008F9CAB40… 1,031
123456(陽性) 7C4A8 D09CA3762AF6… 290,729
Password1!(陽性) 32CA9 FC1A0F5B6330… 14
ランダムな24文字(陰性) 16B60 EE7BD40B6DF6… 0(見つからず)

サーバー側の記録に残ったのは /range/CBFDA などの5文字の問い合わせだけでした。「Password1!」は記号と大文字を混ぜた、いわゆる複雑な形ですが、漏洩した一覧に載っています。文字の種類を混ぜさせる規則より照合のほうが効く、という NIST の考え方がこの1行でわかります。

一方で限界も見えました。手元の一覧は20万件しかなく、先頭5文字ごとの範囲には最大4件しか入りませんでした。k 匿名性の強さは範囲あたりの件数で決まるので、この見本では匿名性はほとんどありません。本物の範囲に何件入っているかは確かめていません。外部 API に頼りたくない場合は、全範囲を手元に落とす公式のツール(PwnedPasswordsDownloader)があります。API が落ちているときに登録を止めるか通すかも、先に決めておく必要があります。

ログから見つける|リスト型攻撃と打ち間違いを分ける

照合は「これから使われるパスワード」にしか効きません。すでに使い回されている組への攻撃は、ログで見つけます。OWASP は、防御ごとに検知数と阻止数を数え、IPなどで絞り込めるようにしておくことを勧めています。見る指標は3つです。

・1つの送信元が、多数の異なるアカウントに失敗している
・存在しないIDへの試行が多い(漏れた名簿には自社の会員でない人が大半を占める)
・新しい端末や、怪しい送信元からの「成功」が増える

合成したログ(時刻,IP,回線事業者,ID,結果)で確かめました。正規の利用者300人に、ときどき打ち間違える人と、同じ端末で6回間違えた人を混ぜています。そこに2種類の攻撃を足しました。1つのIPから120組を試す攻撃Aと、150個のプロキシIPから2組ずつ試す攻撃Bです。

import csv, sys
from collections import defaultdict

rows = list(csv.DictReader(open(sys.argv[1])))
flagged = set()
for key in ("ip", "asn"):          # IPを分散されても、ASN(回線事業者)の単位で寄って見える
    g = defaultdict(lambda: {"n": 0, "fail": 0, "nouser": 0, "users": set()})
    for r in rows:
        s = g[r[key]]
        s["n"] += 1; s["users"].add(r["user"])
        s["fail"] += r["result"] != "ok"; s["nouser"] += r["result"] == "no_user"
    for k, s in g.items():
        fail = s["fail"] / s["n"]
        if len(s["users"]) >= 10 and fail >= 0.8:   # 多数のアカウント × 高い失敗率
            flagged.add((key, k))
            print(f"[{key}] {k}: {s['n']}回 {len(s['users'])}アカウント "
                  f"失敗{fail:.0%} 存在しないID{s['nouser'] / s['n']:.0%}")
hits = {r["user"] for r in rows if r["result"] == "ok"
        and (("ip", r["ip"]) in flagged or ("asn", r["asn"]) in flagged)}
print(f"疑わしい送信元からの成功ログイン: {len(hits)}アカウント(通知・再設定の対象)")
見つけたもの 試行 アカウント数 失敗率 存在しないID
攻撃A(IP単位で検出) 120回 120 98% 83%
攻撃B(回線事業者単位で検出) 300回 112 96% 84%
6回打ち間違えた利用者 検出されない — — —

攻撃Bは、1つのIPあたり最大2アカウントしか試していないため、IP単位では引っかかりませんでした。束ねる単位を回線事業者(ASN)に上げて初めて見えます。攻撃を混ぜないログ(陰性対照)では検出は0件、混ぜたログでは疑わしい送信元からの成功ログインが11アカウントありました。この11人が、通知とパスワード再設定の対象です。

ただし、これは作ったデータでの確認です。家庭用回線を借りるプロキシは多数の回線事業者にまたがるため、実際には ASN でも散らされることがあります。そのときは全体の失敗率や「存在しないID」の割合の急な上昇を見ます(合成ログでは、攻撃なしの19%に対し攻撃ありは61%でした)。

気づいたら利用者に知らせる

OWASP は、パスワードを間違えただけでは通知しなくてよいとしています。正しいパスワードで入ったのに多要素認証で失敗したときは通知し、パスワードを変えてもらうべきだとしています。あわせて、前回のログインの日時と場所、使われているセッションの一覧を利用者に見せ、他のセッションを切れるようにすることを勧めています。使い回しの多さを考えると、利用者のメールアカウント自体が乗っ取られている可能性も考えに入れます。

利用者側のパスワード漏洩確認と、使い回しのやめ方

ここからは、サービスを使う側の手順です。2026年10月のように漏洩の発表が続いたときの影響範囲は、漏れたサービスではなく、同じパスワードを使っているすべてのサービスです。

  1. 漏洩が発表されたサービスと同じパスワードを使っている他のサービスを、先に変える
  2. その中でも、メールアカウントを最初に変える(パスワード再設定の連絡先になるため)
  3. 使えるサービスでは多要素認証をオンにする
  4. パスワードマネージャーで、サービスごとに別のランダムなパスワードを作る
  5. ログイン履歴やセッションの一覧があるサービスでは、心当たりのないものを切る

自分のメールアドレスが既知の漏洩に含まれるかは、Have I Been Pwned のサイトで調べられます。ただし、当サイトではサイトの画面と結果を確かめていません。見つからなくても、まだ公開されていない漏洩があるかもしれないので、安全の証明にはなりません。また、パスワードそのものを入力させる「漏洩チェック」をうたうサイトには入力しないでください。

参照ソース