FCSC Prequals 2020 - Bestiary

Web challenge of the annual CTF organized by the ANSSI, where PHP sessions can be used to perform a LFI.

This CTF is run yearly by ANSSI, in order to get 30 people qualified for another (shorter) CTF. The winners will play the ECSC CTF in Viennes as part of the official French team.

I didn’t manage to be qualified, but I played this CTF because the challenges are always very fun and their goal is to learn you something.

Here is my writeup for a web challenge quite easy to solve, but which taught me a really nice technique to perform PHP code injection from a local file inclusion vulnerability.


We just ask you to find the flag.


Website discovery

When we land on the index page, we see a very minimalist website.


Let’s play its game and select a monster.


Nice! A Dungeons and Dragons theme. But that doesn’t make us omit this GET parameter up there: ?monster=.

As I always do first, I replace the legit value mind_flayer by a single quote %27.


A PHP error betrays the presence of an include() function called on the monster parameter value. So this is probably a LFI here.

Before doing more tests, let’s open up Burp Suite which is more convenient for injecting dynamic parameters thanks to its Repeater tab. Then, we try the classic /etc/passwd inclusion.


The fact that it works instantly tells us that there isn’t anything appended at the beginning or the end of the monster value, like “file_” or “.php” for example. Therefore we can maybe inject a PHP filter, very useful for obtaining the server’s source code. This payload php://filter/convert.base64-encode/resource=index.php is a classic one which will output the base64-encoded index.php page.


Once decoded:

	$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)
		echo "Select a monster to read his description.";

The goal is to get the the flag.php file’s content. We could use the exact same technique as for the index.php file, but here we are facing a constraint: the word flag cannot appear in URL. Additionally, I tried the wrappers data, expect and input but without success.

LFI exploitation via PHP sessions

By scrolling down PayloadAllTheThings’s file inclusion page, I found something that could be applicable in our case, as it involves PHP sessions to execute PHP code. Maybe not a coincidence, there is a suspicious line session_save_path("./sessions/"); at the very beginning of the index.php page.

Indeed, in PHP, $_SESSION variables rely on files to store their content. By default those files are located in the /var/lib/php/sessions or /var/lib/php5/sessions directories, with permissions like drwx-wx-wt root root. So ordinarily, with a PHPSESSID=3a717e2a84b5a538f9d78251b49506c cookie, there would be a /var/lib/php/sessions/sess_3a717e2a84b5a538f9d78251b49506c file, but unreadable using LFI.

Here, the session_save_path line indicates that the sessions files are located alongside the source code. Maybe we can even read them. Besides, according to the line $_SESSION['monster'] = $monster;, we fully control what is contained in our session file: it’s what we put previously in the ?monster= value.

Let’s test this by putting a dumb value like ?monster=bouzygouloum id the URL, send the request, and then try to include our session file, and see its content.


So we effectively can include our session file, and control what is inside, which is passed into an include() function. Now, the PHP documentation tells us “The include statement includes and evaluates the specified file” => PHP code will be executed if it goes through it.

By knowing that, this PHP code should do the trick.

<?php echo base64_encode(file_get_contents("flag.php")) ?>

So we first send a request containing our URL-encoded payload:


And then include it: ?monster=sessions/sess_3a717e2a84b5a538f9d78251b49506c


And this is a flag



This challenge wasn’t too hard and had a lot of solves, but I learnt that trick which is kind of cool.

Finally, here, system function was disabled. But if you have access to it or passthru, etc., you can even achieve RCE from this LFI!