No CSRF protection on the password change form
Description
XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. A cross-site request forgery vulnerability exists in versions prior to 12.10.5, and in versions 13.0 through 13.1. It's possible for forge an URL that, when accessed by an admin, will reset the password of any user in XWiki. The problem has been patched in XWiki 12.10.5 and 13.2RC1. As a workaround, it is possible to apply the patch manually by modifying the register_macros.vm template.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-administration-uiMaven | < 12.10.5 | 12.10.5 |
org.xwiki.platform:xwiki-platform-administration-uiMaven | >= 13.0, < 13.2 | 13.2 |
Affected products
1- Range: < 12.10.5
Patches
10a36dbcc5421XWIKI-18315: Bad check in reset password form.
2 files changed · +67 −60
xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties+1 −0 modified@@ -1629,6 +1629,7 @@ core.register.password=Password core.register.passwordRepeat=Confirm Password core.register.email=Email Address core.register.submit=Register +core.register.badCSRF=Bad CSRF token. # User account validation core.users.activation.validationKey.label=Validation key:
xwiki-platform-core/xwiki-platform-web/src/main/webapp/templates/register_macros.vm+66 −60 modified@@ -172,6 +172,7 @@ $xwiki.get('ssfx').use('uicomponents/widgets/validation/livevalidation.css', tru #end #end </dl> + <input type="hidden" name="form_token" value="$services.csrf.getToken()" ∕> #generateJavascript($fields) #end ## @@ -311,74 +312,79 @@ $xwiki.get('ssfx').use('uicomponents/widgets/validation/livevalidation.css', tru #macro(validateFields, $fields, $request) #set ($allFieldsValid = true) #set ($allFieldsErrors = []) - #foreach($field in $fields) - #if($field.get('validate') && $field.get('name')) - #set($fieldName = $field.get('name')) - #set($validate = $field.get('validate')) - #set($error = '') - #set($value = $request.get($fieldName)) - #if("$!value" != '' || $field.get('type') == 'html') - ## - ## mustMatch validation - #if($error == '' && $validate.get('mustMatch')) - #set($mustMatch = $validate.get('mustMatch')) - #if($mustMatch.get('name') && $mustMatch.get('failureMessage')) - #if($request.get($fieldName) != $request.get($mustMatch.get('name'))) - #set($error = $mustMatch.get('failureMessage')) + #if (!$services.csrf.isTokenValid($request.form_token)) + #set ($allFieldsValid = false) + #set ($discard = $allFieldsErrors.add($services.localization.render('core.register.badCSRF'))) + #else + #foreach($field in $fields) + #if($field.get('validate') && $field.get('name')) + #set($fieldName = $field.get('name')) + #set($validate = $field.get('validate')) + #set($error = '') + #set($value = $request.get($fieldName)) + #if("$!value" != '' || $field.get('type') == 'html') + ## + ## mustMatch validation + #if($error == '' && $validate.get('mustMatch')) + #set($mustMatch = $validate.get('mustMatch')) + #if($mustMatch.get('name') && $mustMatch.get('failureMessage')) + #if($request.get($fieldName) != $request.get($mustMatch.get('name'))) + #set($error = $mustMatch.get('failureMessage')) + #end + #else + ERROR: In field: ${fieldName}: mustMatch validation required both name + (of field which this field must match) and failureMessage. #end - #else - ERROR: In field: ${fieldName}: mustMatch validation required both name - (of field which this field must match) and failureMessage. #end - #end - ## - ## Regex validation - ## We won't bother with regex validation if there is no entry, that would defeat the purpose of 'mandatory' - #if($error == '' && $validate.get('regex') && $value && $value != '') - #set($regex = $validate.get('regex')) - #validateRegex($value, $fieldName, $regex, $error) - #end - ## List of regex validation - #if($error == '' && $validate.get('regexes') && $value && $value != '') - #set($regexes = $validate.get('regexes')) - #foreach ($regex in $regexes) + ## + ## Regex validation + ## We won't bother with regex validation if there is no entry, that would defeat the purpose of 'mandatory' + #if($error == '' && $validate.get('regex') && $value && $value != '') + #set($regex = $validate.get('regex')) #validateRegex($value, $fieldName, $regex, $error) #end - #end - ## - ## If regex and mustMatch validation passed, try programmatic validation - #if($error == '' && $validate.get('programmaticValidation')) - #set($pv = $validate.get('programmaticValidation')) - #if($pv.get('code') && $pv.get('failureMessage')) - #set($pvReturn = "#evaluate($pv.get('code'))") - #if($pvReturn.indexOf('failed') != -1) - #set($error = $pv.get('failureMessage')) + ## List of regex validation + #if($error == '' && $validate.get('regexes') && $value && $value != '') + #set($regexes = $validate.get('regexes')) + #foreach ($regex in $regexes) + #validateRegex($value, $fieldName, $regex, $error) #end - #else - ERROR: In field: ${fieldName}: programmaticValidation requires code and failureMessage #end - #end - #else - ## - ## If no content, check if content is mandatory - #if($validate.get('mandatory')) - #set($mandatory = $validate.get('mandatory')) - #if($mandatory.get('failureMessage')) - #set($error = $mandatory.get('failureMessage')) - #else - ERROR: In field: ${fieldName}: mandatory validation requires a failureMessage + ## + ## If regex and mustMatch validation passed, try programmatic validation + #if($error == '' && $validate.get('programmaticValidation')) + #set($pv = $validate.get('programmaticValidation')) + #if($pv.get('code') && $pv.get('failureMessage')) + #set($pvReturn = "#evaluate($pv.get('code'))") + #if($pvReturn.indexOf('failed') != -1) + #set($error = $pv.get('failureMessage')) + #end + #else + ERROR: In field: ${fieldName}: programmaticValidation requires code and failureMessage + #end + #end + #else + ## + ## If no content, check if content is mandatory + #if($validate.get('mandatory')) + #set($mandatory = $validate.get('mandatory')) + #if($mandatory.get('failureMessage')) + #set($error = $mandatory.get('failureMessage')) + #else + ERROR: In field: ${fieldName}: mandatory validation requires a failureMessage + #end #end #end - #end - #if($error != '') - #set($discard = $field.put('error', $error)) - #set ($discard = $allFieldsErrors.add($error)) - #set($allFieldsValid = false) - #end - #elseif(!$field.get('name')) - ERROR: Field with no name. - #end##if(validate) - #end##loop + #if($error != '') + #set($discard = $field.put('error', $error)) + #set ($discard = $allFieldsErrors.add($error)) + #set($allFieldsValid = false) + #end + #elseif(!$field.get('name')) + ERROR: Field with no name. + #end##if(validate) + #end##loop + #end ## CSRF check #end##macro #*
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-v9j2-q4q5-cxh4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-32730ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/0a36dbcc5421d450366580217a47cc44d32f7257ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-v9j2-q4q5-cxh4ghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-18315ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.