Deserialization Journey: The case of CVE-2023-28115

NetbyteSEC Security Advisory - Phar Deserialization in Snappy

Title: PHAR Deserialization in Snappy
Advisory ID: NBS-2023-0003
Product: Snappy
Affected Version: <= 1.4.1
Patched Version: >=1.4.2
Homepage: https://github.com/KnpLabs/snappy
CVE ID: CVE-2023-28115
Author: Ahmad Shauqi Bin Gushadi | NetbyteSEC




Vendor/product description

Snappy is a PHP library allowing thumbnail, snapshot or PDF generation from a url or a html page. It uses the excellent webkit-based wkhtmltopdf and wkhtmltoimage available on OSX, linux, windows.

Source: https://github.com/KnpLabs/snappy


Vulnerabilty Overview

Snappy is vulnerable to PHAR deserialization due to a lack of checking on the protocol before passing it into the file_exists() function. If an attacker can upload files of any type to the server he can pass in the phar:// protocol to unserialize the uploaded file and instantiate arbitrary PHP objects. This can lead to remote code execution especially when snappy is used with frameworks with documented POP chains like Laravel/Symfony vulnerable developer code. If user can control the output file from the generateFromHtml() function, it will invoke deserialization.


Technical Details

Phar deserialization happens when user passed phar file (including phar:// wrapper) with malicious serialized metadata to vulnerable function such as file_exists, file_get_contents, or unlink (any function that related to I/O filesystems). In this journey, sink to source was the chosen method to find the vulnerability. Snappy provides multiple features such as display pdf in the browser, merge multiple urls into one pdf and generate local pdf file. The vulnerability lies on generate local pdf file when user give the file name, Snappy will check if the file is already exists using file_exists in ./vendor/knplabs/knp-snappy/src/Knp/Snappy/AbstractGenerator.php by passing whole user input to the function. This result to deserialization and could leads to remote code execution depends on what framework it integrated. Below is the stack trace for the bug.

Figure 1: Stack trace

The interested function lies on AbstractGenerator file which is prepareOutput function. It takes filename and overwrite as arguments, then checks if the filename given is exists using file_exists function as shown as figure 2 below.

Figure 2: Vulnerable code snippet

On line 2, snappy does not validate user input and pass it to file_exists function. Deserialization will occur once user use phar wrapper in file_exists. Depends on the implementation and which framework it integrated, it could leads to arbitrary file read, arbitrary file write or remote code execution. Snappy can be integrate with three framework which are Laravel, Symfony and Zend Framework. In order to create the exploit, the challenge is finding correct gadget, which can be time consuming. PHPGGC solved the challenge and it is open source.


Proof of Concept

For the demonstration, install Snappy via `composer require knplabs/knp-snappy`. After that, under Snappy directory, create an index.php file with this vulnerable code as shown as Figure 3.

Figure 3: Example generating PDF file with vulnerable class

As an attacker, script below (Figure 4) will generate malicious phar to exploit custom vulnerable class.

Figure 4: Generating phar payload

Then run this command to generate phar file `php --define phar.readonly=0 generate_phar.php` and run index.php to like `php index.php`. A file name pwned will be created. Noted that attacker can upload a file with any extension such as .png or .jpeg. Thus, poc.png will do the trick.

Solution

Update to the latest version 1.4.2 of snappy on their website.

Vendor Contact Timeline

2023-02-03: Submitting private disclosure report on platform huntr.dev
2023-03-16: Vendor acknowledge the report and add SECURITY.md
2023-03-17: Vendor validate the vulnerability
2023-03-17: Vendor notify the vulnerability is fixed and issue closed
2023-03-17: Public release of security advisory


NetByteSEC Sdn Bhd
===================
NetbyteSEC Sdn Bhd was incorporated under the Malaysian Companies Act 1965 in 2013.
NetbyteSEC is privately owned and is based in Cyberjaya, Selangor, Malaysia.
More information about NetbyteSEC Sdn Bhd can be found at:
https://www.netbytesec.com