Skip to content

Commit

Permalink
[SECURITY] Avoid showing password hashes in backend edit forms
Browse files Browse the repository at this point in the history
Backend form fields of TCA `type=password` should never expose
the persisted value - especially, in case the value is explicitly
configured not to be hashed (having TCA `hashed=false`).

Resolves: #101965
Releases: main, 13.0, 12.4, 11.5
Change-Id: Ie05a708185c621b8a2120ad7851ac4caf180893f
Security-Bulletin: TYPO3-CORE-SA-2024-003
Security-References: CVE-2024-25118
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82947
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
  • Loading branch information
ohader committed Feb 13, 2024
1 parent 6cc1176 commit cafc5af
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 5 deletions.
7 changes: 6 additions & 1 deletion Build/Sources/TypeScript/backend/form-engine-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ export default (function() {
for (let i = 0; i < evalList.length; i++) {
formattedValue = FormEngineValidation.formatValue(evalList[i], formattedValue, config);
}

if ($mainField.prop('disabled') && $mainField.data('enableOnModification')) {
$mainField.prop('disabled', false);
}
$mainField.val(newValue);
// After updating the value of the main field, dispatch a "change" event to inform e.g. the "RequestUpdate"
// component, which always listens to the main field instead of the "human readable field", about it.
Expand Down Expand Up @@ -568,6 +570,9 @@ export default (function() {
modified = true;
}
if (modified) {
if ($field.prop('disabled') && $field.data('enableOnModification')) {
$field.prop('disabled', false);
}
$field.val(newValue);
}
}
Expand Down
19 changes: 17 additions & 2 deletions typo3/sysext/backend/Classes/Form/Element/PasswordElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function render()
$html[] = '<div class="form-wizards-wrap">';
$html[] = '<div class="form-wizards-element">';
$html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
$html[] = '<input class="form-control" id="' . htmlspecialchars($fieldId) . '" value="' . ($itemValue ? '*********' : '') . '" type="text" disabled>';
$html[] = '<input class="form-control" id="' . htmlspecialchars($fieldId) . '" value="' . htmlspecialchars($this->getObfuscatedSecretValue($itemValue)) . '" type="text" disabled>';
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
Expand Down Expand Up @@ -149,7 +149,7 @@ public function render()
$mainFieldHtml[] = '<div class="form-wizards-wrap">';
$mainFieldHtml[] = '<div class="form-wizards-element">';
$mainFieldHtml[] = '<input type="password" ' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
$mainFieldHtml[] = '<input type="hidden" name="' . $itemName . '" value="' . htmlspecialchars((string)$itemValue) . '" />';
$mainFieldHtml[] = '<input type="hidden" disabled data-enable-on-modification="true" name="' . $itemName . '" value="' . htmlspecialchars($this->getObfuscatedSecretValue($itemValue)) . '" />';
$mainFieldHtml[] = '</div>';
if (!empty($fieldControlHtml)) {
$mainFieldHtml[] = '<div class="form-wizards-items-aside form-wizards-items-aside--field-control">';
Expand Down Expand Up @@ -276,4 +276,19 @@ private function renderPasswordPolicyRequirements(

return implode(LF, $passwordPolicyElement);
}

/**
* Obfuscated a (hashed) password secret with a static string.
*
* @todo
* + server-side password obfuscation value is `*********` (9 chars)
* + client-side password obfuscation value is `********` (8 chars)
*/
protected function getObfuscatedSecretValue(?string $value): string
{
if ($value === null || $value === '') {
return '';
}
return '*********';
}
}
Loading

0 comments on commit cafc5af

Please # to comment.