VYPR
Moderate severityNVD Advisory· Published Sep 8, 2022· Updated Apr 23, 2025

XWiki Cross-Site Request Forgery (CSRF) for actions on tags

CVE-2022-36095

Description

XWiki Platform is a generic wiki platform. Prior to versions 13.10.5 and 14.3, it is possible to perform a Cross-Site Request Forgery (CSRF) attack for adding or removing tags on XWiki pages. The problem has been patched in XWiki 13.10.5 and 14.3. As a workaround, one may locally modify the documentTags.vm template in one's filesystem, to apply the changes exposed there.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.xwiki.platform:xwiki-platform-web-templatesMaven
>= 2.0-milestone-1, < 13.10.513.10.5
org.xwiki.platform:xwiki-platform-web-templatesMaven
>= 14.0, < 14.314.3

Affected products

1

Patches

1
7ca56e40cf79

XWIKI-19550: Wrong error message in case of missing tag plugin

https://github.com/xwiki/xwiki-platformSimon UrliApr 21, 2022via ghsa
1 file changed · +67 48
  • xwiki-platform-core/xwiki-platform-web/xwiki-platform-web-templates/src/main/resources/templates/documentTags.vm+67 48 modified
    @@ -25,72 +25,91 @@
     ##
     ##
     #macro(displayTag $tag)
    -<span class="tag-wrapper"><span class="tag"><a href="$xwiki.getURL('Main.Tags', 'view', "do=viewTag&amp;tag=$!{escapetool.url($tag)}")">$!{escapetool.xml($tag)}</a></span>#if($hasedit)<span class="separator">[</span><a href="$doc.getURL('view', "xpage=documentTags&amp;xaction=delete&amp;tag=$!{escapetool.url($tag)}&amp;xredirect=${xredirect}")" class="tag-tool tag-delete" title="$services.localization.render('core.tags.remove.tooltip')">X</a><span class="separator">]</span>#end</span>
    +  #set ($viewTagUrl = $xwiki.getURL('Main.Tags', 'view', "do=viewTag&amp;tag=$!{escapetool.url($tag)}"))
    +  ## Note that the form_token parameter needs to be kept before the xredirect parameter since the JS code might replace the latter
    +  ## All that would need a cleaner fix in the javascript of tags.
    +  #set ($deleteTagUrl = $doc.getURL('view', "xpage=documentTags&amp;xaction=delete&amp;tag=$!{escapetool.url($tag)}&amp;form_token=$!{escapetool.url($services.csrf.token)}&amp;xredirect=${xredirect}"))
    +<span class="tag-wrapper">
    +  <span class="tag"><a href="$viewTagUrl">$!{escapetool.xml($tag)}</a></span>
    +  #if($hasedit)<span class="separator">[</span><a href="$deleteTagUrl" class="tag-tool tag-delete" title="$services.localization.render('core.tags.remove.tooltip')">X</a><span class="separator">]</span>#end
    +</span>
     #end
     ##
     #macro(removeTag $tag)
    -    #if($xwiki.tag)
    -        #set($result = $xwiki.tag.removeTagFromDocument($tag, $doc.fullName))
    -        #if($result == 'OK' && "$!{request.ajax}" != '')
    -            $response.setStatus(200)
    -            #set($responseMessage = 'OK')
    -        #elseif($result == 'NO_EFFECT')
    -            $response.setStatus(409)
    -            #set($responseMessage = $services.localization.render('core.tags.remove.error.notFound', [$tag]))
    -        #elseif($result == 'NOT_ALLOWED')
    -            $response.setStatus(403)
    -            #set($responseMessage = $services.localization.render('core.tags.remove.error.notAllowed', [$tag]))
    -        #elseif($result == 'FAILED')
    -            $response.setStatus(500)
    -            #set($responseMessage = $services.localization.render('core.tags.remove.error.failed', [$tag]))
    -        #end
    -        #if("$!{request.ajax}" != '')
    -            $!responseMessage
    -        #elseif("$!{request.xredirect}" != '')
    -            $response.sendRedirect($request.xredirect)
    +    #if ($services.csrf.isTokenValid($request.get('form_token')))
    +        #if($xwiki.tag)
    +            #set($result = $xwiki.tag.removeTagFromDocument($tag, $doc.fullName))
    +            #if($result == 'OK' && "$!{request.ajax}" != '')
    +                #set ($discard= $response.setStatus(200))
    +                #set($responseMessage = 'OK')
    +            #elseif($result == 'NO_EFFECT')
    +                #set ($discard= $response.setStatus(409))
    +                #set($responseMessage = $services.localization.render('core.tags.remove.error.notFound', [$tag]))
    +            #elseif($result == 'NOT_ALLOWED')
    +                #set ($discard= $response.setStatus(403))
    +                #set($responseMessage = $services.localization.render('core.tags.remove.error.notAllowed', [$tag]))
    +            #elseif($result == 'FAILED')
    +                #set ($discard= $response.setStatus(500))
    +                #set($responseMessage = $services.localization.render('core.tags.remove.error.failed', [$tag]))
    +            #end
    +        #else
    +            #set ($discard= $response.setStatus(501))
    +            #set ($responseMessage = "Tag plugin is missing")
             #end
         #else
    -        ## TODO
    +        #set ($discard= $response.setStatus(401))
    +        #set($responseMessage = $services.localization.render('core.tags.remove.error.notAllowed', [$tag]))
    +    #end
    +    #if("$!{request.ajax}" != '')
    +        $!responseMessage
    +    #elseif("$!{request.xredirect}" != '')
    +        $response.sendRedirect($request.xredirect)
         #end
     #end
     ##
     #macro(addTag $tag)
    -    #if($xwiki.tag)
    -        #set($oldTags = $xwiki.tag.getTagsFromDocument($doc.fullName))
    -        #set($result = $xwiki.tag.addTagsToDocument($tag, $doc.fullName))
    -        #if($result == 'OK' && "$!{request.ajax}" != '')
    -            #set($newTags = $xwiki.tag.getTagsFromDocument($doc.fullName))
    -            #set($discard = $newTags.removeAll($oldTags))
    -            #foreach($t in $newTags)
    -                #if($t != '' && !$oldTags.contains($t))
    -                    #displayTag($t)
    +    #if ($services.csrf.isTokenValid($request.get('form_token')))
    +        #if($xwiki.tag)
    +            #set($oldTags = $xwiki.tag.getTagsFromDocument($doc.fullName))
    +            #set($result = $xwiki.tag.addTagsToDocument($tag, $doc.fullName))
    +            #if($result == 'OK' && "$!{request.ajax}" != '')
    +                #set($newTags = $xwiki.tag.getTagsFromDocument($doc.fullName))
    +                #set($discard = $newTags.removeAll($oldTags))
    +                #foreach($t in $newTags)
    +                    #if($t != '' && !$oldTags.contains($t))
    +                        #displayTag($t)
    +                    #end
                     #end
    +            #elseif($result == 'NO_EFFECT')
    +                $response.setStatus(409)
    +                #set($tagErrorMessage = $services.localization.render('core.tags.add.error.alreadySet', [$tag]))
    +            #elseif($result == 'NOT_ALLOWED')
    +                $response.setStatus(403)
    +                #set($tagErrorMessage = $services.localization.render('core.tags.add.error.notAllowed', [$tag]))
    +            #elseif($result == 'FAILED')
    +                $response.setStatus(500)
    +                #set($tagErrorMessage = $services.localization.render('core.tags.add.error.failed', [$tag]))
                 #end
    -        #elseif($result == 'NO_EFFECT')
    -            $response.setStatus(409)
    -            #set($tagErrorMessage = $services.localization.render('core.tags.add.error.alreadySet', [$tag]))
    -        #elseif($result == 'NOT_ALLOWED')
    -            $response.setStatus(403)
    -            #set($tagErrorMessage = $services.localization.render('core.tags.add.error.notAllowed', [$tag]))
    -        #elseif($result == 'FAILED')
    -            $response.setStatus(500)
    -            #set($tagErrorMessage = $services.localization.render('core.tags.add.error.failed', [$tag]))
    -        #end
    -        #if("$!{request.ajax}" != '')
    -            $tagErrorMessage
    -        #elseif("$!{request.xredirect}" != '')
    -            $response.sendRedirect($request.xredirect)
    +            #if("$!{request.ajax}" != '')
    +                $tagErrorMessage
    +            #elseif("$!{request.xredirect}" != '')
    +                $response.sendRedirect($request.xredirect)
    +            #end
    +        #else
    +            #set ($discard= $response.setStatus(501))
    +            #set ($responseMessage = "Tag plugin is missing")
             #end
         #else
    -        ## TODO
    +        #set ($discard= $response.setStatus(401))
    +        #set($responseMessage = $services.localization.render('core.tags.add.error.notAllowed', [$tag]))
         #end
     #end
     ##
     #macro(displayAddForm)
    -<form action="$doc.getURL('view', "xpage=documentTags&amp;xaction=add&amp;xredirect=${xredirect}")" method="post" class="tag-add-form">
    +## Note that the form_token parameter needs to be kept before the xredirect parameter since the JS code might replace the latter
    +## All that would need a cleaner fix in the javascript of tags.
    +<form action="$doc.getURL('view', "xpage=documentTags&amp;xaction=add&amp;form_token=$!{escapetool.url($services.csrf.token)}&amp;xredirect=${xredirect}")" method="post" class="tag-add-form">
       <div>
    -      ## CSRF prevention
    -    <div class="hidden"><input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" /></div>
         <label for="tag">$services.localization.render('core.tags.add.label')<br/>
           <input class="input-tag" type="text" id="tag" name="tag" autocomplete="off"/></label><br/>
         <span class="buttonwrapper"><input class="button button-add-tag" type="submit" value="$services.localization.render('core.tags.add.submit')"/></span>
    

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

News mentions

0

No linked articles in our index yet.