
listmonkにおけるパスワードリセットおよびパスワード変更後のセッション永続化
listmonk(オープンソースのニュースレター兼メーリングリスト管理ツール)をレビューしているときに、シンプルなセキュリティ上の問いを胸にこの問題を発見しました:
ユーザーがパスワードを変更またはリセットしたとき、アプリケーションは実際に発行済みセッションを終了させるのか?
このケースでは、答えは「いいえ」でした。
発行済みの認証済みセッションは、以下の両方の後も有効なままでした:
つまり、盗まれたセッションCookieが、ユーザーがアカウントを回復するために頼るまさにそのセキュリティイベントを生き延びる可能性があるということです。
この問題は受理され、CVE-2026-34828 が割り当てられました。
プロジェクト: GitHub上のlistmonk
CVE: CVE-2026-34828
影響を受けたのはlistmonkで、5M+ のDockerプルを誇る広く採用されたプロジェクトです。
stolen authenticated session → victim resets or changes password → old session remains valid → attacker retains account access after credential recovery
listmonk は、セルフホスト型のメーリングリスト兼ニュースレター管理ツールです。
以下を提供します:
つまり、そのセッションモデルは実際のセキュリティ境界です。
ここで重要な疑問は、listmonkがパスワードリセットをサポートしているかどうかではありませんでした。
本当の疑問はこれでした:
セッションがすでに盗まれている場合、パスワードリセットまたはパスワード変更は実際に攻撃者の永続性を無効化するのか?
このケースでは、そうなりませんでした。
多くのセキュリティレビューは、ログインバイパスや明白な権限昇格に焦点を絞りすぎています。
それによって見落とされる、重要な脆弱性のクラスがあります:
回復の失敗
ユーザーがパスワードを変更またはリセットした場合、その操作には意味があるはずです。 それは、古い認証情報と古い認証状態に対する信頼を低減するはずです。
攻撃者がすでに有効なセッションを持ち、そのセッションが回復イベント後も生き残るなら、被害者は実際にはアカウントを完全には回復していません。
これが今回の問題でした。
これはログイン検証のバグではありません。 暗号の問題でもありません。 パスワードハッシュ化の失敗でもありません。
これはセッションライフサイクルの失敗でした:
それだけで実際の脆弱性が生まれます。
私はlistmonkに対して、ランダムにエンドポイントを叩いて壊れることを期待するというアプローチは取りませんでした。
より強力な方法は、最初に最も価値の高い信頼境界を特定することでした。
認証が重要なソフトウェアにおいて、テストすべき最良の境界の1つはこれです:
セキュリティ上重要なアカウント変更は、以前に信頼されたセッションを無効化するのか?
この疑問は通常、以下のあたりで興味深くなります:
listmonkでは、最初の2つから最も強いシグナルが得られました。
そこで問題が明確になりました。
このバグは、パスワード変更が失敗したということではありません。
バグは、セッションがそれらよりも長く生き残ったことでした。
ソースレビューによると、パスワードリセットフローは:
しかし、古いセッションの無効化は見当たりませんでした。
同じパターンが認証済みパスワード変更フローにも現れました:
その挙動は実際の動作結果と正確に一致しました。
私がレビューした関連コード領域は以下のとおりです:
cmd/auth.go: パスワード忘れ/リセットの挙動cmd/users.go: 認証済みプロフィールの更新internal/core/users.go: パスワード更新の処理セッションの窃取は現実の攻撃条件だからです。
攻撃者が何らかの手段で有効な認証済みセッションCookieを入手した場合、例えば:
被害者はパスワードの変更またはリセットによって、その攻撃者の永続性を終了させることができるはずです。
しかしここでは、それができませんでした。
攻撃チェーンは単純明快でした:
これが脆弱性の全容です。
重要な違いは回復後の永続性です。
多くのアプリケーションはパスワード変更を純粋に資格情報レベルのイベントとして扱います。 それでは不十分です。
本当の疑問は次のようなものではありません:
「パスワードの値はストレージ上で変更されたか?」
本当の疑問はこれです:
「古いセッションに付随する信頼関係は無効化されたか?」
listmonkでは、そうなりませんでした。
これにより、通常のアカウントメンテナンスで済んだはずのことが、不完全なセキュリティ回復に変わります。
これが以下の違いです:
私はこの問題を2つの別々のフローで検証しました。
まず、通常のテストユーザーを作成してログインし、認証済みセッション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
そのレスポンスには認証済みプロフィールが含まれていました。
これにより中核となる主張が裏付けられました:
次に、同じクラスのバグを認証済みパスワード変更フローで検証しました。
同じユーザーとして2回ログインし、2つの有効な認証済みセッションを保存しました:
セッション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"
}
その後:
session Bを使用した後続リクエストでも、/api/profile から認証済みデータが返されました。
これにより、この問題がパスワード忘れ/リセット経路に限定されないことが証明されました。 通常の認証済みパスワード変更にも影響するものでした。
問題を示すには、再現が1つだけでもすでに十分だったでしょう。
しかし、両方のフローを検証することが重要な理由が2つありました。
このバグが1つのエッジケースの回復経路に限定されないことを示しました。
同じセキュリティ特性が以下の両方で失敗しました:
この問題を偶発的なビジネスロジックとして退けることを難しくしました。
これは明らかに、より広範なセッション管理の脆弱性でした:
これにより、この問題のセキュリティ上の重みははるかに強固になりました。
パスワードリセットが2FAの期待を静かに弱めたりバイパスしたりしないかを確認したかったため、TOTP有効のアカウントでもリセットフローをテストしました。
確認できたことは以下のとおりです:
これは有用な境界チェックでした。
問題を正しく絞り込むことができました。
脆弱性は次のようなものではありません:
本当の問題は依然として次のとおりでした:
これはより明確で、より擁護しやすい発見です。
この問題は妥当に High と分類されました。
ここでの主な影響は、アカウントセキュリティ回復操作後の永続的な不正アクセスです。
アドバイザリの分類は以下のとおりでした:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:Nこれは理にかなっています。
この主張は、攻撃者が何もない状態から認証情報なしでログインできるというものではありません。
この主張は、攻撃者が有効な認証済みセッションを一度取得してしまうと、被害者がアカウントを回復するはずのまさにそのセキュリティ操作、すなわちパスワードリセットとパスワード変更を行っても、そのアクセスを完全に終了させることができないというものです。
これは現実的で擁護可能なセッション管理の脆弱性です。
セッション窃取はすでに「ゲームオーバー」だと思い込んでいるため、セッション永続化バグを過小評価する人もいます。
それはあまりに単純化しすぎています。
本当の疑問は、被害者が何かおかしいと気づいて行動を起こした後に何が起こるかです。
もし:
アカウントの回復は不完全です。
これは単なる気まずい挙動ではありません。 これは回復モデルにおけるセキュリティの失敗です。
特に管理者向けプラットフォームでは、これは機密性への強い影響を伴う重大な問題です。
メンテナは以下のコミットでこの問題を修正しました:
db82035
修正の中心的な方向性は、まさにこのバグが必要としていたものです:
これは、失敗した実際のセキュリティ特性を対象としているため、正しい是正措置です:
認証情報が変わったとき、古い信頼は死ぬべきである
このクラスのバグに対する適切な修正は、パスワード検証を変更することではありません。 アカウントに付随する以前のアクティブなセッション状態を無効化することです。
それこそが実際の回復を復元する部分です。
この問題はGitHubのセキュリティ報告フローを通じて非公開で報告されました。
メンテナは:
CVE-2026-34828
アドバイザリの処理中に浮上したことの1つはスコープでした。
元の報告には以下の両方が含まれていました:
GitHubは当初、CVE割り当ての目的上、これらを独立して修正可能な問題として扱いました。 これは、根本的な脆弱性が概念的に類似している場合でも、アドバイザリのスコープが重要であるという有益な注意喚起です。
最終結果は CVE-2026-34828 でした。
ここでの重要な教訓はシンプルです:
古い認証済みの信頼がまだ生きている場合、認証情報を変更するだけでは不十分です。
多くの開発者は次の観点で考えます:
それらも重要です。
しかし、実際のセキュリティ境界はより広範です:
高リスクのアカウントイベントが発生したとき、以前に信頼されていたどの状態の信頼を停止しなければならないのか?
このケースでは、答えは次のようであるべきでした:
そしてlistmonkはそれを実行していませんでした。
それが本当の教訓です。
この脆弱性は、派手なペイロードや巧妙なパーサーのトリックに関するものではありませんでした。
正しい信頼境界の問いを立てることに関するものでした。
listmonkでは、パスワードは変更されました。 回復操作は完了しました。 しかし、攻撃者の古いセッションは依然として生きていました。
だからこそ、これは CVE-2026-34828 になったのです。
