CVE-2021-32730 (Other, published 2021-07-01T18:15Z)
Description: value: 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.
Severity: MEDIUM (CVSS v3 base score: 5.7)
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/xwiki/xwiki-platform
Files changed:
xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/ApplicationResources.properties
xwiki-platform-core/xwiki-platform-web/src/main/webapp/templates/register_macros.vm
Commit message: XWIKI-18315: Bad check in reset password form.


Vulnerable code:
  #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'))
          #else
          ERROR: In field: ${fieldName}: mustMatch validation required both name
          (of field which this field must match) and failureMessage.
        #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)
        #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'))
          #else
          ERROR: In field: ${fieldName}: programmaticValidation requires code and failureMessage
        #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
      #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

Fixed code:
core.register.badCSRF=Bad CSRF token.
  <input type="hidden" name="form_token" value="$services.csrf.getToken()" ∕>
  #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.
        ##
        ## 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'))
        ## 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)
        ##
        ## 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
        #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