79%対51%:独立系ネオバンクはアプリセキュリティで後れ。 レポートを読む

ディープダイブ

ディープダイブ

Listmonkの2FAバイパス、修正は7.0ベータのみ(CVE-2026-128332)

listmonk 6.0.0から6.2.0は、パスワードを持つ攻撃者がTOTPの2FAを回避できる状態です。修正版は7.0.0-beta.2だけです。

要点

  • listmonkの2つの不具合がTOTPによる二要素認証を無効化します。1つはパスワードリセット後にコードを省略し、もう1つはコードの推測回数を制限しません。VulnCheckは2026年11月13日にCVE-2026-128333とCVE-2026-128332を公開しました。
  • listmonk 6.0.0から6.2.0、および7.0.0-beta.1が影響を受けます。どちらの不具合も、有効なパスワードまたは完了したリセットと、TOTPが有効なアカウントを必要とします。
  • スコアはCVSS v4.0 9.1 Critical、CVSS v3.1 7.4 Highです。悪用は確認されておらず、2026年11月13日時点でどちらのCVEもCISAのKEVカタログにありません。
  • 両方の修正を含む唯一のリリースである7.0.0-beta.2へ更新してください。6.x系にパッチはありません。ベータへ移行できない場合は、管理ログインのエンドポイントにレート制限をかけてください。「修正方法」を参照してください。

影響を受けますか?

次の条件がすべて当てはまる場合に影響を受けます。

  • listmonk 6.0.0以降、6.2.0まで、または7.0.0-beta.1を実行している。
  • 少なくとも1つのアカウント、とくに管理者アカウントでTOTPの二要素認証が有効になっている。
  • CVE-2026-128332では、攻撃者がそのアカウントのパスワードを知っている。CVE-2026-128333では、攻撃者がそのアカウントのパスワードリセットを完了できる。たとえば、そのメールを読める場合です。
  • 攻撃者から/adminインターフェースへ到達できる。

listmonk 5.x以前はアドバイザリの影響範囲外です。TOTPのないアカウントには回避できる第二要素がないため、リセットの不具合ではパスワードだけが障壁になります。総当たりの不具合には、推測する6桁のコードと、開始するためのパスワードが必要です。

修正版があるのは2つの系列のうち1つだけです。7.0ベータは修正済みで、6.x系にバックポートはありません。

系列 影響を受けるバージョン 修正バージョン
6.x 6.0.0から6.2.0 なし。修正は7.0.0-beta.2のみ
7.0ベータ 7.0.0-beta.1 7.0.0-beta.2(2026年11月10日)

実行中のバージョンを確認します。バイナリと公式Dockerイメージはどちらも--versionに応答します。

# Binary install
/path/to/listmonk --version

# Docker
docker exec <container> /listmonk/listmonk --version

2FAが有効なアカウントを一覧表示します。listmonkのデータベースに対して次を実行します。

docker exec -i <postgres-container> psql -U listmonk -d listmonk \
  -c "SELECT username, type, twofa_type FROM users WHERE twofa_type <> 'none';"

結果が空ならTOTPを使うアカウントはなく、どちらの不具合もパスワード以上のものを攻撃者に与えません。

修正方法

7.0.0-beta.2へ更新してください。両方の修正を含む唯一のリリースです。

  1. 開始前にデータベースをバックアップします。7.0では設定と管理画面が変わり、プロジェクトはアップグレード手順でバックアップを求めています。 docker exec -i <postgres-container> pg_dump -U listmonk listmonk > listmonk-backup.sql
  2. Dockerの場合は、composeファイルのイメージタグをlistmonk/listmonk:latestからlistmonk/listmonk:betaに変更し、アップグレードを実行します。 docker compose down docker compose pull docker compose run --rm app ./listmonk --upgrade docker compose up -d app db
  3. バイナリの場合は、listmonkを停止し、バイナリをリリースページの7.0.0-beta.2に置き換え、データベースのアップグレードを実行してからlistmonkを再起動します。 ./listmonk --upgrade ./listmonk --version
  4. ソースからのインストールでは、v7.0.0-beta.2タグに切り替え、プロジェクトのMakefileでバイナリをビルドします。ビルド後に手順3と同じく--upgradeを実行します。
  5. 更新後に、2FAの入力を含めて管理者ログインをテストします。その後、実行中のバージョンが7.0.0-beta.2と表示されることを確認します。

7.0は互換性のない変更を含むベータです。管理画面は書き直され、設定APIはapp.from_emailの形式を変え、SESとAzureのWebhookは要件が変わります。本番環境を更新する前にリリースノートを読み、まずステージングで試してください。

ベータへ移行できない場合は、リバースプロキシで2つのログインエンドポイントにレート制限をかけ、/adminを信頼できるネットワークに制限します。POST /admin/loginとPOST /admin/login/twofaの制限は総当たりを遅くしますが、リセットによる回避は塞ぎません。その経路はコードを一度も要求しないためです。

