Contao may have unencoded insert tags in the frontend
Description
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2024-28191 allows insert tag injection in Contao CMS frontend forms when user output is adjacent, patched in versions 4.13.40 and 5.3.4.
Vulnerability
Details CVE-2024-28191 is an insert tag injection vulnerability in Contao CMS (versions 4.0.0 to before 4.13.40 and 5.3.4). The root cause is insufficient encoding of insert tags when user input is output in frontend forms without any separating characters. The fix involves encoding curly braces that could be misinterpreted as insert tag delimiters [1][3][4].
Exploitation
An attacker can exploit this vulnerability by submitting crafted input through frontend forms. The attack requires that the output places user-supplied data directly adjacent to each other (e.g., two separate form fields output consecutively). No authentication is needed as frontend forms are typically public. The specific structure needed makes exploitation non-trivial but possible [1].
Impact
Successful exploitation allows an attacker to inject arbitrary insert tags into the rendered page. In Contao, insert tags can include dynamic content, execute PHP code, or retrieve sensitive information. This could lead to information disclosure, privilege escalation, or remote code execution, depending on the tags used [1].
Mitigation
Contao has released patches in versions 4.13.40 and 5.3.4 that properly encode insert tags in all contexts. As a workaround, administrators should ensure that user data from different form fields is not output consecutively; insert at least one character (e.g., a space) between them. Users are strongly advised to update to the latest patched version [1][3][4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
contao/core-bundlePackagist | >= 4.0.0, < 4.13.40 | 4.13.40 |
contao/core-bundlePackagist | >= 5.0.0-RC1, < 5.3.4 | 5.3.4 |
Affected products
2- contao/contaov5Range: >= 4.0.0, < 4.13.40
Patches
2474a2fc25f1dMerge pull request from GHSA-747v-52c4-8vj8
4 files changed · +34 −10
core-bundle/contao/library/Contao/Input.php+4 −1 modified@@ -1059,7 +1059,10 @@ public static function encodeInsertTags($varValue) return $varValue; } - return str_replace(array('{{', '}}'), array('{{', '}}'), (string) $varValue); + $varValue = str_replace(array('{{', '}}'), array('{{', '}}'), (string) $varValue); + + // Encode single curly braces at the beginning and end of the string + return preg_replace(array('/^(\s*)\{|\{(\s*)$/', '/^(\s*)\}|\}(\s*)$/'), array('$1{$2', '$1}$2'), $varValue); } /**
core-bundle/src/String/SimpleTokenParser.php+2 −1 modified@@ -12,6 +12,7 @@ namespace Contao\CoreBundle\String; +use Contao\Input; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LogLevel; @@ -108,7 +109,7 @@ function (array $matches) use ($data) { return '##'.$matches[1].'##'; } - return $data[$matches[1]]; + return Input::encodeInsertTags($data[$matches[1]]); }, $subject, );
core-bundle/tests/Contao/InputTest.php+8 −6 modified@@ -168,8 +168,8 @@ public function testBackendRoundtrip(string $source, string $expected, string|nu */ public function testEncodesInsertTags(): void { - $source = '{{ foo }}'; - $encoded = '{{ foo }}'; + $source = ' {{ foo }} { bar } '; + $encoded = ' {{ foo }} { bar } '; $_GET = $_POST = $_COOKIE = [ 'key' => $source, @@ -327,14 +327,14 @@ public function encodeInputProvider(): \Generator * * @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 @@ public function encodeNoneModeProvider(): \Generator yield ['foo', 'foo']; yield ['\X \0 \X', '\X \0 \X']; yield ["a\rb\r\nc\n\rd\ne", "a\nb\nc\n\nd\ne"]; - yield ['{}', '{}']; + yield ['{}', '{}', '{}', '{}{}']; yield ['{{}}', '{{}}', '{{}}']; - yield ['{{{}}}', '{{{}}}', '{{{}}}']; + yield ['{{{}}}', '{{{}}}', '{{{}}}', '{{{}}}{{{}}}']; yield ['{{{{}}}}', '{{{{}}}}', '{{{{}}}}']; + yield ['{ start {and} end }', '{ start {and} end }', '{ start {and} end }', '{ start {and} end }{ start {and} end }']; + yield ["\n\t { foo }\n\t ", "\n\t { foo }\n\t ", "\n\t { foo }\n\t ", "\n\t { foo }\n\t \n\t { foo }\n\t "]; yield ["\0", "\u{FFFD}"]; yield ["\x80", "\u{FFFD}"]; yield ["\xFF", "\u{FFFD}"];
core-bundle/tests/String/SimpleTokenParserTest.php+20 −2 modified@@ -145,16 +145,34 @@ public function parseSimpleTokensProvider(): \Generator '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"}', + '{if token=="foo"}', + ]; + + yield 'Test insert tags insertion not possible' => [ + '##token##', + ['token' => '{{date}}'], + '{{date}}', ]; yield 'Test if-tags insertion not evaluated with multiple tokens' => [ '##token1####token2####token3##', ['token1' => '{', 'token2' => 'if', 'token3' => ' token=="foo"}'], - '{if token=="foo"}', + '{if token=="foo"}', + ]; + + yield 'Test insert tags insertion not possible with multiple tokens' => [ + '##token1####token2####token3##', + ['token1' => '{', 'token2' => '{date}', 'token3' => '}'], + '{{date}}', ]; yield 'Test escaping works correctly' => [
388859dcf110Merge pull request from GHSA-747v-52c4-8vj8
3 files changed · +26 −4
core-bundle/src/Resources/contao/library/Contao/Input.php+4 −1 modified@@ -964,7 +964,10 @@ public static function encodeInsertTags($varValue) return $varValue; } - return str_replace(array('{{', '}}'), array('{{', '}}'), (string) $varValue); + $varValue = str_replace(array('{{', '}}'), array('{{', '}}'), (string) $varValue); + + // Encode single curly braces at the beginning and end of the string + return preg_replace(array('/^(\s*)\{|\{(\s*)$/', '/^(\s*)\}|\}(\s*)$/'), array('$1{$2', '$1}$2'), $varValue); } /**
core-bundle/src/String/SimpleTokenParser.php+2 −1 modified@@ -12,6 +12,7 @@ namespace Contao\CoreBundle\String; +use Contao\Input; use Contao\StringUtil; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; @@ -128,7 +129,7 @@ function (array $matches) use ($data) { return '##'.$matches[1].'##'; } - return $data[$matches[1]]; + return Input::encodeInsertTags($data[$matches[1]]); }, $subject );
core-bundle/tests/String/SimpleTokenParserTest.php+20 −2 modified@@ -145,16 +145,34 @@ public function parseSimpleTokensProvider(): \Generator '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"}', + '{if token=="foo"}', + ]; + + yield 'Test insert tags insertion not possible' => [ + '##token##', + ['token' => '{{date}}'], + '{{date}}', ]; yield 'Test if-tags insertion not evaluated with multiple tokens' => [ '##token1####token2####token3##', ['token1' => '{', 'token2' => 'if', 'token3' => ' token=="foo"}'], - '{if token=="foo"}', + '{if token=="foo"}', + ]; + + yield 'Test insert tags insertion not possible with multiple tokens' => [ + '##token1####token2####token3##', + ['token1' => '{', 'token2' => '{date}', 'token3' => '}'], + '{{date}}', ]; yield 'Test escaping works correctly' => [
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-747v-52c4-8vj8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-28191ghsaADVISORY
- contao.org/en/security-advisories/insert-tag-injection-via-the-form-generatorghsax_refsource_MISCWEB
- github.com/contao/contao/commit/388859dcf110ca70e0fae68a2a5579ab6a702919ghsax_refsource_MISCWEB
- github.com/contao/contao/commit/474a2fc25f1d84d786aba8c6d234af99e64d016bghsax_refsource_MISCWEB
- github.com/contao/contao/security/advisories/GHSA-747v-52c4-8vj8ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.