Best way to handle animated GIF with PHP

Viewed 211

Firstly, sorry for my bad grammer. English is not my main language...

I'm developing a fully AJAXed wordpress theme that contains front end thread submission form in it and I wanted add animated GIF support.

And I wrote a code PHP code that uses imagick library:

 // there is also another function that
 checks image's exif, size, width, height, finfo and retuns true if all good.


  // first imagick resize is basicly re-coding the image to kill shell/hack codes in the gif.
  $imagick =  new Imagick($image['tmp_name']);
  $imagick = $imagick->coalesceImages();

  foreach($imagick as $frame){
    $frame->scaleImage($width, 0);

  }

  $imagick = $imagick->deconstructImages();
  $imagick->writeImages($new_image_name, true);
  $imagick->destroy();

 // and this one for the thumbnail of the post.
 $imagick =  new Imagick($image['tmp_name']);

  $imagick = $imagick->coalesceImages();

  foreach($imagick as $frame){
  $frame->scaleImage(wp_get_registered_image_subsizes()["left-frame-thumbnail"]["width"], 0);
  }
    
      $imagick = $imagick->deconstructImages();
      $imagick->writeImages($new_image_name, true);
      $imagick->destroy();

Code works like charm but I noticed this is a expensive code. I mean if user uploads a big gif file it will cause a CPU spike in my opinion.

And if rather I limit GIF sizes to like 2MB it doesn't matter because other problem is frame count. As we know that when we are resizing a GIF we are splitting all frames, resizing them and re-joining them together. There are a lot of gif around 300kb but contains 50+ frames in it. So frame count is also a problem for server's CPU.

Then I said; hey! let's resize gifs on client-side with Javascript! And I wrote a code that resize gif on client-side. And this really works very well.

I wrote a code on server side to get resized 2 gif file and saved them directly to server (also checking their exif,dimensions,finfo). All fine, server CPU is fine.

But! I noticed the number one rule: "never trust to data that came from client-side."

If I'm resizing it client side it's good. Resizing kills hack/shell codes in images. But what if users sends fake 2 two file that looks like its just resized and came from the form via console or something?

There is already a CSRF token in my ajax submission but I'm sure this is not enough.

Summary: What is the best way to handle with animated GIFs? What I'm doing wrong.

1 Answers

Splitting to frames, resizing and rejoining all on the client is a nice reduction of server load for non-malicious users. However, an attacker can still send any request to your PHP, including a huge gif with a large number of large frames - they need not run your javascript at all. So you're perfectly right it's a potential denial of service that you should mitigate. User input cannot be trusted.

A couple ideas that come to mind (note that I've never really worked with animated gifs). They all revolve around input validation.

I'm not sure what exactly takes long on the server, but I guess it's not splitting the image, more like resizing each frame. As your javascript is supposed to resize gifs on the client, any gif received should only include frames of a limites size. You can split the received gif and check the first frame - if it's too big, you can reject the whole request right away, this should not normally happen. (Maybe you don't even have to split it to tell the size?)

Also your idea of splitting on the client and sending them one by one is great, that takes the load of splitting from the server, and you can (and should) still check the size of the frames on the server. Note that in this case, not only the first can be too big, an attacker can send anything.

You could also check the number of frames, and set a maximum, because without that, this is a potential denial of service either way, as an attacker can just send a gif of say 10x10 pixels, but 1 million frames (I'm not sure if there is a limit in the gif format, but you see the point).

Another kind of sidenote is that whatever library you use to process the gifs on the server, that may be vulnerable to different attacks via malicious images. Imagemagick for example had a few such vulnerabilities in the past. So to make this more secure, apart from the obvious upgrades as new versions of your library are released, you could run image processing on a different server, kind of a background job processor. Only allowing very limited access to and from that server and applying generic hardening best practices could help contain any future compromise, and it also limits the effect of a denial of service attack.

You should also apply some kind of rate limiting to this image upload endpoint, because uploading 1000 images of 100 frames is even worse than 1 image of 100000 frames. So it should be limited for a user how often (or for how many images in a given timeframe) they can do this.

Related