根本原因は何ですか?

listmonkは短命の認証トークンをinternal/tmptokensパッケージのメモリ内マップに保持します。cmd/auth.goの管理者ログイン処理は、パスワードリセットのトークンと2FAのトークンの両方にこのマップを使います。2つの不具合は、これらのトークンの数え方と消費の仕方に由来します。

リセットの不具合はdoResetPasswordにあります。パスワード変更が成功すると、この関数はユーザーのセッションを削除し、そのまま新しいセッションを作成します。

    // Invalidate all existing sessions for the user after password reset.
    if err := a.core.DeleteUserSessions(user.ID, ""); err != nil {
        a.log.Printf("error destroying sessions after password reset for user_id=%d: %v", user.ID, err)
    }

    // Log the user in directly without forcing a manual login right after password change.
    if err := a.auth.SaveSession(user, "", c); err != nil {
        return err
    }

    // Redirect to the admin page.
    return c.Redirect(http.StatusFound, uriAdmin)

この関数はuser.TwofaTypeを読みません。パスワードをリセットしたユーザーは、アカウントでTOTPが有効でも即座にサインインします。したがって、リセットを完了できる攻撃者はコードなしで完全なセッションを得ます。

総当たりの不具合はトークンのカウンターにあります。TwofaPageは/admin/login/twofaへの各リクエストでtmptokens.Check(token)を呼びます。Checkはトークンごとのカウンターを増やし、回数が15を超えるとトークンを削除します。

    // Increment the rate limit counter.
    token.Count++

    // Check if max attempts exceeded.
    if token.Count > maxTries {
        delete(tokens, id)
        return nil, Err
    }

この制限はレート制限に見えますが、ユーザーではなくトークンに結び付いています。doLoginはパスワードログインのたびに新しいトークンを作ります。

        // Set the token.
        tmptokens.Set(token, twofaTokenTTL, user.ID)

SetはCountがゼロの新しいTokenを作り、ログインのエンドポイント自体には試行回数の上限がありません。パスワードを持つ攻撃者は、15回の推測のたびに新しいトークンを要求できるため、カウンターは何度でもゼロに戻ります。

どのように悪用されますか?

リセットの経路は完了したパスワードリセットを必要とします。総当たりの経路はアカウントのパスワードを必要とします。どちらもTOTPで保護されたアカウントのセッションで終わります。

CVE-2026-128333、リセットによる回避:

  1. 攻撃者は対象アカウントのパスワードリセットメールをlistmonkに送らせるか、すでに送られたリセットリンクを別の方法で入手します。
  2. 攻撃者はリンクを開き、メールで届いたトークンとともに新しいパスワードを/admin/resetへ送信します。
  3. doResetPasswordはトークンを検証し、パスワードを更新し、既存のセッションを削除します。
  4. 同じ関数がSaveSessionを直接呼ぶため、応答はTOTPの入力を求めずにセッションCookieを設定します。
  5. 攻撃者はそのアカウントとして認証済みのセッションを持ちます。

CVE-2026-128332、TOTPの総当たり:

  1. 攻撃者は既知のユーザー名とパスワードをPOST /admin/loginへ送信します。
  2. listmonkはパスワードを受け入れ、カウンターがゼロの2FAトークンを作り、/admin/login/twofaへリダイレクトします。
  3. 攻撃者はPOST /admin/login/twofaへ最大15個のコードを送信します。トークンは15回目の確認後に削除されます。
  4. 攻撃者は手順1から繰り返します。パスワードログインのたびに新しいトークンと15回の推測が生まれます。
  5. 正しいコードがセッションを作り、攻撃者は停止します。

公開された実証コードはなく、どちらの経路も不正な形式のリクエストを必要としません。/admin/loginの唯一の速度調整は100ミリ秒の最小応答時間です。

攻撃の流れは?

パスワードを知るかリセットを完了できる攻撃者は、どちらかの経路でTOTPコードなしの認証済み管理者セッションを得ます。

flowchart LR A["既知のパスワードまたは完了したリセット"] --> B["カウンターがゼロの新しい2FAトークン"] B --> C["最大15回のTOTP推測"] C --> D{"コードは正しいか"} D -->|"いいえ"| B D -->|"はい"| E["認証済みの管理者セッション"] A --> F["TOTPを求めないパスワードリセット"] F --> E

ループが総当たりの不具合です。新しいパスワードログインが試行カウンターをリセットします。直接の辺がリセットの不具合です。リセット後のログインは2FAページを完全に飛ばします。

攻撃者に何ができますか?

