| name | wstg-athn-05 |
| description | Testing for Vulnerable Remember Password |
| category | authentication |
| owasp_id | WSTG-ATHN-05 |
| version | 1.0.0 |
| author | cyberstrike-official |
| tags | ["authentication","login","credentials","mfa","wstg","athn"] |
| tech_stack | [] |
| cwe_ids | ["CWE-287"] |
| chains_with | ["wstg-authz-02","wstg-sess-01"] |
| prerequisites | ["wstg-idnt-01"] |
| severity_boost | {} |
wstg-athn-05
Test ID
WSTG-ATHN-05
Test Name
Testing for Vulnerable Remember Password
High-Level Description
"Remember me" or "remember password" functionality allows users to stay logged in across browser sessions. This test examines whether this functionality is implemented securely. Insecure implementations may store credentials in cleartext, use predictable tokens, or create persistent sessions that are vulnerable to theft or abuse.
What to Check
Remember Me Implementation
Common Vulnerabilities
| Vulnerability | Description |
|---|
| Cleartext credentials | Password stored in cookie |
| Predictable tokens | Sequential or guessable values |
| No expiration | Tokens never expire |
| Cross-device usage | Same token works everywhere |
| No revocation | Cannot invalidate tokens |
How to Test
Step 1: Analyze Remember Me Token
response=$(curl -s -X POST "https://target.com/login" \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"password","remember_me":true}' \
-c cookies.txt \
-v 2>&1)
cat cookies.txt
grep -i "remember\|persistent\|token" cookies.txt
curl -sI "https://target.com/login" -X POST \
-d '{"username":"testuser","password":"password","remember_me":true}' | \
grep -i "set-cookie"
Step 2: Check for Cleartext Credentials
echo "cookie_value_here" | base64 -d
cookie_value="your_remember_me_cookie"
echo "$cookie_value" | base64 -d 2>/dev/null
echo "$cookie_value" | python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))"
echo "$cookie_value" | xxd
Step 3: Test Token Predictability
#!/bin/bash
tokens=()
for i in {1..10}; do
token=$(curl -s -X POST "https://target.com/login" \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"password","remember_me":true}' \
-c - | grep -i "remember" | awk '{print $7}')
tokens+=("$token")
echo "Token $i: $token"
curl -s "https://target.com/logout" > /dev/null
sleep 1
done
echo ""
echo "=== Token Analysis ==="
echo "Lengths: ${#tokens[@]} tokens collected"
Step 4: Test Token Expiration
curl -s -X POST "https://target.com/login" \
-d '{"username":"testuser","password":"password","remember_me":true}' \
-c cookies.txt
remember_token=$(grep -i "remember" cookies.txt | awk '{print $7}')
curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$remember_token" \
-w "\nStatus: %{http_code}"
Step 5: Test Cross-Device Token Usage
token=$(curl -s -X POST "https://target.com/login" \
-d '{"username":"testuser","password":"password","remember_me":true}' \
-c - | grep -i "remember" | awk '{print $7}')
curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$token" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-w "\nStatus: %{http_code}"
curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$token" \
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" \
-w "\nStatus: %{http_code}"
curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$token" \
-H "X-Forwarded-For: 1.2.3.4" \
-w "\nStatus: %{http_code}"
Step 6: Test Token Revocation
token=$(curl -s -X POST "https://target.com/login" \
-d '{"username":"testuser","password":"password","remember_me":true}' \
-c - | grep -i "remember" | awk '{print $7}')
curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$token" \
-w "\nStatus: %{http_code}" > /dev/null
echo "Before logout: Token works"
curl -s "https://target.com/logout" \
-H "Cookie: remember_me=$token"
response=$(curl -s "https://target.com/dashboard" \
-H "Cookie: remember_me=$token" \
-w "\nStatus: %{http_code}")
if echo "$response" | grep -q "200"; then
echo "[VULN] Token still works after logout!"
else
echo "[OK] Token properly invalidated after logout"
fi
Step 7: Check Cookie Security Attributes
curl -sI "https://target.com/login" -X POST \
-d '{"username":"testuser","password":"password","remember_me":true}' | \
grep -i "set-cookie" | while read line; do
echo "Cookie: $line"
echo ""
if echo "$line" | grep -qi "secure"; then
echo " [OK] Secure flag present"
else
echo " [VULN] Secure flag missing"
fi
if echo "$line" | grep -qi "httponly"; then
echo " [OK] HttpOnly flag present"
else
echo " [VULN] HttpOnly flag missing"
fi
if echo "$line" | grep -qi "samesite"; then
echo " [OK] SameSite attribute present"
else
echo " [WARN] SameSite attribute missing"
fi
done
Tools
Cookie Analysis
| Tool | Description | Usage |
|---|
| Burp Suite | Cookie inspection | Analyze token values |
| Browser DevTools | Cookie viewer | Inspect cookie attributes |
| CyberChef | Decoding | Decode token values |
Token Analysis
| Tool | Description |
|---|
| jwt.io | JWT decoder |
| Hashcat | Hash analysis |
| Custom scripts | Pattern analysis |
Example Commands/Payloads
Remember Me Token Analyzer
import requests
import base64
import json
import hashlib
from datetime import datetime
class RememberMeAnalyzer:
def __init__(self, base_url):
self.base_url = base_url
self.tokens = []
def login_and_get_token(self, username, password):
"""Login and capture remember me token"""
response = requests.post(
f"{self.base_url}/login",
json={
"username": username,
"password": password,
"remember_me": True
}
)
for cookie in response.cookies:
if "remember" in cookie.name.lower() or "persistent" in cookie.name.lower():
return {
"name": cookie.name,
"value": cookie.value,
"secure": cookie.secure,
"httponly": "httponly" in str(cookie).lower(),
"expires": cookie.expires,
"timestamp": datetime.now().isoformat()
}
return None
def analyze_token(self, token_value):
"""Analyze token structure"""
analysis = {
"length": len(token_value),
"appears_encoded": False,
"decoded_content": None,
"appears_sequential": False,
"contains_username": False,
"appears_hashed": False
}
try:
decoded = base64.b64decode(token_value).decode('utf-8')
analysis["appears_encoded"] = True
analysis["decoded_content"] = decoded
try:
json_data = json.loads(decoded)
analysis["is_json"] = True
analysis["json_keys"] = list(json_data.keys())
except:
pass
except:
pass
if len(token_value) in [32, 40, 64, 128]:
if all(c in '0123456789abcdef' for c in token_value.lower()):
analysis["appears_hashed"] = True
return analysis
def check_predictability(self, num_samples=5):
"""Check if tokens are predictable"""
print(f"Collecting {num_samples} tokens...")
for i in range(num_samples):
token = self.login_and_get_token("testuser", "password")
if token:
self.tokens.append(token)
print(f" Token {i+1}: {token['value'][:50]}...")
if len(self.tokens) < 2:
return {"error": "Not enough tokens collected"}
values = [t["value"] for t in self.tokens]
sequential = False
try:
nums = [int(v, 16) if all(c in '0123456789abcdef' for c in v.lower()) else None for v in values]
if all(n is not None for n in nums):
diffs = [nums[i+1] - nums[i] for i in range(len(nums)-1)]
if len(set(diffs)) == 1:
sequential = True
except:
pass
timestamp_pattern = any("time" in str(self.analyze_token(v).get("decoded_content", "")).lower() for v in values)
return {
"total_collected": len(self.tokens),
"unique_values": len(set(values)),
"sequential_pattern": sequential,
"timestamp_pattern": timestamp_pattern,
"predictable": sequential or len(set(values)) < len(values)
}
def check_token_after_logout(self, token):
"""Check if token still works after logout"""
response = requests.get(
f"{self.base_url}/dashboard",
cookies={"remember_me": token}
)
works_before = response.status_code == 200
requests.get(
f"{self.base_url}/logout",
cookies={"remember_me": token}
)
response = requests.get(
f"{self.base_url}/dashboard",
cookies={"remember_me": token}
)
works_after = response.status_code == 200
return {
"works_before_logout": works_before,
"works_after_logout": works_after,
"properly_invalidated": works_before and not works_after
}
def generate_report(self):
"""Generate analysis report"""
print("\n=== REMEMBER ME SECURITY REPORT ===\n")
if self.tokens:
token = self.tokens[0]
analysis = self.analyze_token(token["value"])
print("Token Attributes:")
print(f" Secure flag: {token.get('secure', 'Unknown')}")
print(f" HttpOnly: {token.get('httponly', 'Unknown')}")
print(f" Expires: {token.get('expires', 'Unknown')}")
print("\nToken Analysis:")
print(f" Length: {analysis['length']}")
print(f" Appears encoded: {analysis['appears_encoded']}")
print(f" Appears hashed: {analysis['appears_hashed']}")
if analysis.get("decoded_content"):
print(f" Decoded content: {analysis['decoded_content'][:100]}")
analyzer = RememberMeAnalyzer("https://target.com")
predictability = analyzer.check_predictability(5)
print(f"Predictability analysis: {predictability}")
analyzer.generate_report()
Remediation Guide
1. Secure Token Generation
import secrets
import hashlib
from datetime import datetime, timedelta
class SecureRememberMe:
def __init__(self, redis_client):
self.redis = redis_client
self.token_expiry = timedelta(days=30)
def create_token(self, user_id, user_agent, ip_address):
"""Create secure remember me token"""
selector = secrets.token_urlsafe(12)
validator = secrets.token_urlsafe(32)
validator_hash = hashlib.sha256(validator.encode()).hexdigest()
token_data = {
"user_id": user_id,
"validator_hash": validator_hash,
"user_agent": user_agent,
"ip_address": ip_address,
"created_at": datetime.utcnow().isoformat()
}
key = f"remember_me:{selector}"
self.redis.setex(
key,
int(self.token_expiry.total_seconds()),
json.dumps(token_data)
)
return f"{selector}:{validator}"
def verify_token(self, token, user_agent, ip_address):
"""Verify remember me token"""
try:
selector, validator = token.split(':')
except ValueError:
return None
key = f"remember_me:{selector}"
stored_data = self.redis.get(key)
if not stored_data:
return None
data = json.loads(stored_data)
validator_hash = hashlib.sha256(validator.encode()).hexdigest()
if not secrets.compare_digest(data["validator_hash"], validator_hash):
return None
return data["user_id"]
def revoke_token(self, token):
"""Revoke a specific token"""
try:
selector, _ = token.split(':')
self.redis.delete(f"remember_me:{selector}")
except:
pass
def revoke_all_tokens(self, user_id):
"""Revoke all tokens for a user"""
for key in self.redis.scan_iter(match="remember_me:*"):
data = self.redis.get(key)
if data:
token_data = json.loads(data)
if token_data.get("user_id") == user_id:
self.redis.delete(key)
2. Secure Cookie Settings
from flask import Flask, make_response
@app.route('/login', methods=['POST'])
def login():
user = authenticate(request.json)
if user and request.json.get('remember_me'):
remember_token = remember_me.create_token(
user.id,
request.headers.get('User-Agent'),
request.remote_addr
)
response = make_response(jsonify({"status": "success"}))
response.set_cookie(
'remember_me',
value=remember_token,
max_age=30 * 24 * 60 * 60,
secure=True,
httponly=True,
samesite='Lax',
path='/'
)
return response
3. Token Rotation
def verify_and_rotate_token(token, user_agent, ip_address):
"""Verify token and issue new one (rotate)"""
user_id = remember_me.verify_token(token, user_agent, ip_address)
if user_id:
remember_me.revoke_token(token)
new_token = remember_me.create_token(user_id, user_agent, ip_address)
return user_id, new_token
return None, None
Risk Assessment
CVSS Score
| Finding | CVSS | Severity |
|---|
| Cleartext password in cookie | 9.8 | Critical |
| Predictable token generation | 8.8 | High |
| Token works after logout | 7.5 | High |
| Missing Secure flag | 5.3 | Medium |
| Token never expires | 6.5 | Medium |
CWE Categories
| CWE ID | Title | Description |
|---|
| CWE-312 | Cleartext Storage of Sensitive Information | Password in cookie |
| CWE-330 | Use of Insufficiently Random Values | Weak tokens |
| CWE-613 | Insufficient Session Expiration | No expiration |
| CWE-614 | Sensitive Cookie Without Secure Flag | Missing security |
References
Checklist
[ ] Remember me token analyzed
[ ] Token encoding checked
[ ] Cleartext credentials checked
[ ] Token predictability tested
[ ] Token expiration tested
[ ] Cross-device usage tested
[ ] Token revocation on logout tested
[ ] Token revocation on password change tested
[ ] Cookie Secure flag checked
[ ] Cookie HttpOnly flag checked
[ ] Cookie SameSite attribute checked
[ ] Findings documented
[ ] Remediation recommendations provided