
Persistência de Sessão do listmonk Após Redefinição e Alteração de Senha
Persistência de Sessão do listmonk Após Redefinição e Alteração de Senha
Encontrei este problema ao revisar o listmonk, um gerenciador open-source de newsletters e listas de e-mail, com uma pergunta simples de segurança em mente:
Quando um usuário altera ou redefine a senha, o aplicativo realmente encerra as sessões já emitidas?
Neste caso, a resposta foi não.
Sessões autenticadas emitidas anteriormente permaneceram válidas tanto após:
Isso significava que um cookie de sessão roubado poderia sobreviver exatamente aos eventos de segurança em que os usuários confiam para recuperar a conta.
O problema foi aceito e recebeu a identificação CVE-2026-34828.
Projeto: listmonk no GitHub
CVE: CVE-2026-34828
Isso afetou o listmonk, um projeto amplamente adotado com 5M+ de pulls no Docker.
sessão autenticada roubada → vítima redefine ou altera a senha → sessão antiga permanece válida → atacante mantém acesso à conta após a recuperação da credencial
O listmonk é um gerenciador auto-hospedado de listas de e-mail e newsletters.
Ele oferece:
Isso significa que seu modelo de sessão é um limite de segurança real.
A pergunta importante aqui não era se o listmonk suporta redefinição de senha.
A verdadeira pergunta era:
A redefinição ou alteração de senha realmente revoga a persistência do atacante se uma sessão já tiver sido roubada?
Neste caso, não.
Muitas análises de segurança focam de forma demasiadamente restrita em bypass de login e escalonamento de privilégio óbvio.
Isso deixa passar uma classe importante de fraquezas:
falhas de recuperação
Se um usuário altera ou redefine a senha, essa ação deve ser significativa.
Ela deve reduzir a confiança em credenciais antigas e no estado de autenticação antigo.
Se um atacante já possui uma sessão válida e essa sessão sobrevive ao evento de recuperação, então a vítima não recuperou a conta completamente.
Este foi o problema aqui.
Não era um bug de validação de login. Não era um problema de criptografia. Não era uma falha de hash de senha.
Era uma falha no ciclo de vida da sessão:
Isso é suficiente para criar uma vulnerabilidade real.
Não abordei o listmonk atacando endpoints aleatoriamente na esperança de que um deles caísse.
O caminho mais forte era identificar primeiro o limite de confiança de maior valor.
Para software com forte dependência de autenticação, um dos melhores limites a testar é este:
Alterações de conta sensíveis à segurança revogam sessões anteriormente confiáveis?
Essa pergunta geralmente se torna interessante em torno de:
No listmonk, o sinal mais forte veio dos dois primeiros.
Foi aí que o problema ficou claro.
O bug não era que as alterações de senha falhavam.
O bug era que as sessões sobreviviam a elas.
Pela revisão do código-fonte, o fluxo de redefinição de senha:
mas não havia revogação visível de sessões mais antigas.
O mesmo padrão aparecia no fluxo autenticado de alteração de senha:
Esse comportamento correspondia exatamente aos resultados reais.
As áreas de código relevantes que revisei foram:
cmd/auth.go para o comportamento de esqueci/redefinircmd/users.go para atualizações de perfil autenticadasinternal/core/users.go para o tratamento de atualização de senhaPorque o roubo de sessão é uma condição de ataque real.
Uma vez que um atacante obtém um cookie de sessão autenticado válido por qualquer meio, como:
a vítima deveria ser capaz de encerrar a persistência do atacante alterando ou redefinindo a senha.
Aqui, ela não conseguia.
A cadeia de ataque era direta:
Essa é toda a vulnerabilidade.
A distinção importante é a persistência após a recuperação.
Muitos aplicativos tratam a alteração de senha como um evento puramente no nível da credencial. Isso não é suficiente.
A verdadeira pergunta não é:
“O valor da senha mudou no armazenamento?”
A verdadeira pergunta é:
“A relação de confiança associada a sessões mais antigas foi revogada?”
No listmonk, não foi.
Isso transforma o que poderia ser uma manutenção comum de conta em uma recuperação de segurança incompleta.
Essa é a diferença entre:
Validei o problema em dois fluxos separados.
Primeiro, criei um usuário de teste normal e fiz login, salvando o cookie de sessão autenticado.
Em seguida, acionei o fluxo de esqueci a senha, capturei o link de redefinição e redefini a senha.
Após a redefinição:
Uma requisição de validação representativa era assim:
GET /api/profile HTTP/1.1
Host: 127.0.0.1:9000
Cookie: session=<old_pre_reset_session>
E o servidor ainda retornava:
HTTP/1.1 200 OK
Content-Type: application/json
com o perfil autenticado.
Isso estabeleceu a afirmação central:
Em seguida, validei a mesma classe de bug no fluxo autenticado de alteração de senha.
Fiz login duas vezes como o mesmo usuário e salvei duas sessões autenticadas válidas:
Usando a sessão A, alterei a senha pelo endpoint de atualização de perfil.
Exemplo de requisição:
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"
}
Depois disso:
Uma requisição de acompanhamento usando a sessão B ainda retornava dados autenticados de /api/profile.
Isso provou que o problema não se limitava ao caminho de esqueci/redefinir.
Também afetava alterações normais de senha autenticadas.
Uma reprodução já teria sido suficiente para mostrar um problema.
Mas validar os dois fluxos importava por dois motivos.
Mostrou que o bug não estava isolado a um caminho de recuperação de caso extremo.
A mesma propriedade de segurança falhava em:
Tornou mais difícil descartar o problema como lógica de negócio acidental.
Isso era claramente uma fraqueza mais ampla de gerenciamento de sessão:
Isso deu ao problema um peso de segurança muito maior.
Também testei o fluxo de redefinição em uma conta com TOTP habilitado, porque queria saber se a redefinição de senha enfraqueceria silenciosamente ou contornaria as expectativas de 2FA.
O que confirmei foi:
Essa foi uma verificação de limite útil.
Ela restringiu o problema corretamente.
A vulnerabilidade não era:
O problema real permanecia:
Essa é uma descoberta mais limpa e mais defensável.
Este problema foi razoavelmente classificado como Alta.
O impacto principal aqui é acesso não autorizado persistente após ações de recuperação da segurança da conta.
A classificação do advisory foi:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:NIsso faz sentido.
A afirmação não é que um atacante possa fazer login sem credenciais a partir do nada. A afirmação é que, uma vez que um atacante obtém uma sessão autenticada válida, a vítima não consegue encerrar totalmente esse acesso executando exatamente as ações de segurança que deveriam recuperar a conta, ou seja, redefinição e alteração de senha.
Essa é uma vulnerabilidade real e defensável de gerenciamento de sessão.
Algumas pessoas subestimam bugs de persistência de sessão porque assumem que o roubo de sessão já é “fim de jogo”.
Isso é simplista demais.
A verdadeira pergunta é o que acontece depois que a vítima percebe que algo está errado e age.
Se:
então a recuperação da conta está incompleta.
Isso não é apenas um comportamento estranho. É uma falha de segurança no modelo de recuperação.
Especialmente em uma plataforma voltada para administradores, é um problema significativo com forte impacto de confidencialidade.
O mantenedor corrigiu o problema no commit:
db82035
A direção central da correção é exatamente o que este bug precisava:
Essa é a remediação correta porque atinge a propriedade de segurança real que falhou:
a confiança antiga deve morrer quando as credenciais mudam
Uma boa correção para esta classe de bug não se trata de alterar a validação de senha. Trata-se de revogar o estado de sessão anteriormente ativo associado à conta.
Essa é a parte que restaura a recuperação real.
Este problema foi reportado de forma privada pelo fluxo de reporte de segurança do GitHub.
O mantenedor:
CVE-2026-34828
Uma coisa que surgiu durante o tratamento do advisory foi o escopo.
O relatório original incluía ambos:
O GitHub inicialmente tratou esses como problemas independentemente corrigíveis para fins de atribuição de CVE. Isso é um lembrete útil de que o escopo do advisory importa mesmo quando a fraqueza subjacente é conceitualmente semelhante.
O resultado final foi CVE-2026-34828.
A lição principal aqui é simples:
alterar as credenciais não é suficiente se a confiança autenticada antiga ainda estiver viva.
Muitos desenvolvedores pensam em termos de:
Essas coisas importam.
Mas o limite de segurança real é mais amplo:
quando um evento de conta de alto risco acontece, qual estado anteriormente confiável deve deixar de ser confiável?
Neste caso, a resposta deveria ter sido:
E o listmonk não estava fazendo isso.
Essa é a verdadeira conclusão.
Esta vulnerabilidade não envolvia payloads sofisticados ou truques inteligentes de parser.
Tratava-se de fazer a pergunta certa sobre o limite de confiança.
No listmonk, a senha mudou.
A ação de recuperação foi concluída.
Mas a sessão antiga do atacante ainda vivia.
É por isso que isso se tornou CVE-2026-34828.
