CVE-2024-22188
Description
TYPO3 before 13.0.1 allows an authenticated admin user (with system maintainer privileges) to execute arbitrary shell commands (with the privileges of the web server) via a command injection vulnerability in form fields of the Install Tool. The fixed versions are 8.7.57 ELTS, 9.5.46 ELTS, 10.4.43 ELTS, 11.5.35 LTS, 12.4.11 LTS, and 13.0.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
TYPO3 Install Tool command injection allows authenticated admin users with system maintainer privileges to execute arbitrary shell commands.
CVE-2024-22188 is a command injection vulnerability in TYPO3's Install Tool, affecting versions before 13.0.1 and various ELTS/LTS releases [3]. The root cause is insufficient input validation in form fields, allowing an authenticated admin user with system maintainer privileges to inject arbitrary shell commands [3].
The attack requires the attacker to have an authenticated admin account with system maintainer privileges. By crafting malicious input in specific Install Tool form fields, the attacker can execute shell commands with the permissions of the web server [3]. No additional authentication is needed once the admin session is established.
Successful exploitation allows the attacker to execute arbitrary commands on the underlying server, potentially leading to complete compromise of the TYPO3 installation and the host system [3].
TYPO3 has fixed this issue in versions 8.7.57 ELTS, 9.5.46 ELTS, 10.4.43 ELTS, 11.5.35 LTS, 12.4.11 LTS, and 13.0.1 [3]. The commit [4] shows that fields like processor_path and processor_path_lzw were made readonly to prevent command injection. Users are strongly advised to upgrade to a patched version or apply the workaround of restricting access to the Install Tool.
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 |
|---|---|---|
typo3/cms-corePackagist | >= 8.0.0, < 8.7.57 | 8.7.57 |
typo3/cms-corePackagist | >= 9.0.0, < 9.5.46 | 9.5.46 |
typo3/cms-corePackagist | >= 10.0.0, < 10.4.43 | 10.4.43 |
typo3/cms-corePackagist | >= 11.0.0, < 11.5.35 | 11.5.35 |
typo3/cms-corePackagist | >= 12.0.0, < 12.4.11 | 12.4.11 |
typo3/cms-corePackagist | >= 13.0.0, < 13.0.1 | 13.0.1 |
Affected products
1Patches
347e897f8c766[SECURITY] Prevent RCE via install tool settings
20 files changed · +199 −52
typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php+14 −8 modified@@ -398,7 +398,7 @@ public function resize(string $sourceFile, string $targetFileExtension, int|stri } // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB) if (!str_contains($command, '-colorspace')) { - $command .= ' -colorspace ' . $this->colorspace; + $command .= ' -colorspace ' . CommandUtility::escapeShellArgument($this->colorspace); } if ($this->alternativeOutputKey) { $theOutputName = md5($command . $processingInstructions->cropArea . PathUtility::basename($sourceFile) . $this->alternativeOutputKey . '[' . $frame . ']'); @@ -651,14 +651,20 @@ public function combineExec($input, $overlay, $mask, $output) */ protected function modifyImageMagickStripProfileParameters(string $parameters, array $options): string { - if (isset($options['stripProfile'])) { - if ($options['stripProfile'] && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== '') { - $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . ' ' . $parameters; - } else { - $parameters .= '###SkipStripProfile###'; - } + if (!isset($options['stripProfile'])) { + return $parameters; } - return $parameters; + + $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'] ?? []; + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? [])); + if ($options['stripProfile'] && $stripColorProfileCommand !== '') { + return $stripColorProfileCommand . ' ' . $parameters; + } + + return $parameters . '###SkipStripProfile###'; } /***********************************
typo3/sysext/core/Classes/Mail/TransportFactory.php+5 −0 modified@@ -27,6 +27,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Log\LogManagerInterface; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -150,6 +151,10 @@ public function get(array $mailSettings): TransportInterface if ($mboxFile === '') { throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] needs to be set when transport is set to "mbox".', 1294586645); } + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if (!$fileNameValidator->isValid($mboxFile)) { + throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] failed against deny-pattern', 1705312431); + } // Create our transport $transport = GeneralUtility::makeInstance( MboxTransport::class,
typo3/sysext/core/Classes/Utility/CommandUtility.php+8 −4 modified@@ -119,14 +119,18 @@ public static function imageMagickCommand(string $command, string $parameters, s } // strip profile information for thumbnails and reduce their size if ($parameters && $command !== 'identify') { + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? [])); // Determine whether the strip profile action has be disabled by TypoScript: if ($gfxConf['processor_stripColorProfileByDefault'] - && $gfxConf['processor_stripColorProfileCommand'] !== '' + && $stripColorProfileCommand !== '' && $parameters !== '-version' - && !str_contains($parameters, $gfxConf['processor_stripColorProfileCommand']) + && !str_contains($parameters, $stripColorProfileCommand) && !str_contains($parameters, '###SkipStripProfile###') ) { - $parameters = $gfxConf['processor_stripColorProfileCommand'] . ' ' . $parameters; + $parameters = $stripColorProfileCommand . ' ' . $parameters; } else { $parameters = str_replace('###SkipStripProfile###', '', $parameters); } @@ -137,7 +141,7 @@ public static function imageMagickCommand(string $command, string $parameters, s } // set interlace parameter for convert command if ($command !== 'identify' && $gfxConf['processor_interlace']) { - $parameters = '-interlace ' . $gfxConf['processor_interlace'] . ' ' . $parameters; + $parameters = '-interlace ' . CommandUtility::escapeShellArgument($gfxConf['processor_interlace']) . ' ' . $parameters; } $cmdLine = $path . ' ' . $parameters; // It is needed to change the parameters order when a mask image has been specified
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+7 −4 modified@@ -13,6 +13,7 @@ GFX: description: 'Enables the use of Image- or GraphicsMagick.' processor_path: type: text + readonly: true description: 'Path to the IM tools ''convert'', ''combine'', ''identify''.' processor: type: dropdown @@ -31,10 +32,10 @@ GFX: description: 'If set, the [x] frame selector is appended to input filenames in stdgraphic. This speeds up image processing for PDF files considerably. Disable if your image processor or environment can''t cope with the frame selection.' processor_stripColorProfileByDefault: type: bool - description: 'If set, the processor_stripColorProfileCommand is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' - processor_stripColorProfileCommand: - type: text - description: 'String: Specify the command to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' + description: 'If set, the processor_stripColorProfileParameters is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' + processor_stripColorProfileParameters: + type: array + description: 'Comma separated list of parameters: Specify the parameters to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' processor_colorspace: type: text description: 'String: Specify the colorspace to use. Defaults to "RGB" when using GraphicsMagick as processor and "sRGB" when using ImageMagick. Images would be rendered darker than the original when using ImageMagick in combination with "RGB". <br />Possible Values: CMY, CMYK, Gray, HCL, HSB, HSL, HWB, Lab, LCH, LMS, Log, Luv, OHTA, Rec601Luma, Rec601YCbCr, Rec709Luma, Rec709YCbCr, RGB, sRGB, Transparent, XYZ, YCbCr, YCC, YIQ, YCbCr, YUV' @@ -351,6 +352,7 @@ BE: description: 'Content-Security-Policy reporting HTTP endpoint, if empty system default will be used' fileDenyPattern: type: text + readonly: true description: 'A perl-compatible and JavaScript-compatible regular expression (without delimiters "/"!) that - if it matches a filename - will deny the file upload/rename or whatever. For security reasons, files with multiple extensions have to be denied on an Apache environment with mod_alias, if the filename contains a valid php handler in an arbitrary position. Also, ".htaccess" files have to be denied. Matching is done case-insensitive. Default value is stored in PHP constant FILE_DENY_PATTERN_DEFAULT' versionNumberInFilename: type: bool @@ -598,6 +600,7 @@ MAIL: transport_sendmail_command: type: text description: '<em>only with transport=sendmail</em>: The command to call to send a mail locally.' + readonly: true transport_mbox_file: type: text description: '<em>only with transport=mbox</em>: The file where to write the mails into. This file will be conforming the mbox format described in RFC 4155. It is a simple text file with a concatenation of all mails. Path must be absolute.'
typo3/sysext/core/Configuration/DefaultConfiguration.php+1 −1 modified@@ -46,7 +46,7 @@ 'processor_allowUpscaling' => true, 'processor_allowFrameSelection' => true, 'processor_stripColorProfileByDefault' => true, - 'processor_stripColorProfileCommand' => '+profile \'*\'', + 'processor_stripColorProfileParameters' => ['+profile', '*'], 'processor_colorspace' => '', 'processor_interlace' => 'None', 'jpg_quality' => 85,
typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102799-TYPO3_CONF_VARSGFXprocessor_stripColorProfileParametersOptionAdded.rst+40 −0 added@@ -0,0 +1,40 @@ +.. include:: /Includes.rst.txt + +.. _important-102799-1707403491: + +=========================================================================================== +Important: #102799 - TYPO3_CONF_VARS.GFX.processor_stripColorProfileParameters option added +=========================================================================================== + +See :issue:`102799` + +Description +=========== + +The string-based configuration option +:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']` +has been superseded by +:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters']` +for security reasons. + +The former option expected a string of command line parameters. The defined +parameters had to be shell-escaped beforehand, while the new option expects an +array of strings that will be shell-escaped by TYPO3 when used. + +The existing configuration will continue to be supported. Still, it is suggested +to use the new configuration format, as the Install Tool is adapted to allow +modification of the new configuration option only: + +.. code-block:: php + + // Before + $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] = '+profile \'*\''; + + // After + $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] = [ + '+profile', + '*' + ]; + + +.. index:: LocalConfiguration, ext:core
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php+1 −1 modified@@ -3627,7 +3627,7 @@ public function getImgResource($file, $fileArray) } // Possibility to cancel/force profile extraction - // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] + // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] if (isset($fileArray['stripProfile'])) { $processingConfiguration['stripProfile'] = $fileArray['stripProfile']; }
typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php+19 −4 modified@@ -70,25 +70,40 @@ public function isAvailable() } /** - * Get configuration values is used in fluid to show configuration options. + * Get configuration values is used to persist data and is merged with given $postValues. + * + * @return array Configuration values needed to activate prefix + */ + public function getConfigurationValues() + { + return array_map(static fn($configuration) => $configuration['value'], $this->getConfigurationDescriptors()); + } + + /** + * Build configuration descriptors to be used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration and * merged with given $postValues. * * @return array Configuration values needed to activate prefix */ - public function getConfigurationValues() + public function getConfigurationDescriptors() { $configurationValues = []; foreach ($this->configurationValues as $configurationKey => $configurationValue) { - if (isset($this->postValues['enable']) + $readonly = isset($this->readonlyConfigurationValues[$configurationKey]); + if (!$readonly + && isset($this->postValues['enable']) && $this->postValues['enable'] === $this->name && isset($this->postValues[$this->name][$configurationKey]) ) { $currentValue = $this->postValues[$this->name][$configurationKey]; } else { $currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey); } - $configurationValues[$configurationKey] = $currentValue; + $configurationValues[$configurationKey] = [ + 'value' => $currentValue, + 'readonly' => $readonly, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Configuration/AbstractPreset.php+5 −0 modified@@ -45,6 +45,11 @@ abstract class AbstractPreset implements PresetInterface */ protected $configurationValues = []; + /** + * @var array Configuration values that are visible but not editable via presets GUI + */ + protected $readonlyConfigurationValues = []; + /** * @var array List of $POST values */
typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php+4 −0 modified@@ -34,4 +34,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'GFX/processor_effects' => false, 'GFX/processor_colorspace' => '', ]; + + protected $readonlyConfigurationValues = [ + 'GFX/processor_path' => true, + ]; }
typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php+4 −0 modified@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'MAIL/transport_smtp_username' => '', 'MAIL/transport_smtp_password' => '', ]; + + protected $readonlyConfigurationValues = [ + 'MAIL/transport_sendmail_command' => true, + ]; }
typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php+20 −7 modified@@ -32,22 +32,35 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface * Get configuration values is used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration. * + * They are not merged with postValues for security reasons, as + * all options are readonly. + * * @return array Current custom configuration values */ - public function getConfigurationValues(): array + public function getConfigurationDescriptors(): array { $configurationValues = []; - $configurationValues['BE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'); + $configurationValues['BE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['BE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['BE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } - $configurationValues['FE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'); + $configurationValues['FE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['FE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['FE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php+31 −1 modified@@ -21,6 +21,8 @@ use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Messaging\FlashMessageQueue; +use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; +use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -90,6 +92,7 @@ protected function recursiveConfigurationFetching(array $sections, array $sectio $itemData['path'] = '[' . implode('][', $newPath) . ']'; $itemData['fieldType'] = $descriptionInfo['type']; $itemData['description'] = $descriptionInfo['description'] ?? ''; + $itemData['readonly'] = $descriptionInfo['readonly'] ?? false; $itemData['allowedValues'] = $descriptionInfo['allowedValues'] ?? []; $itemData['differentValueInCurrentConfiguration'] = (!isset($descriptionInfo['compareValuesWithCurrentConfiguration']) || $descriptionInfo['compareValuesWithCurrentConfiguration']) && @@ -151,11 +154,28 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $commentArray = $this->getDefaultConfigArrayComments(); $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); foreach ($valueList as $path => $value) { - $oldValue = $configurationManager->getConfigurationValueByPath($path); + try { + $oldValue = $configurationManager->getConfigurationValueByPath($path); + } catch (MissingArrayPathException) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, the category of this setting does not exist', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue; + } $pathParts = explode('/', $path); $descriptionData = $commentArray[$pathParts[0]]; while ($part = next($pathParts)) { + if (!isset($descriptionData['items'][$part])) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is not writable', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue 2; + } $descriptionData = $descriptionData['items'][$part]; } @@ -183,6 +203,16 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $valueHasChanged = (string)$oldValue !== (string)$value; } + $readonly = $descriptionData['readonly'] ?? false; + if ($readonly && $valueHasChanged) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is readonly', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue; + } + // Save if value changed if ($valueHasChanged) { $configurationPathValuePairs[$path] = $value;
typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html+9 −6 modified@@ -32,6 +32,7 @@ <h3 class="panel-title"> </f:if> </div> <div class="localconf-item-body"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: item}" /> <f:if condition="{item.differentValueInCurrentConfiguration}"> <div class="t3js-infobox callout callout-warning"> <div class="callout-body"> @@ -44,7 +45,9 @@ <h3 class="panel-title"> <f:then> <div class="form-group"> <div class="form-description">{item.description -> f:sanitize.html()}</div> - <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" {f:if(condition: '!{isWritable}', then: 'disabled')}> + <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} + > <f:for each="{item.allowedValues}" key="optionKey" as="optionLabel"> <option value="{optionKey}" {f:if(condition: '{item.value} == {optionKey}', then: 'selected="selected"')}>{optionLabel} ({optionKey})</option> </f:for> @@ -62,7 +65,7 @@ <h3 class="panel-title"> id="{sectionName}_{item.key}" data-path="{sectionName}/{item.key}" {f:if(condition: item.checked, then:'checked="checked"')} - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> <label class="form-check-label" for="{sectionName}_{item.key}"> {item.description -> f:sanitize.html()} @@ -81,7 +84,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -97,7 +100,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="new-password" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -114,7 +117,7 @@ <h3 class="panel-title"> class="t3- install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -129,7 +132,7 @@ <h3 class="panel-title"> cols="60" data-path="{sectionName}/{item.key}" class="form-control t3js-localConfiguration-pathValue" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} >{item.value}</textarea> </div> </f:if>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html+4 −3 modified@@ -16,18 +16,19 @@ </label> </div> <p>Custom cache settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-cache-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html+4 −3 modified@@ -17,18 +17,19 @@ </div> <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-context-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html+4 −3 modified@@ -18,18 +18,19 @@ <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-image-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html+5 −3 modified@@ -16,19 +16,21 @@ </label> </div> <p>Custom mail settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'password', else: 'text')}" autocomplete="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'new-password', else: 'off')}" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-mail-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html+4 −4 modified@@ -18,20 +18,20 @@ <p>Custom password hash settings. This interface does not allow modification of the values, they are just shown. Configuring custom hash settings is for advanced users who know exactly what they are doing. Refer to the core documentation for details.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" - disabled="disabled" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-passwordHashing-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/ReadonlyInfo.html+10 −0 added@@ -0,0 +1,10 @@ +<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> + <f:if condition="{configuration.readonly}"> + <div class="t3js-infobox callout callout-info"> + <div class="callout-body"> + For security reasons, this option cannot be changed here.<br> + Please configure via <code>system/settings.php</code> or <code>system/additional.php</code>. + </div> + </div> + </f:if> +</html>
6cc11761b8e2[SECURITY] Prevent RCE via install tool settings
21 files changed · +200 −55
typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php+1 −1 modified@@ -2160,7 +2160,7 @@ public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $ } $command .= ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' '; // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB) - $command .= ' -colorspace ' . $this->colorspace; + $command .= ' -colorspace ' . CommandUtility::escapeShellArgument($this->colorspace); $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : ''; if ($this->alternativeOutputKey) { $theOutputName = md5($command . $cropscale . PathUtility::basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
typo3/sysext/core/Classes/Mail/TransportFactory.php+5 −0 modified@@ -27,6 +27,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Log\LogManagerInterface; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -150,6 +151,10 @@ public function get(array $mailSettings): TransportInterface if ($mboxFile === '') { throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] needs to be set when transport is set to "mbox".', 1294586645); } + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if (!$fileNameValidator->isValid($mboxFile)) { + throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] failed against deny-pattern', 1705312431); + } // Create our transport $transport = GeneralUtility::makeInstance( MboxTransport::class,
typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php+14 −10 modified@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Imaging\GraphicalFunctions; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ProcessedFile; +use TYPO3\CMS\Core\Utility\CommandUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Frontend\Imaging\GifBuilder; @@ -286,18 +287,21 @@ protected function getFilenameForImageCropScaleMask(TaskInterface $task) */ protected function modifyImageMagickStripProfileParameters(string $parameters, array $configuration) { + if (!isset($configuration['stripProfile'])) { + return $parameters; + } + + $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'] ?? []; + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? [])); + // Strips profile information of image to save some space: - if (isset($configuration['stripProfile'])) { - if ( - $configuration['stripProfile'] - && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== '' - ) { - $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . $parameters; - } else { - $parameters .= '###SkipStripProfile###'; - } + if ($configuration['stripProfile'] && $stripColorProfileCommand !== '') { + return $stripColorProfileCommand . $parameters; } - return $parameters; + return $parameters . '###SkipStripProfile###'; } protected function isTemporaryFile(string $filePath): bool
typo3/sysext/core/Classes/Utility/CommandUtility.php+8 −4 modified@@ -119,14 +119,18 @@ public static function imageMagickCommand(string $command, string $parameters, s } // strip profile information for thumbnails and reduce their size if ($parameters && $command !== 'identify') { + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map(CommandUtility::escapeShellArgument(...), $gfxConf['processor_stripColorProfileParameters'] ?? [])); // Determine whether the strip profile action has be disabled by TypoScript: if ($gfxConf['processor_stripColorProfileByDefault'] - && $gfxConf['processor_stripColorProfileCommand'] !== '' + && $stripColorProfileCommand !== '' && $parameters !== '-version' - && !str_contains($parameters, $gfxConf['processor_stripColorProfileCommand']) + && !str_contains($parameters, $stripColorProfileCommand) && !str_contains($parameters, '###SkipStripProfile###') ) { - $parameters = $gfxConf['processor_stripColorProfileCommand'] . ' ' . $parameters; + $parameters = $stripColorProfileCommand . ' ' . $parameters; } else { $parameters = str_replace('###SkipStripProfile###', '', $parameters); } @@ -137,7 +141,7 @@ public static function imageMagickCommand(string $command, string $parameters, s } // set interlace parameter for convert command if ($command !== 'identify' && $gfxConf['processor_interlace']) { - $parameters = '-interlace ' . $gfxConf['processor_interlace'] . ' ' . $parameters; + $parameters = '-interlace ' . CommandUtility::escapeShellArgument($gfxConf['processor_interlace']) . ' ' . $parameters; } $cmdLine = $path . ' ' . $parameters; // It is needed to change the parameters order when a mask image has been specified
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+7 −4 modified@@ -25,6 +25,7 @@ GFX: description: 'Enables the use of Image- or GraphicsMagick.' processor_path: type: text + readonly: true description: 'Path to the IM tools ''convert'', ''combine'', ''identify''.' processor: type: dropdown @@ -46,10 +47,10 @@ GFX: description: 'This should be set if your processor supports using PNGs as masks as this is usually faster.' processor_stripColorProfileByDefault: type: bool - description: 'If set, the processor_stripColorProfileCommand is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' - processor_stripColorProfileCommand: - type: text - description: 'String: Specify the command to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' + description: 'If set, the processor_stripColorProfileParameters is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' + processor_stripColorProfileParameters: + type: array + description: 'Comma separated list of parameters: Specify the parameters to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' processor_colorspace: type: text description: 'String: Specify the colorspace to use. Some ImageMagick versions (like 6.7.0 and above) use the sRGB colorspace, so all images are darker then the original. <br />Possible Values: CMY, CMYK, Gray, HCL, HSB, HSL, HWB, Lab, LCH, LMS, Log, Luv, OHTA, Rec601Luma, Rec601YCbCr, Rec709Luma, Rec709YCbCr, RGB, sRGB, Transparent, XYZ, YCbCr, YCC, YIQ, YCbCr, YUV' @@ -377,6 +378,7 @@ BE: description: 'Content-Security-Policy reporting HTTP endpoint, if empty system default will be used' fileDenyPattern: type: text + readonly: true description: 'A perl-compatible and JavaScript-compatible regular expression (without delimiters "/"!) that - if it matches a filename - will deny the file upload/rename or whatever. For security reasons, files with multiple extensions have to be denied on an Apache environment with mod_alias, if the filename contains a valid php handler in an arbitrary position. Also, ".htaccess" files have to be denied. Matching is done case-insensitive. Default value is stored in PHP constant FILE_DENY_PATTERN_DEFAULT' flexformForceCDATA: type: bool @@ -627,6 +629,7 @@ MAIL: transport_sendmail_command: type: text description: '<em>only with transport=sendmail</em>: The command to call to send a mail locally.' + readonly: true transport_mbox_file: type: text description: '<em>only with transport=mbox</em>: The file where to write the mails into. This file will be conforming the mbox format described in RFC 4155. It is a simple text file with a concatenation of all mails. Path must be absolute.'
typo3/sysext/core/Configuration/DefaultConfiguration.php+1 −1 modified@@ -37,7 +37,7 @@ 'processor_allowFrameSelection' => true, 'processor_allowTemporaryMasksAsPng' => false, 'processor_stripColorProfileByDefault' => true, - 'processor_stripColorProfileCommand' => '+profile \'*\'', + 'processor_stripColorProfileParameters' => ['+profile', '*'], 'processor_colorspace' => 'RGB', 'processor_interlace' => 'None', 'jpg_quality' => 85,
typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102799-TYPO3_CONF_VARSGFXprocessor_stripColorProfileParametersOptionAdded.rst+40 −0 added@@ -0,0 +1,40 @@ +.. include:: /Includes.rst.txt + +.. _important-102799-1707403491: + +=========================================================================================== +Important: #102799 - TYPO3_CONF_VARS.GFX.processor_stripColorProfileParameters option added +=========================================================================================== + +See :issue:`102799` + +Description +=========== + +The string-based configuration option +:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand']` +has been superseded by +:php:`$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters']` +for security reasons. + +The former option expected a string of command line parameters. The defined +parameters had to be shell-escaped beforehand, while the new option expects an +array of strings that will be shell-escaped by TYPO3 when used. + +The existing configuration will continue to be supported. Still, it is suggested +to use the new configuration format, as the Install Tool is adapted to allow +modification of the new configuration option only: + +.. code-block:: php + + // Before + $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] = '+profile \'*\''; + + // After + $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] = [ + '+profile', + '*' + ]; + + +.. index:: LocalConfiguration, ext:core
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php+1 −1 modified@@ -3821,7 +3821,7 @@ public function getImgResource($file, $fileArray) } // Possibility to cancel/force profile extraction - // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] + // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] if (isset($fileArray['stripProfile'])) { $processingConfiguration['stripProfile'] = $fileArray['stripProfile']; }
typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php+19 −4 modified@@ -70,25 +70,40 @@ public function isAvailable() } /** - * Get configuration values is used in fluid to show configuration options. + * Get configuration values is used to persist data and is merged with given $postValues. + * + * @return array Configuration values needed to activate prefix + */ + public function getConfigurationValues() + { + return array_map(static fn($configuration) => $configuration['value'], $this->getConfigurationDescriptors()); + } + + /** + * Build configuration descriptors to be used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration and * merged with given $postValues. * * @return array Configuration values needed to activate prefix */ - public function getConfigurationValues() + public function getConfigurationDescriptors() { $configurationValues = []; foreach ($this->configurationValues as $configurationKey => $configurationValue) { - if (isset($this->postValues['enable']) + $readonly = isset($this->readonlyConfigurationValues[$configurationKey]); + if (!$readonly + && isset($this->postValues['enable']) && $this->postValues['enable'] === $this->name && isset($this->postValues[$this->name][$configurationKey]) ) { $currentValue = $this->postValues[$this->name][$configurationKey]; } else { $currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey); } - $configurationValues[$configurationKey] = $currentValue; + $configurationValues[$configurationKey] = [ + 'value' => $currentValue, + 'readonly' => $readonly, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Configuration/AbstractPreset.php+5 −0 modified@@ -45,6 +45,11 @@ abstract class AbstractPreset implements PresetInterface */ protected $configurationValues = []; + /** + * @var array Configuration values that are visible but not editable via presets GUI + */ + protected $readonlyConfigurationValues = []; + /** * @var array List of $POST values */
typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php+4 −0 modified@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'GFX/processor_allowTemporaryMasksAsPng' => true, 'GFX/processor_colorspace' => '', ]; + + protected $readonlyConfigurationValues = [ + 'GFX/processor_path' => true, + ]; }
typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php+4 −0 modified@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'MAIL/transport_smtp_username' => '', 'MAIL/transport_smtp_password' => '', ]; + + protected $readonlyConfigurationValues = [ + 'MAIL/transport_sendmail_command' => true, + ]; }
typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php+20 −7 modified@@ -32,22 +32,35 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface * Get configuration values is used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration. * + * They are not merged with postValues for security reasons, as + * all options are readonly. + * * @return array Current custom configuration values */ - public function getConfigurationValues(): array + public function getConfigurationDescriptors(): array { $configurationValues = []; - $configurationValues['BE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'); + $configurationValues['BE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['BE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['BE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } - $configurationValues['FE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'); + $configurationValues['FE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['FE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['FE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php+31 −1 modified@@ -21,6 +21,8 @@ use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Messaging\FlashMessageQueue; +use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; +use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -90,6 +92,7 @@ protected function recursiveConfigurationFetching(array $sections, array $sectio $itemData['path'] = '[' . implode('][', $newPath) . ']'; $itemData['fieldType'] = $descriptionInfo['type']; $itemData['description'] = $descriptionInfo['description'] ?? ''; + $itemData['readonly'] = $descriptionInfo['readonly'] ?? false; $itemData['allowedValues'] = $descriptionInfo['allowedValues'] ?? []; $itemData['differentValueInCurrentConfiguration'] = (!isset($descriptionInfo['compareValuesWithCurrentConfiguration']) || $descriptionInfo['compareValuesWithCurrentConfiguration']) && @@ -151,11 +154,28 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $commentArray = $this->getDefaultConfigArrayComments(); $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); foreach ($valueList as $path => $value) { - $oldValue = $configurationManager->getConfigurationValueByPath($path); + try { + $oldValue = $configurationManager->getConfigurationValueByPath($path); + } catch (MissingArrayPathException) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, the category of this setting does not exist', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue; + } $pathParts = explode('/', $path); $descriptionData = $commentArray[$pathParts[0]]; while ($part = next($pathParts)) { + if (!isset($descriptionData['items'][$part])) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is not writable', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue 2; + } $descriptionData = $descriptionData['items'][$part]; } @@ -183,6 +203,16 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $valueHasChanged = (string)$oldValue !== (string)$value; } + $readonly = $descriptionData['readonly'] ?? false; + if ($readonly && $valueHasChanged) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is readonly', + $path, + ContextualFeedbackSeverity::ERROR + )); + continue; + } + // Save if value changed if ($valueHasChanged) { $configurationPathValuePairs[$path] = $value;
typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html+9 −6 modified@@ -32,6 +32,7 @@ <h3 class="panel-title"> </f:if> </div> <div class="localconf-item-body"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: item}" /> <f:if condition="{item.differentValueInCurrentConfiguration}"> <div class="t3js-infobox callout callout-warning"> <div class="callout-body"> @@ -44,7 +45,9 @@ <h3 class="panel-title"> <f:then> <div class="form-group"> <div class="form-description">{item.description -> f:sanitize.html()}</div> - <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" {f:if(condition: '!{isWritable}', then: 'disabled')}> + <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} + > <f:for each="{item.allowedValues}" key="optionKey" as="optionLabel"> <option value="{optionKey}" {f:if(condition: '{item.value} == {optionKey}', then: 'selected="selected"')}>{optionLabel} ({optionKey})</option> </f:for> @@ -62,7 +65,7 @@ <h3 class="panel-title"> id="{sectionName}_{item.key}" data-path="{sectionName}/{item.key}" {f:if(condition: item.checked, then:'checked="checked"')} - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> <label class="form-check-label" for="{sectionName}_{item.key}"> {item.description -> f:sanitize.html()} @@ -81,7 +84,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -97,7 +100,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="new-password" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -114,7 +117,7 @@ <h3 class="panel-title"> class="t3- install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} /> </div> </f:if> @@ -129,7 +132,7 @@ <h3 class="panel-title"> cols="60" data-path="{sectionName}/{item.key}" class="form-control t3js-localConfiguration-pathValue" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {item.readonly}', then: 'disabled')} >{item.value}</textarea> </div> </f:if>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html+4 −3 modified@@ -16,18 +16,19 @@ </label> </div> <p>Custom cache settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-cache-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html+4 −3 modified@@ -17,18 +17,19 @@ </div> <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-context-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html+4 −3 modified@@ -18,18 +18,19 @@ <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-image-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html+5 −3 modified@@ -16,19 +16,21 @@ </label> </div> <p>Custom mail settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'password', else: 'text')}" autocomplete="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'new-password', else: 'off')}" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-mail-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html+4 −4 modified@@ -18,19 +18,19 @@ <p>Custom password hash settings. This interface does not allow modification of the values, they are just shown. Configuring custom hash settings is for advanced users who know exactly what they are doing. Refer to the core documentation for details.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> + <f:render partial="Settings/ReadonlyInfo" arguments="{configuration: configuration}" /> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" - disabled="disabled" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-passwordHashing-custom" - {f:if(condition: '!{isWritable}', then: 'disabled')} + {f:if(condition: '!{isWritable} || {configuration.readonly}', then: 'disabled')} /> </div> </div>
typo3/sysext/install/Resources/Private/Partials/Settings/ReadonlyInfo.html+10 −0 added@@ -0,0 +1,10 @@ +<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> + <f:if condition="{configuration.readonly}"> + <div class="t3js-infobox callout callout-info"> + <div class="callout-body"> + For security reasons, this option cannot be changed here.<br> + Please configure via <code>system/settings.php</code> or <code>system/additional.php</code>. + </div> + </div> + </f:if> +</html>
84e07e35b880[SECURITY] Prevent RCE via install tool settings
20 files changed · +169 −45
typo3/sysext/core/Classes/Imaging/GraphicalFunctions.php+1 −1 modified@@ -2153,7 +2153,7 @@ public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $ } $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' '; // re-apply colorspace-setting for the resulting image so colors don't appear to dark (sRGB instead of RGB) - $command .= ' -colorspace ' . $this->colorspace; + $command .= ' -colorspace ' . CommandUtility::escapeShellArgument($this->colorspace); $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : ''; if ($this->alternativeOutputKey) { $theOutputName = md5($command . $cropscale . PathUtility::basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
typo3/sysext/core/Classes/Mail/TransportFactory.php+5 −0 modified@@ -27,6 +27,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Exception; use TYPO3\CMS\Core\Log\LogManagerInterface; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -144,6 +145,10 @@ public function get(array $mailSettings): TransportInterface if ($mboxFile === '') { throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] needs to be set when transport is set to "mbox".', 1294586645); } + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if (!$fileNameValidator->isValid($mboxFile)) { + throw new Exception('$GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'transport_mbox_file\'] failed against deny-pattern', 1705312431); + } // Create our transport $transport = GeneralUtility::makeInstance( MboxTransport::class,
typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php+14 −10 modified@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Resource; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ProcessedFile; +use TYPO3\CMS\Core\Utility\CommandUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Frontend\Imaging\GifBuilder; @@ -272,18 +273,21 @@ protected function getFilenameForImageCropScaleMask(TaskInterface $task) */ protected function modifyImageMagickStripProfileParameters(string $parameters, array $configuration) { + if (!isset($configuration['stripProfile'])) { + return $parameters; + } + + $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'] ?? []; + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map([CommandUtility::class, 'escapeShellArgument'], $gfxConf['processor_stripColorProfileParameters'] ?? [])); + // Strips profile information of image to save some space: - if (isset($configuration['stripProfile'])) { - if ( - $configuration['stripProfile'] - && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== '' - ) { - $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . $parameters; - } else { - $parameters .= '###SkipStripProfile###'; - } + if ($configuration['stripProfile'] && $stripColorProfileCommand !== '') { + return $stripColorProfileCommand . $parameters; } - return $parameters; + return $parameters . '###SkipStripProfile###'; } protected function isTemporaryFile(string $filePath): bool
typo3/sysext/core/Classes/Utility/CommandUtility.php+8 −4 modified@@ -118,14 +118,18 @@ public static function imageMagickCommand($command, $parameters, $path = '') } // strip profile information for thumbnails and reduce their size if ($parameters && $command !== 'identify') { + // Use legacy processor_stripColorProfileCommand setting if defined, otherwise + // use the preferred configuration option processor_stripColorProfileParameters + $stripColorProfileCommand = $gfxConf['processor_stripColorProfileCommand'] ?? + implode(' ', array_map([CommandUtility::class, 'escapeShellArgument'], $gfxConf['processor_stripColorProfileParameters'] ?? [])); // Determine whether the strip profile action has be disabled by TypoScript: if ($gfxConf['processor_stripColorProfileByDefault'] - && $gfxConf['processor_stripColorProfileCommand'] !== '' - && !str_contains($parameters, $gfxConf['processor_stripColorProfileCommand']) + && $stripColorProfileCommand !== '' && $parameters !== '-version' + && !str_contains($parameters, $stripColorProfileCommand) && !str_contains($parameters, '###SkipStripProfile###') ) { - $parameters = $gfxConf['processor_stripColorProfileCommand'] . ' ' . $parameters; + $parameters = $stripColorProfileCommand . ' ' . $parameters; } else { $parameters = str_replace('###SkipStripProfile###', '', $parameters); } @@ -136,7 +140,7 @@ public static function imageMagickCommand($command, $parameters, $path = '') } // set interlace parameter for convert command if ($command !== 'identify' && $gfxConf['processor_interlace']) { - $parameters = '-interlace ' . $gfxConf['processor_interlace'] . ' ' . $parameters; + $parameters = '-interlace ' . CommandUtility::escapeShellArgument($gfxConf['processor_interlace']) . ' ' . $parameters; } $cmdLine = $path . ' ' . $parameters; // It is needed to change the parameters order when a mask image has been specified
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+8 −4 modified@@ -25,9 +25,11 @@ GFX: description: 'Enables the use of Image- or GraphicsMagick.' processor_path: type: text + readonly: true description: 'Path to the IM tools ''convert'', ''combine'', ''identify''.' processor_path_lzw: type: text + readonly: true description: 'Path to the IM tool ''convert'' with LZW enabled! See ''gif_compress''. If your version 4.2.9 of ImageMagick is compiled with LZW you may leave this field blank AND disable the flag ''gif_compress''! Tip: You can call LZW ''convert'' with a prefix like ''myver_convert'' by setting this path with it, eg. <code>/usr/bin/myver_</code> instead of just <code>/usr/bin/</code>.' processor: type: dropdown @@ -49,10 +51,10 @@ GFX: description: 'This should be set if your processor supports using PNGs as masks as this is usually faster.' processor_stripColorProfileByDefault: type: bool - description: 'If set, the processor_stripColorProfileCommand is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' - processor_stripColorProfileCommand: - type: text - description: 'String: Specify the command to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' + description: 'If set, the processor_stripColorProfileParameters is used with all processor image operations by default. See tsRef for setting this parameter explicitly for IMAGE generation.' + processor_stripColorProfileParameters: + type: array + description: 'Comma separated list of parameters: Specify the parameters to strip the profile information, which can reduce thumbnail size up to 60KB. Command can differ in IM/GM, IM also know the -strip command. See <a href="http://www.imagemagick.org/Usage/thumbnails/#profiles" target="_blank" rel="noreferrer">imagemagick.org</a> for details' processor_colorspace: type: text description: 'String: Specify the colorspace to use. Some ImageMagick versions (like 6.7.0 and above) use the sRGB colorspace, so all images are darker then the original. <br />Possible Values: CMY, CMYK, Gray, HCL, HSB, HSL, HWB, Lab, LCH, LMS, Log, Luv, OHTA, Rec601Luma, Rec601YCbCr, Rec709Luma, Rec709YCbCr, RGB, sRGB, Transparent, XYZ, YCbCr, YCC, YIQ, YCbCr, YUV' @@ -380,6 +382,7 @@ BE: description: 'If set, make a loose comparison ('''' equals 0) when validating record values after saving in DataHandler.' fileDenyPattern: type: text + readonly: true description: 'A perl-compatible and JavaScript-compatible regular expression (without delimiters "/"!) that - if it matches a filename - will deny the file upload/rename or whatever. For security reasons, files with multiple extensions have to be denied on an Apache environment with mod_alias, if the filename contains a valid php handler in an arbitrary position. Also, ".htaccess" files have to be denied. Matching is done case-insensitive. Default value is stored in PHP constant FILE_DENY_PATTERN_DEFAULT' interfaces: type: text @@ -625,6 +628,7 @@ MAIL: transport_sendmail_command: type: text description: '<em>only with transport=sendmail</em>: The command to call to send a mail locally.' + readonly: true transport_mbox_file: type: text description: '<em>only with transport=mbox</em>: The file where to write the mails into. This file will be conforming the mbox format described in RFC 4155. It is a simple text file with a concatenation of all mails. Path must be absolute.'
typo3/sysext/core/Configuration/DefaultConfiguration.php+1 −1 modified@@ -38,7 +38,7 @@ 'processor_allowFrameSelection' => true, 'processor_allowTemporaryMasksAsPng' => false, 'processor_stripColorProfileByDefault' => true, - 'processor_stripColorProfileCommand' => '+profile \'*\'', + 'processor_stripColorProfileParameters' => ['+profile', '*'], 'processor_colorspace' => 'RGB', 'processor_interlace' => 'None', 'jpg_quality' => 85,
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php+1 −1 modified@@ -4128,7 +4128,7 @@ public function getImgResource($file, $fileArray) } // Possibility to cancel/force profile extraction - // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] + // see $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileParameters'] if (isset($fileArray['stripProfile'])) { $processingConfiguration['stripProfile'] = $fileArray['stripProfile']; }
typo3/sysext/install/Classes/Configuration/AbstractCustomPreset.php+19 −4 modified@@ -70,25 +70,40 @@ public function isAvailable() } /** - * Get configuration values is used in fluid to show configuration options. + * Get configuration values is used to persist data and is merged with given $postValues. + * + * @return array Configuration values needed to activate prefix + */ + public function getConfigurationValues() + { + return array_map(static fn($configuration) => $configuration['value'], $this->getConfigurationDescriptors()); + } + + /** + * Build configuration descriptors to be used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration and * merged with given $postValues. * * @return array Configuration values needed to activate prefix */ - public function getConfigurationValues() + public function getConfigurationDescriptors() { $configurationValues = []; foreach ($this->configurationValues as $configurationKey => $configurationValue) { - if (isset($this->postValues['enable']) + $readonly = isset($this->readonlyConfigurationValues[$configurationKey]); + if (!$readonly + && isset($this->postValues['enable']) && $this->postValues['enable'] === $this->name && isset($this->postValues[$this->name][$configurationKey]) ) { $currentValue = $this->postValues[$this->name][$configurationKey]; } else { $currentValue = $this->configurationManager->getConfigurationValueByPath($configurationKey); } - $configurationValues[$configurationKey] = $currentValue; + $configurationValues[$configurationKey] = [ + 'value' => $currentValue, + 'readonly' => $readonly, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Configuration/AbstractPreset.php+5 −0 modified@@ -45,6 +45,11 @@ abstract class AbstractPreset implements PresetInterface */ protected $configurationValues = []; + /** + * @var array Configuration values that are visible but not editable via presets GUI + */ + protected $readonlyConfigurationValues = []; + /** * @var array List of $POST values */
typo3/sysext/install/Classes/Configuration/Image/CustomPreset.php+5 −0 modified@@ -36,4 +36,9 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'GFX/processor_allowTemporaryMasksAsPng' => true, 'GFX/processor_colorspace' => '', ]; + + protected $readonlyConfigurationValues = [ + 'GFX/processor_path' => true, + 'GFX/processor_path_lzw' => true, + ]; }
typo3/sysext/install/Classes/Configuration/Mail/CustomPreset.php+4 −0 modified@@ -35,4 +35,8 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface 'MAIL/transport_smtp_username' => '', 'MAIL/transport_smtp_password' => '', ]; + + protected $readonlyConfigurationValues = [ + 'MAIL/transport_sendmail_command' => true, + ]; }
typo3/sysext/install/Classes/Configuration/PasswordHashing/CustomPreset.php+20 −7 modified@@ -32,22 +32,35 @@ class CustomPreset extends AbstractCustomPreset implements CustomPresetInterface * Get configuration values is used in fluid to show configuration options. * They are fetched from LocalConfiguration / DefaultConfiguration. * + * They are not merged with postValues for security reasons, as + * all options are readonly. + * * @return array Current custom configuration values */ - public function getConfigurationValues(): array + public function getConfigurationDescriptors(): array { $configurationValues = []; - $configurationValues['BE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'); + $configurationValues['BE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('BE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['BE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['BE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } - $configurationValues['FE/passwordHashing/className'] = - $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'); + $configurationValues['FE/passwordHashing/className'] = [ + 'value' => $this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/className'), + 'readonly' => true, + ]; $options = (array)$this->configurationManager->getConfigurationValueByPath('FE/passwordHashing/options'); foreach ($options as $optionName => $optionValue) { - $configurationValues['FE/passwordHashing/options/' . $optionName] = $optionValue; + $configurationValues['FE/passwordHashing/options/' . $optionName] = [ + 'value' => $optionValue, + 'readonly' => true, + ]; } return $configurationValues; }
typo3/sysext/install/Classes/Service/LocalConfigurationValueService.php+31 −1 modified@@ -19,8 +19,10 @@ use TYPO3\CMS\Core\Configuration\ConfigurationManager; use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader; +use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Messaging\FlashMessageQueue; +use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -96,6 +98,7 @@ protected function recursiveConfigurationFetching(array $sections, array $sectio $itemData['path'] = '[' . implode('][', $newPath) . ']'; $itemData['fieldType'] = $descriptionInfo['type']; $itemData['description'] = $descriptionInfo['description'] ?? ''; + $itemData['readonly'] = $descriptionInfo['readonly'] ?? false; $itemData['allowedValues'] = $descriptionInfo['allowedValues'] ?? []; $itemData['differentValueInCurrentConfiguration'] = (!isset($descriptionInfo['compareValuesWithCurrentConfiguration']) || $descriptionInfo['compareValuesWithCurrentConfiguration']) && @@ -158,11 +161,28 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $commentArray = $this->getDefaultConfigArrayComments(); $configurationManager = GeneralUtility::makeInstance(ConfigurationManager::class); foreach ($valueList as $path => $value) { - $oldValue = $configurationManager->getConfigurationValueByPath($path); + try { + $oldValue = $configurationManager->getConfigurationValueByPath($path); + } catch (MissingArrayPathException $e) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, the category of this setting does not exist', + $path, + AbstractMessage::ERROR + )); + continue; + } $pathParts = explode('/', $path); $descriptionData = $commentArray[$pathParts[0]]; while ($part = next($pathParts)) { + if (!isset($descriptionData['items'][$part])) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is not writable', + $path, + AbstractMessage::ERROR + )); + continue 2; + } $descriptionData = $descriptionData['items'][$part]; } @@ -190,6 +210,16 @@ public function updateLocalConfigurationValues(array $valueList): FlashMessageQu $valueHasChanged = (string)$oldValue !== (string)$value; } + $readonly = $descriptionData['readonly'] ?? false; + if ($readonly && $valueHasChanged) { + $messageQueue->enqueue(new FlashMessage( + 'Update rejected, this setting is readonly', + $path, + AbstractMessage::ERROR + )); + continue; + } + // Save if value changed if ($valueHasChanged) { $configurationPathValuePairs[$path] = $value;
typo3/sysext/install/Resources/Private/Partials/Settings/LocalConfiguration/SubSection.html+16 −1 modified@@ -31,6 +31,14 @@ <h3 class="panel-title"> </f:if> </div> <div class="item-body"> + <f:if condition="{item.readonly}"> + <div class="t3js-infobox callout callout-info"> + <div class="callout-body"> + For security reasons, this option cannot be changed here.<br> + Please configure via <code>typo3conf/LocalConfiguration.php</code> or <code>typo3conf/AdditionalConfiguration.php</code>. + </div> + </div> + </f:if> <f:if condition="{item.differentValueInCurrentConfiguration}"> <div class="t3js-infobox callout callout-sm callout-warning"> <div class="callout-body"> @@ -43,7 +51,9 @@ <h3 class="panel-title"> <f:then> <div class="form-group"> <span class="help-block">{item.description -> f:sanitize.html()}</span> - <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue"> + <select data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-select t3js-localConfiguration-pathValue" + {f:if(condition: item.readonly, then: 'disabled')} + > <f:for each="{item.allowedValues}" key="optionKey" as="optionLabel"> <option value="{optionKey}" {f:if(condition: '{item.value} == {optionKey}', then: 'selected="selected"')}>{optionLabel} ({optionKey})</option> </f:for> @@ -60,6 +70,7 @@ <h3 class="panel-title"> id="{sectionName}_{item.key}" data-path="{sectionName}/{item.key}" {f:if(condition: item.checked, then:'checked="checked"')} + {f:if(condition: item.readonly, then: 'disabled')} /> <label class="form-check-label" for="{sectionName}_{item.key}"> <span class="form-check-label-text"> @@ -77,6 +88,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" + {f:if(condition: item.readonly, then: 'disabled')} /> </div> </f:if> @@ -90,6 +102,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="new-password" + {f:if(condition: item.readonly, then: 'disabled')} /> </div> </f:if> @@ -103,6 +116,7 @@ <h3 class="panel-title"> data-path="{sectionName}/{item.key}" class="t3-install-form-input-text form-control t3js-localConfiguration-pathValue" autocomplete="off" + {f:if(condition: item.readonly, then: 'disabled')} /> </div> </f:if> @@ -115,6 +129,7 @@ <h3 class="panel-title"> cols="60" data-path="{sectionName}/{item.key}" class="form-control t3js-localConfiguration-pathValue" + {f:if(condition: item.readonly, then: 'disabled')} >{item.value}</textarea> </div> </f:if>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Cache/Custom.html+4 −2 modified@@ -13,18 +13,20 @@ <strong>Custom configuration</strong> {f:if(condition: preset.isActive, then:' [Active]')} </label> <p>Custom cache settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-cache-custom" + {f:if(condition: configuration.readonly, then: 'disabled')} /> + <f:render partial="Settings/Presets/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Context/Custom.html+4 −2 modified@@ -14,18 +14,20 @@ </label> <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-context-custom" + {f:if(condition: configuration.readonly, then: 'disabled')} /> + <f:render partial="Settings/Presets/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Image/Custom.html+4 −2 modified@@ -15,18 +15,20 @@ <p>Custom configuration mixture if no other preset fits.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-4 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-8"> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-image-custom" + {f:if(condition: configuration.readonly, then: 'disabled')} /> + <f:render partial="Settings/Presets/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/Mail/Custom.html+5 −2 modified@@ -13,7 +13,8 @@ <strong>Custom configuration</strong> {f:if(condition: preset.isActive, then:' [Active]')} </label> <p>Custom mail settings:</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> @@ -22,10 +23,12 @@ type="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'password', else: 'text')}" autocomplete="{f:if(condition: '{configurationKey} == "MAIL/transport_smtp_password"', then: 'new-password', else: 'off')}" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-mail-custom" + {f:if(condition: configuration.readonly, then: 'disabled')} /> + <f:render partial="Settings/Presets/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/PasswordHashing/Custom.html+4 −3 modified@@ -16,19 +16,20 @@ <p>Custom password hash settings. This interface does not allow modification of the values, they are just shown. Configuring custom hash settings is for advanced users who know exactly what they are doing. Refer to the core documentation for details.</p> - <f:for each="{preset.configurationValues}" as="configurationValue" key="configurationKey"> + <f:for each="{preset.configurationDescriptors}" as="configuration" key="configurationKey"> <div class="row mb-3"> <label class="col-sm-6 col-form-label" for="{feature.name}{preset.name}{configurationKey}">{configurationKey}</label> <div class="col-sm-6"> <input id="{feature.name}{preset.name}{configurationKey}" type="text" name="install[values][{feature.name}][{preset.name}][{configurationKey}]" - value="{configurationValue}" - disabled="disabled" + value="{configuration.value}" class="form-control t3js-custom-preset" data-radio="t3-install-tool-configuration-passwordHashing-custom" + {f:if(condition: configuration.readonly, then: 'disabled')} /> + <f:render partial="Settings/Presets/ReadonlyInfo" arguments="{configuration: configuration}" /> </div> </div> </f:for>
typo3/sysext/install/Resources/Private/Partials/Settings/Presets/ReadonlyInfo.html+10 −0 added@@ -0,0 +1,10 @@ +<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> + <f:if condition="{configuration.readonly}"> + <div class="t3js-infobox callout callout-info mt-1 mb-0"> + <div class="callout-body"> + For security reasons, this option cannot be changed here.<br> + Please configure via <code>typo3conf/LocalConfiguration.php</code> or <code>typo3conf/AdditionalConfiguration.php</code>. + </div> + </div> + </f:if> +</html>
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-5w2h-59j3-8x5wghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-22188ghsaADVISORY
- github.com/TYPO3/typo3/commit/47e897f8c7668ef299ecc9ce93f52cafbb3497edghsaWEB
- github.com/TYPO3/typo3/commit/6cc11761b8e2434fa4ccc9f096c65ca82569cfdfghsaWEB
- github.com/TYPO3/typo3/commit/84e07e35b880a544b517868432c56987d05d46d4ghsaWEB
- github.com/TYPO3/typo3/security/advisories/GHSA-5w2h-59j3-8x5wghsaWEB
- typo3.org/help/security-advisoriesghsaWEB
- typo3.org/security/advisory/typo3-core-sa-2024-002ghsaWEB
- typo3.org/security/advisory/typo3-psa-2020-002ghsaWEB
News mentions
0No linked articles in our index yet.