listmonk 密码重置与密码更改后的会话持久化
我在审查 listmonk(一款开源新闻通讯与邮件列表管理器)时,带着一个简单的安全问题发现了这个问题:
当用户更改或重置密码时,应用程序是否真的终止了已颁发的会话?
在本例中,答案是否定的。
之前颁发的已验证会话在以下两种情况下仍然有效:
这意味着,即便用户依赖账户恢复的安全事件已经发生,被盗用的会话 cookie 仍然可以存活。
此问题已被确认并分配了 CVE-2026-34828。
项目: GitHub 上的 listmonk
CVE: CVE-2026-34828
此漏洞影响广泛使用的 listmonk 项目,其 Docker 拉取量超过 500 万。
被盗用的已验证会话 → 受害者重置或更改密码 → 旧会话仍然有效 → 攻击者在凭据恢复后仍保持账户访问权限
listmonk 是一个自托管的邮件列表与新闻通讯管理器。
它提供:
这意味着其会话模型是一个真正的安全边界。
这里的关键问题不在于 listmonk 是否支持密码重置。
真正的问题是:
如果会话已被盗用,密码重置或密码更改是否真的能撤销攻击者的持久访问?
在本例中,不能。
许多安全审查过于关注登录绕过和明显的权限提升。
这忽略了一类重要的弱点:
恢复失败
如果用户更改或重置密码,该操作本应具有意义。
它应当降低对旧凭据和旧认证状态的信任。
如果攻击者已经拥有一个有效会话,而该会话在恢复事件后仍然存活,那么受害者实际上并未完全恢复账户。
这就是本漏洞的问题所在。
这不是登录验证的 bug。
也不是加密问题。
也不是密码哈希失败。
这是一个会话生命周期失败:
这足以构成一个真正的漏洞。
我并非随机地测试 listmonk 的端点并指望某个端点出问题。
更有效的方法是先识别最高价值的信任边界。
对于认证密集型软件,最佳测试边界之一如下:
安全敏感的账户更改是否会撤销先前信任的会话?
这个问题通常在以下场景变得有趣:
在 listmonk 中,最强烈的信号来自前两个场景。
问题就在那里变得清晰。
bug 并非密码更改失败。
bug 在于会话的生命周期超过了密码更改。
从源代码审查来看,密码重置流程:
但并没有明显撤销旧会话的操作。
同样的模式也出现在已认证的密码更改流程中:
该行为与实机测试结果完全一致。
我审查的相关代码区域包括:
cmd/auth.go:忘记密码/重置行为cmd/users.go:已认证的个人资料更新internal/core/users.go:密码更新处理因为会话窃取是一个真实的攻击场景。
一旦攻击者通过任何方式获得有效的认证会话 cookie,例如:
受害者应当能够通过更改或重置密码来终止攻击者的持久访问。
但在此例中,他们无法做到。
攻击链很简单:
这就是整个漏洞。
重要的区别在于恢复后的持久化。
许多应用程序将密码更改仅仅视为凭据层事件。
这还不够。
真正的问题不在于:
“存储中的密码值是否改变了?”
真正的问题在于:
“与旧会话关联的信任关系是否被撤销了?”
在 listmonk 中,没有。
这使得本来可能是普通的账户维护变成了不完整的安全恢复。
这就是以下两者之间的区别:
我在两个独立流程中验证了此问题。
首先,我创建了一个普通测试用户并登录,保存了认证会话 cookie。
然后我触发了“忘记密码”流程,捕获重置链接,并重置密码。
重置后:
一个典型的验证请求如下:
GET /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<old_pre_reset_session>
服务器仍然返回:
HTTP/1.1 200 OK
Content-Type: application/json
以及已认证的个人资料。
这证实了核心主张:
接着我在已认证的密码更改流程中验证了同一类 bug。
我以同一用户身份登录两次,保存了两个有效的认证会话:
使用会话 A,我通过个人资料更新端点更改了密码。
示例请求:
PUT /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<session_A>
Content-Type: application/json
{
"name":"victim1",
"email":"[email protected]",
"password":"VictimChanged123"
}
之后:
随后使用会话 B 发出的请求仍然从 /api/profile 返回了已认证的数据。
这证明了该问题不仅限于忘记密码/重置路径。
它也影响正常的已认证密码更改。
一次复现已经足以证明问题。
但验证两个流程之所以重要,有两个原因。
它表明该 bug 并非孤立于某一个边缘情况的恢复路径。
相同的安全属性在以下两种场景中均失效:
这使得该问题更难被归咎于意外的业务逻辑。
这显然是更广泛的会话管理弱点:
这使得该漏洞具有更强的安全权重。
我还在一个启用了 TOTP 的账户上测试了重置流程,因为我想知道密码重置是否会静默地弱化或绕过双因素认证的预期。
我所确认的是:
这是一个有用的边界检查。
它正确地缩小了问题范围。
漏洞并非:
真正的问题仍然是:
这是一个更清晰、更站得住脚的发现。
该问题被合理地归类为高。
关键影响是在账户安全恢复操作后,仍然存在未授权的持久访问。
安全公告的分类如下:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N这很合理。
这并不是说攻击者能在没有任何凭据的情况下登录。
而是说,一旦攻击者获得了有效的认证会话,受害者无法通过执行本应恢复账户的安全操作(即密码重置和密码更改)来完全终止该访问。
这是一个真实且站得住脚的会话管理漏洞。
有些人低估了会话持久化 bug,因为他们认为会话窃取已经是“游戏结束”。
这种想法过于简单。
真正的问题是受害者发现问题并采取行动之后会发生什么。
如果:
那么账户恢复就是不完整的。
这不仅仅是别扭的行为。
这是恢复模型中的一个安全故障。
尤其是在面向管理员平台的上下文中,这是一个具有强机密性影响的重要问题。
维护者在以下提交中修复了该问题:
db82035
核心修复方向正是这个 bug 所需要的:
这是正确的补救措施,因为它针对的是失败的实际安全属性:
凭据更改时,旧的信任应当终止
对于此类 bug 的良好修复不在于改变密码验证逻辑。
而在于撤销与账户关联的先前活跃会话状态。
这部分才是真正恢复安全的所在。
此问题通过 GitHub 的安全报告流程私下报告。
维护者:
CVE-2026-34828
在安全公告处理过程中出现了一个关于范围的问题。
原始报告包含两个部分:
GitHub 最初为了 CVE 分配目的,将它们视为可独立修复的问题。
这是一个有用的提醒:即使底层弱点的概念相似,安全公告的范围也很重要。
最终结果是 CVE-2026-34828。
关键教训很简单:
如果旧认证信任仍然存活,仅仅更改凭据是不够的。
许多开发者习惯于思考:
这些都很重要。
但真正的安全边界更广:
当高风险账户事件发生时,哪些先前信任的状态必须停止被信任?
在本例中,答案应该是:
而 listmonk 并未做到这一点。
这就是真正的收获。
这个漏洞无关华丽的 payload 或巧妙的解析器技巧。
它只关乎问出正确的信任边界问题。
在 listmonk 中,密码被更改了。
恢复操作完成了。
但攻击者的旧会话仍然存活。
这就是它成为 CVE-2026-34828 的原因。
