4

I currently use Imagick library on PHP and use the resizing functionality of Image Magick. I've just learned about decompression bombs and how ImageMagick is vulnerable to it.

I have checked how we can ping the image and verify the dimensions of the image without actually loading it into memory/disk. It's also safer to limit the memory and disk limits of ImageMagick so it wouldn't just write a huge file on disk.

I've read and I can do that with setResourceLimit(). http://php.net/manual/en/imagick.setresourcelimit.php

IMagick::setResourceLimit(IMagick::RESOURCETYPE_MEMORY , 100);
IMagick::setResourceLimit(IMagick::RESOURCETYPE_DISK , 100);

$thumb = new Imagick('image.png');
$thumb->resizeImage(320,240,Imagick::FILTER_LANCZOS,1);

However, what happens is that after setting the limit of disk and memory, if an image does hit this limit, all I get is a segmentation fault error, no exceptions are thrown. This makes it impossible for me to handle it properly.

Update:

Here are the package versions I'm using:

dpkg -l | grep magick
ii  imagemagick-common                    8:6.6.9.7-5ubuntu3.3              image manipulation programs -- infrastructure
ii  libmagickcore4                        8:6.6.9.7-5ubuntu3.3              low-level image manipulation library
ii  libmagickwand4                        8:6.6.9.7-5ubuntu3.3              image manipulation library
ii  php5-imagick                          3.1.0~rc1-1                       ImageMagick module for php5
gerky
  • 6,267
  • 11
  • 55
  • 82
  • Open question, is there perhaps some low-level code that can scan the compression blocks and/or image size prior to calling ImageMagick? – halfer Jul 21 '15 at 16:46

3 Answers3

6

Setting the 'Resource Area' limit only sets the size at which images are not held in memory, and instead are paged to disk. If you want to use that setting to actually restrict the maximum size image that can be openend, you also need to set the 'Resource Disk' limit.

The code below correctly gives a memory allocation error for the image bombs taken from here.

try {
    Imagick::setResourceLimit(Imagick::RESOURCETYPE_AREA, 2000 * 2000);
    Imagick::setResourceLimit(Imagick::RESOURCETYPE_DISK, 2000 * 2000);

    $imagick = new Imagick("./picture-100M-6000x6000.png");
    $imagick->modulateImage(100, 50, 120);
    $imagick->writeImage("./output.png");

    echo "Complete";
}
catch(\Exception $e) {
    echo "Exception: ".$e->getMessage()."\n";
}

Output is:

