Skip to content

Commit

Permalink
fix: Add patch for PHAR deserialization vulnerability for Timber 2.x …
Browse files Browse the repository at this point in the history
…(security advisory GHSA-6363-v5m4-fvq3)

---------

Co-authored-by: Lukas Gaechter <lukas.gaechter@mind.ch>
  • Loading branch information
nlemoine and gchtr committed Apr 10, 2024
1 parent 152e173 commit 13c6b0f
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/Attachment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Timber;

use InvalidArgumentException;
use Timber\Factory\PostFactory;

/**
Expand Down Expand Up @@ -245,6 +246,10 @@ public function size(): ?int
return $this->size = (int) $size;
}

if (!ImageHelper::is_protocol_allowed($this->file_loc())) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

/**
* Filesize wasn't found in the metadata, so we'll try to get it from the file itself.
*
Expand Down
5 changes: 5 additions & 0 deletions src/Image/Operation/ToJpg.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Timber\Image\Operation;

use InvalidArgumentException;
use Timber\Image\Operation as ImageOperation;
use Timber\ImageHelper;

Expand Down Expand Up @@ -44,6 +45,10 @@ public function filename($src_filename, $src_extension = 'jpg')
*/
public function run($load_filename, $save_filename)
{
if (!ImageHelper::is_protocol_allowed($load_filename)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

if (!\file_exists($load_filename)) {
return false;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Image/Operation/ToWebp.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Timber\Image\Operation;

use InvalidArgumentException;
use Timber\Helper;
use Timber\Image\Operation as ImageOperation;
use Timber\ImageHelper;
Expand Down Expand Up @@ -45,6 +46,10 @@ public function filename($src_filename, $src_extension = 'webp')
*/
public function run($load_filename, $save_filename)
{
if (!ImageHelper::is_protocol_allowed($load_filename)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

if (!\is_file($load_filename)) {
return false;
}
Expand Down
6 changes: 6 additions & 0 deletions src/ImageDimensions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Timber;

use InvalidArgumentException;

/**
* Class FileSize
*
Expand Down Expand Up @@ -117,6 +119,10 @@ public function get_dimension($dimension): ?int
return $this->get_dimension_loaded($dimension);
}

if (!ImageHelper::is_protocol_allowed($this->file_loc)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

// Load dimensions.
if (\file_exists($this->file_loc) && \filesize($this->file_loc)) {
if (ImageHelper::is_svg($this->file_loc)) {
Expand Down
61 changes: 60 additions & 1 deletion src/ImageHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Timber;

use InvalidArgumentException;
use Timber\Image\Operation;

/**
Expand Down Expand Up @@ -29,6 +30,10 @@ class ImageHelper

public static $home_url;

protected const ALLOWED_PROTOCOLS = ['file', 'http', 'https'];

protected const WINDOWS_LOCAL_FILENAME_REGEX = '/^[a-z]:(?:[\\\\\/]?(?:[\w\s!#()-]+|[\.]{1,2})+)*[\\\\\/]?/i';

/**
* Inits the object.
*/
Expand Down Expand Up @@ -140,6 +145,11 @@ public static function is_animated_gif($file)
//doesn't have .gif, bail
return false;
}

if (!ImageHelper::is_protocol_allowed($file)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

// Its a gif so test
if (!($fh = @\fopen($file, 'rb'))) {
return false;
Expand Down Expand Up @@ -169,7 +179,15 @@ public static function is_animated_gif($file)
*/
public static function is_svg($file_path)
{
if ('' === $file_path || !\file_exists($file_path)) {
if ('' === $file_path) {
return false;
}

if (!ImageHelper::is_protocol_allowed($file_path)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

if (!\file_exists($file_path)) {
return false;
}

Expand Down Expand Up @@ -431,6 +449,10 @@ public static function get_sideloaded_file_loc($file)
*/
public static function sideload_image($file)
{
if (!ImageHelper::is_protocol_allowed($file)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

/**
* Adds a filter to change the upload folder temporarily.
*
Expand All @@ -444,6 +466,7 @@ public static function sideload_image($file)
\add_filter('upload_dir', [__CLASS__, 'set_sideload_image_upload_dir']);

$loc = self::get_sideloaded_file_loc($file);

if (\file_exists($loc)) {
$url = URLHelper::file_system_to_url($loc);

Expand Down Expand Up @@ -806,6 +829,10 @@ private static function _operate($src, $op, $force = false)
return '';
}

if (!ImageHelper::is_protocol_allowed($src)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

$allow_fs_write = \apply_filters('timber/allow_fs_write', true);

if ($allow_fs_write === false) {
Expand Down Expand Up @@ -949,4 +976,36 @@ public static function get_resize_file_path($url, $w, $h, $crop)
);
return $new_path;
}

/**
* Checks if the protocol of the given filename is allowed.
*
* This fixes a security issue with a PHAR deserialization vulnerability
* with file_exists() in PHP < 8.0.0.
*
* @param string $filepath File path.
* @return bool
*/
public static function is_protocol_allowed($filepath)
{
$parsed_url = \parse_url($filepath);

if (false === $parsed_url) {
throw new InvalidArgumentException('The filename is not valid.');
}

$protocol = isset($parsed_url['scheme'])
? \mb_strtolower($parsed_url['scheme'])
: 'file';

if (
\PHP_OS_FAMILY === 'Windows'
&& \strlen($protocol) === 1
&& \preg_match(self::WINDOWS_LOCAL_FILENAME_REGEX, $filepath)
) {
$protocol = 'file';
}

return \in_array($protocol, self::ALLOWED_PROTOCOLS, true);
}
}
5 changes: 5 additions & 0 deletions src/Timber.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Timber;

use InvalidArgumentException;
use Timber\Factory\CommentFactory;
use Timber\Factory\MenuFactory;
use Timber\Factory\PagesMenuFactory;
Expand Down Expand Up @@ -659,6 +660,10 @@ public static function get_attachment_by(string $field_or_ident, string $ident =
return null;
}

if (!ImageHelper::is_protocol_allowed($ident)) {
throw new InvalidArgumentException('The output file scheme is not supported.');
}

if (!\file_exists($ident)) {
// Deal with a relative path.
$ident = URLHelper::get_full_path($ident);
Expand Down
50 changes: 50 additions & 0 deletions tests/test-timber-image.php
Original file line number Diff line number Diff line change
Expand Up @@ -1185,4 +1185,54 @@ public function testSVGDimensions()
$this->assertSame(23, $image->width());
$this->assertSame(20, $image->height());
}

public function testPharProtocolIsNotAllowedwithResize()
{
$object = new ImageOperation\Resize(400, 300, 'center');
$load_filename = 'phar://test.jpg';
$save_filename = 'test-new.jpg';

$this->expectException(InvalidArgumentException::class);
$object->run($load_filename, $save_filename);
}

public function testPharProtocolIsNotAllowedwithLetterbox()
{
$object = new ImageOperation\Letterbox(400, 300, '#FFFFFF');
$load_filename = 'phar://test.jpg';
$save_filename = 'test-new.jpg';

$this->expectException(InvalidArgumentException::class);
$object->run($load_filename, $save_filename);
}

public function testPharProtocolIsNotAllowedwithRetina()
{
$object = new ImageOperation\Retina(2);
$load_filename = 'phar://test.jpg';
$save_filename = 'test-new.jpg';

$this->expectException(InvalidArgumentException::class);
$object->run($load_filename, $save_filename);
}

public function testPharProtocolIsNotAllowedwithToJpg()
{
$object = new ImageOperation\ToJpg('#FFFFFF');
$load_filename = 'phar://test.svg';
$save_filename = 'test-new.svg';

$this->expectException(InvalidArgumentException::class);
$object->run($load_filename, $save_filename);
}

public function testPharProtocolIsNotAllowedwithToWebp()
{
$object = new ImageOperation\ToWebp(80);
$load_filename = 'phar://test.png';
$save_filename = 'test-new.png';

$this->expectException(InvalidArgumentException::class);
$object->run($load_filename, $save_filename);
}
}

0 comments on commit 13c6b0f

Please sign in to comment.