org.xwiki.platform:xwiki-platform-rest-server allows SQL injection in query endpoint of REST API
Description
XWiki is a generic wiki platform. In versions starting from 1.8 and prior to 15.10.16, 16.4.6, and 16.10.1, it is possible for a remote unauthenticated user to escape from the HQL execution context and perform a blind SQL injection to execute arbitrary SQL statements on the database backend, including when "Prevent unregistered users from viewing pages, regardless of the page rights" and "Prevent unregistered users from editing pages, regardless of the page rights" options are enabled. Depending on the used database backend, the attacker may be able to not only obtain confidential information such as password hashes from the database, but also execute UPDATE/INSERT/DELETE queries. This issue has been patched in versions 16.10.1, 16.4.6 and 15.10.16. There is no known workaround, other than upgrading XWiki.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-rest-serverMaven | >= 1.8, < 15.10.16 | 15.10.16 |
org.xwiki.platform:xwiki-platform-rest-serverMaven | >= 16.0.0-rc-1, < 16.4.6 | 16.4.6 |
org.xwiki.platform:xwiki-platform-rest-serverMaven | >= 16.5.0-rc-1, < 16.10.1 | 16.10.1 |
Affected products
1- Range: >= 1.8, < 15.10.16
Patches
15c11a874bd24XWIKI-22718, XWIKI-22691: Improve query validation
4 files changed · +35 −15
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/hibernate/query/HqlQueryExecutor.java+26 −10 modified@@ -131,12 +131,23 @@ private Set<String> getAllowedNamedQueries() */ protected static boolean isSafeSelect(String statementString) { - return HqlQueryUtils.isShortFormStatement(statementString) || HqlQueryUtils.isSafe(statementString); + // An empty statement is safe + if (statementString.isEmpty()) { + return true; + } + + // HqlQueryUtils#isSafe only works with complete statements + String completeStatement = toCompleteShortForm(statementString); + + // Parse and validate the statement + return HqlQueryUtils.isSafe(completeStatement); } protected void checkAllowed(final Query query) throws QueryException { - if (query instanceof SecureQuery && ((SecureQuery) query).isCurrentAuthorChecked()) { + // Check if the query needs to be validated according to the current author + if (query instanceof SecureQuery secureQuery && secureQuery.isCurrentAuthorChecked()) { + // Not need to check the details if current author has programming right if (!this.authorization.hasAccess(Right.PROGRAM)) { if (query.isNamed() && !getAllowedNamedQueries().contains(query.getStatement())) { throw new QueryException("Named queries requires programming right", query, null); @@ -152,15 +163,15 @@ protected void checkAllowed(final Query query) throws QueryException @Override public <T> List<T> execute(final Query query) throws QueryException { - // Make sure the query is allowed in the current context - checkAllowed(query); - String oldDatabase = getContext().getWikiId(); try { if (query.getWiki() != null) { getContext().setWikiId(query.getWiki()); } + // Make sure the query is allowed. Make sure to do it in the target context. + checkAllowed(query); + // Filter the query Query filteredQuery = filterQuery(query); @@ -333,13 +344,18 @@ private boolean hasQueryParametersType(Query query) */ protected String completeShortFormStatement(String statement) { - String lcStatement = statement.toLowerCase().trim(); - if (lcStatement.isEmpty() || lcStatement.startsWith(",") || lcStatement.startsWith("where ") - || lcStatement.startsWith("order by ")) { - return "select doc.fullName from XWikiDocument doc " + statement.trim(); + return toCompleteShortForm(statement); + } + + private static String toCompleteShortForm(String statement) + { + String filteredStatement = statement; + + if (statement.isEmpty() || HqlQueryUtils.isShortFormStatement(statement)) { + filteredStatement = "select doc.fullName from XWikiDocument doc " + statement.trim(); } - return statement; + return filteredStatement; } private <T> org.hibernate.query.Query<T> createNamedHibernateQuery(Session session, Query query)
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/internal/store/hibernate/query/HqlQueryUtilsTest.java+2 −0 modified@@ -63,6 +63,8 @@ public void isSafe() .isSafe("select doc.name, ot.field from XWikiDocument doc, XWikiSpace space, OtherTable as ot")); assertFalse(HqlQueryUtils.isSafe("select count(*) from OtherTable")); assertFalse(HqlQueryUtils.isSafe("select count(other.*) from OtherTable other")); + assertFalse(HqlQueryUtils.isSafe("select doc.fullName from XWikiDocument doc union all select name from OtherTable")); + assertFalse(HqlQueryUtils.isSafe("select doc.fullName from XWikiDocument doc where 1<>'1\\'' union select name from OtherTable #'")); } @Test
xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/store/hibernate/query/HqlQueryExecutorTest.java+6 −5 modified@@ -220,7 +220,7 @@ public void setNamedParameterArray() { org.hibernate.query.Query query = mock(org.hibernate.query.Query.class); String name = "bar"; - Integer[] value = new Integer[] { 1, 2, 3 }; + Integer[] value = new Integer[] {1, 2, 3}; this.executor.setNamedParameter(query, name, value); verify(query).setParameterList(name, value); @@ -403,7 +403,7 @@ public void executeWhenNotAllowedSelect() throws Exception assertEquals( "The query requires programming right." + " Query statement = [select notallowed.name from NotAllowedTable notallowed]", - expected.getMessage()); + expected.getCause().getMessage()); } } @@ -415,7 +415,7 @@ public void executeDeleteWithoutProgrammingRight() throws Exception fail("Should have thrown an exception here"); } catch (QueryException expected) { assertEquals("The query requires programming right. Query statement = [delete from XWikiDocument as doc]", - expected.getMessage()); + expected.getCause().getMessage()); } } @@ -426,7 +426,8 @@ public void executeNamedQueryWithoutProgrammingRight() throws Exception executeNamed("somename", false); fail("Should have thrown an exception here"); } catch (QueryException expected) { - assertEquals("Named queries requires programming right. Named query = [somename]", expected.getMessage()); + assertEquals("Named queries requires programming right. Named query = [somename]", + expected.getCause().getMessage()); } } @@ -439,7 +440,7 @@ public void executeUpdateWithoutProgrammingRight() throws Exception } catch (QueryException expected) { assertEquals( "The query requires programming right. Query statement = [update XWikiDocument set name='name']", - expected.getMessage()); + expected.getCause().getMessage()); } } }
xwiki-platform-core/xwiki-platform-rest/xwiki-platform-rest-server/src/main/java/org/xwiki/rest/internal/resources/search/AbstractDatabaseSearchSource.java+1 −0 modified@@ -63,6 +63,7 @@ public abstract class AbstractDatabaseSearchSource extends AbstractSearchSource protected Provider<XWikiContext> xcontextProvider; @Inject + @Named("secure") protected QueryManager queryManager; @Inject
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-f69v-xrj8-rhxfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-32969ghsaADVISORY
- github.com/xwiki/xwiki-platform/commit/5c11a874bd24a581f534d283186e209bbccd8113ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-f69v-xrj8-rhxfghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-22691ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.