79% 对 51%:独立数字银行的应用安全落后。 阅读报告

深度解析

深度解析

Listmonk 2FA 绕过,仅在 7.0 测试版修复(CVE-2026-128332)

listmonk 6.0.0 至 6.2.0 允许持有密码的攻击者绕过 TOTP 双因素认证;唯一修复版本是 7.0.0-beta.2。

TL;DR

  • listmonk 的两个缺陷使 TOTP 双因素认证失效:一个在密码重置后跳过验证码,另一个允许攻击者无限次猜测验证码。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 严重、CVSS v3.1 7.4 高危。目前没有已知利用,截至 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。
  • 至少一个账户,尤其是管理员账户,启用了 TOTP 双因素认证。
  • 对于 CVE-2026-128332,攻击者知道该账户的密码。对于 CVE-2026-128333,攻击者能为该账户完成密码重置,例如读取其电子邮件。
  • 攻击者能够访问 /admin 界面。

listmonk 5.x 及更早版本不在公告的影响范围内。没有 TOTP 的账户没有可绕过的第二因素,因此重置缺陷只剩下密码这一道障碍。暴力破解缺陷需要猜测六位验证码,并需要密码作为起点。

两个分支中只有一个有修复版本。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 的要求也发生了变化。升级生产环境前请阅读发布说明,并先在预发布环境测试。

如果无法迁移到测试版,请在反向代理上对两个登录端点限速,并将 /admin 限制在可信网络。对 POST /admin/login 和 POST /admin/login/twofa 的限速会减慢暴力破解。它不能关闭重置绕过,因为该路径从不要求验证码。

根本原因是什么?

listmonk 将短期认证令牌保存在 internal/tmptokens 包的内存映射中。cmd/auth.go 中的管理员登录流程将这个映射同时用于密码重置令牌和 2FA 令牌。两个缺陷都来自这些令牌的计数和消费方式。

重置缺陷位于 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 个验证码。第十五次检查后令牌被删除。
  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 数据和配置的完全控制,而不是对服务器的控制。

为什么修复有效?

两个提交关闭了两条路径。重置流程现在经过与普通登录相同的函数,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,仅当值匹配时才消费令牌。

两个提交覆盖了两个公告。重置令牌的变更是同一流程的加固,不是第三个漏洞。

如何检查是否已遭入侵?

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。

两个修复在 CVE 发布前随 7.0.0-beta.2 发布
图 1:两个修复提交分别在 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 凭据等设置密钥,并注销所有会话。这些缺陷给攻击者的是会话,而不是持久化植入。

攻击者能猜多少次?

每次密码登录 15 次,而每次新登录都会创建新令牌。六位 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 已知被利用漏洞目录,已核查两个 CVE:https://www.cisa.gov/known-exploited-vulnerabilities-catalog(2026 年 11 月 13 日核查,目录版本 2026.11.10)