Adam Innes · Blog

ImageTragick: Treat Every Uploaded Image as Untrusted Input

· 7 min · security, imagemagick, file uploads, web development

If your app lets people upload an avatar, a product photo or a PDF preview, there’s a decent chance ImageMagick is somewhere in the pipeline. Maybe you call convert directly, maybe your framework’s thumbnail plugin wraps the library. Last Tuesday, May 3, the ImageMagick project posted a security announcement about vulnerability reports in some of its coders, and within a day the bugs had a name, ImageTragick, and a CERT/CC vulnerability note. The short version is that a crafted file handed to ImageMagick could lead to command execution on the server doing the processing. This post sticks to what the official sources have said, then gets into what I think every developer handling uploads should take from it.

What the advisories say

The ImageMagick team’s announcement, posted to the Announce section of its forum and titled ImageMagick Security Issue, says the reports for certain coders include possible remote code execution and the ability to render files on the local system. A coder in ImageMagick terms is the module that reads or writes a particular format.

CERT/CC published VU#250519 on May 4. It says ImageMagick does not properly validate user input before processing it using a delegate, which is the name for ImageMagick’s feature for processing files with external libraries, and that this may lead to arbitrary code execution. It quotes the researchers as describing insufficient filtering of a filename passed to a delegate’s command. The part web developers should read twice is where CERT/CC calls out a common vulnerable configuration: a web server that allows image uploads that are then processed with ImageMagick. It says an unauthenticated remote attacker who can upload crafted image files may be able to execute arbitrary code in the context of the user calling ImageMagick. It also notes that exploit code is publicly available and that, according to the researchers’ website, the vulnerability is already being exploited in the wild. Citing that same website, the note credits Stewie and Nikolay Ermishkin of the Mail.Ru Security Team with finding the bugs. US-CERT followed with a short alert warning that exploitation may allow an attacker to take control of an affected system.

The command execution bug is CVE-2016-3714. NVD’s entry, published May 5, names the EPHEMERAL, HTTPS, MVG, MSL, TEXT, SHOW, WIN and PLT coders in ImageMagick before 6.9.3-10 and 7.x before 7.0.1-1. There are four related CVEs that aren’t about running commands but are still ugly on a server. NVD describes CVE-2016-3715 as letting a crafted image delete arbitrary files through the EPHEMERAL coder, CVE-2016-3716 as moving arbitrary files through the MSL coder, CVE-2016-3717 as reading arbitrary files through the LABEL coder, and CVE-2016-3718 as server side request forgery through the HTTP and FTP coders. That last one matters if your image workers sit inside a network that can reach things the internet can’t.

The policy.xml mitigation

ImageMagick has a policy file, policy.xml, that can restrict what the library is allowed to do. The announcement says the policy was developed many years ago to help prevent possible exploits, and its recommended fix is to add these lines to your policy.xml, which set the rights for each listed coder to none:

<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="HTTPS" />
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="coder" rights="none" pattern="MSL" />
<policy domain="coder" rights="none" pattern="TEXT" />
<policy domain="coder" rights="none" pattern="SHOW" />
<policy domain="coder" rights="none" pattern="WIN" />
<policy domain="coder" rights="none" pattern="PLT" />

For HTTPS, the announcement says you can also remove support by deleting it from delegates.xml. It also describes a new policy in 6.9.3-10 and 7.0.1-1 that prevents indirect reads, and says that if you need the HTTPS or MVG coders, that policy is sufficient to prevent the exploits:

<policy domain="path" rights="none" pattern="@*" />

Then check that ImageMagick actually loaded what you think it did. The announcement suggests this command, which lists the policies in effect and the path of the file each one came from:

convert -list policy

Don’t skip that step. Package managers, source builds and bundled copies can each put policy.xml somewhere different, and editing a file ImageMagick never reads protects nothing. Also keep in mind that a PHP, Ruby or Node wrapper doesn’t get you out of this. If the wrapper calls into ImageMagick, the policy is ImageMagick’s to enforce, so check the copy of the library your app really uses.

