Skip to content

Commit

Permalink
EZP-31040: Remote Code Execution in file uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
glye committed Mar 3, 2020
1 parent 5a39130 commit 8c9b706
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 12 deletions.
1 change: 1 addition & 0 deletions autoload/ezp_kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@
'eZExtensionPackageHandler' => 'kernel/classes/packagehandlers/ezextension/ezextensionpackagehandler.php',
'eZFSFileHandler' => 'kernel/classes/clusterfilehandlers/ezfsfilehandler.php',
'eZFile' => 'lib/ezfile/classes/ezfile.php',
'eZFileExtensionBlackListValidator' => 'lib/ezutils/classes/ezfileextensionblacklistvalidator.php',
'eZFileHandler' => 'lib/ezfile/classes/ezfilehandler.php',
'eZFilePackageHandler' => 'kernel/classes/packagehandlers/ezfile/ezfilepackagehandler.php',
'eZFilePassthroughHandler' => 'kernel/classes/binaryhandlers/ezfilepassthrough/ezfilepassthroughhandler.php',
Expand Down
42 changes: 37 additions & 5 deletions kernel/classes/datatypes/ezbinaryfile/ezbinaryfiletype.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __construct()
{
parent::__construct( self::DATA_TYPE_STRING, ezpI18n::tr( 'kernel/classes/datatypes', "File", 'Datatype name' ),
array( 'serialize_supported' => true ) );
$this->FileExtensionBlackListValidator = new eZFileExtensionBlackListValidator();
}

