Safe escaping of fragments for eval()

This commit is contained in:
Nicolas Grekas 2015-03-16 15:12:02 +01:00 committed by Fabien Potencier
parent 9215c222ff
commit 195c57e1f5
2 changed files with 33 additions and 33 deletions

View File

@ -29,6 +29,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
class Esi
{
private $contentTypes;
private $phpEscapeMap = array(
array('<?', '<%', '<s', '<S'),
array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>', '<?php echo "<s"; ?>', '<?php echo "<S"; ?>'),
);
/**
* Constructor.
@ -158,10 +162,34 @@ class Esi
// we don't use a proper XML parser here as we can have ESI tags in a plain text response
$content = $response->getContent();
$content = str_replace(array('<?', '<%'), array('<?php echo "<?"; ?>', '<?php echo "<%"; ?>'), $content);
$content = preg_replace_callback('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', array($this, 'handleEsiIncludeTag'), $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#', '', $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
$i = 1;
while (isset($chunks[$i])) {
$options = array();
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
}
if (!isset($options['src'])) {
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
$chunks[$i] = sprintf('<?php echo $this->esi->handle($this, %s, %s, %s) ?>'."\n",
var_export($options['src'], true),
var_export(isset($options['alt']) ? $options['alt'] : '', true),
isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false'
);
++$i;
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
++$i;
}
$content = implode('', $chunks);
$response->setContent($content);
$response->headers->set('X-Body-Eval', 'ESI');
@ -214,32 +242,4 @@ class Esi
}
}
}
/**
* Handles an ESI include tag (called internally).
*
* @param array $attributes An array containing the attributes.
*
* @return string The response content for the include.
*
* @throws \RuntimeException
*/
private function handleEsiIncludeTag($attributes)
{
$options = array();
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $attributes[1], $matches, PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
}
if (!isset($options['src'])) {
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
return sprintf('<?php echo $this->esi->handle($this, %s, %s, %s) ?>'."\n",
var_export($options['src'], true),
var_export(isset($options['alt']) ? $options['alt'] : '', true),
isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false'
);
}
}

View File

@ -124,10 +124,10 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$esi = new Esi();
$request = Request::create('/');
$response = new Response('foo <?php die("foo"); ?><%= "lala" %>');
$response = new Response('<?php <? <% <script language=php>');
$esi->process($request, $response);
$this->assertEquals('foo <?php echo "<?"; ?>php die("foo"); ?><?php echo "<%"; ?>= "lala" %>', $response->getContent());
$this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent());
}
/**