Something I noticed comparing advisories is that the list of coders isn’t fixed. Red Hat’s update, which I’ll get to below, ships a policy.xml that disables EPHEMERAL, HTTPS, HTTP, URL, FTP, MVG, MSL, TEXT and LABEL. A blocklist like this is a good emergency brake, but it only covers the coders someone has already worried about. If your app only needs to read JPEG and PNG, the more durable idea is to stop feeding ImageMagick anything that isn’t JPEG or PNG in the first place.

Check magic bytes, not file extensions

Plenty of upload code decides a file is an image because its name ends in .jpg, or because the browser said the content type was image/jpeg. Both come from the client, so neither tells you anything. With ImageMagick it’s worse than just trusting a lie, because ImageMagick mostly doesn’t care what the file is called. Its command line processing docs explain that most formats have a signature inside the image that identifies the format, and that ImageMagick only falls back to the filename extension when there isn’t one. So your code might believe it’s resizing a JPEG while ImageMagick looks at the bytes and hands the file to a completely different coder.

That’s why the first mitigation the researchers suggested, as quoted in the CERT/CC note, is to verify that image files begin with the expected magic bytes for the types you support before sending them to ImageMagick. The second is the policy file. I’d do both.

A signature check is only a few lines. Here’s a Python version that accepts JPEG, PNG and GIF and nothing else:

SIGNATURES = (
    b"\xff\xd8\xff",       # JPEG
    b"\x89PNG\r\n\x1a\n",  # PNG
    b"GIF87a",             # GIF
    b"GIF89a",             # GIF
)

def has_allowed_signature(path):
    with open(path, "rb") as f:
        head = f.read(8)
    return head.startswith(SIGNATURES)

Run it on the uploaded bytes before anything else touches the file, and reject the upload if it returns False. I’d do this check with your own code rather than by asking ImageMagick what the file is, since any command that reads the file through ImageMagick is running the same library you’re trying to protect. And be clear about what the check proves. It says the file starts like a PNG. It doesn’t say the rest of the file is harmless, which is why the other layers still matter.

Update, and read your distribution’s advisory

CERT/CC says ImageMagick 6.9.3-10 and 7.0.1-1 were released to address these issues and recommends updating to the latest version as soon as possible. The project didn’t stop there. Its ChangeLog for 7.0.1-2, released a few days later along with 6.9.4-0 on the 6.x branch, includes removing support for the internal ephemeral coder. If you build from source, take the newest release rather than the minimum.

Most of us get ImageMagick from an operating system package, though, and that’s a different track. Red Hat published RHSA-2016:0726 on May 9, rated Important, for Red Hat Enterprise Linux 6 and 7. It covers all five CVEs and includes the updated policy.xml I mentioned, with a note that you may need to adjust the file if something breaks and a reminder to take extra precautions so your applications don’t process malicious or untrusted files. The fixed packages are still versioned 6.7.2.7 and 6.7.8.9, so if you compare convert -version against 6.9.3-10 you’ll get the wrong answer. Trust your vendor’s advisory, not upstream version numbers. CERT/CC’s vendor list also marks Debian, Ubuntu, CentOS, Fedora, SUSE, Gentoo and others as affected, so if you’re on one of those, watch its security announcements and apply the policy change in the meantime.

Assume the next image bug is coming

ImageMagick supports an enormous number of formats, and each one is parsing code that an uploaded file might end up exercising. These particular bugs will get patched, but I’d set things up so the next one hurts less.

Run image processing with as little privilege as you can. CERT/CC’s wording is that code runs in the context of the user calling ImageMagick, so make that user boring. A separate worker process with its own account, no access to your app’s config files or credentials, write access only to a scratch directory, and no ability to make outbound network requests limits what command execution, file reads and request forgery can do. If you already push thumbnail jobs onto a queue, you’re most of the way to putting that worker in its own container or VM.

Limit accepted formats to the few your product needs and enforce that with the signature check, not the extension. Keep image libraries on the same patch schedule as your web framework, including the ones that come in through a gem, a PHP extension or a base image you haven’t rebuilt in months.

The takeaway

An uploaded image is untrusted input, exactly like a form field, and ImageTragick is a very public demonstration of what happens when we forget that. Update ImageMagick through your vendor, add the policy.xml restrictions today and confirm they loaded with convert -list policy, check magic bytes against a short allowlist before ImageMagick sees a file, and run the processing somewhere that can’t reach anything worth stealing.

← all posts