Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Testing for Weak Password Policy
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 114% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 283% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 274% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 514% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 841% | 0% |
WSTG-ATHN-07
Testing for Weak Password Policy
Password policy testing evaluates the strength requirements enforced by an application during password creation and change. Weak policies allow users to create easily guessable passwords, making accounts vulnerable to brute-force and dictionary attacks. This test examines minimum length, complexity requirements, and protection against common weak passwords.
| Weakness | Risk | | -------------------------- | --------------------- | | Short minimum length (<8) | Easy brute-force | | No complexity requirements | Dictionary attacks | | No blocklist | Common password usage | | Allows personal info | Social engineering | | No history check | Password cycling |
bash#!/bin/bash # Test minimum password length lengths=(1 2 3 4 5 6 7 8 10 12 16) for len in "${lengths[@]}"; do password=$(head -c $len < /dev/urandom | base64 | head -c $len) response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"OldPassword123!\", \"new_password\": \"$password\" }") echo "Length $len: $response" | head -c 100 echo "" done
bash#!/bin/bash # Test password complexity requirements # Test passwords with different complexity passwords=( "password" # lowercase only "PASSWORD" # uppercase only "12345678" # numbers only "!!!!!!!!!" # symbols only "Password" # mixed case "password1" # lowercase + numbers "Password1" # mixed case + numbers "Password1!" # all complexity ) for password in "${passwords[@]}"; do response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"OldPassword123!\", \"new_password\": \"$password\" }") echo "$password: $response" | head -c 100 echo "" done
bash#!/bin/bash # Test if common passwords are blocked common_passwords=( "password" "password123" "123456" "12345678" "qwerty" "abc123" "letmein" "welcome" "admin" "iloveyou" "monkey" "dragon" "master" "123456789" "Password1!" ) for password in "${common_passwords[@]}"; do response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"OldPassword123!\", \"new_password\": \"$password\" }") if echo "$response" | grep -qi "success"; then echo "[WEAK] Accepted common password: $password" else echo "[OK] Rejected: $password" fi done
bash# Test if password can contain username or email username="johndoe" email="john.doe@example.com" test_passwords=( "${username}123!" "${username}Password" "Password${username}" "john.doe123" "johndoe2024" ) for password in "${test_passwords[@]}"; do response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"new_password\": \"$password\" }") echo "$password: $response" | head -c 100 echo "" done
bash#!/bin/bash # Test if previous passwords can be reused original_password="OldPassword123!" new_password="NewPassword456!" # Change password curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"$original_password\", \"new_password\": \"$new_password\" }" # Try to change back to original response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"$new_password\", \"new_password\": \"$original_password\" }") if echo "$response" | grep -qi "success"; then echo "[WEAK] Password reuse allowed" else echo "[OK] Password reuse prevented" fi
bash# Test maximum password length # Some systems truncate long passwords silently lengths=(64 128 256 512 1024) for len in "${lengths[@]}"; do password=$(head -c $len < /dev/urandom | base64 | head -c $len) response=$(curl -s -X POST "https://target.com/api/change-password" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"current_password\": \"OldPassword123!\", \"new_password\": \"$password\" }" \ -w "\nStatus: %{http_code}") echo "Length $len: $(echo "$response" | tail -1)" done # Test if long password is truncated # Set very long password, then try to login with truncated version
bash# Test password policy during registration # May differ from change password policy test_passwords=( "123" "password" "test" "abc123" "Password1" "Str0ngP@ssw0rd!" ) for password in "${test_passwords[@]}"; do response=$(curl -s -X POST "https://target.com/api/register" \ -H "Content-Type: application/json" \ -d "{ \"username\": \"testuser$(date +%s)\", \"email\": \"test$(date +%s)@example.com\", \"password\": \"$password\" }") echo "$password: $response" | head -c 100 echo "" done
| Tool | Description | Usage | | ------------------ | ----------------- | ------------------------- | | Burp Intruder | Payload testing | Test password variations | | Custom scripts | Automated testing | Systematic policy testing | | SecLists | Common passwords | Wordlist testing |
| Tool | Description | | --------------------- | -------------------------- | | zxcvbn | Password strength meter | | Have I Been Pwned | Compromised password check |
python#!/usr/bin/env python3 import requests import string import random class PasswordPolicyTester: def __init__(self, base_url, token): self.base_url = base_url self.headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } self.results = {} def try_password(self, password, endpoint="/api/change-password"): """Try to set a password and check if accepted""" try: response = requests.post( f"{self.base_url}{endpoint}", headers=self.headers, json={ "current_password": "CurrentPass123!", "new_password": password } ) accepted = response.status_code == 200 and \ "error" not in response.text.lower() and \ "invalid" not in response.text.lower() return accepted, response.text[:200] except Exception as e: return None, str(e) def test_minimum_length(self): """Find minimum password length""" print("[*] Testing minimum length...") for length in range(1, 20): # Create password that would pass complexity if length is ok password = "Aa1!" + "a" * (length - 4) if length >= 4 else "a" * length accepted, _ = self.try_password(password) if accepted: self.results["min_length"] = length print(f" Minimum length: {length}") return length self.results["min_length"] = ">20" return None def test_complexity(self): """Test complexity requirements""" print("[*] Testing complexity requirements...") base_length = 12 # Use length that should pass length check tests = { "lowercase_only": "a" * base_length, "uppercase_only": "A" * base_length, "numbers_only": "1" * base_length, "symbols_only": "!" * base_length, "lower_upper": "Aa" * (base_length // 2), "lower_number": "a1" * (base_length // 2), "lower_upper_number": "Aa1" + "a" * (base_length - 3), "all_types": "Aa1!" + "a" * (base_length - 4), } complexity_results = {} for name, password in tests.items(): accepted, _ = self.try_password(password) complexity_results[name] = accepted status = "ACCEPTED" if accepted else "REJECTED" print(f" {name}: {status}") self.results["complexity"] = complexity_results # Determine required complexity if complexity_results.get("lowercase_only"): self.results["min_complexity"] = "None (lowercase only accepted)" elif complexity_results.get("lower_upper"): self.results["min_complexity"] = "Low (mixed case only)" elif complexity_results.get("lower_upper_number"): self.results["min_complexity"] = "Medium (letters + numbers)" elif complexity_results.get("all_types"): self.results["min_complexity"] = "High (all character types)" else: self.results["min_complexity"] = "Very High (all types rejected?)" def test_common_passwords(self): """Test common password blocking""" print("[*] Testing common password blocking...") common = [ "password", "password123", "123456", "qwerty", "letmein", "welcome", "admin123", "Password1!", "iloveyou", "sunshine", "princess", "dragon" ] blocked = 0 for pwd in common: # Add complexity if needed test_pwd = pwd if len(pwd) >= 8 else pwd + "123!" accepted, _ = self.try_password(test_pwd) if not accepted: blocked += 1 self.results["common_blocked"] = f"{blocked}/{len(common)}" print(f" Blocked {blocked}/{len(common)} common passwords") def test_password_history(self): """Test password reuse prevention""" print("[*] Testing password history...") # This would require actual password changes # Simplified version self.results["password_history"] = "Requires manual testing" def generate_report(self): """Generate policy report""" print("\n" + "=" * 50) print("PASSWORD POLICY ANALYSIS REPORT") print("=" * 50 + "\n") for key, value in self.results.items(): print(f"{key}: {value}") # Security assessment print("\n--- SECURITY ASSESSMENT ---") issues = [] if isinstance(self.results.get("min_length"), int): if self.results["min_length"] < 8: issues.append("Minimum length too short (< 8)") elif self.results["min_length"] < 12: issues.append("Consider increasing minimum to 12+") if "lowercase_only" in str(self.results.get("min_complexity", "")): issues.append("No complexity requirements") if self.results.get("common_blocked", "0/") == "0/": issues.append("Common passwords not blocked") if issues: print("\nISSUES FOUND:") for issue in issues: print(f" - {issue}") else: print("\nNo major issues found.") # Usage tester = PasswordPolicyTester("https://target.com", "auth_token") tester.test_minimum_length() tester.test_complexity() tester.test_common_passwords() tester.generate_report()
pythonimport re from zxcvbn import zxcvbn # Common password list (use full list in production) COMMON_PASSWORDS = { 'password', 'password123', '123456', 'qwerty', 'letmein', 'welcome', 'admin', 'admin123' } class PasswordValidator: MIN_LENGTH = 12 MAX_LENGTH = 128 HISTORY_COUNT = 10 def validate(self, password, user=None): """Validate password against policy""" errors = [] # Length check if len(password) < self.MIN_LENGTH: errors.append(f"Password must be at least {self.MIN_LENGTH} characters") if len(password) > self.MAX_LENGTH: errors.append(f"Password cannot exceed {self.MAX_LENGTH} characters") # Complexity check if not re.search(r'[a-z]', password): errors.append("Password must contain lowercase letters") if not re.search(r'[A-Z]', password): errors.append("Password must contain uppercase letters") if not re.search(r'\d', password): errors.append("Password must contain numbers") if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): errors.append("Password must contain special characters") # Common password check if password.lower() in COMMON_PASSWORDS: errors.append("This password is too common") # Personal info check if user: if user.username.lower() in password.lower(): errors.append("Password cannot contain username") if user.email.split('@')[0].lower() in password.lower(): errors.append("Password cannot contain email") # Strength check using zxcvbn result = zxcvbn(password) if result['score'] < 3: errors.append(f"Password is too weak: {result['feedback']['warning']}") return errors def check_history(self, user, new_password): """Check against password history""" for old_hash in user.password_history[-self.HISTORY_COUNT:]: if check_password_hash(old_hash, new_password): return False return True
pythonimport hashlib import requests def check_pwned_password(password): """Check if password has been compromised""" sha1_hash = hashlib.sha1(password.encode()).hexdigest().upper() prefix = sha1_hash[:5] suffix = sha1_hash[5:] response = requests.get( f'https://api.pwnedpasswords.com/range/{prefix}', headers={'Add-Padding': 'true'} ) if response.status_code == 200: hashes = response.text.split('\n') for hash_line in hashes: parts = hash_line.split(':') if parts[0] == suffix: return int(parts[1]) # Number of times seen return 0 # Usage in validation def validate_password(password): pwned_count = check_pwned_password(password) if pwned_count > 0: return f"This password has been seen in {pwned_count} data breaches" return None
javascript// Client-side password strength feedback import zxcvbn from "zxcvbn" function checkPasswordStrength(password, userInputs = []) { const result = zxcvbn(password, userInputs) return { score: result.score, // 0-4 feedback: result.feedback, crackTime: result.crack_times_display.offline_slow_hashing_1e4_per_second, suggestions: result.feedback.suggestions, } } // Usage document.getElementById("password").addEventListener("input", (e) => { const strength = checkPasswordStrength(e.target.value, [username, email]) updateStrengthMeter(strength.score) showFeedback(strength.feedback) })
| Finding | CVSS | Severity | | ----------------------------- | ---- | -------- | | No minimum length requirement | 7.5 | High | | No complexity requirements | 6.5 | Medium | | Common passwords allowed | 7.5 | High | | Password reuse allowed | 5.3 | Medium | | Very short minimum (<6) | 8.8 | High |
| CWE ID | Title | Description | | ----------- | ----------------------------------- | ------------------- | | CWE-521 | Weak Password Requirements | Insufficient policy | | CWE-262 | Not Using Password Aging | No history check | | CWE-263 | Password Aging with Long Expiration | Policy issues |
[ ] Minimum length tested
[ ] Maximum length tested
[ ] Lowercase requirement tested
[ ] Uppercase requirement tested
[ ] Number requirement tested
[ ] Symbol requirement tested
[ ] Common passwords tested
[ ] Dictionary words tested
[ ] Personal info restriction tested
[ ] Password history tested
[ ] Password strength meter available
[ ] Breached password check implemented
[ ] Findings documented
[ ] Remediation recommendations providedOther measured skills in the registry, with their headline benchmark lift.