diff --git a/src/Image/ImageProcessor.php b/src/Image/ImageProcessor.php index d4577cbe7..8bdf0afa0 100644 --- a/src/Image/ImageProcessor.php +++ b/src/Image/ImageProcessor.php @@ -150,6 +150,14 @@ public function setLogger(LoggerInterface $logger) public function getImage(&$file, $firsttime = true, $allowvector = true, $orig_srcpath = false, $interpolation = false) { + /** + * Prevents insecure PHP deserialization through phar:// wrapper + * @see https://github.com/mpdf/mpdf/issues/949 + */ + if ($this->hasBlacklistedStreamWrapper($file)) { + return $this->imageError($file, $firsttime, 'File contains an invalid stream. Only http://, https://, and file:// streams are valid.'); + } + // mPDF 6 // firsttime i.e. whether to add to this->images - use false when calling iteratively // Image Data passed directly as var:varname @@ -1421,5 +1429,27 @@ private function urldecodeParts($url) return $file . $query; } + /** + * @param string $filename + * @return bool + * @since 7.1.8 + */ + private function hasBlacklistedStreamWrapper($filename) + { + if (strpos($filename, '://') > 0) { + $wrappers = stream_get_wrappers(); + foreach ($wrappers as $wrapper) { + if (in_array($wrapper, ['http', 'https', 'file'])) { + continue; + } + + if (stripos($filename, $wrapper . '://') === 0) { + return true; + } + } + } + + return false; + } } diff --git a/tests/Mpdf/Image/ImageProcessorTest.php b/tests/Mpdf/Image/ImageProcessorTest.php new file mode 100644 index 000000000..b587802ca --- /dev/null +++ b/tests/Mpdf/Image/ImageProcessorTest.php @@ -0,0 +1,94 @@ +shouldIgnoreMissing(); + + $mpdf->img_dpi = 72; + $mpdf->showImageErrors = true; + $mpdf->PDFAXwarnings = []; + + $otl = Mockery::mock(Otl::class); + $cssManager = Mockery::mock(CssManager::class); + $sizeConverter = Mockery::mock(SizeConverter::class); + $colorConverter = Mockery::mock(ColorConverter::class); + $colorModeConverter = Mockery::mock(ColorModeConverter::class); + $cache = Mockery::mock(Cache::class); + $languageToFont = Mockery::mock(LanguageToFont::class); + $scriptToLanguage = Mockery::mock(ScriptToLanguage::class); + $remoteContentFetcher = Mockery::mock(RemoteContentFetcher::class); + $logger = Mockery::mock(NullLogger::class); + + $this->image = new ImageProcessor( + $mpdf, + $otl, + $cssManager, + $sizeConverter, + $colorConverter, + $colorModeConverter, + $cache, + $languageToFont, + $scriptToLanguage, + $remoteContentFetcher, + $logger + ); + } + + /** + * @dataProvider dataProviderStreamBlacklist + */ + public function testStreamBlacklist($filename, $match) + { + try { + $this->image->getImage($filename); + } catch (\Exception $e) { + + } + + $this->assertRegExp($match, $e->getMessage()); + } + + public function dataProviderStreamBlacklist() + { + $testData = []; + + $wrappers = stream_get_wrappers(); + foreach ($wrappers as $wrapper) { + if (in_array($wrapper, ['http', 'https', 'file'])) { + $testData[] = [$wrapper . '://', '/does not exist on this mock object/']; + } else { + $testData[] = [$wrapper . '://', '/File contains an invalid stream./']; + } + } + + return $testData; + } +}