Zero Day WebP vulnerability (CVE-2023–4863)

vsociety
13 min readMay 25, 2024

--

by @Smartkeyss

Summary

CVE-2023–4863 (CVSS score: 8.8), also known as the heap buffer overflow in Chrome libWebP, is a client-side vulnerability. This means that the end user of the affected application is at risk. The vulnerability arises from an exploitation involving the writing of more data to a dynamically allocated memory space (heap buffer) than it can hold, using crafted HTML. This vulnerability could lead to a crash or enable the exploiter to execute arbitrary code on the end user’s system.

Description

INTRODUCTION

On September 11, 2023, Google released an emergency update to fix a zero-day heap buffer overflow vulnerability in WebP, an open-source software used by Google Chrome. This flaw has the potential to affect millions of people, as many popular software applications utilize WebP. The vulnerability occurs by overwriting data into a dynamically allocated memory called the heap buffer, hence the term “buffer overflow.”

The vulnerability in WebP enables the attacker to send malicious images using HTML. When the victim interacts with the image, it could lead to denial of service, arbitrary code execution, data corruption, bypass of security controls, or privilege escalation.

WEBP

WebP is an open-source modern image format designed to produce high-quality pictures/images on the internet with smaller file sizes without losing quality (utilizing both lossless and lossy compression for images). Developed by Google, it aims to assist web developers in creating websites that load images faster. Compared to JPEG and PNG, WebP images are smaller while maintaining higher quality. It was released on September 30, 2010. WebP employs Huffman’s coding for both lossless and lossy compression.

SUPPORT SYSTEM

  • Google, the owner, supports and promotes WebP.
  • All Chromium-based browsers support WebP.
  • 96.3% of browsers support WebP.
  • Some popular photo editing applications like Adobe and Canvas support WebP.
  • Over 700 applications support WebP.

NUMBER OF INSTANCES

Although the vulnerability has been reported to be exploited, there have been no concrete details about the corresponding malicious activity published yet. However, reports show that BLASTPASS (CVE-2023–41061), reported by Apple on September 9, 2023, just a few days before the Chrome emergency update, shares many similarities with CVE-2023–4863, making it the only publicly known exploit.

NATURE OF EXPLOIT

Initially, it was believed that the vulnerability was specific to Chrome. However, it was later discovered that the vulnerability could affect all software utilizing WebP. As mentioned earlier, numerous software applications support WebP. Additionally, since it is a client-side vulnerability (exploiting the end user), this poses a high-risk threat, as the exploit can take place with just the end user interacting with the malicious image.

Some of the Common Weakness Enumeration (CWE) vulnerabilities associated with CVE-2023–4863 include:

1. Out-of-Bound Write (CWE-787): Writing data past the end or before the beginning of the intended memory.

2. Stack-based Buffer Overflow (CWE-121): This occurs when temporary memory being overwritten is allocated on the stack.

3. Heap-based Buffer Overflow (CWE-122): This occurs when the temporary memory being overwritten is allocated on the heap portion (dynamic memory).

TECHNICAL ANALYSIS

STATIC ANALYSIS

The vulnerability in WebP occurred in lossless compression support, also known as VP8L. This support can store and restore image pixels with 100% accuracy, and it makes use of an algorithm called Huffman coding.

Understanding Huffman Coding and Image Compression

  • - Huffman coding is a data compression technique used for text and image data.
  • - It assigns shorter codes to frequently occurring elements and longer codes to less frequent ones, reducing overall data size.
  • In text compression, common words are replaced with shorter codes, minimizing space needed.
  • In image compression, each color channel (Red, Blue, Green, Alpha) is compressed separately using Huffman coding.
  • This process reduces image byte size while preserving quality, making it a lossless compression method.
  • Huffman coding is utilized in formats like WebP for efficient storage and transmission.
  • For further understanding, refer to this video: https://youtu.be/saofdNsZiYY?si=8GX8t7AVQaMEqR3J

In Huffman encoding for WebP images, lookup tables directly map symbols to their Huffman codes, bypassing the need to traverse the Huffman tree during encoding or decoding, which speeds up processing. While symbol frequency tables are crucial for constructing the Huffman tree, lookup tables provide immediate access to Huffman codes once the tree is built, enhancing efficiency, especially for real-time applications or systems with limited computational resources. WebP utilizes lookup tables to optimize encoding and decoding, improving compression performance and reducing file sizes while maintaining image quality.

