Fix security breaches

modifié:         core/lib/Thelia/Controller/Admin/FileController.php
	nouveau fichier: core/lib/Thelia/Tests/Tools/MimeTypesToolsTest.php
	nouveau fichier: core/lib/Thelia/Tools/MimeTypeTools.php
	nouveau fichier: local/config/mime.types
This commit is contained in:
Benjamin Perche
2014-07-28 10:59:57 +02:00
parent d30dc2c30b
commit d186ec4320
4 changed files with 1921 additions and 9 deletions

View File

@@ -26,6 +26,7 @@ use Thelia\Files\FileModelInterface;
use Thelia\Form\Exception\FormValidationException; use Thelia\Form\Exception\FormValidationException;
use Thelia\Log\Tlog; use Thelia\Log\Tlog;
use Thelia\Model\Lang; use Thelia\Model\Lang;
use Thelia\Tools\MimeTypeTools;
use Thelia\Tools\Rest\ResponseRest; use Thelia\Tools\Rest\ResponseRest;
use Thelia\Tools\URL; use Thelia\Tools\URL;
@@ -60,12 +61,20 @@ class FileController extends BaseAdminController
* @param string $parentType Parent Type owning files being saved (product, category, content, etc.) * @param string $parentType Parent Type owning files being saved (product, category, content, etc.)
* @param string $objectType Object type, e.g. image or document * @param string $objectType Object type, e.g. image or document
* @param array $validMimeTypes an array of valid mime types. If empty, any mime type is allowed. * @param array $validMimeTypes an array of valid mime types. If empty, any mime type is allowed.
* * @param array $blackList an array of blacklisted mime types.
* @return Response * @return Response
*/ */
public function saveFileAjaxAction($parentId, $parentType, $objectType, $validMimeTypes = array()) public function saveFileAjaxAction(
{ $parentId,
$this->checkAuth(AdminResources::retrieve($parentType), array(), AccessManager::UPDATE); $parentType,
$objectType,
$validMimeTypes = array(),
$blackList = array()
) {
if (null !== $response = $this->checkAuth(AdminResources::retrieve($parentType), array(), AccessManager::UPDATE)) {
return $response;
}
$this->checkXmlHttpRequest(); $this->checkXmlHttpRequest();
if ($this->getRequest()->isMethod('POST')) { if ($this->getRequest()->isMethod('POST')) {
@@ -87,13 +96,32 @@ class FileController extends BaseAdminController
return new ResponseRest($message, 'text', 403); return new ResponseRest($message, 'text', 403);
} }
$mimeType = $fileBeingUploaded->getMimeType();
$mimeTypeTools = MimeTypeTools::getInstance();
$validateMimeType = $mimeTypeTools
->validateMimeTypeExtension(
$mimeType,
$fileBeingUploaded->getClientOriginalName()
);
$message = null;
if ($validateMimeType === $mimeTypeTools::TYPE_NOT_MATCH) {
$message = $this->getTranslator()
->trans(
"There's a conflict between your file extension \"%ext\" and the mime type \"%mime\"",
[
'%mime' => $mimeType,
'%ext' => $fileBeingUploaded->getClientOriginalExtension()
]
);
}
if (! empty($validMimeTypes)) { if (! empty($validMimeTypes)) {
// Check if we have the proper file type // Check if we have the proper file type
$isValid = false; $isValid = false;
$mimeType = $fileBeingUploaded->getMimeType();
if (in_array($mimeType, $validMimeTypes)) { if (in_array($mimeType, $validMimeTypes)) {
$isValid = true; $isValid = true;
} }
@@ -104,11 +132,23 @@ class FileController extends BaseAdminController
'Only files having the following mime type are allowed: %types%', 'Only files having the following mime type are allowed: %types%',
[ '%types%' => implode(', ', $validMimeTypes)] [ '%types%' => implode(', ', $validMimeTypes)]
); );
return new ResponseRest($message, 'text', 415);
} }
} }
if (!empty($blackList)) {
if (in_array($mimeType, $blackList)) {
$message = $this->getTranslator()
->trans(
'Files with the following mime type are not allowed: %type, please do an archive of the file if you want to upload it',
[ '%type' => $mimeType]
);
}
}
if ($message !== null) {
return new ResponseRest($message, 'text', 415);
}
$fileModel = $fileManager->getModelInstance($objectType, $parentType); $fileModel = $fileManager->getModelInstance($objectType, $parentType);
$parentModel = $fileModel->getParentFileModel(); $parentModel = $fileModel->getParentFileModel();
@@ -182,7 +222,19 @@ class FileController extends BaseAdminController
*/ */
public function saveDocumentAjaxAction($parentId, $parentType) public function saveDocumentAjaxAction($parentId, $parentType)
{ {
return $this->saveFileAjaxAction($parentId, $parentType, 'document'); return $this->saveFileAjaxAction(
$parentId,
$parentType,
'document',
[],
[
'text/x-php',
'application/x-httpd-php',
'application/x-httpd-php3',
'application/x-httpd-php4',
'application/x-httpd-php5',
]
);
} }
/** /**

View File

@@ -0,0 +1,108 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Tests\Type;
use Symfony\Component\DependencyInjection\Container;
use Thelia\Core\Translation\Translator;
use Thelia\Tools\MimeTypeTools;
/**
* Class MimeTypesToolsTest
* @package Thelia\Tests\Type
* @author Benjamin Perche <bperche@openstudio.fr>
*/
class MimeTypesToolsTest extends \PHPUnit_Framework_TestCase
{
/** @var MimeTypeTools */
protected $tool;
public function setUp()
{
new Translator(new Container());
$this->tool = MimeTypeTools::getInstance();
}
public function testTrim()
{
$this->assertEquals(
"foo",
$this->tool->realTrim(" foo ")
);
$this->assertEquals(
"foo bar",
$this->tool->realTrim(" foo bar ")
);
$this->assertEquals(
"foo bar",
$this->tool->realTrim(" foo bar ")
);
$this->assertEquals(
"# foO/x-bar",
$this->tool->realTrim(" # foO/x-bar ")
);
$this->assertEquals(
"foO/x-bar bar baz",
$this->tool->realTrim(" foO/x-bar \t\t bar baz ")
);
}
public function testParseFile()
{
$array = $this->tool->parseFile();
/**
* check the format
*/
foreach ($array as $key => $value) {
$this->assertTrue(is_string($key));
$this->assertTrue(is_array($value));
foreach ($value as $entry) {
$this->assertTrue(is_string($entry));
}
}
}
/**
* @expectedException \Thelia\Exception\FileException
*/
public function testParseFileFail()
{
$this->tool->parseFile("foo.bar");
}
public function testValidation()
{
$this->assertTrue($this->tool->validateMimeTypeExtension(
"image/png", "foo.png"
));
$this->assertTrue($this->tool->validateMimeTypeExtension(
"image/png", "foo.PNg"
));
$this->assertFalse($this->tool->validateMimeTypeExtension(
"image/png", "foo.jpeg"
));
$this->assertFalse($this->tool->validateMimeTypeExtension(
"thismimetype/doesntexists", "foo.bar"
));
}
}

