Widget Factory Joomla Content Editor CVE-2026-48907: KEV Unauthenticated Profile Upload to PHP RCE
CISA added CVE-2026-48907 to KEV on 2026-06-16. JCE 2.9.99[.]5 and 2.9.99[.]6 fix an unauthenticated editor-profile upload flaw that can lead to PHP code execution on Joomla sites.
On this page 0% read
Executive Summary
Widget Factory’s Joomla Content Editor (JCE) has a critical unauthenticated access-control flaw, tracked as CVE-2026-48907. The vendor says the attack path lets an unauthenticated actor create a new editor profile, grant upload permission, and then upload executable PHP. The vendor also states that active exploitation is already public and automated [1].
CISA added the vulnerability to the Known Exploited Vulnerabilities catalog on 2026-06-16 with a due date of 2026-06-19 [2]. JCE’s changelog shows the vulnerable logic was fixed in 2.9.99[.]5 on 2026-06-03, with 2.9.99[.]6 on 2026-06-08 adding further hardening [3]. Treat any internet-facing JCE deployment below 2.9.99[.]5 as presumed exposed until the exact installed version is verified [1][3].
Key Facts
Cve: CVE-2026-48907
Vendor: Widget Factory / JCE
Product: Joomla Content Editor
Vulnerability Class: improper access control leading to unauthenticated profile upload and PHP execution
Cwe: CWE-284
Vendor Severity: Critical
Kev Added: 2026-06-16
Kev Due Date: 2026-06-19
Fixed Versions:
- 2.9.99[.]5 or later
- 2.9.99[.]6 recommended for added hardening
High Value Evidence:
- JCE version output from the Joomla administrator panel or package inventory
- Web server access logs around
index[.]php?option=com_jce&task=profiles.import - Filesystem review of
images/,media/, andtmp/for unexpected PHP or XML-wrapped PHP payloads
Evidence Assessment
- confirmed: The vendor says JCE 2.9.99[.]5 patched the critical flaw in all earlier versions and 2.9.99[.]6 added hardening [1][3].
- confirmed: The attack path is unauthenticated, creates a new editor profile, and enables upload of executable PHP [1][4].
- confirmed: CISA added CVE-2026-48907 to KEV on 2026-06-16 with a 2026-06-19 due date [2].
- confirmed: The JCE advisories say the attack is actively exploited and public exploit code exists [1].
- not_observed: Public sources in this packet do not identify a victim list, a campaign name, or a separate malware family.
Impact Determination
| Classification | Criteria | Required evidence | Handling decision |
|---|---|---|---|
| Confirmed compromise | Access logs show profiles.import activity followed by new editor profiles, unexpected PHP uploads, or execution of uploaded code. | Access logs, file timestamps, webshell contents, and process history. | Isolate the host, preserve logs, remove the rogue profile, and rebuild from a known-good backup. |
| Presumed exposed | The site runs JCE below 2.9.99[.]5 and the admin surface is reachable. | Package version, plugin inventory, and exposure scan. | Upgrade to 2.9.99[.]6 or later, then review logs and files. |
| Potentially exposed | JCE is installed but version or reachability is unknown. | Inventory and network scan. | Collect exact version and exposure evidence immediately. |
| Not exposed | JCE is absent or already on a fixed version and no suspicious profile activity exists. | Version evidence and negative log/file review. | Document closure and keep credentials monitored. |
| Unknown | Version, log, or file evidence is missing. | Named gap and owner. | Keep the site in high-priority triage until evidence is recovered. |
Timeline
- 2026-06-03: JCE 2.9.99[.]5 ships and fixes the critical access-control flaw [3].
- 2026-06-08: JCE 2.9.99[.]6 ships with additional hardening [3].
- 2026-06-12: The vendor publishes a security-update post with response guidance and confirms active exploitation [1].
- 2026-06-16: CISA adds CVE-2026-48907 to KEV [2].
Technical Analysis
The attack is simple but high impact: unauthenticated requests create an editor profile that permits executable file uploads, then the attacker uploads PHP into a writeable location and executes it through the web server [1][4]. The vendor specifically tells defenders to look for unauthenticated requests to index[.]php?option=com_jce&task=profiles.import, suspicious editor profiles, and PHP files in upload directories [1].
The response priority is version-first, cleanup-second. If the vulnerable editor remains reachable, removing the rogue profile alone is not enough because the same automated attack can recreate access before cleanup is complete [1][3].
Applicability Decision
- Developer endpoint / workstation module: applicable only if administrators manage Joomla from a local workstation that may hold hosting credentials or backup access [1][3].
- Cloud / CI-CD / GitHub / browser modules: not applicable for the primary attack path [1][3].
- Hosting control-plane module: applicable if the same credentials manage the site, its backups, or its file-transfer tooling [1][3].
Detection and Hunting
Hunt Manifest: widget-factory-joomla-content-editor-cve-2026-48907-kev-hunt-1
- Title: local repository and exported telemetry scope
- Question: Does the telemetry scope contain patterns associated with CVE-2026-48907 exploitation on a Joomla site?
- Telemetry Family: process
- Telemetry Context: web-server logs and filesystem export
- Positive Signal: Indicators of compromise matched in telemetry
#!/usr/bin/env python3
import argparse
import json
from pathlib import Path
PROFILE_IMPORT = "index.php?option=com_jce&task=profiles.import"
DOMAINS = {"joomlacontenteditor.net", "cisa.gov", "nvd.nist.gov"}
URLS = {
"https://www.joomlacontenteditor.net/news/jce-security-update-and-a-free-patch-for-older-sites",
"https://www.joomlacontenteditor.net/support/changelog/editor",
"https://www.cisa.gov/known-exploited-vulnerabilities-catalog?field_cve=CVE-2026-48907",
"https://nvd.nist.gov/vuln/detail/CVE-2026-48907",
}
SUSPICIOUS_DIRS = {"images", "media", "tmp"}
FILE_PATTERNS = {"images/*.php", "media/*.php", "tmp/*.php"}
SUSPICIOUS_SUFFIXES = {".php", ".phtml", ".php5", ".php7", ".phar"}
FIXED_VERSIONS = {"2.9.99.5", "2.9.99.6"}
def scan(root: Path) -> dict[str, list[str]]:
log_hits: list[str] = []
file_hits: list[str] = []
version_hits: list[str] = []
source_hits: list[str] = []
for path in root.rglob("*"):
if path.is_dir() or any(part in {".git", "node_modules", "vendor", "dist"} for part in path.parts):
continue
try:
text = path.read_text(errors="ignore")
except Exception:
text = ""
if PROFILE_IMPORT in text:
log_hits.append(f"{path}: {PROFILE_IMPORT}")
for domain in DOMAINS:
if domain in text:
source_hits.append(f"{path}: {domain}")
for url in URLS:
if url in text:
source_hits.append(f"{path}: {url}")
for version in FIXED_VERSIONS:
if version in text:
version_hits.append(f"{path}: {version}")
rel = path.relative_to(root)
if any(rel.match(pattern) for pattern in FILE_PATTERNS):
file_hits.append(f"{path}: suspicious PHP-like upload path")
elif path.suffix.lower() in SUSPICIOUS_SUFFIXES and any(part in SUSPICIOUS_DIRS for part in path.parts):
file_hits.append(f"{path}: suspicious PHP-like upload path")
return {
"log_hits": sorted(set(log_hits)),
"file_hits": sorted(set(file_hits)),
"version_hits": sorted(set(version_hits)),
"source_hits": sorted(set(source_hits)),
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("root", nargs="?", default=".", help="filesystem or log root to scan")
parser.add_argument("--out", default="jce-scope", help="output directory")
args = parser.parse_args()
root = Path(args.root).expanduser().resolve()
out = Path(args.out).expanduser().resolve()
out.mkdir(parents=True, exist_ok=True)
result = scan(root)
(out / "scan-summary.json").write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"scanned_root": str(root), **result}, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Remediation and Closure
- Upgrade JCE to 2.9.99[.]6 or later and verify the installed version in the Joomla admin or package inventory [1][3].
- Delete any editor profile you did not create and confirm the upload permissions on every remaining profile [1].
- Remove uploaded PHP or XML-wrapped PHP files from
images/,media/, andtmp/and check timestamps before deleting anything else [1]. - Change administrator, database, and hosting/FTP credentials because the attack path is designed to reach secrets after code execution [1].
- Preserve logs before cleanup so you can reconstruct when
profiles.importfirst appeared and whether the same account was reused elsewhere [1].
Close the case only after the site is patched, the rogue profile is removed, files are cleaned, and the log review shows no additional malicious requests.
Sources
- JCE security update and a free patch for older sites - Role: DIRECT_SOURCE - Impact: Confirms active exploitation, the profile-import attack path, and the recommended response sequence.
- CISA KEV catalog entry for CVE-2026-48907 - Role: GOVERNMENT_SOURCE - Impact: Confirms known exploitation and the 2026-06-19 remediation due date.
- JCE changelog for editor releases - Role: DIRECT_SOURCE - Impact: Shows 2.9.99[.]5 and 2.9.99[.]6 fix and harden the flaw.
- NVD CVE-2026-48907 - Role: PRIMARY_REFERENCE - Impact: Describes the unauthenticated profile-creation and PHP-execution behavior.
IOC Clipboard
2 IOCsindex.php index[.]php profiles.import profiles[.]import