Symfony Cross-Site Request Forgery vulnerability in the Web Profiler
Description
All 2.0.X, 2.1.X, 2.2.X, 2.3.X, 2.4.X, and 2.5.X versions of the Symfony WebProfiler bundle are affected by this security issue.
This issue has been fixed in Symfony 2.3.19, 2.4.9, and 2.5.4. Note that no fixes are provided for Symfony 2.0, 2.1, and 2.2 as they are not maintained anymore.
Description
The Symfony Web Profiler is a great development tool, but it should not be enabled on production servers. If it is enabled in production, it must be properly secured so that only authorized people have access to it. Developers must be very cautious about this as the Web Profiler gives many sensitive information about a Symfony project and any attackers can exploit many of them. Just to name a few sensitive information: user logins, user cookies, executed SQL statements, ...
That being said, the import/export feature of the web profiler is exploitable even if the Web Profiler is secured as the form to import a profiler is not protected against CSRF attacks. Combined with the fact that profiles are imported as a PHP serialized string, it makes your application vulnerable to code injection.
Resolution
As the import/export feature of the Web Profiler is not that useful, and because PHP serialize/unserialize functions have a long history of vulnerabilities, I decided to remove this feature from the Web interface and move it as CLI commands.
If you were relying on this feature, you now need to use the profiler:import and profiler:export Symfony commands provided by the WebProfiler bundle from the command line interface.
Those commands are not enabled by default and must be activated explicitly. For Symfony 2.4+, you can import them in your app/config.yml configuration file:
import:
- { resource: "%kernel.root_dir%/../vendor/symfony/symfony/src/Symfony/Bundle/WebProfilerBundle/Resources/config/commands.xml" }
For Symfony 2.3, you can use the following snippet of code in app/console:
$kernel = new AppKernel($env, $debug);
$application = new Application($kernel);
if ($kernel->getContainer()->has('profiler')) {
$profiler = $kernel->getContainer()->get('profiler');
$application->add(new ImportCommand($profiler));
$application->add(new ExportCommand($profiler));
}
$application->run($input);
At this point, I want to reiterate that you should never enable the Symfony Web Profiler on your production servers as this is a development tool. And if you need to enable it, double-check that it is properly secured.
The patch for this issue is available here: https://github.com/symfony/symfony/pull/11832
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
symfony/symfonyPackagist | >= 2.0.0, < 2.3.19 | 2.3.19 |
symfony/symfonyPackagist | >= 2.4.0, < 2.4.9 | 2.4.9 |
symfony/symfonyPackagist | >= 2.5.0, < 2.5.4 | 2.5.4 |
symfony/web-profiler-bundlePackagist | >= 2.0.0, < 2.3.19 | 2.3.19 |
symfony/web-profiler-bundlePackagist | >= 2.4.0, < 2.4.9 | 2.4.9 |
symfony/web-profiler-bundlePackagist | >= 2.5.0, < 2.5.4 | 2.5.4 |
Patches
25b589ba83faf[WebProfiler] replaced the import/export feature from the web interface to a CLI tool
8 files changed · +287 −83
Command/ExportCommand.php+75 −0 added@@ -0,0 +1,75 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Exports a profile. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExportCommand extends Command +{ + private $profiler; + + public function __construct(Profiler $profiler = null) + { + $this->profiler = $profiler; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (null === $this->profiler) { + return false; + } + + return parent::isEnabled(); + } + + protected function configure() + { + $this + ->setName('profiler:export') + ->setDescription('Exports a profile') + ->setDefinition(array( + new InputArgument('token', InputArgument::REQUIRED, 'The profile token'), + )) + ->setHelp(<<<EOF +The <info>%command.name%</info> command exports a profile to the standard output: + +<info>php %command.full_name% profile_token</info> +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $token = $input->getArgument('token'); + + if (!$profile = $this->profiler->loadProfile($token)) { + throw new \LogicException(sprintf('Profile with token "%s" does not exist.', $token)); + } + + $output->writeln($this->profiler->export($profile), OutputInterface::OUTPUT_RAW); + } +}
Command/ImportCommand.php+90 −0 added@@ -0,0 +1,90 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Imports a profile. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ImportCommand extends Command +{ + private $profiler; + + public function __construct(Profiler $profiler = null) + { + $this->profiler = $profiler; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (null === $this->profiler) { + return false; + } + + return parent::isEnabled(); + } + + protected function configure() + { + $this + ->setName('profiler:import') + ->setDescription('Imports a profile') + ->setDefinition(array( + new InputArgument('filename', InputArgument::OPTIONAL, 'The profile path'), + )) + ->setHelp(<<<EOF +The <info>%command.name%</info> command imports a profile: + +<info>php %command.full_name% profile_filepath</info> + +You can also pipe the profile via STDIN: + +<info>cat profile_file | php %command.full_name%</info> +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $data = ''; + if ($input->getArgument('filename')) { + $data = file_get_contents($input->getArgument('filename')); + } else { + if (0 !== ftell(STDIN)) { + throw new \RuntimeException('Please provide a filename or pipe the profile to STDIN.'); + } + + while (!feof(STDIN)) { + $data .= fread(STDIN, 1024); + } + } + + if (!$profile = $this->profiler->import($data)) { + throw new \LogicException('The profile already exists in the database.'); + } + + $output->writeln(sprintf('Profile "%s" has been successfully imported.', $profile->getToken())); + } +}
Controller/ProfilerController.php+0 −57 modified@@ -111,33 +111,6 @@ public function panelAction(Request $request, $token) )), 200, array('Content-Type' => 'text/html')); } - /** - * Exports data for a given token. - * - * @param string $token The profiler token - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function exportAction($token) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - if (!$profile = $this->profiler->loadProfile($token)) { - throw new NotFoundHttpException(sprintf('Token "%s" does not exist.', $token)); - } - - return new Response($this->profiler->export($profile), 200, array( - 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename= '.$token.'.txt', - )); - } - /** * Purges all tokens. * @@ -157,36 +130,6 @@ public function purgeAction() return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'purge')), 302, array('Content-Type' => 'text/html')); } - /** - * Imports token data. - * - * @param Request $request The current HTTP Request - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function importAction(Request $request) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - $file = $request->files->get('file'); - - if (empty($file) || !$file->isValid()) { - return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'upload_error')), 302, array('Content-Type' => 'text/html')); - } - - if (!$profile = $this->profiler->import(file_get_contents($file->getPathname()))) { - return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'already_exists')), 302, array('Content-Type' => 'text/html')); - } - - return new RedirectResponse($this->generator->generate('_profiler', array('token' => $profile->getToken())), 302, array('Content-Type' => 'text/html')); - } - /** * Displays information page. *
Resources/config/commands.xml+23 −0 added@@ -0,0 +1,23 @@ +<?xml version="1.0" ?> + +<container xmlns="http://symfony.com/schema/dic/services" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + <parameters> + <parameter key="web_profiler.command.import.class">Symfony\Bundle\WebProfilerBundle\Command\ImportCommand</parameter> + <parameter key="web_profiler.command.export.class">Symfony\Bundle\WebProfilerBundle\Command\ExportCommand</parameter> + </parameters> + + <services> + <service id="web_profiler.command.import" class="%web_profiler.command.import.class%"> + <argument type="service" id="profiler" on-invalid="null" /> + <tag name="console.command" /> + </service> + + <service id="web_profiler.command.export" class="%web_profiler.command.export.class%"> + <argument type="service" id="profiler" on-invalid="null" /> + <tag name="console.command" /> + </service> + </services> +</container>
Resources/views/Profiler/admin.html.twig+9 −26 modified@@ -1,27 +1,10 @@ -<div class="search import clearfix" id="adminBar"> - <h3> - <img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg=="> - Admin - </h3> +{% if token is not empty %} + <div class="search import clearfix" id="adminBar"> + <h3> + <img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg=="> + Admin + </h3> - <form action="{{ path('_profiler_import') }}" method="post" enctype="multipart/form-data"> - {% if token is not empty %} - <div style="margin-bottom: 10px"> - » <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a> - </div> - <div style="margin-bottom: 10px"> - » <a href="{{ path('_profiler_export', { 'token': token }) }}">Export</a> - </div> - {% endif %} - » <label for="file">Import</label><br> - <input type="file" name="file" id="file"><br> - <button type="submit" class="sf-button"> - <span class="border-l"> - <span class="border-r"> - <span class="btn-bg">UPLOAD</span> - </span> - </span> - </button> - <div class="clear-fix"></div> - </form> -</div> + <div style="margin-bottom: 10px">» <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a></div> + </div> +{% endif %}
Tests/Command/ExportCommandTest.php+53 −0 added@@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Command; + +use Symfony\Bundle\WebProfilerBundle\Command\ExportCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class ExportCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \LogicException + */ + public function testExecuteWithUnknownToken() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $command = new ExportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('token' => 'TOKEN')); + } + + public function testExecuteWithToken() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $profile = new Profile('TOKEN'); + $profiler->expects($this->once())->method('loadProfile')->with('TOKEN')->will($this->returnValue($profile)); + + $command = new ExportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('token' => 'TOKEN')); + $this->assertEquals($profiler->export($profile), $commandTester->getDisplay()); + } +}
Tests/Command/ImportCommandTest.php+36 −0 added@@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Command; + +use Symfony\Bundle\WebProfilerBundle\Command\ImportCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class ImportCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testExecute() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $profiler->expects($this->once())->method('import')->will($this->returnValue(new Profile('TOKEN'))); + + $command = new ImportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('filename' => __DIR__.'/../Fixtures/profile.data')); + $this->assertRegExp('/Profile "TOKEN" has been successfully imported\./', $commandTester->getDisplay()); + } +}
Tests/Fixtures/profile.data+1 −0 added@@ -0,0 +1 @@ +Tzo0NToiU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlIjo4OntzOjUyOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHRva2VuIjtzOjU6IlRPS0VOIjtzOjUzOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHBhcmVudCI7TjtzOjU1OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNoaWxkcmVuIjthOjA6e31zOjU3OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNvbGxlY3RvcnMiO2E6MDp7fXM6NDk6IgBTeW1mb255XENvbXBvbmVudFxIdHRwS2VybmVsXFByb2ZpbGVyXFByb2ZpbGUAaXAiO047czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQBtZXRob2QiO047czo1MDoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB1cmwiO047czo1MToiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB0aW1lIjtOO30= \ No newline at end of file
f38536ab7905[WebProfiler] replaced the import/export feature from the web interface to a CLI tool
8 files changed · +287 −83
src/Symfony/Bundle/WebProfilerBundle/Command/ExportCommand.php+75 −0 added@@ -0,0 +1,75 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Exports a profile. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExportCommand extends Command +{ + private $profiler; + + public function __construct(Profiler $profiler = null) + { + $this->profiler = $profiler; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (null === $this->profiler) { + return false; + } + + return parent::isEnabled(); + } + + protected function configure() + { + $this + ->setName('profiler:export') + ->setDescription('Exports a profile') + ->setDefinition(array( + new InputArgument('token', InputArgument::REQUIRED, 'The profile token'), + )) + ->setHelp(<<<EOF +The <info>%command.name%</info> command exports a profile to the standard output: + +<info>php %command.full_name% profile_token</info> +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $token = $input->getArgument('token'); + + if (!$profile = $this->profiler->loadProfile($token)) { + throw new \LogicException(sprintf('Profile with token "%s" does not exist.', $token)); + } + + $output->writeln($this->profiler->export($profile), OutputInterface::OUTPUT_RAW); + } +}
src/Symfony/Bundle/WebProfilerBundle/Command/ImportCommand.php+90 −0 added@@ -0,0 +1,90 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Imports a profile. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ImportCommand extends Command +{ + private $profiler; + + public function __construct(Profiler $profiler = null) + { + $this->profiler = $profiler; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function isEnabled() + { + if (null === $this->profiler) { + return false; + } + + return parent::isEnabled(); + } + + protected function configure() + { + $this + ->setName('profiler:import') + ->setDescription('Imports a profile') + ->setDefinition(array( + new InputArgument('filename', InputArgument::OPTIONAL, 'The profile path'), + )) + ->setHelp(<<<EOF +The <info>%command.name%</info> command imports a profile: + +<info>php %command.full_name% profile_filepath</info> + +You can also pipe the profile via STDIN: + +<info>cat profile_file | php %command.full_name%</info> +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $data = ''; + if ($input->getArgument('filename')) { + $data = file_get_contents($input->getArgument('filename')); + } else { + if (0 !== ftell(STDIN)) { + throw new \RuntimeException('Please provide a filename or pipe the profile to STDIN.'); + } + + while (!feof(STDIN)) { + $data .= fread(STDIN, 1024); + } + } + + if (!$profile = $this->profiler->import($data)) { + throw new \LogicException('The profile already exists in the database.'); + } + + $output->writeln(sprintf('Profile "%s" has been successfully imported.', $profile->getToken())); + } +}
src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php+0 −57 modified@@ -111,33 +111,6 @@ public function panelAction(Request $request, $token) )), 200, array('Content-Type' => 'text/html')); } - /** - * Exports data for a given token. - * - * @param string $token The profiler token - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function exportAction($token) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - if (!$profile = $this->profiler->loadProfile($token)) { - throw new NotFoundHttpException(sprintf('Token "%s" does not exist.', $token)); - } - - return new Response($this->profiler->export($profile), 200, array( - 'Content-Type' => 'text/plain', - 'Content-Disposition' => 'attachment; filename= '.$token.'.txt', - )); - } - /** * Purges all tokens. * @@ -157,36 +130,6 @@ public function purgeAction() return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'purge')), 302, array('Content-Type' => 'text/html')); } - /** - * Imports token data. - * - * @param Request $request The current HTTP Request - * - * @return Response A Response instance - * - * @throws NotFoundHttpException - */ - public function importAction(Request $request) - { - if (null === $this->profiler) { - throw new NotFoundHttpException('The profiler must be enabled.'); - } - - $this->profiler->disable(); - - $file = $request->files->get('file'); - - if (empty($file) || !$file->isValid()) { - return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'upload_error')), 302, array('Content-Type' => 'text/html')); - } - - if (!$profile = $this->profiler->import(file_get_contents($file->getPathname()))) { - return new RedirectResponse($this->generator->generate('_profiler_info', array('about' => 'already_exists')), 302, array('Content-Type' => 'text/html')); - } - - return new RedirectResponse($this->generator->generate('_profiler', array('token' => $profile->getToken())), 302, array('Content-Type' => 'text/html')); - } - /** * Displays information page. *
src/Symfony/Bundle/WebProfilerBundle/Resources/config/commands.xml+23 −0 added@@ -0,0 +1,23 @@ +<?xml version="1.0" ?> + +<container xmlns="http://symfony.com/schema/dic/services" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> + + <parameters> + <parameter key="web_profiler.command.import.class">Symfony\Bundle\WebProfilerBundle\Command\ImportCommand</parameter> + <parameter key="web_profiler.command.export.class">Symfony\Bundle\WebProfilerBundle\Command\ExportCommand</parameter> + </parameters> + + <services> + <service id="web_profiler.command.import" class="%web_profiler.command.import.class%"> + <argument type="service" id="profiler" on-invalid="null" /> + <tag name="console.command" /> + </service> + + <service id="web_profiler.command.export" class="%web_profiler.command.export.class%"> + <argument type="service" id="profiler" on-invalid="null" /> + <tag name="console.command" /> + </service> + </services> +</container>
src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/admin.html.twig+9 −26 modified@@ -1,27 +1,10 @@ -<div class="search import clearfix" id="adminBar"> - <h3> - <img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg=="> - Admin - </h3> +{% if token is not empty %} + <div class="search import clearfix" id="adminBar"> + <h3> + <img style="margin: 0 5px 0 0; vertical-align: middle; height: 16px" width="16" height="16" alt="Import" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADo0lEQVR42u2XS0hUURjHD5njA1oYbXQ2MqCmIu2iEEISUREEEURxFB8ovt+DEsLgaxBRQQeUxnQ0ZRYSQasgiDaFqxAy2jUtCjdCoEjFwHj6/+F+dbvN6PQAN37wm++c7/z/35x7uPcOo7TW58rFBs59A7GGQ51XBAIBlZmZuYOhE1zm/A/4PxvY3NwMO53OYEJCgp+nccqXXQc94D54boAxalyLNayNtra2NJmbmzvOyMj4cRqoKYK4AsZzc3Nft7e3f5qZmTnCpk8Ix6xxjRpDGzmkUU5Ozuu2trZP09PTR+vr6ycbGxtaWFtbC9fU1AQTExPdmNNzLSUlZXt4ePhANNGghlp6lDWkkcvlOsCX6LNYXV0N8BTS0tK2cDJfWIsFaumhV0lIIxzXl5WVFX0aPp8vhDwJbMnJyc6JiYkji8YP7oI4YowfmDX00KskOHG73UfLy8vahB/cBXFSW1pa2kPOA7RdqqysfGtaCyOXA2VGgmvUiJ5e9lD8qKioeOv1ejVZXFwMI5eLEWOFWgh5Etg4J0lJSTdwYiHxLSwseFi3Yg5qRE8veyh+TE1Nhebn5zWZnZ31mE2okTxmM6WlpS7xeDyeQ2Qb61bMQQ214mMPVVxc7MJuNBkfHz9EtplNmEcET4JPfL29va+i6azR19f3UnzV1dUrqqqqyocT0KSzs/OV1YB6ROrr67fF19TU9DSazhp1dXXPxdfS0vJQNTY2+sfGxjSpra19YTWgHhHs/pn40OhRNJ0lLuON+kF8ra2tY9yAe3R0VBMc6wfr84n6b1BDrfiam5snImgczObAq7ylv7//q/hGRkbuqMHBwTt4Q2nS3d39jSKzCfXfoKarq+ur+NhD1owLcNrt9h3OTXGrqKgoKJ6hoaFD5DhuIA43xiGyJoWFhUGKxYXaL3CNGtH39PR8Zg9jzREfH+8vKCgI4krDRu0GcGVnZ78ZGBg4ER/Wf+4OVzOMRhrwFE6ysrLe0EQzaopII65RI3p478lVp6am7uDmPJY11F44HI7dsrKyfc5Nnj1km5Lo6Oiw4cdnD1kLJSUl++np6btsQjhmzayB5x29uGp3fn5+EPMw66eBX8b3yHZlDdyRdtzN75F1LED7kR6gMA7E6HsMrqpogbv5KngM9Bk8MbTKwAYmQSiCdhd4wW0VazQ0NNwEXrALNDHGS+A2UFHIA3smj/rX4JvrT7GBSRDi/J8Db8e/JY/5jLj4Y3KxgfPfwHc53iL+IQDMOgAAAABJRU5ErkJggg=="> + Admin + </h3> - <form action="{{ path('_profiler_import') }}" method="post" enctype="multipart/form-data"> - {% if token is not empty %} - <div style="margin-bottom: 10px"> - » <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a> - </div> - <div style="margin-bottom: 10px"> - » <a href="{{ path('_profiler_export', { 'token': token }) }}">Export</a> - </div> - {% endif %} - » <label for="file">Import</label><br> - <input type="file" name="file" id="file"><br> - <button type="submit" class="sf-button"> - <span class="border-l"> - <span class="border-r"> - <span class="btn-bg">UPLOAD</span> - </span> - </span> - </button> - <div class="clear-fix"></div> - </form> -</div> + <div style="margin-bottom: 10px">» <a href="{{ path('_profiler_purge', { 'token': token }) }}">Purge</a></div> + </div> +{% endif %}
src/Symfony/Bundle/WebProfilerBundle/Tests/Command/ExportCommandTest.php+53 −0 added@@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Command; + +use Symfony\Bundle\WebProfilerBundle\Command\ExportCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class ExportCommandTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \LogicException + */ + public function testExecuteWithUnknownToken() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $command = new ExportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('token' => 'TOKEN')); + } + + public function testExecuteWithToken() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $profile = new Profile('TOKEN'); + $profiler->expects($this->once())->method('loadProfile')->with('TOKEN')->will($this->returnValue($profile)); + + $command = new ExportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('token' => 'TOKEN')); + $this->assertEquals($profiler->export($profile), $commandTester->getDisplay()); + } +}
src/Symfony/Bundle/WebProfilerBundle/Tests/Command/ImportCommandTest.php+36 −0 added@@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\Command; + +use Symfony\Bundle\WebProfilerBundle\Command\ImportCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class ImportCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testExecute() + { + $profiler = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler') + ->disableOriginalConstructor() + ->getMock() + ; + + $profiler->expects($this->once())->method('import')->will($this->returnValue(new Profile('TOKEN'))); + + $command = new ImportCommand($profiler); + $commandTester = new CommandTester($command); + $commandTester->execute(array('filename' => __DIR__.'/../Fixtures/profile.data')); + $this->assertRegExp('/Profile "TOKEN" has been successfully imported\./', $commandTester->getDisplay()); + } +}
src/Symfony/Bundle/WebProfilerBundle/Tests/Fixtures/profile.data+1 −0 added@@ -0,0 +1 @@ +Tzo0NToiU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlIjo4OntzOjUyOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHRva2VuIjtzOjU6IlRPS0VOIjtzOjUzOiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAHBhcmVudCI7TjtzOjU1OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNoaWxkcmVuIjthOjA6e31zOjU3OiIAU3ltZm9ueVxDb21wb25lbnRcSHR0cEtlcm5lbFxQcm9maWxlclxQcm9maWxlAGNvbGxlY3RvcnMiO2E6MDp7fXM6NDk6IgBTeW1mb255XENvbXBvbmVudFxIdHRwS2VybmVsXFByb2ZpbGVyXFByb2ZpbGUAaXAiO047czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQBtZXRob2QiO047czo1MDoiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB1cmwiO047czo1MToiAFN5bWZvbnlcQ29tcG9uZW50XEh0dHBLZXJuZWxcUHJvZmlsZXJcUHJvZmlsZQB0aW1lIjtOO30= \ No newline at end of file
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-v35g-4rrw-h4fwghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/symfony/CVE-2014-6072.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/symfony/web-profiler-bundle/CVE-2014-6072.yamlghsaWEB
- github.com/symfony/symfony/commit/f38536ab79058f6a934426c41170256ba9623a02ghsaWEB
- github.com/symfony/symfony/pull/11832ghsaWEB
- github.com/symfony/web-profiler-bundle/commit/5b589ba83faf7eb20cec50725cd657075aebdd36ghsaWEB
- symfony.com/cve-2014-6072ghsaWEB
News mentions
0No linked articles in our index yet.