Skip to content

Commit

Permalink
[SECURITY] Prohibit TypoScript in form yaml files
Browse files Browse the repository at this point in the history
Only evaluate TypoScript-like instructions like

```
submitButtonLabel = TEXT
submitButtonLabel.value = Bar
```

defined within

`plugin.tx_form.settings.formDefinitionOverrides`
and
`plugin.tx_form.settings.yamlSettingsOverrides`

and **not** within form definition yaml files or
the form setup yaml files.

This is achieved by not searching the entire form
definition or form setup for TypoScript instructions,
but only the actual TypoScript.

Resolves: #98403
Releases: main, 11.5, 10.4
Change-Id: I7b066f109d6061715c2240b01ed15185c58fa9f5
Security-Bulletin: TYPO3-CORE-SA-2022-015
Security-References: CVE-2022-23503
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77102
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
  • Loading branch information
waldhacker1 authored and ohader committed Dec 13, 2022
1 parent d9ffbf2 commit 1302e88
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 10 deletions.
Expand Up @@ -122,17 +122,18 @@ protected function overrideConfigurationByTypoScript(array $yamlSettings, string
{
$typoScript = parent::getConfiguration(self::CONFIGURATION_TYPE_SETTINGS, $extensionName);
if (is_array($typoScript['yamlSettingsOverrides'] ?? null) && !empty($typoScript['yamlSettingsOverrides'])) {
ArrayUtility::mergeRecursiveWithOverrule(
$yamlSettings,
$typoScript['yamlSettingsOverrides']
);

$yamlSettingsOverrides = $typoScript['yamlSettingsOverrides'];
if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
) {
$yamlSettings = GeneralUtility::makeInstance(TypoScriptService::class)
->resolvePossibleTypoScriptConfiguration($yamlSettings);
$yamlSettingsOverrides = GeneralUtility::makeInstance(TypoScriptService::class)
->resolvePossibleTypoScriptConfiguration($yamlSettingsOverrides);
}

ArrayUtility::mergeRecursiveWithOverrule(
$yamlSettings,
$yamlSettingsOverrides
);
}
return $yamlSettings;
}
Expand Down
Expand Up @@ -626,12 +626,13 @@ public function isAllowedPersistencePath(string $persistencePath): bool
protected function overrideByTypoScriptSettings(array $formDefinition): array
{
if (!empty($this->typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']] ?? null)) {
$formDefinitionOverrides = GeneralUtility::makeInstance(TypoScriptService::class)
->resolvePossibleTypoScriptConfiguration($this->typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']]);

ArrayUtility::mergeRecursiveWithOverrule(
$formDefinition,
$this->typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']]
$formDefinitionOverrides
);
$formDefinition = GeneralUtility::makeInstance(TypoScriptService::class)
->resolvePossibleTypoScriptConfiguration($formDefinition);
}

return $formDefinition;
Expand Down
@@ -0,0 +1,144 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Configuration;

use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager;
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManager;
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Form\Mvc\Configuration\TypoScriptService;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

class ConfigurationManagerTest extends UnitTestCase
{
protected bool $resetSingletonInstances = true;

public function tearDown(): void
{
unset($GLOBALS['TYPO3_REQUEST']);
parent::tearDown();
}

/**
* @test
*/
public function getConfigurationDoesNotEvaluateTypoScriptLookalikeInstructionsFromYamlSettingsInFrontendContext(): void
{
$yamlSettings = [
'prototypes' => [
'standard' => [
'formElementsDefinition' => [
'Form' => [
'renderingOptions' => [
'submitButtonLabel' => 'Foo',
'templateVariant' => 'version1',
'addQueryString' => [
'value' => 'Baz',
'_typoScriptNodeValue' => 'TEXT',
],
],
],
],
],
],
];

$formTypoScript = [
'settings' => [
'yamlSettingsOverrides' => [
'prototypes' => [
'standard' => [
'formElementsDefinition' => [
'Form' => [
'renderingOptions' => [
'submitButtonLabel' => [
'value' => 'Bar',
'_typoScriptNodeValue' => 'TEXT',
],
],
],
],
],
],
],
],
];

$evaluatedFormTypoScript = [
'prototypes' => [
'standard' => [
'formElementsDefinition' => [
'Form' => [
'renderingOptions' => [
'submitButtonLabel' => 'Bar',
],
],
],
],
],
];

$expected = [
'prototypes' => [
'standard' => [
'formElementsDefinition' => [
'Form' => [
'renderingOptions' => [
'submitButtonLabel' => 'Bar',
'templateVariant' => 'version1',
'addQueryString' => [
'value' => 'Baz',
'_typoScriptNodeValue' => 'TEXT',
],
],
],
],
],
],
];

$configurationManagerMock = $this->getAccessibleMock(ConfigurationManager::class, [
'getTypoScriptSettings',
'getYamlSettingsFromCache',
], [], '', false);
$configurationManagerMock->method('getYamlSettingsFromCache')->with(self::anything())->willReturn($yamlSettings);

$serverRequest = (new ServerRequest())->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
$GLOBALS['TYPO3_REQUEST'] = $serverRequest;

$typoScriptServiceMock = $this->createMock(TypoScriptService::class);
$typoScriptServiceMock
->method('resolvePossibleTypoScriptConfiguration')
->with($formTypoScript['settings']['yamlSettingsOverrides'])
->willReturn($evaluatedFormTypoScript);
GeneralUtility::addInstance(TypoScriptService::class, $typoScriptServiceMock);

$frontendConfigurationManager = $this->createMock(FrontendConfigurationManager::class);
$frontendConfigurationManager
->method('getConfiguration')
->with('form', null)
->willReturn($formTypoScript);

$configurationManagerMock->_set('concreteConfigurationManager', $frontendConfigurationManager);
$configurationManagerMock->method('getTypoScriptSettings')->with(self::anything())->willReturn([]);

self::assertSame($expected, $configurationManagerMock->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_YAML_SETTINGS, 'form'));
}
}
Expand Up @@ -734,6 +734,104 @@ public function overrideByTypoScriptSettingsReturnsOverriddenConfigurationIfTypo
self::assertSame($expected, $mockController->_call('overrideByTypoScriptSettings', $input));
}

/**
* @test
*/
public function overrideByTypoScriptSettingsDoesNotEvaluateTypoScriptLookalikeInstructionsFromYamlSettings(): void
{
$formDefinitionYaml = [
'identifier' => 'ext-form-identifier',
'prototypeName' => 'standard',
'label' => [
'value' => 'Label override',
'_typoScriptNodeValue' => 'TEXT',
],
'renderables' => [
0 => [
'identifier' => 'page-1',
'type' => 'Page',
'label' => 'Label',
'renderables' => [
0 => [
'identifier' => 'text-1',
'type' => 'Text',
'label' => 'Label',
],
],
],
],
];

$formTypoScript = [
'formDefinitionOverrides' => [
'ext-form-identifier' => [
'renderables' => [
0 => [
'renderables' => [
0 => [
'label' => [
'value' => 'Label override',
'_typoScriptNodeValue' => 'TEXT',
],
],
],
],
],
],
],
];

$evaluatedFormTypoScript = [
'renderables' => [
0 => [
'renderables' => [
0 => [
'label' => 'Label override',
],
],
],
],
];

$expected = [
'identifier' => 'ext-form-identifier',
'prototypeName' => 'standard',
'label' => [
'value' => 'Label override',
'_typoScriptNodeValue' => 'TEXT',
],
'renderables' => [
0 => [
'identifier' => 'page-1',
'type' => 'Page',
'label' => 'Label',
'renderables' => [
0 => [
'identifier' => 'text-1',
'type' => 'Text',
'label' => 'Label override',
],
],
],
],
];

$controllerMock = $this->getAccessibleMock(FormPersistenceManager::class, [
'dummy',
], [], '', false);

$typoScriptServiceMock = $this->createMock(TypoScriptService::class);
$typoScriptServiceMock
->method('resolvePossibleTypoScriptConfiguration')
->with($formTypoScript['formDefinitionOverrides']['ext-form-identifier'])
->willReturn($evaluatedFormTypoScript);
GeneralUtility::addInstance(TypoScriptService::class, $typoScriptServiceMock);

$controllerMock->_set('typoScriptSettings', $formTypoScript);

self::assertSame($expected, $controllerMock->_call('overrideByTypoScriptSettings', $formDefinitionYaml));
}

/**
* @test
*/
Expand Down

0 comments on commit 1302e88

Please sign in to comment.