# WordPress 5.1 CSRF to Remote Code Execution

CSRF on WordPress comment form plus unescaped attribute reassembly in wp_rel_nofollow_callback yields stored XSS, escalating to PHP RCE via theme editor.

- Payout: unconfirmed
- Severity: critical
- Program: WordPress
- Credited: Simon Scannell
- Disclosed: 2019-03-13
- CVE: CVE-2019-9787
- Write-up quality: 9/10
- Original: https://www.sonarsource.com/blog/wordpress-csrf-to-rce/
- Library page: https://bugbountylibrary.com/r/blog%3Asonarsource-com-blog-wordpress-csrf-to-rce
- Tags: domain:web impact:ato impact:rce kind:bounty platform:blog sev:critical tech:php technique:chained technique:source-review vuln:csrf vuln:rce vuln:xss

**Target:** WordPress core comment form and wp_rel_nofollow_callback
**Root cause:** Comment form lacks CSRF validation, and admins without a valid nonce get wp_filter_post_kses (permissive) instead of wp_filter_kses; wp_rel_nofollow_callback then reassembles <a> attributes without escaping, allowing attribute injection.
**Steps:**
1. Craft CSRF page that auto-submits a comment as the admin to `/wp-comments-post.php` with `comment=<a title='XSS " onmouseover=evilCode() id=" '>x</a>` and no valid `_wp_unfiltered_html_comment` nonce.
2. Comment passes wp_filter_post_kses (single-quoted title is valid), then wp_rel_nofollow_callback parses atts and re-emits `title="XSS " onmouseover=evilCode() id=" "`, injecting the event handler.
3. Lure admin to attacker page that loads the target comment in a hidden iframe (no X-Frame-Options) and tracks mouse to fire onmouseover.
4. XSS runs as admin; use admin theme/plugin editor to write a PHP backdoor for RCE.
**How found:** Noticed comment form has no CSRF protection (known since 2009) but admins fall back to the more permissive wp_filter_post_kses when nonce is missing; then code-read wp_rel_nofollow_callback and spotted attributes re-concatenated without escaping, with single-quoted input re-wrapped in double quotes enabling attribute breakout.
**Exploitation:** Attacker hosts a page that silently POSTs a crafted comment to the victim blog (CSRF), then embeds the comment permalink in a hidden iframe whose position follows the victim's cursor to trigger onmouseover. The resulting JS executes in the admin's session, letting the attacker edit theme .php files and drop a backdoor.
**Escalation:** CSRF -> permissive sanitizer -> attribute injection -> stored XSS -> admin session -> theme editor PHP write -> RCE.
**Insight:** When a sanitizer runs before a later re-serialization step, look for quote-style mismatches: single-quoted attributes that get re-emitted in double quotes let you break out after sanitization. Also check nonce-failure fallback paths that downgrade to a weaker-but-still-permissive filter.
