An attacker can take over any WordPress site that has comments enabled by tricking an administrator of a target blog to visit a website set up by the attacker. As soon as the victim administrator visits the malicious website, a cross-site request forgery (CSRF) exploit is run against the target WordPress blog in the background, without the victim noticing. The CSRF exploit abuses multiple logic flaws and sanitization errors that when combined lead to Remote Code Execution and a full site takeover.
The vulnerabilities exist in WordPress versions prior to 5.1.1 and is exploitable with default settings.
WordPress is used by over 33% of all websites on the internet, according to its own download page. Considering that comments are a core feature of blogs and are enabled by default, the vulnerability affected millions of sites.
CSRF in comment form leads to HTML injection
WordPress performs no CSRF validation when a user posts a new comment. This is because some WordPress features such as trackbacks and pingbacks would break if there was any validation. This means an attacker can create comments in the name of administrative users of a WordPress blog via CSRF attacks.
This can become a security issue since administrators of a WordPress blog are allowed to use arbitrary HTML tags in comments, even
<script> tags. In theory, an attacker could simply abuse the CSRF vulnerability
WordPress tries to solve this problem by generating an extra nonce for administrators in the comment form. When the administrator submits a comment and supplies a valid nonce, the comment is created without any sanitization. If the nonce is invalid, the comment is still created but is sanitized.
The following code snippet shows how this is handled in the WordPress core:
/wp-includes/comment.php (Simplified code)
The fact that no CSRF protection is implemented for the comment form has been known since 20091.
However, we discovered a logic flaw in the sanitization process for administrators.
As you can see in the above code snippet, the comment is always sanitized with
wp_filter_kses(), unless the user creating the comment is an administrator
unfiltered_html capability. If that is the case and no valid nonce is supplied, the comment is sanitized with
wp_filter_post_kses() instead (line 3242 of the above code snippet).
The difference between
wp_filter_kses() lies in their strictness. Both functions take in the unsanitized comment and leave only a
selected list of HTML tags and attributes in the string. Usually, comments are sanitized with
wp_filter_kses() which only allows very basic HTML tags and attributes, such as the
<a> tag in combination with the
This allows an attacker to create comments that can contain much more HTML tags and attributes than comments should usually be allowed to contain. However, although
wp_filter_post_kses() is much more permissive, it still removes
any HTML tags and attributes that could lead to Cross-Site-Scripting vulnerabilities.
Escalating the additional HTML injection to a Stored XSS
The fact that we can inject additional HTML tags and attributes still leads to a stored XSS vulnerability in the WordPress core. This is because some attributes that usually can’t be set in comments are parsed and manipulated in a faulty way that leads to an arbitrary attribute injection.
After WordPress is done sanitizing the comment it will modify
<a> tags within the comment string to optimize them for SEO purposes.
This is done by parsing the attribute string (e.g.
href="#" title="some link" rel="nofollow") of the
<a>tags into an associative array (line 3004 of the following snippet), where the key is the name of an attribute and the
value the attribute value.
WordPress then checks if the
rel attribute is set. This attribute can only be set if the comment is filtered via
wp_filter_post_kses(). If it is, it processes the
rel attribute and then puts the
<a> tag back together.
The flaw occurs in the lines 3017 and 3018 of the above snippet, where the attribute values are concatenated back together without being escaped.
An attacker can create a comment containing a crafted
<a> tag and set for example the
title attribute of the anchor to
title='XSS " onmouseover=alert(1) id="'. This attribute is valid HTML and would pass the sanitization step.
However, this only works because the crafted
title tag uses single quotes.
When the attributes are put back together, the value of the
title attribute is wrapped around in double quotes (line 3018).
This means an attacker can inject additional HTML attributes by injecting an additional double quote that closes the
<a title='XSS " onmouseover=evilCode() id=" '> would turn into
<a title="XSS " onmouseover=evilCode() id=" "> after processing.
Since the comment has already been sanitized at this point, the injected
onmouseover event handler is stored in the database and does not get removed. This allows attackers to inject a stored XSS payload
into the target website by chaining this sanitization flaw with the CSRF vulnerability.
Directly executing the XSS via an iframe
of the targeted WordPress blog. The frontend is not protected by the
X-Frame-Options header by WordPress itself. This means the comment can be displayed in a hidden
<iframe> on the website of the attacker.
Since the injected attribute is an
onmouseover event handler, the attacker can make
the iframe follow the mouse of the victim to instantly trigger the XSS payload.
allows administrators of a blog to directly edit the
.php files of themes and plugins from within the admin dashboard. By simply inserting a PHP backdoor, the attacker
can gain arbitrary PHP code execution on the remote server.
By default, WordPress automatically installs security updates and you should already run the latest version 5.1.1. In case you or your hoster disabled the auto-update functionality for some reason, you can also disable comments until the security patch is installed. Most importantly, make sure to logout of your administrator session before visiting other websites.
|2018/10/24||Reported that it is possible to inject more HTML tags than should be allowed via CSRF to WordPress.|
|2018/10/25||WordPress triages the report on Hackerone.|
|2019/02/05||WordPress proposes a patch, we provide feedback.|
|2019/03/01||Informed WordPress that we managed to escalate the additional HTML injection to a Stored XSS vulnerability.|
|2019/03/01||WordPress informs us that a member of the WordPress security team already found the issue and a patch is ready.|
|2019/03/13||WordPress 5.1.1 Security and Maintenance Release|
This blog detailed an exploit chain that starts with a CSRF vulnerability. The chain allows for any WordPress site with default settings to be taken over by an attacker, simply by luring an administrator of that website onto a malicious website. The victim administrator does not notice anything on the website of the attacker and does not have to engange in any other form of interaction, other than visiting the website set up by the attacker.
We would like to thank the volunteers of the WordPress security team which have been very friendly and acted professionally when working with us on this issue.