/*!
Expand Down Expand Up @@ -246,16 +247,38 @@ function validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute
$httpFileName = $base . "_data_binaryfilename_" . $contentObjectAttribute->attribute( "id" );
$maxSize = 1024 * 1024 * $classAttribute->attribute( self::MAX_FILESIZE_FIELD );

if ( $contentObjectAttribute->validateIsRequired() )
$contentObjectAttributeID = $contentObjectAttribute->attribute( 'id' );
$version = $contentObjectAttribute->attribute( 'version' );
$binary = eZBinaryFile::fetch( $contentObjectAttributeID, $version );
$extensionsBlackList = implode(', ', $this->FileExtensionBlackListValidator->extensionsBlackList() );
if ( $binary === null )
{
$contentObjectAttributeID = $contentObjectAttribute->attribute( "id" );
$version = $contentObjectAttribute->attribute( "version" );
$binary = eZBinaryFile::fetch( $contentObjectAttributeID, $version );
if ( $binary === null )
if ( $contentObjectAttribute->validateIsRequired() )
{
$mustUpload = true;
}
}
else
{
$state = $this->FileExtensionBlackListValidator->validate( $binary->attribute( 'filename' ) );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are blacklisted: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}
}

if ( isset( $_FILES[$httpFileName] ) && $_FILES[$httpFileName]['tmp_name'] !== '')
{
$state = $this->FileExtensionBlackListValidator->validate( $_FILES[$httpFileName]['name'] );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are blacklisted: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}
}

$canFetchResult = eZHTTPFile::canFetch( $httpFileName, $maxSize );
if ( $mustUpload && $canFetchResult == eZHTTPFile::UPLOADEDFILE_DOES_NOT_EXIST )
Expand Down Expand Up @@ -290,6 +313,11 @@ function fetchObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute )
return false;
}

if ( $this->validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute ) !== eZInputValidator::STATE_ACCEPTED )
{
return false;
}

if ( !eZHTTPFile::canFetch( $base . "_data_binaryfilename_" . $contentObjectAttribute->attribute( "id" ) ) )
return false;

Expand Down Expand Up @@ -765,6 +793,10 @@ private function isDeletingFile( eZHTTPTool $http, eZContentObjectAttribute $con

return $isDeletingFile;
}

/// \privatesection
/// The file extension blacklist validator
private $FileExtensionBlackListValidator;
}

eZDataType::register( eZBinaryFileType::DATA_TYPE_STRING, "eZBinaryFileType" );
Expand Down
26 changes: 24 additions & 2 deletions kernel/classes/datatypes/ezimage/ezimagetype.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __construct()
{
parent::__construct( self::DATA_TYPE_STRING, ezpI18n::tr( 'kernel/classes/datatypes', "Image", 'Datatype name' ),
array( 'serialize_supported' => true ) );
$this->FileExtensionBlackListValidator = new eZFileExtensionBlackListValidator();
}

function initializeObjectAttribute( $contentObjectAttribute, $currentVersion, $originalContentObjectAttribute )
Expand Down Expand Up @@ -210,16 +211,25 @@ function validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute
$maxSize = 1024 * 1024 * $classAttribute->attribute( self::FILESIZE_FIELD );
$mustUpload = false;

$tmpImgObj = $contentObjectAttribute->attribute( 'content' );
$original = $tmpImgObj->attribute( 'original' );
if( $contentObjectAttribute->validateIsRequired() )
{
$tmpImgObj = $contentObjectAttribute->attribute( 'content' );
$original = $tmpImgObj->attribute( 'original' );
if ( !$original['is_valid'] )
{
$mustUpload = true;
}
}

$extensionsBlackList = implode(', ', $this->FileExtensionBlackListValidator->extensionsBlackList() );
$state = $this->FileExtensionBlackListValidator->validate( $original['filename'] );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are blacklisted: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}

$canFetchResult = eZHTTPFile::canFetch( $httpFileName, $maxSize );
if ( isset( $_FILES[$httpFileName] ) and $_FILES[$httpFileName]["tmp_name"] != "" )
{
Expand All @@ -231,6 +241,14 @@ function validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute
return eZInputValidator::STATE_INVALID;
}

$state = $this->FileExtensionBlackListValidator->validate( $_FILES[$httpFileName]['name'] );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are on the blacklist: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}

if ( !self::validateImageFileExtension( $_FILES[$httpFileName]['name'] ) )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
Expand Down Expand Up @@ -652,6 +670,10 @@ function postStore( $objectAttribute )
eZImageFile::appendFilepath( $objectAttributeId, $url, true );
}
}

/// \privatesection
/// The file extension blacklist validator
private $FileExtensionBlackListValidator;
}

eZDataType::register( eZImageType::DATA_TYPE_STRING, "eZImageType" );
Expand Down
42 changes: 37 additions & 5 deletions kernel/classes/datatypes/ezmedia/ezmediatype.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public function __construct()
{
parent::__construct( self::DATA_TYPE_STRING, ezpI18n::tr( 'kernel/classes/datatypes', "Media", 'Datatype name' ),
array( 'serialize_supported' => true ) );
$this->FileExtensionBlackListValidator = new eZFileExtensionBlackListValidator();
}

/*!
Expand Down Expand Up @@ -181,16 +182,38 @@ function validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute
$maxSize = 1024 * 1024 * $classAttribute->attribute( self::MAX_FILESIZE_FIELD );
$mustUpload = false;

if ( $contentObjectAttribute->validateIsRequired() )
$contentObjectAttributeID = $contentObjectAttribute->attribute( 'id' );
$version = $contentObjectAttribute->attribute( 'version' );
$media = eZMedia::fetch( $contentObjectAttributeID, $version );
$extensionsBlackList = implode(', ', $this->FileExtensionBlackListValidator->extensionsBlackList() );
if ( $media === null || !$media->attribute( 'filename' ) )
{
$contentObjectAttributeID = $contentObjectAttribute->attribute( "id" );
$version = $contentObjectAttribute->attribute( "version" );
$media = eZMedia::fetch( $contentObjectAttributeID, $version );
if ( $media === null || !$media->attribute( 'filename' ) )
if ( $contentObjectAttribute->validateIsRequired() )
{
$mustUpload = true;
}
}
else
{
$state = $this->FileExtensionBlackListValidator->validate( $media->attribute( 'filename' ) );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are blacklisted: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}
}

if ( isset( $_FILES[$httpFileName] ) && $_FILES[$httpFileName]['tmp_name'] !== '')
{
$state = $this->FileExtensionBlackListValidator->validate( $_FILES[$httpFileName]['name'] );
if ( $state === eZInputValidator::STATE_INVALID || $state === eZInputValidator::STATE_INTERMEDIATE )
{
$contentObjectAttribute->setValidationError( ezpI18n::tr( 'kernel/classes/datatypes',
"A valid file is required. The following file extensions are blacklisted: $extensionsBlackList" ) );
return eZInputValidator::STATE_INVALID;
}
}

$canFetchResult = eZHTTPFile::canFetch( $httpFileName, $maxSize );
if ( $mustUpload && $canFetchResult == eZHTTPFile::UPLOADEDFILE_DOES_NOT_EXIST )
Expand Down Expand Up @@ -274,6 +297,11 @@ function fetchObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute )

eZMediaType::checkFileUploads();

if ( $this->validateObjectAttributeHTTPInput( $http, $base, $contentObjectAttribute ) !== eZInputValidator::STATE_ACCEPTED )
{
return false;
}

$classAttribute = $contentObjectAttribute->contentClassAttribute();
$player = $classAttribute->attribute( "data_text1" );
$pluginPage = eZMediaType::pluginPage( $player );
Expand Down Expand Up @@ -799,6 +827,10 @@ function supportsBatchInitializeObjectAttribute()
{
return true;
}

/// \privatesection
/// The file extension blacklist validator
private $FileExtensionBlackListValidator;
}

eZDataType::register( eZMediaType::DATA_TYPE_STRING, "eZMediaType" );
Expand Down
58 changes: 58 additions & 0 deletions lib/ezutils/classes/ezfileextensionblacklistvalidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* File containing the eZFileExtensionBlackListValidator class.
*
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
* @version //autogentag//
* @package lib
*/

