CVE-2024-28191 (published 2024-04-09T14:15Z)

Contao is an open source content management system. Starting in version 4.0.0 and prior to version 4.13.40 and 5.3.4, it is possible to inject insert tags in frontend forms if the output is structured in a very specific way. Contao versions 4.13.40 and 5.3.4 have a patch for this issue. As a workaround, do not output user data from frontend forms next to each other, always separate them by at least one character.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/contao/contao
commit_details: repo_url: https://github.com/contao/contao
author: Martin Auswöger
author_date: 2024-04-09 07:19:09+02:00
msg: Merge pull request from GHSA-747v-52c4-8vj8

# Conflicts:
#	core-bundle/src/String/SimpleTokenParser.php
#	core-bundle/tests/Contao/InputTest.php

Co-authored-by: Leo Feyer <1192057+leofeyer@users.noreply.github.com>
repository: repo_url: https://github.com/contao/contao
repo_name: contao/contao
description: Contao Open Source CMS
repo_language: PHP
owner: contao
file_changes: filename: Input.php
change_type: ModificationType.MODIFY
diff: @@ -964,7 +964,10 @@ class Input
 			return $varValue;
 		}
 
-		return str_replace(array('{{', '}}'), array('&#123;&#123;', '&#125;&#125;'), (string) $varValue);
+		$varValue = str_replace(array('{{', '}}'), array('&#123;&#123;', '&#125;&#125;'), (string) $varValue);
+
+		// Encode single curly braces at the beginning and end of the string
+		return preg_replace(array('/^(\s*)\{|\{(\s*)$/', '/^(\s*)\}|\}(\s*)$/'), array('$1&#123;$2', '$1&#125;$2'), $varValue);
 	}
 
 	/**

programming_language: PHP
filename: SimpleTokenParser.php
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@ declare(strict_types=1);
 
 namespace Contao\CoreBundle\String;
 
+use Contao\Input;
 use Contao\StringUtil;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
@@ -128,7 +129,7 @@ class SimpleTokenParser implements LoggerAwareInterface
                     return '##'.$matches[1].'##';
                 }
 
-                return $data[$matches[1]];
+                return Input::encodeInsertTags($data[$matches[1]]);
             },
             $subject
         );

programming_language: PHP
filename: SimpleTokenParserTest.php
change_type: ModificationType.MODIFY
diff: @@ -145,16 +145,34 @@ class SimpleTokenParserTest extends TestCase
             'This is my ',
         ];
 
+        yield 'Test regular curly braces do not get encoded' => [
+            '##token##',
+            ['token' => 'foo { bar } baz'],
+            'foo { bar } baz',
+        ];
+
         yield 'Test if-tags insertion not evaluated' => [
             '##token##',
             ['token' => '{if token=="foo"}'],
-            '{if token=="foo"}',
+            '&#123;if token=="foo"&#125;',
+        ];
+
+        yield 'Test insert tags insertion not possible' => [
+            '##token##',
+            ['token' => '{{date}}'],
+            '&#123;&#123;date&#125;&#125;',
         ];
 
         yield 'Test if-tags insertion not evaluated with multiple tokens' => [
             '##token1####token2####token3##',
             ['token1' => '{', 'token2' => 'if', 'token3' => ' token=="foo"}'],
-            '{if token=="foo"}',
+            '&#123;if token=="foo"&#125;',
+        ];
+
+        yield 'Test insert tags insertion not possible with multiple tokens' => [
+            '##token1####token2####token3##',
+            ['token1' => '{', 'token2' => '{date}', 'token3' => '}'],
+            '&#123;&#123;date&#125;&#125;',
         ];
 
         yield 'Test escaping works correctly' => [

programming_language: Perl
repo_url: https://github.com/contao/contao
commit_details: repo_url: https://github.com/contao/contao
author: Martin Auswöger
author_date: 2024-04-09 07:19:09+02:00
msg: Merge pull request from GHSA-747v-52c4-8vj8

Co-authored-by: Leo Feyer <1192057+leofeyer@users.noreply.github.com>
repository: repo_url: https://github.com/contao/contao
repo_name: contao/contao
description: Contao Open Source CMS
repo_language: PHP
owner: contao
file_changes: filename: Input.php
change_type: ModificationType.MODIFY
diff: @@ -1059,7 +1059,10 @@ class Input
 			return $varValue;
 		}
 
-		return str_replace(array('{{', '}}'), array('&#123;&#123;', '&#125;&#125;'), (string) $varValue);
+		$varValue = str_replace(array('{{', '}}'), array('&#123;&#123;', '&#125;&#125;'), (string) $varValue);
+
+		// Encode single curly braces at the beginning and end of the string
+		return preg_replace(array('/^(\s*)\{|\{(\s*)$/', '/^(\s*)\}|\}(\s*)$/'), array('$1&#123;$2', '$1&#125;$2'), $varValue);
 	}
 
 	/**

programming_language: PHP
filename: SimpleTokenParser.php
change_type: ModificationType.MODIFY
diff: @@ -12,6 +12,7 @@ declare(strict_types=1);
 
 namespace Contao\CoreBundle\String;
 
+use Contao\Input;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use Psr\Log\LogLevel;
@@ -108,7 +109,7 @@ class SimpleTokenParser implements LoggerAwareInterface
                     return '##'.$matches[1].'##';
                 }
 
-                return $data[$matches[1]];
+                return Input::encodeInsertTags($data[$matches[1]]);
             },
             $subject,
         );

programming_language: PHP
filename: InputTest.php
change_type: ModificationType.MODIFY
diff: @@ -168,8 +168,8 @@ class InputTest extends TestCase
      */
     public function testEncodesInsertTags(): void
     {
-        $source = '{{ foo }}';
-        $encoded = '&#123;&#123; foo &#125;&#125;';
+        $source = ' {{ foo }} { bar } ';
+        $encoded = ' &#123;&#123; foo &#125;&#125; { bar &#125; ';
 
         $_GET = $_POST = $_COOKIE = [
             'key' => $source,
@@ -327,14 +327,14 @@ class InputTest extends TestCase
      *
      * @group legacy
      */
-    public function testEncodeNoneMode(string $source, string $expected, string|null $expectedEncoded = null): void
+    public function testEncodeNoneMode(string $source, string $expected, string|null $expectedEncoded = null, string|null $expectedEncodedDouble = null): void
     {
         $expectedEncoded ??= $expected;
 
         $this->assertSame($expected, Input::encodeInput($source, InputEncodingMode::encodeNone, false));
         $this->assertSame($expectedEncoded, Input::encodeInput($source, InputEncodingMode::encodeNone));
         $this->assertSame($expected.$expected, Input::encodeInput($source.$source, InputEncodingMode::encodeNone, false));
-        $this->assertSame($expectedEncoded.$expectedEncoded, Input::encodeInput($source.$source, InputEncodingMode::encodeNone));
+        $this->assertSame($expectedEncodedDouble ?? $expectedEncoded.$expectedEncoded, Input::encodeInput($source.$source, InputEncodingMode::encodeNone));
 
         System::getContainer()->set('request_stack', $stack = new RequestStack());
         $stack->push(new Request([], ['key' => $source]));
@@ -355,10 +355,12 @@ class InputTest extends TestCase
         yield ['foo', 'foo'];
         yield ['\X \0 \X', '\X &#92;0 \X'];
         yield ["a\rb\r\nc\n\rd\ne", "a\nb\nc\n\nd\ne"];
-        yield ['{}', '{}'];
+        yield ['{}', '{}', '&#123;&#125;', '&#123;}{&#125;'];
         yield ['{{}}', '{{}}', '&#123;&#123;&#125;&#125;'];
-        yield ['{{{}}}', '{{{}}}', '&#123;&#123;{&#125;&#125;}'];
+        yield ['{{{}}}', '{{{}}}', '&#123;&#123;{&#125;&#125;&#125;', '&#123;&#123;{&#125;&#125;}&#123;&#123;{&#125;&#125;&#125;'];
         yield ['{{{{}}}}', '{{{{}}}}', '&#123;&#123;&#123;&#123;&#125;&#125;&#125;&#125;'];
+        yield ['{ start {and} end }', '{ start {and} end }', '&#123; start {and} end &#125;', '&#123; start {and} end }{ start {and} end &#125;'];
+        yield ["\n\t { foo }\n\t ", "\n\t { foo }\n\t ", "\n\t &#123; foo &#125;\n\t ", "\n\t &#123; foo }\n\t \n\t { foo &#125;\n\t "];
         yield ["\0", "\u{FFFD}"];
         yield ["\x80", "\u{FFFD}"];
         yield ["\xFF", "\u{FFFD}"];

programming_language: PHP
filename: SimpleTokenParserTest.php
change_type: ModificationType.MODIFY
diff: @@ -145,16 +145,34 @@ class SimpleTokenParserTest extends TestCase
             'This is my ',
         ];
 
+        yield 'Test regular curly braces do not get encoded' => [
+            '##token##',
+            ['token' => 'foo { bar } baz'],
+            'foo { bar } baz',
+        ];
+
         yield 'Test if-tags insertion not evaluated' => [
             '##token##',
             ['token' => '{if token=="foo"}'],
-            '{if token=="foo"}',
+            '&#123;if token=="foo"&#125;',
+        ];
+
+        yield 'Test insert tags insertion not possible' => [
+            '##token##',
+            ['token' => '{{date}}'],
+            '&#123;&#123;date&#125;&#125;',
         ];
 
         yield 'Test if-tags insertion not evaluated with multiple tokens' => [
             '##token1####token2####token3##',
             ['token1' => '{', 'token2' => 'if', 'token3' => ' token=="foo"}'],
-            '{if token=="foo"}',
+            '&#123;if token=="foo"&#125;',
+        ];
+
+        yield 'Test insert tags insertion not possible with multiple tokens' => [
+            '##token1####token2####token3##',
+            ['token1' => '{', 'token2' => '{date}', 'token3' => '}'],
+            '&#123;&#123;date&#125;&#125;',
         ];
 
         yield 'Test escaping works correctly' => [

programming_language: Perl