| **Product** | [SiteSkite]( WordPress plugin |
| **CWE** | [CWE-94]( — Improper Control of Generation of Code |
| **CVSS** | **10.0 (Critical)** — [Patchstack advisory]( |
| **PoCbit catalog** | [ |
| **PoCbit page** | [ |
| **Verify marker** | `POCBIT-96349-OK` |
Python PoC in the same style as [CVE-2026-102425](../CVE-2026-102425/poc.py): interactive , **mass exploit** (default), `--check`, `-u`, `--lab`, thread pool, `hits.txt` / `exploited.txt`.
Patchstack and the CVE Program describe **CVE-2026-96349** as an **unauthenticated remote code execution** flaw in the SiteSkite plugin. Public text is intentionally short; vendor fix **2.2.0** (September 2026) touches authentication and auto-login heavily. Static comparison of **2.1.8** vs **2.2.0** in this repo (`extract-2.1.8/`, `extract-2.2.0/`) shows the practical attack surface this PoC implements.
SiteSkite is a large WebOps plugin: backups, portal API, and **on-site AI MCP** (“abilities”) including advanced tools that call **`eval()`** on attacker-supplied PHP (`siteskite_execute_php`) and arbitrary file write (`siteskite_write_file`). Those abilities are reachable over REST and over **`admin-ajax.php`** when the site is linked with a **SiteSkite API key**.
The critical mistake in **≤ 2.1.8** is that the **same long-lived API key** doubles as an **unauthenticated WordPress admin login bearer**:
- **`AutoLoginController`** accepts `?token=` on **`/siteskite-autologin`** and, via **`handle_token_login()`**, on **any front-end URL** that includes `?token=…`.
- Comparison uses plain equality against `get_option('siteskite_api_key')` (no portal signature, no one-time nonce).
- Successful validation calls `wp_set_auth_cookie()` for the **first administrator**, with optional **2FA bypass** for Solid Security / similar plugins.
**2.2.0** removes this path: raw API key bearers are rejected (`hash_equals` checks, “Login Unavailable”), and login requires **portal-issued signed links** (`AutologinPortalSignature`). The mu-plugin safety core no longer honors legacy key logins.
From an attacker’s perspective the chain is:
1. Obtain the victim site’s **SiteSkite API key** (portal “Login as” links, logs, referrer leaks, compromised backups, insider, or another bug). Patchstack classifies the issue as **unauthenticated RCE** because **no WordPress username/password** is required—only the plugin’s bearer secret, which is often exposed off-site.
2. Optionally prove admin session via autologin (`/?token=KEY` or `/siteskite-autologin?token=KEY`).
3. Call **`POST /wp-json/siteskite/v1/abilities/execute`** (or **`admin-ajax.php?action=siteskite_api`**) with header **`X-SiteSkite-Key: KEY`** and run **`siteskite_execute_php`** or drop **`up.php`** via **`siteskite_write_file`** → **`POCBIT-96349-OK`**.
Advanced MCP tools default **on** in 2.1.8 (`McpSettings::DEFAULT_ADVANCED = '1'`), so many linked sites expose `eval` once the key is known.
| Component | 2.1.8 behavior | 2.2.0 change |
| `includes/Core/AutoLoginController.php` | `?token=` equals stored API key → admin cookie | Raw key rejected; portal HMAC login only |
| `includes/Mcp/Abilities/AdvancedAbilities.php` | `siteskite_execute_php` → `eval($code)` | Still present; gated by auth + settings |
| `includes/Mcp/AbilitiesRest.php` | `X-SiteSkite-Key` → execute abilities | Same surface; autologin no longer leaks session without key |
| `includes/API/FallbackAPI.php` | `wp_ajax_nopriv_siteskite_api` proxies abilities when key valid | Still requires key / guest key |
Other unauthenticated REST routes (`UploadRest`, OAuth registration) exist but require tokens or PKCE; they are **not** the primary chain modeled here.
| `poc.py` | CLI / interactive / **mass exploit** |
| `_engine.py` | HTTP logic, PoCbit metadata, lab server |
| `up.php` | Optional web shell probe (`?pocbit=1` → marker) |
| `targets.example.txt` | URL list; optional `URLapi_key` |
| `extract-2.1.8/` / `extract-2.2.0/` | Vendor source for diff (not required to run PoC) |
### Interactive (default mode = **exploit**)
Prompts: mode, optional global API key, target file, threads (default **16**), timeout.
python poc.py urls.txt --key YOUR_SITESKITE_API_KEY
Per-target keys (tab, space, or pipe after URL):
abc123…siteskiteKey…
Environment fallback: `SITESKITE_API_KEY` or `SITESKITE_KEY`.
Fingerprint: `readme.txt` **Stable tag**, legacy **`/siteskite-autologin`** missing-token response, version ≤ 2.1.8.
python poc.py -u --check
python poc.py -u --key SITE_SKITE_KEY
Spins `127.0.0.1:8796` with fake readme **2.1.8**, autologin, and abilities endpoint; must return `"exploited": true`.
E -->|fail| H[write_file / ajax fallback]
1. **`siteskite_execute_php`** with `echo POCBIT-96349-OK;`
2. Same via **admin-ajax** fallback (`route=/abilities/execute`)
3. **`siteskite_write_file`** → `wp-content/uploads/pocbit_96349.php`
JSON result rows include `pocbit`, `pocbit_catalog`, `pocbit_page`, CVE URLs, and `verify_marker` for logging pipelines.
header="siteskite" && body="wp-content/plugins"
Check mode does **not** brute-force API keys. Exploit mode requires a key unless you already have `url\tkey` pairs from your own portal or leak intelligence.
1. Upgrade SiteSkite to **≥ 2.2.0** immediately.
2. **Rotate** the SiteSkite API key after upgrade (old key was a login bearer).
3. Disable unused MCP / advanced tools if you do not need AI editing.
4. Block external access to `/siteskite-autologin` and audit logs for `?token=` / `X-SiteSkite-Key` abuse.
5. Patchstack WAF users: enable the vendor mitigation rule until patched.
Use only on systems you own or have **written authorization** to test. Unauthorized access is illegal.
## GitHub repository Description (copy/paste)
CVE-2026-96349 — WordPress SiteSkite plugin ≤2.1.8 unauthenticated RCE (CVSS 10.0, CWE-94). PoCbit mass-exploit PoC: legacy API-key autologin (?token=) + REST MCP siteskite_execute_php eval chain; fixed in 2.2.0. Check/exploit/mass bulk, hits.txt & exploited.txt, --lab self-test. Marker: POCBIT-96349-OK. Catalog:
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
