Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Testing for Vulnerable Remember Password
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 297% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 500% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 2166% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 837% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 889% | 0% |
WSTG-ATHN-05
Testing for Vulnerable Remember Password
"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.
| 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 |
bash# Login with remember me enabled 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) # Examine cookies cat cookies.txt # Look for remember me specific cookies grep -i "remember\|persistent\|token" cookies.txt # Check cookie attributes curl -sI "https://target.com/login" -X POST \ -d '{"username":"testuser","password":"password","remember_me":true}' | \ grep -i "set-cookie"
bash# Decode cookie values # If base64 encoded echo "cookie_value_here" | base64 -d # Check for username/password in cookie cookie_value="your_remember_me_cookie" # Try base64 decode echo "$cookie_value" | base64 -d 2>/dev/null # Try URL decode echo "$cookie_value" | python3 -c "import sys, urllib.parse; print(urllib.parse.unquote(sys.stdin.read()))" # Check if value contains readable text echo "$cookie_value" | xxd
bash#!/bin/bash # Collect multiple tokens to analyze patterns tokens=() for i in {1..10}; do # Create new account or use existing 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" # Logout between requests curl -s "https://target.com/logout" > /dev/null sleep 1 done # Analyze tokens for patterns echo "" echo "=== Token Analysis ===" echo "Lengths: ${#tokens[@]} tokens collected" # Check for sequential patterns # Check for timestamp patterns # Check for username inclusion
bash# Login and get remember me token curl -s -X POST "https://target.com/login" \ -d '{"username":"testuser","password":"password","remember_me":true}' \ -c cookies.txt # Get the remember me cookie remember_token=$(grep -i "remember" cookies.txt | awk '{print $7}') # Test if token works curl -s "https://target.com/dashboard" \ -H "Cookie: remember_me=$remember_token" \ -w "\nStatus: %{http_code}" # Test after simulated time (if possible) # Or wait and test periodically # Check if old tokens still work # Store tokens from previous tests and try them
bash# Login and get token token=$(curl -s -X POST "https://target.com/login" \ -d '{"username":"testuser","password":"password","remember_me":true}' \ -c - | grep -i "remember" | awk '{print $7}') # Try token from different User-Agent 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}" # Try from different simulated IP curl -s "https://target.com/dashboard" \ -H "Cookie: remember_me=$token" \ -H "X-Forwarded-For: 1.2.3.4" \ -w "\nStatus: %{http_code}"
bash# Get remember me token token=$(curl -s -X POST "https://target.com/login" \ -d '{"username":"testuser","password":"password","remember_me":true}' \ -c - | grep -i "remember" | awk '{print $7}') # Verify token works curl -s "https://target.com/dashboard" \ -H "Cookie: remember_me=$token" \ -w "\nStatus: %{http_code}" > /dev/null echo "Before logout: Token works" # Logout curl -s "https://target.com/logout" \ -H "Cookie: remember_me=$token" # Try token again 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 # Test after password change # Token should be invalidated after password change
bash# Analyze cookie 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 "" # Check for security attributes 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
| Tool | Description | Usage | | -------------------- | ----------------- | ------------------------- | | Burp Suite | Cookie inspection | Analyze token values | | Browser DevTools | Cookie viewer | Inspect cookie attributes | | CyberChef | Decoding | Decode token values |
| Tool | Description | | ------------------ | ---------------- | | jwt.io | JWT decoder | | Hashcat | Hash analysis | | Custom scripts | Pattern analysis |
python#!/usr/bin/env python3 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 } ) # Look for remember me cookie 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 base64 decode try: decoded = base64.b64decode(token_value).decode('utf-8') analysis["appears_encoded"] = True analysis["decoded_content"] = decoded # Check if it's JSON try: json_data = json.loads(decoded) analysis["is_json"] = True analysis["json_keys"] = list(json_data.keys()) except: pass except: pass # Check if it looks like a hash if len(token_value) in [32, 40, 64, 128]: # MD5, SHA1, SHA256, SHA512 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"} # Analyze patterns values = [t["value"] for t in self.tokens] # Check for sequential patterns 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 # Check for timestamp patterns 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""" # First verify token works response = requests.get( f"{self.base_url}/dashboard", cookies={"remember_me": token} ) works_before = response.status_code == 200 # Logout requests.get( f"{self.base_url}/logout", cookies={"remember_me": token} ) # Try again 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]}") # Usage analyzer = RememberMeAnalyzer("https://target.com") predictability = analyzer.check_predictability(5) print(f"Predictability analysis: {predictability}") analyzer.generate_report()
pythonimport 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""" # Generate cryptographically secure random token selector = secrets.token_urlsafe(12) validator = secrets.token_urlsafe(32) # Hash the validator for storage validator_hash = hashlib.sha256(validator.encode()).hexdigest() # Store token data token_data = { "user_id": user_id, "validator_hash": validator_hash, "user_agent": user_agent, "ip_address": ip_address, "created_at": datetime.utcnow().isoformat() } # Store in Redis with expiration key = f"remember_me:{selector}" self.redis.setex( key, int(self.token_expiry.total_seconds()), json.dumps(token_data) ) # Return token as selector:validator 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 # Get stored data key = f"remember_me:{selector}" stored_data = self.redis.get(key) if not stored_data: return None data = json.loads(stored_data) # Verify validator hash validator_hash = hashlib.sha256(validator.encode()).hexdigest() if not secrets.compare_digest(data["validator_hash"], validator_hash): return None # Optional: Verify device fingerprint # if data["user_agent"] != user_agent: # 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""" # Scan and delete all tokens for this 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)
pythonfrom 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"})) # Set secure cookie response.set_cookie( 'remember_me', value=remember_token, max_age=30 * 24 * 60 * 60, # 30 days secure=True, # HTTPS only httponly=True, # No JavaScript access samesite='Lax', # CSRF protection path='/' ) return response
pythondef 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: # Revoke old token remember_me.revoke_token(token) # Issue new token new_token = remember_me.create_token(user_id, user_agent, ip_address) return user_id, new_token return None, None
| 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 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 |
[ ] 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 providedOther measured skills in the registry, with their headline benchmark lift.