View File

@@ -0,0 +1,157 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Tools;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\FileException;
/**
* Class MimeTypeTools
* @package Thelia\Tools
* @author Benjamin Perche <bperche@openstudio.fr>
*/
class MimeTypeTools
{
const TYPES_FILE = "local/config/mime.types";
const TYPE_UNKNOWN = 0;
const TYPE_NOT_MATCH = 1;
const TYPE_MATCH = 2;
protected static $instance;
protected static $typesCache;
/**
* @return $this
*/
public static function getInstance()
{
if (null === static::$instance) {
static::$instance = new static();
}
return static::$instance;
}
/**
* @param $mimeType
* @return array|bool
*/
public function guessExtensionsFromMimeType($mimeType)
{
if (null === static::$typesCache) {
static::$typesCache = $this->parseFile();
}
if (!is_scalar($mimeType) || !isset(static::$typesCache[$mimeType])) {
return false;
}
return static::$typesCache[$mimeType];
}
/**
* @param $mimeType
* @param $fileName
* @return bool
*/
public function validateMimeTypeExtension($mimeType, $fileName) {
$mimeType = strtolower($mimeType);
$extensions = $this->guessExtensionsFromMimeType($mimeType);
if (false === $extensions || !is_scalar($fileName)) {
return static::TYPE_UNKNOWN;
}
$oneMatch = true;
foreach ($extensions as $extension) {
$oneMatch &= !!preg_match("#\.$extension$#i", $fileName);
}
return (bool)$oneMatch ? static::TYPE_MATCH : static::TYPE_NOT_MATCH;
}
/**
* @param null $filePath
* @return array
* @throws \Thelia\Exception\FileException
*/
public function parseFile($filePath = null)
{
if (null === $filePath) {
$filePath = THELIA_ROOT . static::TYPES_FILE;
}
$fileHandle = @fopen($filePath, "r");
if ($fileHandle === false) {
throw new FileException(
Translator::getInstance()->trans(
"The file %file could not be opened",
[
"%file" => $filePath,
]
)
);
}
$typesArray = [];
while (false !== $line = fgets($fileHandle)) {
$line = $this->realTrim($line);
$line = preg_replace("#\#.*$#", "", $line);
$table = explode(" ", $line);
$mime = array_shift($table);
if (!empty($table) && !empty($mime)) {
$typesArray[$mime] = $table;
}
}
if (!feof($fileHandle)) {
throw new FileException(
Translator::getInstance()->trans(
"An error occurred while reading the file %file",
[
"%file" => $filePath,
]
)
);
}
return $typesArray;
}
/**
* @param $string
* @param string $characterMask
* @return mixed|string
*/
public function realTrim($string, $characterMask = "\t\n\r ")
{
$string = trim($string, $characterMask);
$charLen = strlen($characterMask);
$string = preg_replace(
"#[$characterMask]+#",
" ",
$string
);
return $string;
}
}

1595
local/config/mime.types Normal file

File diff suppressed because it is too large Load Diff