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

Table of contents

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 obviously 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.

chall statement We just ask you to find the flag

Foothold

When we land on the index page, we see a very minimalist website. index page Let’s play its game and select a monster. selecting 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. single quote injected 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. include /etc/passwd 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. php filter injection Once decoded:

<?php
	session_save_path("./sessions/");
	session_start();
	include_once('flag.php');
?>
[...]
<?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.";
?>

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. bouzygouloum

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:

?monster=%3C%3Fphp%20echo%20base64%5Fencode%28file%5Fget%5Fcontents%28%22flag%2Ephp%22%29%29%20%3F%3E

And then include it: ?monster=sessions/sess_3a717e2a84b5a538f9d78251b49506c

flag_base64

And this is a flag

flag

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

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