パスワードリスト攻撃は、別のサービスから漏れたIDとパスワードの組を、そのまま自社のログイン画面に流し込む不正ログインの手口です。リスト型攻撃、英語ではクレデンシャルスタッフィングとも呼ばれます。正しい組み合わせを1回ずつ試すだけなので、通常のログインと見分けにくく、アカウントロックにも引っかかりません。本記事では、ログイン画面を持つサービスの運営者向けに、ブルートフォースとの違い、攻撃を見分ける指標、漏えいパスワードの照合と検知のPythonコード、nginxでのレート制限、検知した後の対応までを手を動かす順に並べます。利用者や社内のパスワードの決め方はパスワード管理のベストプラクティスで扱っており、本記事は守る側の実装に絞ります。
まとめ:パスワードリスト攻撃は漏えい照合と多要素認証の二段で止める
パスワードリスト攻撃の原因は、利用者がパスワードを使い回すことにあります。運営者の側からは使い回しそのものを止められないため、対策は「漏れたパスワードを登録させない」「漏れていても本人以外がログインできないようにする」の二段で組み立てます。
実装の優先順位は次のとおりです。まず多要素認証を用意し、少なくとも決済や個人情報を扱う操作の前には必ず通す。次にパスワードの登録時と変更時に漏えい済みかどうかを照合する。そのうえで、全体のログイン失敗率とIPごとの失敗アカウント数を監視し、異常が出たら追加の認証を求めます。
IPアドレスの遮断や回数によるアカウントロックは、単独では効きません。攻撃者は数千のIPに試行を分散し、1アカウントあたりの試行を1回に抑えてくるからです。
パスワードリスト攻撃とは:漏えいしたIDとパスワードの組を他サービスで試す手口
攻撃の流れ:流出データの入手から自動ツールでのログイン試行まで
攻撃は3つの段階で進みます。最初の段階は、どこかのサービスから流出したIDとパスワードの組を集める作業です。次に、それを自動化ツールに読み込ませ、プロキシを経由して多数のIPアドレスから標的のログイン画面へ送ります。最後に、ログインに成功したアカウントだけを抜き出し、ポイントの換金や登録カードでの購入、個人情報の閲覧に使います。
MITRE ATT&CKはこの手口をT1110.004 Credential Stuffingとして、総当たり攻撃(Brute Force)の下位技術に分類しています。2026年5月更新のv1.7では、狙われる対象としてWebのログイン画面のほか、SSHやRDP、Microsoft 365などのクラウドのシングルサインオンも挙げられています。
攻撃者が自社のサービスに一度も侵入していなくても、被害は起きます。ここがほかの不正アクセスと大きく違う点です。
漏えいリストの出どころ:平文の流出とハッシュ値の解読から作られる組
リストの元になるのは、平文のまま漏れたパスワードと、ハッシュ値から解読されたパスワードの2種類です。ハッシュ値で保存していても、方式が古かったりソルトが無かったりすると、よく使われるパスワードから順に元の文字列が割り出されます。
2026年9月に公表されたGyazoの不正アクセスでも、流出した利用者情報にパスワードのハッシュ値が含まれていました。ハッシュ方式は公表されていないため、解読の難しさは外からは判断できません。利用者が同じパスワードをほかのサービスで使っていれば、そのサービスがリスト攻撃の次の標的になります。
自社に漏えいが無くても、他社の事故がそのまま自社の不正ログインに変わる。運営者がこの攻撃を他人事にできない理由です。
ブルートフォースやパスワードスプレーとの違いと、ロックアウトが効かない理由
試行の向きで比べる3つの攻撃:1アカウント多数か多数アカウント1回か
OWASPのCredential Stuffing Prevention Cheat Sheetは、パスワードを狙う攻撃を試行の向きで3つに分けています。違いを表にまとめました。
| 攻撃 | 試すもの | 1アカウントあたりの試行 | 効きやすい対策 |
|---|---|---|---|
| ブルートフォース | 1アカウントに大量のパスワード | 数百〜数万回 | アカウントロック |
| パスワードスプレー | 多数のアカウントに少数の弱いパスワード | 1〜数回 | 弱いパスワードの禁止 |
| パスワードリスト攻撃 | 漏えいした正しい組 | 1回 | 漏えい照合と多要素認証 |
パスワードリスト攻撃だけが「正解かもしれない組」を試します。成功率は試行のうちわずかでも、リストが数百万件あれば成功するアカウントの数は無視できない規模になります。
連続失敗の回数でのアカウントロックがリスト攻撃を素通りさせる仕組み
アカウントロックは「同じアカウントへの連続失敗」を数える仕組みです。リスト攻撃は1つのアカウントに1回しか試さないため、5回で止める設定でも100回で止める設定でも発動しません。
NISTのSP 800-63B-4(2025年7月確定)は3.2.2節で、同じ認証器への連続失敗を100回以下に制限するよう求めています。これはブルートフォースへの備えであり、リスト攻撃への答えにはなりません。むしろロックの回数を極端に下げると、攻撃者が利用者IDを使って正規の利用者を締め出す嫌がらせに使われます。
電話で聞き出した情報による不正ログインとリスト攻撃との切り分け方
正規のログイン画面を通る不正は、リスト攻撃だけではありません。ハイホーの不正ログインでは、電話で聞き出した電話番号や生年月日などの本人の情報でマイページに入られたと公表されています。
見分ける手がかりは、試された組の出どころです。リスト攻撃なら、自社に登録の無いIDが大量に混ざります。聞き出した情報による不正なら、試行は少数で、対象の契約者に偏るのが特徴です。対策も分かれ、前者は漏えい照合と多要素認証、後者は知識情報だけで通る認証の作り直しが中心になります。
攻撃を見分ける検知の指標:失敗率・IPごとのアカウント数・未登録IDの割合
全体のログイン失敗率:平常時の値を基準に倍率でしきい値を置く
最も早く異常が出るのは、サービス全体のログイン失敗率です。平常時は入力ミスによる失敗が一定の割合で続きますが、リスト攻撃が始まると、成功しない組が一斉に流れ込んで失敗率が跳ね上がります。
しきい値を決める基準は固定値ではなく、1〜2週間測った自社の平常時の値です。たとえば平常時が10%前後なら、5分間の失敗率が30%を超えたら警告、50%を超えたら追加の認証を全体に求める、のように倍率で段階を置くと誤報が減ります。キャンペーン初日のように正規のログインが急増する日は失敗率も揺れるため、母数が少ない時間帯は判定から外します。
IPごとの失敗アカウント数と未登録IDの割合で分散型を見つける
1つのIPアドレスから短時間に20以上の異なるアカウントへ失敗していれば、ほぼ自動ツールです。家庭や会社の共有回線でも、5分で20アカウントの入力ミスは起きにくいからです。
やっかいなのは、IPを分散させた攻撃です。1つのIPから1回ずつしか来ないため、IP単位の集計には何も出ません。ここで効くのが、失敗したログインのうち自社に存在しないIDが占める割合です。流出リストは他社の利用者のものなので、自社に登録の無いメールアドレスが大量に混ざります。平常時はほとんどが登録済みIDのタイプミスなので、未登録IDの割合が急に上がれば攻撃の兆候と判断できます。
MITRE ATT&CKの検知の項目でも、単一のIPやセッションから異なるユーザー名で短時間に失敗が重なる状態が分析例に挙がっています。
検知と漏えい照合をPythonで実装する手順:Pwned Passwordsと集計ロジック
Pwned Passwordsのk匿名APIでパスワードの漏えい件数を確かめる
NIST SP 800-63B-4の3.1.1.2節は、新しいパスワードを「よく使われる、推測されやすい、漏えいした」パスワードのブロックリストと照合することを必須(SHALL)としています。照合先としてOWASPが紹介しているのが、Have I Been PwnedのPwned Passwords APIです。
この仕組みでは、パスワードをSHA-1でハッシュ化し、先頭5文字だけを送ります。応答は、その5文字で始まるハッシュの残り35文字と出現回数の一覧で、通常は約800件です。手元で残りの35文字と突き合わせるため、パスワードもハッシュ値の全体も外部に出ません。APIキーは不要で、レート制限も無いと公式に記載されています。Add-Padding: trueを付けると応答件数が800〜1,000件のランダムな数にそろえられ、通信量から照合内容を推測されにくくなります。パディングで足された行の件数は0なので、そのまま読み捨ててよい行です。
失敗ログインをIP単位と全体で集計する検知クラスのコード全文
漏えい照合の関数と、前の章の指標のうち2つを数える検知クラスを1つのファイルにまとめました。標準ライブラリだけで動きます。
import hashlib
import urllib.request
from collections import defaultdict, deque
def pwned_count(password: str) -> int:
"""Pwned Passwordsに先頭5文字だけを送り、漏えい件数を返す"""
digest = hashlib.sha1(password.encode("utf-8")).hexdigest().upper()
prefix, suffix = digest[:5], digest[5:]
req = urllib.request.Request(
f"https://api.pwnedpasswords.com/range/{prefix}",
headers={"User-Agent": "pwcheck-sample/1.0", "Add-Padding": "true"},
)
with urllib.request.urlopen(req, timeout=5) as res:
for line in res.read().decode("utf-8").splitlines():
hash_suffix, count = line.split(":")
if hash_suffix == suffix:
return int(count) # パディング行は0件
return 0
class StuffingDetector:
"""直近window秒の失敗ログインを、送信元IPごとと全体で数える"""
def __init__(self, window=300, ip_users=20, global_fail_rate=0.5, min_total=100):
self.window = window
self.ip_users = ip_users
self.global_fail_rate = global_fail_rate
self.min_total = min_total
self.by_ip = defaultdict(deque)
self.events = deque()
def record(self, ts, ip, user, ok):
self.events.append((ts, ok))
while self.events and self.events[0][0] < ts - self.window:
self.events.popleft()
alerts = []
if not ok:
q = self.by_ip[ip]
q.append((ts, user))
while q and q[0][0] < ts - self.window:
q.popleft()
users = {u for _, u in q}
if len(users) >= self.ip_users:
alerts.append(f"IP {ip}: {len(users)}アカウントに失敗")
total = len(self.events)
fails = sum(1 for _, s in self.events if not s)
if total >= self.min_total and fails / total >= self.global_fail_rate:
alerts.append(f"全体の失敗率 {fails / total:.0%}({total}件中)")
return alerts
record()はログインの成否が決まるたびに呼び、戻り値のリストが空でなければ警告を出します。本番では状態をプロセスのメモリではなくRedisなどの共有ストアに置き、複数のアプリケーションサーバーで同じ数を見るようにします。
Python 3.14.7の実行結果:漏えい件数と集中型・分散型の検知
このコードをPython 3.14.7で実行した結果です(2026年10月22日時点)。passwordは52,372,427件、P@ssw0rdは6,421,042件の漏えいデータに含まれていました。記号を混ぜた「強そうな」パスワードでも、よく使われる形なら漏えい済みです。ランダムに作った24文字の文字列は0件でした。
検知側は、平常時を想定した失敗率10%の200件では何も出ません。1つのIPから30アカウントへ失敗させるとIP 198.51.100.7: 30アカウントに失敗を返しました。250のIPから1回ずつ試す分散型ではIP単位の警告は出ず、代わりに全体の失敗率 84%(301件中)で検知しています。IP単位と全体の2つを併せて持つ理由が、この差に表れています。
入口で試行を減らす設定:nginxのレート制限と多要素認証の段階的な適用
nginxのlimit_reqでログインURLだけに送信元ごとの上限を置く
アプリケーションに届く前に試行の速度を落とすなら、リバースプロキシで制限をかけます。nginxのngx_http_limit_req_moduleを使い、ログインのURLだけに送信元IPごとの上限を置く設定例です。
limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m;
server {
location = /login {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
proxy_pass http://app;
}
}
rate=10r/mで1分あたり10回、burst=5で瞬間的な5回の超過までを許します。limit_req_statusを書かないと既定の503が返るため、429を指定すればアプリ側の監視で区別が可能です。会社の共有回線のように多数の利用者が同じIPから来る環境では、上限を低くしすぎると正規の利用者が締め出されます。
IPだけに頼らない防御:レート制限が分散型の攻撃で破られる前提
OWASPのチートシートは、IPのブロックは回避が容易なため、単独の防御や主要な防御にしてはならないと書いています。プロキシ網を使えば、IPのブロックリストとレート制限の両方が破られうるとも明記しています。先の実行結果で分散型がIP単位の集計に出なかったのと同じ理屈です。
レート制限は、安価なツールでの単純な攻撃を落とす一枚目の網と位置づけます。分散型への備えは、ボットかどうかを通信の特徴から判定するWAFやボット対策の層に任せるのが現実的です。判定の仕組みはCloudflare Adaptive Intelligenceのボットスコアの記事で具体例を挙げています。
多要素認証とパスキー:全員に強制するか高リスクの操作に限るか
OWASPは多要素認証を最も有効な対策としています。パスワードが漏れていても、2つ目の要素が無ければログインは成立しないからです。NIST SP 800-63B-4も、多要素認証の一部として使うパスワードの最小長を8文字に緩め、単一要素で使う場合の15文字と区別しています。
ただし、会員制の通販サイトで全員に毎回の多要素認証を強制すると、ログインの離脱が増えます。現実的な落としどころは、普段と違う端末や国からのログイン、検知の警告が出ている時間帯、そして配送先の変更や登録カードでの決済の直前にだけ2つ目の要素を求める形です。パスワードそのものをやめる選択肢として、パスキー認証への移行も並行して検討してください。パスキーはサービスごとに別の鍵ができるため、使い回しという前提が成り立ちません。
不正ログインを検知した後の対応:パスワード再設定・セッション失効・利用者通知
検知後に成功したログインの洗い出しと強制パスワード再設定を進める手順
警告が出たら、失敗ではなく成功したログインを先に調べます。被害は成功したアカウントでしか起きないからです。手順は次の順で進めます。
- 警告の時間帯に、警告を出したIPや同じ特徴の通信から成功したログインを抽出する
- 該当アカウントのパスワードを無効にし、次回ログイン時に再設定を求める
- 該当アカウントで発行済みのセッションとAPIトークンをすべて失効させる
- ログイン後に配送先・メールアドレス・登録カードが変更されていないかを確認する
3番目を忘れると、パスワードを変えても攻撃者が開いたセッションで操作を続けられます。メールアドレスの変更が見つかった場合は、元のアドレスへ連絡し、変更を差し戻す手続きを用意しておきます。
不正ログイン後の利用者への通知文と個人情報保護委員会への報告の判断
OWASPは、正しいパスワードで多要素認証に失敗した利用者には、パスワードの変更を促す通知を送るよう勧めています。パスワードは攻撃者の手元にあると考えるのが自然だからです。通知文には、ログインがあった日時と場所の目安、取ってほしい操作、そして同じパスワードを使っているほかのサービスでも変更が必要なことを書きます。
不正ログインで会員の個人データが閲覧された可能性があり、それが不正の目的によるものなら、個人情報保護委員会への報告の対象になりえます。報告の類型と期限は個人情報保護委員会への報告義務で整理しています。
対策の採用と見送りの判断:会員規模と守る資産で決める優先順位
会員規模と守る資産に応じた、優先して入れる対策と単独では入れない対策の線引き
ポイントの換金や登録カードでの決済のように、ログインがそのままお金に変わるサービスは、多要素認証と漏えい照合を最初に入れます。ここは見送れません。会員数が数千規模で、ログイン後に見えるのが購入履歴程度なら、漏えい照合と失敗率の監視から始め、多要素認証は高リスクの操作に限る形で十分です。
反対に、次の対策は単独では入れません。国やIPでの一律の遮断は分散型に効かず、海外在住の正規の利用者を締め出します。アカウントロックの回数を3回などに下げる対策も、リスト攻撃には効かないまま正規の利用者の問い合わせだけを増やす結果です。秘密の質問を追加の認証に使うのも同様で、OWASPは知識情報を足しても多要素認証にはならないとしています。
外部の手を借りる境界:認証基盤の改修と監視の設計をどこまで委ねるか
自社で進めやすいのは、ログイン失敗率の計測と、パスワード登録時の漏えい照合の組み込みまでです。どちらも本記事のコード程度の規模で始められます。
手間がかかるのは、端末やリスクに応じて多要素認証を出し分ける仕組み、パスキーの導入、複数のアプリケーションで認証を共通化する作業です。既存の会員データベースを抱えたままの改修は、移行中にログインを止めない段取りが要ります。認証の方式設計から改修までを外へ出すなら、認証基盤・ID管理システム開発のような形で、守る操作の範囲と許容する認証方式を先に決めてから依頼すると無駄が少ない。
よくある質問
パスワードリスト攻撃について、調べ始めた段階で出やすい質問に答えます。
パスワードリスト攻撃とブルートフォース攻撃は何が違いますか?
試すパスワードの出どころが違います。ブルートフォース攻撃は1つのアカウントに対してあり得る文字列を次々に試すため、同じアカウントへの失敗が積み上がり、アカウントロックで止められます。パスワードリスト攻撃は、別のサービスから漏れた正しい組を1回ずつ試すため、失敗が各アカウントに散らばり、ロックが発動しません。対策も、前者は回数制限、後者は漏えい照合と多要素認証が中心になります。
二段階認証を設定していればパスワードリスト攻撃は防げますか?
パスワードだけでのログインは防げます。2つ目の要素が無ければログインが成立しないためです。ただし、SMSのコードを偽のログイン画面で入力させるフィッシングとは別の問題として残ります。また、二段階認証を設定していない機能や、パスワードだけで呼べるAPIが残っていると、そこが入口になります。サービス全体で、どの経路にもパスワード単独のログインが残っていないかを確かめてください。
利用者個人ができるパスワードリスト攻撃の対策は何ですか?
利用者個人の対策の基本は、サービスごとに別のパスワードを使うことです。覚えきれない数になるので、パスワードマネージャーで生成と保存を任せます。そのうえで、ネットバンキングや主要なメールのように乗っ取られると被害が大きいアカウントから、二段階認証かパスキーを設定します。漏えいの通知を受けたサービスと、同じパスワードを使っていたサービスは、その日のうちに変更してください。
自社のサービスが狙われているかどうかはどう確かめますか?
ログインの記録から、5分ごとの失敗率、IPごとの失敗アカウント数、失敗のうち自社に存在しないIDが占める割合の3つを出してみてください。平常時の値と比べて、失敗率が急に跳ね上がった時間帯や、登録の無いメールアドレスでの失敗がまとまって出ている時間帯があれば、攻撃を受けていた可能性が高いと判断できます。記録が残っていない場合は、まずログイン成否とIPとIDを残すところから始めます。
Pwned Passwordsにパスワードを送っても安全ですか?
パスワードそのものは送りません。送るのはSHA-1でハッシュ化した値の先頭5文字だけで、同じ5文字で始まるハッシュは通常約800件あるため、どのパスワードを調べたかは相手に分かりません。照合は手元で行います。外部への通信自体を避けたい環境では、公開されているハッシュの一覧を自社のサーバーに置き、同じ照合を社内で完結させる方法もあります。
関連記事
- パスワード管理のベストプラクティス:NIST SP 800-63B-4の要件と総務省・NISCの推奨、企業ポリシーの決め方:利用者と組織のパスワードの決め方をまとめています。
- パスキー認証とは?仕組み・パスワードとの違い・企業の導入判断を解説:使い回しが起きない認証方式への移行判断を扱っています。
- Gyazoの不正アクセスと2,362万件の流出|利用者の点検手順とアップロード基盤の見直し:パスワードのハッシュ値が流出した事案の経緯と点検手順です。
- Fail2banとは?SSH総当たり攻撃を自動遮断する仕組みと設定・導入判断を実装目線で解説:サーバーへの総当たり攻撃を遮断する設定を解説しています。
- CIAMとは?顧客ID基盤の設計と会員DB移行・同意管理を実装視点で解説:会員向けの認証基盤を設計し直すときの論点を整理しています。