XSS Injection Campaign Exploits WordPress AMP Plugin
News broke last week disclosing a number of vulnerabilities in the AMP For WP plugin, installed on over 100,000 WordPress sites. WordPress contributor Sybre Waaijer identified the security issue and confidentially disclosed it to the WordPress plugins team. To exploit the flaw, an attacker needs to have a minimum of subscriber-level access on a vulnerable site.
The Wordfence team has identified an XSS (cross-site scripting) campaign that is actively exploiting this security flaw. In the post below, we describe this sophisticated attack campaign in detail. It is critical that site owners using AMP For WP update to the most recent version of this plugin as soon as possible. At the time of writing, the newest version of AMP For WP is version 0.9.97.20.
The Wordfence firewall has a new rule that defends sites against this exploit. This rule has been released to Premium Wordfence customers and will be available for free customers 30 days after release. In addition, the Wordfence firewall has a generic XSS rule which has been available to free and Premium customers for over 2.5 years, which catches most exploits targeting this vulnerability.
In addition, the Wordfence team released malware signatures into production that detect the malware payloads that are being deposited on servers targeted in this attack. These are currently in production for Wordfence Premium customers.
The rest of this post documents the attack campaign that our team has identified, which is exploiting the recent vulnerability discovered in the AMP For WP plugin. The rest of this post is written for security operations teams, developers, vendors and other network defenders. It describes the attack chain and includes IOCs (indicators of compromise) that can be used to improve security products and harden firewalls and intrusion detection systems against this threat.
A number of individual security flaws were patched in the recent release of the plugin. The crux of the situation is an overall lack of capabilities checks associated with the plugin’s AJAX hooks. A user needs to have an active login session to make the necessary calls to the plugin and it does not matter what permissions that user has been granted on the impacted site.
The code above from
install/index.php iterates over POST data without any capabilities checks.
The active exploits we have identified are leveraging this set of flaws to modify the plugin’s own options stored in the WordPress database.
Attacking The Admin
The most prevalent attacks against this vector attempt to inject the following XSS payload into the victim’s site content with the goal of affecting a logged-in administrator:
sslapis.com. This script,
stat.js, contains a number of notable features.
One area of concern is the
processNewUser() function, which attempts to hijack the affected administrator’s browser session in order to register a new administrator account named supportuuser:
processNewUser() function attempts to use a hidden iframe to execute the user registration process.
After creating a hidden iframe element on the page being viewed by the affected administrator, the script simulates the process of filling out the New User form. As part of this process it selects the Administrator role and sends a
click() event to the submit button to create a new user with admin access.
In addition to the creation of a rogue administrator account, the script also attempts to inject backdoor code into an affected site’s plugins. This is accomplished similarly to the administrator creation above, with a hidden iframe appended to the page’s content and used to simulate an admin’s interactions with the Plugin areas of the dashboard.
The function defined above is used to inject malicious PHP into a site’s plugins.
The PHP backdoors injected into a site’s plugins are as follows:
Both of these backdoors are effective ways to allow an attacker to execute arbitrary PHP code on infected sites, even if the rogue administrator account mentioned above is successfully removed.
The command and control (C2) server for this campaign is currently located at
While attacks targeting this vulnerability are coming from an array of source addresses, a flaw in the execution of these attacks make them easily trackable. It is common for attack platforms to spoof the User-Agent string of a well known browser in an effort to make their traffic blend in with normal browsing activity. In this case however, the User-Agent string contained in these malicious requests is broken:
Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv. Note that in similar User-Agent strings, a version number follows “rv”. This suggests that the attacker intended to rotate or otherwise change the version number in the string programmatically. This broken User-Agent was found in all attacks associated with this adversary.
Indicators Of Compromise (IOCs)
Most Prevalent Attacking IPs
Outbound Domains Accessed
- Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv
- The presence of unauthorized accounts in your site’s users table, including but not limited to the following example:
We considered a content security policy (CSP) as possible mitigation of this attack, but the attacker could modify the XSS payload to be an inline version of the script loaded from the sslapis C2 server.
As always, the best defense against these attacks is to keep your site’s software up to date. AMP For WP’s security fix was available for nearly two weeks before these attacks began, hopefully placing a hard limit on the exploitable attack surface of this vulnerability.
For sites unable to update, or those which have not updated for any other reason, a rule has been added to the Wordfence firewall preventing these attacks. This rule is already in place on all Premium Wordfence users, and will be released to Free Wordfence users after 30 days. However, most attempts at exploiting this vulnerability happen to trigger a preexisting firewall rule built to block generic XSS payloads, and this rule has been protecting free Wordfence users for over 2.5 years. Our team has also released malware signatures into production to detect the malware being deposited on servers targeted in this attack.
Written by Mikey Veenstra with research assistance from Stephen Rees-Carter, James Yokobosky and Matt Barry.
Thanks for the heads up. I appreciate it.
Is the link to a Wikipedia article about Kyle Reese in the second paragraph deliberate?
LOL!! Thanks Aidan. No that was not deliberate. While editing this I made a joke with my colleague Stephen Rees-Carter about the way I continually misspell his name. I said I'd figured out why. And I posted a link to Kyle Reese (different spelling) in our internal Slack. Then went back to editing and added a link by pasting. Guess I forgot I had switched the link to WP AMP Plugin with a link to Kyle Reese's wikipedia page in my buffer.
Too funny! Fixed it. Much appreciated!
Original link I had used in case anyone's interested. Yes, I'm a Terminator fan.
Would I be correct that AMP by Automattic is not affected?
That is correct. It's a different plugin. You are referring to this plugin.
So if someone is using this plugin and they are using the current version... no cause for concern?
Thanks always. what's annoying though is how wordpress do not notify admins. so only people who have your plugin know.
I read this piece. I thought that it contained some very good expressive content about ways to make sure that your personal or business website is safe. It suggests some things that can happen when your website and social media accounts etc. can be compromised.
A nod to Tracy's comment... It is confusing that plugins have the same names! I mean, development is very complex, so I would think developers could come up with more unique names!
But I did notice that in the Wordpress plugin repository, the plugin this article refers to is called "AMP for WP – Accelerated Mobile Pages" https://wordpress.org/plugins/accelerated-mobile-pages/, not
"AMP for WordPress" https://wordpress.org/plugins/amp/.
I thought update_option() sanitized the input?? How i the call to the script saved?
"How IS the call"
Hi Ashley, update_option() does perform some sanitization on options used by WordPress core (see https://codex.wordpress.org/Function_Reference/sanitize_option), but because this is a custom option used by the plugin, there's no sanitization done to it that would've prevented this.
Just to expand on this, after a chat with Matt:
If you have a custom option that you need sanitized, you need to hook into a filter. You do it like this:
apply_filters( 'sanitize_option_$option', $value, $option )
You would of course need to write a callback for that filter that would actually perform the sanitization. Then when you call update_option() it will be sanitized.
Matt also commented that: "There's more than one way to do it. Sanitization can be done prior to passing user input to `update_option`, and the data itself should be encoded on output. HTML can be run through `wp_kses` to strip out XSS."
I'm expanding on this because I think this could be confusing for developers, who may make the assumption that everything, even custom options, will be auto-sanitized when passed to update_option().
Sheesh... even though I don't use this particular AMP plugin, so not affected by this vulnerability, it's another reminder of just how much you guys do to keep even us "free" subscribers protected. Seems like every week or so I get another email from your plugin on my site, letting me know I have something needing to be updated. Because of that reminder, I rarely have out-of-date plugins for more than 24 hours. You guys are absolutely the best!! Thank you again.
Thanks Charles!! We really appreciate the feedback!
WOW! Everywhere I read devs this update_option() is sanitized automatically. I have no idea it would actually print the call to the JS. Good to know.
Great write up of the exploit, appreciate the details.
Thank you very much, Mikey, for writing such a detailed post and explaining how hackers can exploit the code.
After this post, we are receiving a lot of questions from the users and they are not sure if the vulnerability is being fixed or not. They are getting confused on what to do with the plugin.
So just to clarify the issue reported has been fixed in 0.9.97.20 and here is the link to the post that explains everything
Thank you once again, Mikey, for writing the detailed article.