管理者セッションに到達した攻撃者は、そのアカウントが管理するlistmonkのインストールを支配します。購読者リストとその個人データ、キャンペーン、テンプレート、設定、他のユーザーアカウントが含まれます。Super Adminロールは、送信メールの設定を含むインストール全体の設定を変更できます。

リセットの経路では、攻撃者はパスワードリセットを完了する必要があり、通常は対象のメールを読むことを意味します。総当たりの経路では、アカウントのパスワードと多数のログインリクエストが必要です。どちらも/adminへのネットワークアクセスを必要とし、TOTPが有効なアカウントでのみ機能します。TOTPがなければ回避できる第二要素はなく、影響は盗まれたパスワードと同じです。

VulnCheckのアドバイザリは、管理者セッションからホスト上のコード実行に至る経路を説明していません。影響はlistmonkのデータと設定の完全な制御であり、サーバーの制御ではないと考えてください。

修正が機能する理由

2つのコミットが2つの経路を塞ぎます。リセットの流れは通常のログインと同じ関数を通り、TOTPのカウンターは単一のトークンではなくユーザーに結び付きます。

リセットの修正は、リセット後のログインをfinishPasswordLoginに通します。

    // Log the user in directly without forcing a manual login right after password change.
-   if err := a.auth.SaveSession(user, "", c); err != nil {
-       return err
-   }
-
-   // Redirect to the admin page.
-   return c.Redirect(http.StatusFound, uriAdmin)
+   return a.finishPasswordLogin(c, user, uriAdmin)
 }

finishPasswordLoginはuser.TwofaTypeを読み、TOTPのアカウントではセッションを保存する代わりに2FAトークンを作り/admin/login/twofaへリダイレクトします。パスワードリセットはもうコードを飛ばせません。