Exception: Memory allocation failed `./picture-100M-6000x6000.png' @ error/png.c/MagickPNGErrorHandler/1630

If you want to set the width and height resource, and have a version of ImageMagick >= 6.9.0-1 you should be able to using the values directly of WidthResource = 9, HeightResource = 10

//Set max image width of 2000
Imagick::setResourceLimit(9, 2000);
//Set max image height of 1000
Imagick::setResourceLimit(10, 1000);

These don't have to be set programmatically, you can set them through the policy.xml file installed with ImageMagick. ImageMagick reads that file and uses those settings if none are set in a program - which may be a more convenient way of setting them, as you can change them per machine.

This makes it impossible for me to handle it properly.

It makes it impossible for you to handle it in the same process. You can handle it just fine by running the image processing in a background task.

Personally I think anyway that uses Imagick in a server that is accessed directly by web-browsers is nuts. It is far safer to run it in as a background task (managed by something like http://supervisord.org/) and communicating with that background task via a queue of jobs that need to be processed.

Not only does that solve the 'bad images can bring down my website' problem, it also makes it far easier to monitor resource usage, or shift the image processing to a machine with a faster CPU than a web-front end server needs.

Source - I'm the maintainer of the Imagick extension and I recently added this to the Imagick readme:

Security

The PHP extension Imagick works by calling the ImageMagick library. Although the ImageMagick developers take good care in avoiding bugs it is inevitable that some bugs will be present in the code. ImageMagick also uses a lot of third party libraries to open, read and manipulate files. The writers of these libraries also take care when writing their code. However everyone makes mistakes and there will inevitably be some bugs present.

Because ImageMagick is used to process images it is feasibly possible for hackers to create images that contain invalid data to attempt to exploit these bugs. Because of this we recommend the following:

1) Do not run Imagick in a server that is directly accessible from outside your network. It is better to either use it as a background task using something like SupervisorD or to run it in a separate server that is not directly access on the internet.

Doing this will make it difficult for hackers to exploit a bug, even if one should exist in the libraries that ImageMagick is using.

2) Run it as a very low privileged process. As much as possible the files and system resources accessible to the PHP script that Imagick is being called from should be locked down.

3) Check the result of the image processing is a valid image file before displaying it to the user. In the extremely unlikely event that a hacker is able to pipe arbitrary files to the output of Imagick, checking that it is an image file, and not the source code of your application that is being sent, is a sensible precaution.

Danack
  • 24,939
  • 16
  • 90
  • 122
  • Thank you for updating the README file! – emcconville Jul 22 '15 at 15:11
  • I've updated the question with the package versions I use. That's weird, when I use imagemagick 8:6.7.7.10-6ubuntu3 and php5-imagick 3.1.2-1build1, it is properly throwing the exceptions. However, I'm using ubuntu 12.04 on the server, and I'm not sure how I can install the specific/updated versions of imagemagick and php5-imagick. – gerky Jul 23 '15 at 09:26
  • Anyway, sounds like a separate issue. This is the most useful answer for me. Thanks for the suggestions! – gerky Jul 23 '15 at 15:32
3

Starting with ImageMagick-6.9.0-1, "width" and "height" resource limits were added. From the commandline, use "-limit width 32000", etc. ImageMagick's PNG decoder will bail out without decompressing the image if the width or height exceeds the specified limit.

The PNG decoder will not attempt to decompress images whose width or height exceeds the limits.

The "area" resource is available in earlier versions of ImageMagick (and Imagick); however the PNG decoder does not reject images based on the "area" limit (see Danack's comment).

In versions of ImageMagick earlier than 6.9.0, the width and height limits come from libpng, and depend upon the libpng version. Current libpng versions (1.0.16 and later, 1.2.6 and later, 1.5.22 and later, and 1.6.17 and later) impose 1,000,000-column and width limits. In versions 1.2.0 through 1.2.5, 1.5.0 through 1.5.23, and 1.6.0 through 1.6.16, the limits were 2.7 billion rows and columns by default.

Look for RESOURCETYPE_AREA in Imagick (I don't see _WIDTH or _HEIGHT in the manual that you referenced, so either Imagick or its manual needs to be updated). So try

IMagick::setResourceLimit(IMagick::RESOURCETYPE_AREA , 100M);

to set a 100MegaPixel limit. Hopefully, some future version of Imagick will support RESOURCETYPE_WIDTH and RESOURCETYPE_HEIGHT, to provide a better solution to the decompression-bomb vulnerability. See Danack's answer about setting these with the current version of IMagick.

Glenn Randers-Pehrson
  • 11,940
  • 3
  • 37
  • 61
  • The area resource is the only appropriate setting. ImageMagick does not have a separate resource for width/height https://github.com/ImageMagick/ImageMagick/blob/ImageMagick-6/magick/resource_.h#L27-L38 – Danack Jul 22 '15 at 10:11
  • @Danack They are "WidthResource" and "HeightResource" in the ResourceType enumeration. – Glenn Randers-Pehrson Jul 22 '15 at 12:30
  • It appears the 'area' resource sets the limit at which images are paged to disk, rather than stored in memory. Both the area and RESOURCETYPE_DISK would need to be set, to make the image reading respect the limit. The width and height resources aren't in Imagick yet, but can be used by using the direct values (WidthResource = 9, HeightResource = 10 ), if someone has a high enough version of ImageMagick. – Danack Jul 22 '15 at 13:43
  • Yes, I've already tried setResourceLimit() with area, disk, memory. As mentioned, I'm not getting any exceptions I can handle, rather, I only get segfaults. – gerky Jul 22 '15 at 14:21
1

all I get is a segmentation fault error, no exceptions are thrown

I'm guessing your segment fault is from resources being set too low for ImageMagick (and related delegates) to operate. The value of the resource are in bytes, not megabytes.

Imagick does throw an exception if a resource is reached. Usually something like...

"cache resource exhausted"

Decompression bombs, or Zip-Bombs, are extremely difficult to identify. What your doing by ping-ing the image, and setting resource limits is the correct course of action. I would roughly outline a solution as...

// Define limits in application settings, or bootstrap (not dynamically!)
define('MY_MAGICK_MEMORY_LIMIT', 5e+8);
// Repeat for AREA, DISK, & etc.

// In application
$image = new Imagick(); // allocate IM structrues
// Set limits on instance
$image->setResourceLimit(Imagick::RESOURCETYPE_MEMORY, MY_MEMORY_LIMIT);
// Repeat for RESOURCETYPE_AREA, RESOURCETYPE_DISK, & etc.

$filename = 'input.png';
if($image->ping($filename)) {
    // Validate that this image is what your expecting
    // ...
    try {
       $image->read($filename); // <-- Bomb will explode here
       $image->resizeImage(320,240,Imagick::FILTER_LANCZOS,1);
    } catch( ImageickException $err ) {
       // Handle error
    }
}
unset($image)

If you don't trust decompression, you can leverage Imagick::getImageCompression during ping validation to inspect which compression an image requires. The compression type would be a integer that would map to the following enum...

typedef enum
{
  UndefinedCompression,
  B44ACompression,
  B44Compression,
  BZipCompression,
  DXT1Compression,
  DXT3Compression,
  DXT5Compression,
  FaxCompression,
  Group4Compression,
  JBIG1Compression,
  JBIG2Compression,
  JPEG2000Compression,
  JPEGCompression,
  LosslessJPEGCompression,
  LZMACompression,
  LZWCompression,
  NoCompression,
  PizCompression,
  Pxr24Compression,
  RLECompression,
  ZipCompression,
  ZipSCompression
} CompressionType;

MagickStudio (written in PERL) offers a good starting point for default resource limits, and how they are checked against uploaded images (search for Ping.)

emcconville
  • 23,800
  • 4
  • 50
  • 66
  • Based on the doc, the resource limit is in megabytes. I've tried different numbers (100, 100000, etc) but still get the segfault. I'm not getting "cache resource exhausted" using my exact script above, just segfaults (core dumped), which is pointless because I thought that's what setResourceLimit() is for. – gerky Jul 22 '15 at 14:19