The memory requirements for lookup tables in a single Huffman tree group are outlined. The red, blue, and alpha alphabets each consist of 256 symbols each, while the distance alphabet has 40 symbols. The worst-case sizes for their lookup tables are 630 and 410 respectively. The size of the green alphabet varies based on the color cache size, ranging from 256 (green component values) to 256 plus additional values for length prefixes which is 24 and the color cache size (between 0 and 2048). These values are calculated for an 8-bit first-level lookup and a maximum 15-bit second-level lookup using a tool called enough.c. The maximum table size is derived from the cache. Each table may have it second level table. And all the 5 Huffman tables are allocated in one big memory.

In cases where alpha data solely depends on the color indexing transform and doesn’t use the color cache, a streamlined approach is possible. By using the DecodeAlphaData() method, which allocates only 1 byte per pixel for the alpha channel, instead of allocating memory for the color cache, memory overhead is significantly reduced. This optimized method is especially advantageous when color cache usage is unnecessary or rare, ensuring efficient memory use while maintaining decoding performance. (personal thought: An attacker can craft a malicious image that behaves as if it doesn't have a cache but does).

Lab Setup for generating maximum table size using Enough.c

I used Ubuntu 22.4 on visual studio code since the code is written in C. First download and install Visual studio code. On visual studio code, download the WSL extension (this is because I am using Windows). From WSL, download Ubuntu22.4. Switch to Ubuntu server.

On terminal, you install the install the gcc compiler.

Sudo apt update 
Sudo apt install gcc

Once you are done, you can check the version.

gcc --version

We then clone enough.c from GitHub.

git clone https://github.com/madler/zlib/blob/develop/examples/enough.c

Once it is save in your folder. Change directory to the folder name and run

gcc enough.c -o enough

It becomes an .exe file. Then run

./enough.c 256 8 15

Where 256 is number of symbol for Red, 8 is number of bits and 15 is the maximum bit allowed. The result is 630 as the maximum table size as shown below. Same can be done for the other tables.

When you run the code, you should get this.

While encoding the table, an accurate table can be achieved. Since it is a precalculated table, the memory is allocated. Meanwhile, decoding the table can lead to inaccurate results which can end up overflowing the table.

FUZZING

Detecting the vulnerability using fuzzing is quite challenging as it can take months to detect a crash from fuzzing the whole program.

An alternative approach is doing unit fuzzing with AFL++. In this method, a part of the program (a function) which responsible for generating the size table is fuzzed. Invalid images are imputed to find a crash and calculate the table size.

Lab Setup

The script for AFL++ persistent fuzzing was gotten from @liveoverflow. I used Ubuntu 22.4 on Visual studio code.

First download and install Visual studio code. On visual studio code, download the WSL extension (this is because I am using Windows). From WSL, download Ubuntu22.4. Switch to Ubuntu server.

On terminal, you install the install the gcc compiler.

Sudo apt update
Sudo install gcc

You can then check the version.

gcc --version

We then clone @liveoverflow’s script from GitHub.

git clone https://github.com/LiveOverflow/webp-CVE-2023-4863/persistent_fuzzing/fuzz_test.c

Once it is save in a folder.

From root run to install the necessary tool.

sudo apt install -y apt-transport-https curl gnupg gcc make wget git vim gdb clang llvm lld llvm-dev bsdmainutils libstdc++-10-dev python3 python3-pip python3-dev automake autoconf flex bison build-essential libglib2.0-dev libpixman-1-dev python3-setuptools ninja-build libtool screen

You should be getting this

I had already installed mine.

To install AFL++

cd ~ git clone https://github.com/AFLplusplus/AFLplusplus cd AFLplusplus make source-only sudo make install

For better understanding of AFL++ refer to https://github.com/AFLplusplus/AFLplusplus/blob/0c054f520eda67b7bb15f95ca58c028e9b68131f/instrumentation/README.persistent_mode.md

While installing.

Once installed,

ls

and you should get this file

Next, you clone the vulnerable WebP repository and run it.

cd ~  git clone https://chromium.googlesource.com/webm/libwebp cd libwebp git checkout v1.3.1 ./autogen.sh ./configure make clean all sudo make install

While installing

Once installed,

ls

and you should get this

Understanding the test script

Before fuzzing, lets understand how @liveoverflow made test script.

The AFL++ provides a framework for the fuzzing. Which is found in https://github.com/AFLplusplus/AFLplusplus/blob/0c054f520eda67b7bb15f95ca58c028e9b68131f/instrumentation/README.persistent_mode.md

It is either you call the function into the AFL framework or you copy the function into the framework. The framework takes two arguments, the function at which the loop will be pointing to and the loop function.

Since we are fuzzing the buildhuffmantable, we would copy the buildhuffmantable function into the framework and define terms.

You can then define the symbol size and and fixed table size.

The symbol size can 40 or 256.

And the maximum table size that was calculated from enough.c it is 410 and 630.

You can use either of that.

In a fuzzing framework utilizing AFL, a code snippet has been developed to test the VP8LBuildHuffmanTable function associated with Huffman coding. This snippet iterates through 10,000 test cases or until AFL stops, gathering test case lengths and initializing arrays to store Huffman code lengths. It calculates code length frequencies, ensuring they remain within predefined limits, and subsequently calls the VP8LBuildHuffmanTable function. Results, including code length frequencies and function call outcomes, are printed. This approach aims to thoroughly test the VP8LBuildHuffmanTable function for potential vulnerabilities or bugs through fuzz testing.

A little change was made the fuzz_test.c script.

Add

#include <unistd.h>

Where there is

const HuffmanCode last_table[TABLE_SIZE];
const HuffmanCode* table = last_table;

Change it to

HuffmanCode last_table[TABLE_SIZE];
HuffmanCode* table = last_table;

LET THE FUZZING BEGIN!!!

While in that libwebp directory, copy the fuzz_test.c file into the libwebp directory.

cp /root/{insert the folder}/fuzz_test.c .

Put in the folder in which you clone the fuzz_test.c

After that, we create some random data for input.

mkdir /root/libwebp/in
mkdir /root/libwebp/out
rm -rf /root/libwebp/out/*
dd if=/dev/random of=/root/libwebp/in/1 bs=8 count=8
dd if=/dev/random of=/root/libwebp/in/2 bs=8 count=32
dd if=/dev/random of=/root/libwebp/in/3 bs=8 count=64
dd if=/dev/random of=/root/libwebp/in/4 bs=8 count=128

Next is to build the fuzzing target

cd /root/libwebp &&                 gcc            -DTABLE_SIZE=410 -DSYM=40 -g -I. fuzz_test.c -lwebp -o /root/libwebp/fuzz_check
cd /root/libwebp &&                 afl-clang-fast -DTABLE_SIZE=410 -DSYM=40 -g -I. fuzz_test.c -lwebp -o /root/libwebp/fuzz_test
cd /root/libwebp && AFL_USE_ASAN=1  afl-clang-fast -DTABLE_SIZE=410 -DSYM=40 -g -I. fuzz_test.c -lwebp -o /root/libwebp/fuzz_test_asan

Start the fuzzer.

screen -S master -d -m afl-fuzz -i /root/libwebp/in -o /root/libwebp/out -M master /root/libwebp/fuzz_test
screen -S asan -d -m afl-fuzz -i /root/libwebp/in -o /root/libwebp/out -S asan /root/libwebp/fuzz_test_asan
watch -n 1 afl-whatsup /root/libwebp/out

Congratulations,

the fuzzing has begun.

If all is done correctly, you get this.

To view the fuzzing screen.

On the terminal, run

screen -rd master

It brings up the fuzzing screen.

After few minutes, the crashes begins to show.

This is result shown after 20hrs of fuzzing. The screen shows that there are have been 6866 crashes with 17 crash file saved in 15,000 loop.

To view the crashed files. Run

cd /root/libwebp/out/master/crashes
ls

This is the result. The result show malicious files that have higher table size than the allocated table size. This files were saved during fuzzing.

To check the max table size of the file,

cat /root/libwebp/out/master/crashes/{filename} | /root/fuzz_check

Replace the filename with one of the crash file. The result is a frequency array.

How to find the maximum table size for the array

To find the maximum table size for array, we used a tool that was created by @liveoverflow called “count_to_size”

We clone the tool

git clone https://github.com/LiveOverflow/webp-CVE-2023-4863/blob/main/utils/count_to_size.c

Or if you cloned the whole repository, CD directory to utils.

Then run

gcc count_to_size.c -o count_to_size
./count_to_size {array}

Replace the array with result you got earlier.

And then

We get a table size that high than the allocated table size.

The actual precalculated maximum table size for 40 symbol with 8bits and maximum of 15bits is 410 as shown earlier. The result her shows 420 which is 10 entries more the actual table size. This means that more data or malicious data can be written in the extra space.

This shows that it is possible to craft a malicious image with a precalculated, predetermined table size into WebP.

However, this may not be easy, as we have only fuzzed one table out of the five, including the cache.

The overflow could most likely occur in the last two tables (distance and green+cache) after the first table is almost filled.

Never trust the user’s input

Patch diffing

The first patch was issued by Google on September 7, 2023, following the BLASTPASS vulnerability in Apple. A further update was announced on September 11, 2023 but was updated on September 13, 2023

libwebp version 1.3.2

The patch addressed the buffer overflow in the image decoder of the Huffman table. Two major changes were implemented: input validation and modification of dynamic memory.

The bulk of the diffing was done in utils where new structures and functions were added.

In the Utils/huffman_util.h, which is the header file, two structures and functions which were not initially in version 1.3.1 were added.

The structures are:

  • HuffmanTableSegment: This structure represents a contiguous (connected) memory segment of Huffman codes.
  • HuffmanTable: This structure represents the chained memory segments of the Huffman codes.

The functions are:

  • VP8LHuffmanTablesAllocate: This function allocates memory for Huffman Tables. It takes the desired size of contiguous Huffman code and a pointer to a HuffmanTables structure and returns 0 on memory allocation error and returns 1 otherwise.
  • VP8LHuffmanTablesDeallocate:This function deallocates memory for Huffman tables. It also takes a pointer to a HuffmanTable.

These structures and functions are designed to manage Huffman tables efficiently. The HuffmanTable structure organizes the memory segment and the allocation and deallocation function handle the memory management aspect. This helps allocate the necessary memory required for the table, thereby avoiding overflow if it is a big size, and also declares memory as free after deallocating to prevent subsequent access.

In the utils/huffman_utils.c, the first part of the change was made to do the following:

  • Check that the total_size and root_table are not zero or null, respectively, then initialize.
  • When the Huffman table structure is initialized, if the current segment does not have enough space, a new segment is allocated.
  • This newly allocated segment size is based on the maximum of total_size and the size of the current segment.
  • The code ensures that the allocated memory segments are connected as contiguous memory is required for Huffman codes.
  • It dynamically expands the memory segment as needed by allocating a new segment when the current segment runs out of space.

The second part contains two functions. These functions are VP8LHuffmanTablesAllocate and VP8LHuffmanTablesDeallocate.

VP8LHuffmanTablesAllocate:

  • This function helps allocate memory for Huffman tables by taking two parameters which are the size of the memory to allocate and huffman_tables (a pointer to HuffmanTables structure).
  • It allocates memory for the root segment using WebPSafeMalloc(). The size of the allocation is determined by the size multiplied by the size of HuffmanCode.
  • If the memory allocation is successful, it initializes the start, curr table, next, and size field of the root segment and returns 1.
  • If the memory allocation fails, it returns 0.

VP8LHuffmanTablesDeallocate:

  • This function deallocates memory for Huffman tables.
  • It takes a single parameter, which is the huffman_tables.
  • The function checks if the huffman_tables are NULL.
  • It initializes two pointers, current and next, to traverse the linked list of the segment.
  • It starts by freeing up the memory allocated for the root segment (current) using WebPSafeFree().
  • Then it iterates through the remaining segments and frees their memory as well.
  • After each iteration, it updates the current to point to the next segment (next).
  • Finally, it frees up the memory for the huffman_tables structure itself.

These changes are supposed to validate and prevent memory leaks, ensuring efficient memory usage and avoiding malicious data execution.

A little change was also made in the dec/vp8l.c which was made by changing pointers and functions to VP8LHuffmanTablesAllocate and VP8LHuffmanTablesDeallocate.

The overall change aided in input validation and modifications of dynamic memory.

CONCLUSION

CVE-2023–4863, or the Zero-Day WebP vulnerability, poses a high risk as it could have significant impact when exploited. Google’s prompt software update is commendable, as it helps prevent further instances of exploitation. The complexity of the libwebp program makes it difficult to fuzz, but unit fuzzing proved to be an effective approach. Due to its complexity, detecting this vulnerability earlier was challenging.

At the time of this analysis, most software utilizing WebP should have updated their software.

Reference

https://blog.isosceles.com/the-webp-0day/

https://github.com/AFLplusplus/AFLplusplus/blob/0c054f520eda67b7bb15f95ca58c028e9b68131f/instrumentation/README.persistent_mode.md

https://youtu.be/PJLWlmp8CDM?si=aIxQn78mcGk1b611

https://cwe.mitre.org/data/definitions/122.html

https://en.m.wikipedia.org/wiki/WebP

https://www.upguard.com/blog/libwebp-cve-2023

https://blog.cloudflare.com/uncovering-the-hidden-webp-vulnerability-cve-2023-4863

https://github.com/LiveOverflow/webp-CVE-2023-4863

https://fossies.org/diffs/libwebp/1.3.1_vs_1.3.2/src/utils/huffman_utils.c-diff.html

--

--

vsociety
vsociety

Written by vsociety

vsociety is a community centered around vulnerability research

No responses yet