SixLabors / ImageSharp

:camera: A modern, cross-platform, 2D Graphics library for .NET
https://sixlabors.com/products/imagesharp/
Other
7.25k stars 842 forks source link

Payloads that caused a DOS attack (GIF / JPEG) #2758

Open ErazerBrecht opened 1 week ago

ErazerBrecht commented 1 week ago

Prerequisites

ImageSharp version

3.1.4

Other ImageSharp packages and versions

-

Environment (Operating system, version and so on)

Windows 11 Alpine 3.19

.NET Framework version

.NET 8

Description

Hello,

GIF

When using a certain GIF we are seeing OOM's. When debugging this locally I allocate +- 20Gb when handling that specific payload 5 times in parallel.

Payload: https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Gif/funnyanim.gif Source code: https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Gif/Program.cs

image

When changing the source code to use the 'earth.gif' it works as intended and there is no spike in memory allocation.

JPEG

We have some payloads that cause resource amplification. There are very small <1460 bytes but when using Imagesharp they result into a very big result (biggest one goes to >40Mb). This only happens when specifying a quality level in the JPG encoder. When not using one the payloads throw on a DividebyZeroException.

Payloads https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Jpeg/83.jpg https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Jpeg/92.jpg https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Jpeg/93.jpg Source code: https://github.com/ErazerBrecht/ImageSharp.Payloads/blob/master/ImageSharp.Payloads.Jpeg/Program.cs

image

Payloads were created by: https://app.intigriti.com/profile/whatevicanhaz

Sincerely, Brecht

Steps to Reproduce

See: https://github.com/ErazerBrecht/ImageSharp.Payloads

Images

No response

JimBobSquarePants commented 1 week ago

Hello @ErazerBrecht, thank you for this.

Upon reviewing the issues, I was unable to open the folder containing a copy of your repository on my Windows 11 machine, as File Explorer would instantly crash. It's unclear how you were able to view the output there.

However, I was able to conduct an investigation on an older MacBook.

Regarding the GIF issue, I have opened a pull request which has been successfully tested against your image. It's important to note that your test code has a memory leak due to not disposing of images after allocation.

For the JPEG issue, the DividebyZeroException you mentioned is unclear to me. The encoder in your sample code seems to replicate the default encoder options, and since the encoder only encodes the decoded pixel buffer, the input appears to be irrelevant. Further clarification is needed on this matter.

The increase in JPEG encoded output size is to be expected. The images provided have been modified to specify large dimensions, such as 59787x511 pixels. These dimensions are taken from the jpeg header, and we attempt to decode to a buffer that matches these dimensions. Consequently, when encoding, we are working with a pixel buffer that reflects the full manipulated dimensions, resulting in the encoding of a significantly larger amount of data.

ErazerBrecht commented 1 week ago

Hey @JimBobSquarePants Already thanks for taking a look at this.

Yeah I can just use the file explorer on Windows 11. Here are the payloads in a ZIP so hopefully that works.

funnyanim.gif.zip funnyjpegs.zip

What I mean is that if I swap this part of the code:

using var output = new MemoryStream();
var encoder = new JpegEncoder { Quality = 75 };
image.Save(output, encoder);
Console.WriteLine($"Input: {input.Length} vs Output: {output.Length}");

To this:

using var output = new MemoryStream();
var encoder = new JpegEncoder();
image.Save(output, encoder);
Console.WriteLine($"Input: {input.Length} vs Output: {output.Length}");

Than it throws the exception:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
   at SixLabors.ImageSharp.Formats.Jpeg.JpegEncoderCore.WriteDefineQuantizationTables(JpegQuantizationTableConfig[] configs, Nullable`1 optionsQuality, JpegMetadata metadata, Span`1 tmpBuffer)
   at SixLabors.ImageSharp.Formats.Jpeg.JpegEncoderCore.Encode[TPixel](Image`1 image, Stream stream, CancellationToken cancellationToken)
   at SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder.Encode[TPixel](Image`1 image, Stream stream, CancellationToken cancellationToken)
   at SixLabors.ImageSharp.Formats.ImageEncoder.EncodeWithSeekableStream[TPixel](Image`1 image, Stream stream, CancellationToken cancellationToken)
   at SixLabors.ImageSharp.Formats.ImageEncoder.Encode[TPixel](Image`1 image, Stream stream)
   at SixLabors.ImageSharp.Image.EncodeVisitor.Visit[TPixel](Image`1 image)
   at SixLabors.ImageSharp.Image`1.Accept(IImageVisitor visitor)
   at SixLabors.ImageSharp.Advanced.AdvancedImageExtensions.AcceptVisitor(Image source, IImageVisitor visitor)
   at SixLabors.ImageSharp.Image.Save(Stream stream, IImageEncoder encoder)
   at Program.<<Main>$>g__SaveLoadImage|0_0(String fileName) in I:\POC\ImageSharp.Payloads\ImageSharp.Payloads.Jpeg\Program.cs:line 32
   at Program.<Main>$(String[] args) in I:\POC\ImageSharp.Payloads\ImageSharp.Payloads.Jpeg\Program.cs:line 14

For me the exception is better since it prevents a malicious actor of uploading the file. We fixed it in our own code by checking if the output ImageSharp is 10x bigger than the input, if this is the case we don't allow the image (only if the result is bigger than x amount of Mb).

JimBobSquarePants commented 1 week ago

Thanks for the update @ErazerBrecht I think understand what must be happening with the jpeg encoder now.

You can see the progress of the PR here https://github.com/SixLabors/ImageSharp/pull/2759

I've solved the GIF problem by refactoring the LZW decoder to work line by line. This limits allocations for the index buffer to 64K preventing the massive allocation you were seeing.

I'll have a look at the JPEG issue in the morning. I believe my inability to view the images in your repository has something to do with my usage of the new dev drive. I can see them in my standard drive.

P.S you can use the Image.Identify(...) methods to determine the decoded dimensions of an image and also configure memory allocation limitations which will throw exceptions when exceeded.

JimBobSquarePants commented 1 week ago

@ErazerBrecht I've added a fix for the DividebyZeroException to the PR. There are no fixes for the output file size since that is simply how large a true encoded JPEG for those dimensions would be. This is how any library that can open the images would behave.