総当たりの修正は、ユーザーごとに共有の試行予算を与えます。

 func (a *App) doTwofaVerify(c *echo.Context, token string, userID int, next string) error {
+   // Share a budget across all login tokens for this user. Only requests with a
+   // valid password-authenticated token reach here. New logins do not reset it.
+   attemptKey := fmt.Sprintf("twofa-attempts:%d", userID)
+   tmptokens.SetNX(attemptKey, twofaTokenTTL, nil)
+   if _, err := tmptokens.Check(attemptKey); err != nil {
+       return echo.NewHTTPError(http.StatusTooManyRequests, a.i18n.T("globals.messages.invalidValue"))
+   }
+
    totpCode := strings.TrimSpace(c.FormValue("totp_code"))

SetNXはキーが存在しないか期限切れのときだけ作り、新しいログインはカウンターをリセットしません。Checkも、使い切ったトークンを削除せず期限まで保持するよう変更され、SetNXが予算のキーを再作成するのを防ぎます。同じコミットは、リセットトークンのGet比較を定時間のMatchに置き換え、値が一致したときだけトークンを消費します。

2つのコミットは2つのアドバイザリを覆います。リセットトークンの変更は同じ流れの強化であり、3つ目の脆弱性ではありません。

侵害されたかどうかを確認するには?

VulnCheckもメンテナーも侵害の指標を公開していません。listmonkはTOTP確認の失敗をすべて記録しないため、失敗した試行は痕跡を残さないことがあります。

listmonkの前段にあるリバースプロキシやWebサーバーのログで、繰り返されるログイン通信を探します。総当たりは、同じアドレスからの多数のPOST /admin/loginとそれに続くPOST /admin/login/twofa、および/admin/login/twofaへの多数のリダイレクトとして現れます。

grep -E 'POST /admin/login(/twofa)?' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head

誰も作業していない時間に作られたセッションがないか、セッションテーブルを確認し、ユーザーのloggedin_atの値と比較します。

docker exec -i <postgres-container> psql -U listmonk -d listmonk \
  -c "SELECT created_at, data FROM sessions ORDER BY created_at DESC LIMIT 20;"

管理者に、要求していないパスワードリセットメールが届いていないか尋ねます。完了したリセットはアカウントのパスワードを変え、以前のセッションを破棄します。誰も要求していないリセットは、リセット経路の最も明確な兆候です。

年表

  • 2026-06-26: listmonk 6.2.0がリリース。修正前の最後の安定版です。
  • 2026-11-01: コミット14b25edcが、リセット後のログインを2FAの入力へ通します。
  • 2026-11-09: コミット3f62a55cが、ユーザーごとのTOTP試行予算とリセットトークンの強化を加えます。
  • 2026-11-10: listmonk 7.0.0-beta.2が両方の修正とともにリリースされます。
  • 2026-11-13: VulnCheckがCVE-2026-128333とCVE-2026-128332を公開します。

2つの修正はCVE公開前に7.0.0-beta.2で出荷された
図1: 2つの修正コミットは11月1日と11月9日にあり、7.0.0-beta.2は11月10日、CVEレコードは11月13日に公開されました。出典: listmonkの修正コミット、7.0.0-beta.2のリリース、CVEレコード、2026年11月13日確認

よくある質問

すべてのlistmonkインストールが影響を受けますか?

いいえ。影響を受けるのは6.0.0から6.2.0と7.0.0-beta.1だけで、TOTPが有効なアカウントだけが回避できる第二要素を持ちます。2FAのないインストールは通常どおりパスワード窃取にさらされます。

6.x系の修正はありますか?

いいえ。メンテナーは両方の不具合を7.0.0-beta.2で修正し、6.xのリリースには含まれていません。最新の安定版6.2.0は2026年6月26日に公開されました。

ベータを実行する必要がありますか?

修正版を持つには必要です。7.0.0-beta.2は設定とWebhookに互換性のない変更を含むベータなので、データベースをバックアップし、本番更新の前にテストしてください。リバースプロキシのレート制限と/adminのネットワーク制限は露出を減らしますが、リセット経路は塞ぎません。

更新せずに緩和できますか?

一部は可能です。POST /admin/loginとPOST /admin/login/twofaにレート制限をかけ、/adminを信頼できるアドレスに制限します。これで総当たりは遅くなります。リセットによる回避は残ります。脆弱なコードはリセット成功後に実行され、コードを一度も要求しないためです。

更新後に認証情報をローテーションすべきですか?

侵害を否定できない場合は必要です。管理者パスワードをリセットし、SMTPの認証情報などの設定の秘密をローテーションし、すべてのセッションをサインアウトします。この不具合が与えるのはセッションであり、永続的なインプラントではありません。

攻撃者は何回推測できますか?

パスワードログイン1回につき15回で、新しいログインは新しいトークンを作ります。6桁のTOTPコードには1,000,000通りの値があるため、攻撃者は多くのログインを必要としますが、アプリケーションは繰り返しを止めません。

参考資料

  • CVE-2026-128332のレコード、2FAエンドポイント経由のTOTP総当たり: https://www.cve.org/CVERecord?id=CVE-2026-128332 (2026年11月13日確認)
  • CVE-2026-128333のレコード、パスワードリセット経由の2FA回避: https://www.cve.org/CVERecord?id=CVE-2026-128333 (2026年11月13日確認)
  • CVE-2026-128332のVulnCheckアドバイザリ、影響範囲とクレジット: https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-totp-brute-force-via-admin-login-twofa (2026年11月13日確認)
  • CVE-2026-128333のVulnCheckアドバイザリ、影響範囲とクレジット: https://www.vulncheck.com/advisories/listmonk-6.0.0-before-7.0.0-beta.2-2fa-bypass-via-password-reset-flow (2026年11月13日確認)
  • CVE-2026-128332のGitHubアドバイザリGHSA-p42g-4rv2-xg2f: https://github.com/advisories/GHSA-p42g-4rv2-xg2f (2026年11月13日確認)
  • CVE-2026-128333のGitHubアドバイザリGHSA-h254-fw2f-gggx: https://github.com/advisories/GHSA-h254-fw2f-gggx (2026年11月13日確認)
  • 修正コミット14b25edc、リセット後のログインを2FAの入力へ送る: https://github.com/knadh/listmonk/commit/14b25edc31614a1d661e3af72b3d46b887931534 (2026年11月13日確認)
  • 修正コミット3f62a55c、共有のTOTP試行予算を加える: https://github.com/knadh/listmonk/commit/3f62a55c13dbc679b29238e890e4b90d6d4996d8 (2026年11月13日確認)
  • listmonk 7.0.0-beta.2のリリース、アップグレードコマンドと互換性のない変更: https://github.com/knadh/listmonk/releases/tag/v7.0.0-beta.2 (2026年11月13日確認)
  • listmonk 6.2.0のリリース、修正前の最後の安定版: https://github.com/knadh/listmonk/releases/tag/v6.2.0 (2026年11月13日確認)
  • 7.0.0-beta.1タグの脆弱なtmptokens.CheckとSet: https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/internal/tmptokens/tmptokens.go (2026年11月13日確認)
  • 7.0.0-beta.1タグの脆弱なdoResetPasswordとdoLogin: https://github.com/knadh/listmonk/blob/v7.0.0-beta.1/cmd/auth.go (2026年11月13日確認)
  • listmonkのDocker Hubページ、ダウンロード数の出典: https://hub.docker.com/r/listmonk/listmonk (2026年11月13日確認)
  • listmonkリポジトリ、スター数の出典: https://github.com/knadh/listmonk (2026年11月13日確認)
  • CISAのKnown Exploited Vulnerabilitiesカタログ、両方のCVEを確認: https://www.cisa.gov/known-exploited-vulnerabilities-catalog (2026年11月13日確認、カタログバージョン2026.11.10)