Issue #2489024 by lauriii, Fabianx, Gábor Hojtsy, Cottser, dawehner, zeropx: Arbitrary code execution via 'trans' extension for dynamic twig templates (when debug output is on)
parent
4e787ea3d9
commit
2ac614c25a
|
@ -82,20 +82,7 @@ class TwigNodeTrans extends \Twig_Node {
|
||||||
// Write function closure.
|
// Write function closure.
|
||||||
$compiler->raw(')');
|
$compiler->raw(')');
|
||||||
|
|
||||||
// Append translation debug markup, if necessary.
|
// @todo Add debug output, see https://www.drupal.org/node/2512672
|
||||||
if ($compiler->getEnvironment()->isDebug()) {
|
|
||||||
$compiler->raw(" . '\n<!-- TRANSLATION: ");
|
|
||||||
$compiler->subcompile($singular);
|
|
||||||
if (!empty($plural)) {
|
|
||||||
$compiler->raw(', PLURAL: ')->subcompile($plural);
|
|
||||||
}
|
|
||||||
if (!empty($options)) {
|
|
||||||
foreach ($options->getKeyValuePairs() as $pair) {
|
|
||||||
$compiler->raw(', ' . Unicode::strtoupper($pair['key']->getAttribute('value')) . ': ')->subcompile($pair['value']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$compiler->raw(" -->\n'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// End writing.
|
// End writing.
|
||||||
$compiler->raw(";\n");
|
$compiler->raw(";\n");
|
||||||
|
|
|
@ -83,8 +83,26 @@ class TwigTransTest extends WebTestBase {
|
||||||
* Test Twig "trans" tags.
|
* Test Twig "trans" tags.
|
||||||
*/
|
*/
|
||||||
public function testTwigTransTags() {
|
public function testTwigTransTags() {
|
||||||
|
// Run this once without and once with Twig debug because trans can work
|
||||||
|
// differently depending on that setting.
|
||||||
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
|
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
|
||||||
|
$this->assertTwigTransTags();
|
||||||
|
|
||||||
|
// Enable debug, rebuild the service container, and clear all caches.
|
||||||
|
$parameters = $this->container->getParameter('twig.config');
|
||||||
|
$parameters['debug'] = TRUE;
|
||||||
|
$this->setContainerParameter('twig.config', $parameters);
|
||||||
|
$this->rebuildContainer();
|
||||||
|
$this->resetAll();
|
||||||
|
|
||||||
|
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
|
||||||
|
$this->assertTwigTransTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts Twig trans tags.
|
||||||
|
*/
|
||||||
|
protected function assertTwigTransTags() {
|
||||||
$this->assertText(
|
$this->assertText(
|
||||||
'OH HAI SUNZ',
|
'OH HAI SUNZ',
|
||||||
'{% trans "Hello sun." %} was successfully translated.'
|
'{% trans "Hello sun." %} was successfully translated.'
|
||||||
|
@ -154,71 +172,9 @@ class TwigTransTest extends WebTestBase {
|
||||||
'O HAI NU TXTZZZZ.',
|
'O HAI NU TXTZZZZ.',
|
||||||
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %} was successfully translated with context in specified language.'
|
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %} was successfully translated with context in specified language.'
|
||||||
);
|
);
|
||||||
|
// Makes sure https://www.drupal.org/node/2489024 doesn't happen without
|
||||||
// Ensure debug output does not print.
|
// twig debug.
|
||||||
$this->checkForDebugMarkup(FALSE);
|
$this->assertNoText(pi(), 'Running php code inside a Twig trans is not possible.');
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test Twig "trans" debug markup.
|
|
||||||
*/
|
|
||||||
public function testTwigTransDebug() {
|
|
||||||
// Enable debug, rebuild the service container, and clear all caches.
|
|
||||||
$parameters = $this->container->getParameter('twig.config');
|
|
||||||
$parameters['debug'] = TRUE;
|
|
||||||
$this->setContainerParameter('twig.config', $parameters);
|
|
||||||
$this->rebuildContainer();
|
|
||||||
$this->resetAll();
|
|
||||||
|
|
||||||
// Get page for assertion testing.
|
|
||||||
$this->drupalGet('twig-theme-test/trans', array('language' => \Drupal::languageManager()->getLanguage('xx')));
|
|
||||||
|
|
||||||
// Ensure debug output is printed.
|
|
||||||
$this->checkForDebugMarkup(TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests rendering a placeholder outside of translate.
|
|
||||||
*
|
|
||||||
* This test ensures that the security problem described in
|
|
||||||
* https://www.drupal.org/node/2495179 doesn't exist.
|
|
||||||
*/
|
|
||||||
public function testPlaceholderOutsideOfTrans() {
|
|
||||||
$this->drupalGet(Url::fromRoute('twig_theme_test.placeholder_outside_trans'));
|
|
||||||
|
|
||||||
$script = '<script>alert(123);</script>';
|
|
||||||
$this->assertNoRaw($script);
|
|
||||||
$this->assertEqual(2, substr_count($this->getRawContent(), '<em class="placeholder">' . SafeMarkup::checkPlain($script) . '</em>'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function: test twig debug translation markup.
|
|
||||||
*
|
|
||||||
* @param bool $visible
|
|
||||||
* Toggle determining which assertion to use for test.
|
|
||||||
*/
|
|
||||||
protected function checkForDebugMarkup($visible) {
|
|
||||||
$tests = array(
|
|
||||||
'{% trans "Hello sun." %}' => '<!-- TRANSLATION: "Hello sun." -->',
|
|
||||||
'{% trans "Hello sun." with {"context": "Lolspeak"} %}' => '<!-- TRANSLATION: "Hello sun.", CONTEXT: "Lolspeak" -->',
|
|
||||||
'{{ "Hello moon."|trans }}' => '<!-- TRANSLATION: "Hello moon." -->',
|
|
||||||
'{% trans %} with {% plural %}' => '<!-- TRANSLATION: "Hello star.", PLURAL: "Hello @count stars." -->',
|
|
||||||
'{{ token }}' => '<!-- TRANSLATION: "Escaped: @string" -->',
|
|
||||||
'{{ token|passthrough }}' => '<!-- TRANSLATION: "Pass-through: !string" -->',
|
|
||||||
'{{ token|placeholder }}' => '<!-- TRANSLATION: "Placeholder: %string" -->',
|
|
||||||
'{{ complex.tokens }}' => '<!-- TRANSLATION: "This @token.name has a length of: @count. It contains: %token.numbers and @token.bad_text. Lets pass the bad text through: !token.bad_text." -->',
|
|
||||||
'{% trans with {"context": "Lolspeak"} %}I have context.{% endtrans %}' => '<!-- TRANSLATION: "I have context.", CONTEXT: "Lolspeak" -->',
|
|
||||||
'{% trans with {"langcode": "zz"} %}Hello new text.{% endtrans %}' => '<!-- TRANSLATION: "Hello new text.", LANGCODE: "zz" -->',
|
|
||||||
'{% trans with {"context": "Lolspeak", "langcode": "zz"} %}Hello new text.{% endtrans %}' => '<!-- TRANSLATION: "Hello new text.", CONTEXT: "Lolspeak", LANGCODE: "zz" -->',
|
|
||||||
);
|
|
||||||
foreach ($tests as $test => $markup) {
|
|
||||||
if ($visible) {
|
|
||||||
$this->assertRaw($markup, "Twig debug translation markup exists in source for: $test");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$this->assertNoRaw($markup, "Twig debug translation markup does not exist in source for: $test");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -94,3 +94,10 @@
|
||||||
Hello new text.
|
Hello new text.
|
||||||
{% endtrans %}
|
{% endtrans %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Tests that https://www.drupal.org/node/2489024 doesn't happen without twig debug. #}
|
||||||
|
<div>
|
||||||
|
{% trans %}
|
||||||
|
Number I never remember: ' . print(pi()) . '
|
||||||
|
{% endtrans %}
|
||||||
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue