mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-13 10:56:38 +00:00
Encode single quotes as \x27 in Compiler::string()
This is a defense-in-depth measure: callers must always concatenate the
result into a double-quoted PHP context, but if one ever (mistakenly)
embeds it inside a single-quoted PHP literal, an attacker-controlled
single quote in the source value could break out of that context. The
previous commit fixed exactly such a bug in ModuleNode for the {% use %}
template name.
Encoding ' as the hex escape \x27 guarantees that the emitted PHP source
never contains a literal single quote derived from user input, while the
decoded runtime value is unchanged. \' is not used because it is not a
recognized escape sequence in PHP double-quoted strings (the backslash
would be kept literally).
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 3.26.0 (2026-XX-XX)
|
||||
|
||||
* n/a
|
||||
* Encode single quotes as `\x27` in `Compiler::string()` as a defense-in-depth measure
|
||||
|
||||
# 3.25.0 (2026-05-17)
|
||||
|
||||
|
||||
+7
-1
@@ -143,7 +143,13 @@ class Compiler
|
||||
*/
|
||||
public function string(string $value)
|
||||
{
|
||||
$this->source .= \sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
|
||||
// Single quotes are encoded as \x27 (not \') as a defense-in-depth measure:
|
||||
// it guarantees that the compiled output never contains a literal "'" derived
|
||||
// from user input, which prevents breaking out of a surrounding single-quoted
|
||||
// PHP context if a caller mistakenly concatenates the result into one.
|
||||
// \' is not a recognized escape sequence in PHP double-quoted strings (the
|
||||
// backslash would be kept literally), so \x27 is used instead.
|
||||
$this->source .= \sprintf('"%s"', str_replace("'", '\\x27', addcslashes($value, "\0\t\"\$\\")));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,24 @@ use Twig\Loader\ArrayLoader;
|
||||
|
||||
class CompilerTest extends TestCase
|
||||
{
|
||||
public function testStringEncodesSingleQuotesAsHexEscape()
|
||||
{
|
||||
$compiler = new Compiler(new Environment(new ArrayLoader()));
|
||||
|
||||
// Defense in depth: a single quote in the source value must NOT appear as a
|
||||
// literal "'" in the compiled output, so that even if a caller mistakenly
|
||||
// concatenates the result into a single-quoted PHP string, the value cannot
|
||||
// break out of that context. It must still decode back to the original byte.
|
||||
$source = $compiler->string("it's \"a\" test")->getSource();
|
||||
|
||||
$this->assertStringNotContainsString("'", $source);
|
||||
$this->assertSame('"it\\x27s \\"a\\" test"', $source);
|
||||
|
||||
$decoded = null;
|
||||
eval('$decoded = '.$source.';');
|
||||
$this->assertSame("it's \"a\" test", $decoded);
|
||||
}
|
||||
|
||||
public function testReprNumericValueWithLocale()
|
||||
{
|
||||
$compiler = new Compiler(new Environment(new ArrayLoader()));
|
||||
|
||||
Reference in New Issue
Block a user