A public form endpoint that accepts .php as if it were a JPEG, gift-wrapped for anyone with curl
Balbooa Forms is a widely deployed Joomla forms/builder extension. Through version 2.4.0, its frontend attachment upload handler took files from anonymous visitors with no authentication, no CSRF token, and no allowlist on file type or extension. An attacker POSTs a PHP payload to the form endpoint, the file lands under the site's writable uploads directory, and then a second GET executes it as the web-server user. The fix ships in 2.4.1 (released 2026-07-09); everything ≤ 2.4.0 is exploitable.
The vendor CRITICAL / 9.8 rating is accurate and if anything conservative. The vector is AV:N/AC:L/PR:N/UI:N with full CIA impact, and CISA added it to KEV on the same day as disclosure because in-the-wild exploitation was already underway. There is no meaningful friction — no auth, no user interaction, no rare configuration. This is a single-request unauth RCE against internet-facing PHP.
5 steps from start to impact.
Enumerate Joomla sites running Balbooa Forms
/administrator/manifests/files/joomla.xml, then greps rendered pages for Balbooa Forms markers (com_baforms, baforms-wrapper, asset paths under /components/com_baforms/). Mass scanning with httpx + nuclei templates finds candidates in minutes.- Target site is internet-reachable
- Balbooa Forms component installed and enabled
- Sites behind WAF with virtual-patching may not respond to fingerprint markers
com_baforms in HTML bodies produce the candidate list; nuclei has templates for Joomla component enumeration.POST malicious file to unauth upload endpoint
multipart/form-data POST with a .php file (or double-extension like shell.php.jpg depending on server handler config). No session, no token, no MIME validation. Public PoCs are circulating; curl one-liners are trivial.- Form containing an attachment field is published on any public URL
- PHP writable upload directory
- Some deployments front Joomla with ModSecurity/CRS rules blocking
.phpin multipart bodies - Cloudflare/Sucuri WAF virtual patch may drop the request
/index.php?option=com_baforms&... with Content-Type: multipart/form-data; EDR on the LAMP host sees new .php files appearing under uploads/.Locate the dropped webshell
/images/baforms/ or /media/com_baforms/uploads/). Response body often echoes the stored filename. Attacker requests the file directly.- Uploaded file lands under docroot
- Apache/Nginx configured to execute
.phpin that directory
- Hardened configs block PHP execution in uploads via
.htaccessor nginxlocationdeny — but default Joomla installs do NOT ship this
Execute as web-server user (RCE)
www-data/apache/nobody. Attacker gains a foothold via China Chopper, WSO shell, or p0wny-shell. From here: exfil configuration.php (DB creds, secret), pivot to Joomla admin via password reset in DB, mine credentials, plant persistence.- PHP handler enabled for the upload directory
- SELinux/AppArmor profiles limiting httpd write/exec
- PHP
disable_functionsblockingexec/system/passthru
apache2 → sh → curl/wget is a classic webshell tell.Post-exploitation and lateral movement
configuration.php for DB creds, dumps #__users, adds a Super User row or resets an existing one. From admin panel they enable Joomla template editing to establish persistent RCE that survives extension removal. Also common: SEO-spam/skimmer injection, or pivoting to backend via credential reuse.- Local DB access from webshell
- Reused credentials or shared hosting
- Site isolation on managed hosting
- Read-only filesystem for templates
/index.php?option=com_baforms* that contain Content-Disposition: filename= with .php, .phtml, .phar, .php3-8, .pht, .inc. Deploy within 24 hours if you cannot disable the component..htaccess with <FilesMatch \.ph(p[3-8]?|tml|ar)$> Require all denied </FilesMatch> under /images/, /media/, and any Balbooa upload path. For nginx add location ~ ^/(images|media)/.*\.php$ { deny all; }. Do this now and keep it after patching.<?php, unusual filenames (high entropy, mixed case), files newer than 2026-06-01, and Joomla #__users for unexpected Super Users. Run within 24 hours.- Relying on EPSS 0.00836 as low-risk — EPSS is a lagging model; KEV is the authoritative signal here.
- Reverse proxy / Cloudflare in front without a specific rule — a passthrough CDN does nothing against a legitimate-looking multipart POST unless you write the virtual patch above.
- Rate limiting — one HTTP request is enough for RCE. Rate limits don't help.
- Disabling Joomla registration — the endpoint is unauth on the frontend, not the admin panel.
The supporting signals.
| In-the-wild status | Actively exploited — added to CISA KEV on disclosure day (2026-07-09). Zero-day exploitation confirmed before patch shipped. |
|---|---|
| Public PoC | PoC details published by mySites.guru; single curl one-liner reproducing upload is circulating in security Twitter/Mastodon. |
| EPSS | 0.00836 (low percentile despite KEV — EPSS lags reality here; treat KEV as the ground truth) |
| KEV | Listed 2026-07-09, FCEB due date per BOD 22-01 is 3 weeks out |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8 CRITICAL — no auth, no interaction, network reachable, full CIA |
| Affected versions | Balbooa Forms ≤ 2.4.0 on Joomla 3.x / 4.x / 5.x |
| Fixed version | 2.4.1 (released 2026-07-09) |
| Exposure population | Balbooa Forms is one of the top form extensions in the Joomla Extensions Directory; Shodan/Censys queries for com_baforms return tens of thousands of hosts |
| Disclosed | 2026-07-09 (coordinated with vendor patch) |
| CWE | CWE-434 — Unrestricted Upload of File with Dangerous Type |
Sources.
Why this verdict
- No auth, no CSRF, no MIME check — the vendor advisory and PoC both confirm the endpoint accepts anonymous PHP uploads. There is no prerequisite compromise stage to soften severity.
- KEV as of disclosure day — CISA doesn't list on speculation. Active exploitation overrides EPSS's low 0.00836 score, which lags by weeks.
- Role multiplier — web-facing CMS component: the chain succeeds on the canonical deployment (public Joomla site with a contact/lead form). Blast radius: full site compromise → DB creds → potential lateral movement on shared hosting or reused admin creds → SEO poisoning, skimming, or foothold for further internal pivot.
- Compensating deployment hardening is rare — Joomla installs do not ship with PHP execution disabled in upload directories by default; SELinux/AppArmor is not the norm on shared LAMP hosting.
- Patch and mitigation are both cheap — 2.4.1 is a drop-in extension update. There is no operational reason to delay.
Why not higher?
9.8 is already at the CRITICAL ceiling for CVSS 3.1 in this configuration; the only headroom would be a Scope-changed variant, which is not applicable to a userland PHP webshell. There is no separate 'CRITICAL+' bucket in noisgate.
Why not lower?
Downgrading would require either evidence that the exposed population is trivial (it is not — Balbooa Forms is a mainstream Joomla forms extension) or evidence that exploitation requires a rare prerequisite (it does not — anonymous single-request). Neither condition holds.
Crowdsourced verification payload.
Run on each Joomla host as any user that can read the webroot. Invoke as ./check-baforms.sh /var/www/html (path to Joomla document root). Also runs a live HTTP probe against the site — provide site URL as SITE_URL=https://example.com ./check-baforms.sh /var/www/html. Prints VULNERABLE, PATCHED, or UNKNOWN and exits with 1/0/2 respectively.
#!/usr/bin/env bash
# noisgate: CVE-2026-56291 — Balbooa Forms unauth file upload
# Usage: ./check-baforms.sh <joomla-webroot>
# Optional: SITE_URL=https://site.tld ./check-baforms.sh <webroot>
set -u
ROOT="${1:-}"
if [[ -z "$ROOT" || ! -d "$ROOT" ]]; then
echo "UNKNOWN: pass Joomla webroot as arg 1" >&2
exit 2
fi
MANIFEST="$ROOT/administrator/components/com_baforms/baforms.xml"
ALT="$(find "$ROOT" -type f -name 'baforms.xml' 2>/dev/null | head -n1 || true)"
[[ -f "$MANIFEST" ]] || MANIFEST="$ALT"
if [[ -z "$MANIFEST" || ! -f "$MANIFEST" ]]; then
echo "UNKNOWN: Balbooa Forms not found under $ROOT"
exit 2
fi
VER=$(grep -oE '<version>[^<]+' "$MANIFEST" | head -n1 | sed 's/<version>//')
if [[ -z "$VER" ]]; then
echo "UNKNOWN: could not parse version from $MANIFEST"
exit 2
fi
FIXED="2.4.1"
# semver compare: is $VER < $FIXED?
lt() { [ "$1" = "$2" ] && return 1; [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }
if lt "$VER" "$FIXED"; then
STATUS="VULNERABLE"
RC=1
else
STATUS="PATCHED"
RC=0
fi
echo "Balbooa Forms version: $VER (fixed: $FIXED)"
echo "Manifest: $MANIFEST"
echo "Status: $STATUS"
# Optional live probe for stray webshells in common Balbooa upload dirs
for d in "$ROOT/images/baforms" "$ROOT/media/com_baforms/uploads" "$ROOT/images/com_baforms"; do
if [[ -d "$d" ]]; then
HITS=$(find "$d" -type f \( -iname '*.php*' -o -iname '*.phtml' -o -iname '*.phar' -o -iname '*.pht' -o -iname '*.inc' \) 2>/dev/null)
if [[ -n "$HITS" ]]; then
echo "WARNING: executable files present in upload dir $d:"
echo "$HITS"
RC=1
fi
fi
done
exit $RC