CVE-2024-21635
Executive Summary
CVE-2024-21635 affects Memos, a privacy-first, lightweight note-taking service. The vulnerability lies in the authentication mechanism: when a user changes their password, existing Access Tokens are not invalidated. This allows an attacker who has compromised a user’s account (e.g., via stolen tokens or session hijacking) to retain persistent access even after the legitimate user changes their password. The user must manually delete the malicious token from the token list, which is difficult because token descriptions are generic and do not identify the attacker.
The CVSS score is 0.0 in the NVD data, but based on the impact (persistent unauthorized access to notes and potentially sensitive data), a realistic score is 8.8 (High). The EPSS score is 0.0%, indicating low exploitation probability in the wild, but the risk is significant for affected deployments.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Memos is a self-hosted Go application distributed via Docker images and GitHub releases. There is no official package manager (NPM, PyPI, Go, Ruby) involved, but the application’s dependencies (Go modules) should be reviewed for similar session management flaws. The vulnerability is in the application logic, not in a third-party library.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) – the attacker can exploit this remotely.
- Attack Complexity (AC): Low (L) – no special conditions required.
- Privileges Required (PR): Low (L) – attacker needs a valid access token (obtained via prior compromise).
- User Interaction (UI): None (N) – no user action needed for exploitation.
- Scope (S): Unchanged (U) – the vulnerable component is the authentication system.
- Confidentiality (C): High (H) – attacker can read all notes.
- Integrity (I): High (H) – attacker can modify or delete notes.
- Availability (A): High (H) – attacker can lock out the legitimate user by deleting their tokens.
Threat Level: High. The vulnerability allows persistent unauthorized access, bypassing password changes.
Deep Dive & Mechanics
Memos uses Access Tokens (bearer tokens) for API authentication. When a user logs in, a token is generated and stored in the database. The token is tied to the user account but not to the password hash. When the password is changed, the application updates the password hash but does not invalidate existing tokens. This means:
- An attacker obtains a valid token (e.g., via phishing, XSS, or database leak).
- The legitimate user changes their password (e.g., after noticing suspicious activity).
- The attacker’s token remains valid, allowing continued access.
- The user must manually revoke the token from the settings page, but the generic description (e.g., “Web Session”) makes it hard to identify the malicious token.
The root cause is the lack of a token revocation mechanism tied to password changes. The fix should iterate over all tokens for the user and delete them upon password update.
Vulnerable vs. Patched Code
Vulnerable Code (Memos <= 0.18.1):
// In user service, password change handler
func (s *UserService) UpdateUserPassword(ctx context.Context, userID int, oldPassword, newPassword string) error {
user, err := s.store.GetUser(ctx, userID)
if err != nil {
return err
}
// Validate old password...
// Update password hash
user.PasswordHash = hashPassword(newPassword)
err = s.store.UpdateUser(ctx, user)
// No token revocation here
return err
}
Patched Code (Recommended Fix):
func (s *UserService) UpdateUserPassword(ctx context.Context, userID int, oldPassword, newPassword string) error {
user, err := s.store.GetUser(ctx, userID)
if err != nil {
return err
}
// Validate old password...
// Update password hash
user.PasswordHash = hashPassword(newPassword)
err = s.store.UpdateUser(ctx, user)
if err != nil {
return err
}
// Revoke all access tokens for this user
err = s.store.DeleteAccessTokensByUserID(ctx, userID)
return err
}
Exploitation & Lab Setup
Red Team (Attacker Simulation)
Prerequisites:
- A valid access token for a Memos user (obtained via prior compromise).
- Network access to the Memos instance.
Steps:
- Obtain a token (e.g., via XSS or database dump).
- Wait for the user to change their password (or trigger it via social engineering).
- Use the old token to access the API:
curl -H "Authorization: Bearer <old_token>" https://memos.example.com/api/v1/memo - The token still works, allowing full access to notes.
Automated Exploit (Python):
import requests
target = "https://memos.example.com"
token = "eyJhbGciOiJIUzI1NiIs..." # Old token
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{target}/api/v1/memo", headers=headers)
if response.status_code == 200:
print("Token still valid after password change!")
print(response.json())
else:
print("Token revoked.")
Blue Team (Defense & Detection)
Detection:
- Monitor for API calls using tokens that were created before a password change event.
- Alert on multiple failed login attempts followed by a password change (possible token theft).
Honeypot:
- Deploy a fake Memos instance and monitor for token reuse after simulated password changes.
Indicators of Compromise (IOCs)
- Network: API requests with tokens that have a creation timestamp older than the last password change timestamp.
- Logs:
password_changedevent followed by continued API access from the same IP/token. - File: Unusual access to
/api/v1/memoor/api/v1/userendpoints with old tokens.
Example IOC (Splunk):
index=memos sourcetype=access_log
| where token_created < password_changed_time
| stats count by user, src_ip
Detection Engineering
YARA Rule
rule CVE_2024_21635_Memos_Token_Reuse {
meta:
description = "Detects Memos access token reuse after password change"
author = "Valters IT"
date = "2024-01-01"
reference = "CVE-2024-21635"
strings:
$token_header = "Authorization: Bearer" nocase
$password_change = "password_changed" nocase
$memo_endpoint = "/api/v1/memo" nocase
condition:
$token_header and $memo_endpoint and #password_change > 0
}
Sigma Rule
title: Memos Access Token Reuse After Password Change
id: 12345678-1234-1234-1234-123456789012
status: experimental
description: Detects API requests using tokens created before the last password change
author: Valters IT
logsource:
category: application
product: memos
detection:
selection:
event: "api_request"
token_created: "< password_changed_time"
condition: selection
falsepositives:
- Legitimate token refresh (if implemented)
level: high
Suricata Rule
alert http any any -> any any (msg:"CVE-2024-21635 Memos Token Reuse"; flow:to_server; http.header; content:"Authorization"; nocase; content:"Bearer"; distance:0; http.uri; content:"/api/v1/memo"; nocase; classtype:attempted-user; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Manual Token Revocation: Instruct users to review and delete all access tokens after changing their password.
- Monitor Logs: Check for token reuse after password changes.
Long-Term Fix:
- Apply the patched code (revoke all tokens on password change).
- If using Docker, rebuild the image with the fix:
FROM memos/memos:latest # Apply patch to user_service.go
Workarounds:
- Implement a script to periodically revoke tokens older than a certain age.
- Use short-lived tokens (e.g., JWT with expiration) to limit the window of abuse.
Valters IT
💣 PoC CODE AVAILABLE
Exploit code for this CVE is tracked in our database.
Access poc_code via API Pro.