/*!
\class eZFileExtensionBlackListValidator ezfileextensionblacklistvalidator.php
\brief The class eZFileExtensionBlackListValidator validates file extensions based on a blacklist.
*/

class eZFileExtensionBlackListValidator extends eZInputValidator
{
/*!
Constructor
*/
function __construct()
{
parent::eZInputValidator();

$fileIni = eZINI::instance('file.ini');
$this->constraints['extensionsBlackList'] = $fileIni->variable('FileSettings','FileExtensionBlackList');
}

/*!
Tries to validate to the filename \a $filename and returns one of the validator states
eZInputValidator::STATE_ACCEPTED, eZInputValidator::STATE_INTERMEDIATE or
eZInputValidator::STATE_INVALID.
*/
function validate( $filename )
{
if (
pathinfo($filename, PATHINFO_BASENAME) !== $filename ||
in_array(strtolower(pathinfo($filename, PATHINFO_EXTENSION)), $this->constraints['extensionsBlackList'], true)
) {
return eZInputValidator::STATE_INVALID;
}

return eZInputValidator::STATE_ACCEPTED;
}

/*!
Return the list of blacklisted file extensions.
*/
function extensionsBlackList()
{
return $this->constraints['extensionsBlackList'];
}

/// \privatesection
protected $constraints = array(
'extensionsBlackList' => array(),
);
}
11 changes: 11 additions & 0 deletions settings/file.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ Handlers[gzip]=ezgzipcompressionhandler
Handlers[gzipzlib]=ezgzipzlibcompressionhandler
Handlers[gzipshell]=ezgzipshellcompressionhandler

# The file extension blacklist is used to validate uploaded files.
# File types on the blacklist are rejected, for security or other reasons.
FileExtensionBlackList[]
FileExtensionBlackList[]=php
FileExtensionBlackList[]=php3
FileExtensionBlackList[]=phar
FileExtensionBlackList[]=phpt
FileExtensionBlackList[]=pht
FileExtensionBlackList[]=phtml
FileExtensionBlackList[]=pgif

[ClusteringSettings]
# Cluster file handler.
# Since 4.1 name of the filehandlers have changed
Expand Down

0 comments on commit 8c9b706

Please sign in to comment.