XWiki Platform's searchDocuments API allows for SQL injection
Description
XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. In versions between 17.0.0-rc1 to 17.2.2 and versions 16.10.5 and below, it's possible to execute any SQL query in Oracle by using the function like DBMS_XMLGEN or DBMS_XMLQUERY. The XWiki#searchDocuments APIs pass queries directly to Hibernate without sanitization. Even when these APIs enforce a specific SELECT clause, attackers can still inject malicious code through HQL's native function support in other parts of the query (such as the WHERE clause). This is fixed in versions 16.10.6 and 17.3.0-rc-1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.xwiki.platform:xwiki-platform-oldcoreMaven | >= 1.0, < 16.10.6 | 16.10.6 |
org.xwiki.platform:xwiki-platform-oldcoreMaven | >= 17.0.0-rc1, < 17.3.0-rc-1 | 17.3.0-rc-1 |
Affected products
1- Range: < 16.10.6
Patches
27c4087d44ac5XWIKI-22728: Improve search query validation
3 files changed · +137 −49
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/XWiki.java+59 −9 modified@@ -44,6 +44,7 @@ import org.xwiki.model.reference.PageReference; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; +import org.xwiki.query.hql.internal.HQLStatementValidator; import org.xwiki.rendering.renderer.PrintRendererFactory; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.security.authorization.AuthorizationException; @@ -58,6 +59,7 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.XWikiInitializerJob; import com.xpn.xwiki.internal.XWikiInitializerJobStatus; +import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils; import com.xpn.xwiki.objects.meta.MetaClass; import com.xpn.xwiki.user.api.XWikiUser; import com.xpn.xwiki.util.Programming; @@ -105,6 +107,8 @@ public class XWiki extends Api private ContextualAuthorizationManager contextualAuthorizationManager; + private HQLStatementValidator hqlValidator; + /** * XWiki API Constructor * @@ -167,6 +171,15 @@ private DocumentRevisionProvider getDocumentRevisionProvider() return this.documentRevisionProvider; } + private HQLStatementValidator getHQLStatementValidator() + { + if (this.hqlValidator == null) { + this.hqlValidator = Utils.getComponent(HQLStatementValidator.class); + } + + return this.hqlValidator; + } + /** * Privileged API allowing to access the underlying main XWiki Object * @@ -710,6 +723,23 @@ public MetaClass getMetaclass() return this.xwiki.getMetaclass(); } + private void checkSearchQueryAllowed(String whereSQL) throws XWikiException + { + if (!hasProgrammingRights()) { + try { + if (!getHQLStatementValidator() + .isSafe(HqlQueryUtils.createLegacySQLQuery("select distinct doc.fullName", whereSQL))) { + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, + XWikiException.ERROR_XWIKI_ACCESS_DENIED, + "The query [" + whereSQL + "] requires programming right"); + } + } catch (Exception e) { + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_ACCESS_DENIED, + "Failed to validate the query [" + whereSQL + "], requiring programming right", e); + } + } + } + /** * API allowing to search for document names matching a query. Examples: * <ul> @@ -742,6 +772,8 @@ public MetaClass getMetaclass() @Deprecated public List<String> searchDocuments(String wheresql) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return this.xwiki.getStore().searchDocumentsNames(wheresql, getXWikiContext()); } @@ -760,6 +792,8 @@ public List<String> searchDocuments(String wheresql) throws XWikiException @Deprecated public List<String> searchDocuments(String wheresql, int nb, int start) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return this.xwiki.getStore().searchDocumentsNames(wheresql, nb, start, getXWikiContext()); } @@ -796,6 +830,8 @@ public List<String> searchDocuments(String wheresql, int nb, int start, String s */ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return convert(this.xwiki.getStore().searchDocuments(wheresql, distinctbylocale, getXWikiContext())); } @@ -812,6 +848,8 @@ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale) public List<Document> searchDocuments(String wheresql, boolean distinctbylocale, int nb, int start) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return convert(this.xwiki.getStore().searchDocuments(wheresql, distinctbylocale, nb, start, getXWikiContext())); } @@ -845,6 +883,8 @@ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale, public List<String> searchDocuments(String parameterizedWhereClause, int maxResults, int startOffset, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedWhereClause); + return this.xwiki.getStore().searchDocumentsNames(parameterizedWhereClause, maxResults, startOffset, parameterValues, getXWikiContext()); } @@ -858,6 +898,8 @@ public List<String> searchDocuments(String parameterizedWhereClause, int maxResu @Deprecated public List<String> searchDocuments(String parameterizedWhereClause, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedWhereClause); + return this.xwiki.getStore().searchDocumentsNames(parameterizedWhereClause, parameterValues, getXWikiContext()); } @@ -885,6 +927,8 @@ public List<String> searchDocumentsNames(String wikiName, String parameterizedWh try { this.context.setWikiId(wikiName); + checkSearchQueryAllowed(parameterizedWhereClause); + return searchDocuments(parameterizedWhereClause, maxResults, startOffset, parameterValues); } finally { this.context.setWikiId(database); @@ -895,18 +939,20 @@ public List<String> searchDocumentsNames(String wikiName, String parameterizedWh * Search spaces by passing HQL where clause values as parameters. See * {@link #searchDocuments(String, int, int, List)} for more about parameterized hql clauses. * - * @param parametrizedSqlClause the HQL where clause. For example + * @param parameterizedSqlClause the HQL where clause. For example * {@code where doc.fullName <> ?1 and (doc.parent = ?2 or (doc.parent = ?3 and doc.space = ?4))} * @param nb the number of rows to return. If 0 then all rows are returned * @param start the number of rows to skip. If 0 don't skip any row * @param parameterValues the where clause values that replace the question marks (?) * @return a list of spaces names. * @throws XWikiException in case of error while performing the query */ - public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int start, List<?> parameterValues) + public List<String> searchSpacesNames(String parameterizedSqlClause, int nb, int start, List<?> parameterValues) throws XWikiException { - return this.xwiki.getStore().search("select distinct doc.space from XWikiDocument doc " + parametrizedSqlClause, + checkSearchQueryAllowed(parameterizedSqlClause); + + return this.xwiki.getStore().search("select distinct doc.space from XWikiDocument doc " + parameterizedSqlClause, nb, start, parameterValues, this.context); } @@ -915,7 +961,7 @@ public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int * {@link #searchDocuments(String, int, int, List)} for more about parameterized hql clauses. You can specify * properties of attach (the attachment) or doc (the document it is attached to) * - * @param parametrizedSqlClause The HQL where clause. For example + * @param parameterizedSqlClause The HQL where clause. For example * {@code where doc.fullName <> ?1 and (doc.parent = ?2 or (doc.parent = ?3 and doc.space = ?4))} * @param nb The number of rows to return. If 0 then all rows are returned * @param start The number of rows to skip at the beginning. @@ -924,27 +970,31 @@ public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int * @throws XWikiException in case of error while performing the query * @since 5.0M2 */ - public List<Attachment> searchAttachments(String parametrizedSqlClause, int nb, int start, List<?> parameterValues) + public List<Attachment> searchAttachments(String parameterizedSqlClause, int nb, int start, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedSqlClause); + return convertAttachments( - this.xwiki.searchAttachments(parametrizedSqlClause, true, nb, start, parameterValues, this.context)); + this.xwiki.searchAttachments(parameterizedSqlClause, true, nb, start, parameterValues, this.context)); } /** * Count attachments returned by a given parameterized query * - * @param parametrizedSqlClause Everything which would follow the "WHERE" in HQL see: + * @param parameterizedSqlClause Everything which would follow the "WHERE" in HQL see: * {@link #searchDocuments(String, int, int, List)} * @param parameterValues A {@link java.util.List} of the where clause values that replace the question marks (?) * @return int number of attachments found. * @throws XWikiException * @see #searchAttachments(String, int, int, List) * @since 5.0M2 */ - public int countAttachments(String parametrizedSqlClause, List<?> parameterValues) throws XWikiException + public int countAttachments(String parameterizedSqlClause, List<?> parameterValues) throws XWikiException { - return this.xwiki.countAttachments(parametrizedSqlClause, parameterValues, this.context); + checkSearchQueryAllowed(parameterizedSqlClause); + + return this.xwiki.countAttachments(parameterizedSqlClause, parameterValues, this.context); } /**
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/store/hibernate/query/HqlQueryUtils.java+75 −1 modified@@ -19,6 +19,7 @@ */ package com.xpn.xwiki.internal.store.hibernate.query; +import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,6 +35,16 @@ */ public final class HqlQueryUtils { + private static final String FROM = "from"; + + private static final String WHERE = "where "; + + private static final String ORDER = "order"; + + private static final String ORDER_BY = "order by"; + + private static final String COMMA = ","; + private static final Pattern LEGACY_ORDINAL_PARAMS_PATTERN = Pattern.compile("([=\\s,\\(<>])\\?([=\\s,\\)<>]|$)"); private HqlQueryUtils() @@ -47,7 +58,7 @@ private HqlQueryUtils() */ public static boolean isShortFormStatement(String statement) { - return StringUtils.startsWithAny(statement.trim().toLowerCase(), ",", "where ", "order by"); + return StringUtils.startsWithAny(statement.trim().toLowerCase(), COMMA, WHERE, ORDER_BY); } /** @@ -129,4 +140,67 @@ public String getStatement() return completeQuery; } + + /** + * @param queryPrefix the start of the SQL query (for example "select distinct doc.space, doc.name") + * @param whereSQL the where clause to append + * @return the full formed SQL query, to which the order by columns have been added as returned columns (this is + * required for example for HSQLDB). + * @since 17.0.3RC1 + * @since 16.10.6 + */ + public static String createLegacySQLQuery(String queryPrefix, String whereSQL) + { + StringBuilder sql = new StringBuilder(queryPrefix); + + String normalizedWhereSQL; + if (StringUtils.isBlank(whereSQL)) { + normalizedWhereSQL = ""; + } else { + normalizedWhereSQL = whereSQL.trim(); + } + + sql.append(getColumnsForSelectStatement(normalizedWhereSQL)); + sql.append(" from XWikiDocument as doc"); + + if (!normalizedWhereSQL.equals("")) { + if ((!normalizedWhereSQL.startsWith(WHERE)) && (normalizedWhereSQL.charAt(0) != ',')) { + sql.append(" where "); + } else { + sql.append(" "); + } + sql.append(normalizedWhereSQL); + } + + return sql.toString(); + } + + /** + * @param whereSQL the SQL where clause + * @return the list of columns to return in the select clause as a string starting with ", " if there are columns or + * an empty string otherwise. The returned columns are extracted from the where clause. One reason for doing + * so is because HSQLDB only support SELECT DISTINCT SQL statements where the columns operated on are + * returned from the query. + * @since 17.0.3RC1 + * @since 16.10.6 + */ + public static String getColumnsForSelectStatement(String whereSQL) + { + StringBuilder columns = new StringBuilder(); + + int orderByPos = whereSQL.toLowerCase().indexOf(ORDER_BY); + if (orderByPos >= 0) { + String orderByStatement = whereSQL.substring(orderByPos + ORDER_BY.length() + 1); + StringTokenizer tokenizer = new StringTokenizer(orderByStatement, COMMA); + while (tokenizer.hasMoreTokens()) { + String column = tokenizer.nextToken().trim(); + // Remove "desc" or "asc" from the column found + column = StringUtils.removeEndIgnoreCase(column, " desc"); + column = StringUtils.removeEndIgnoreCase(column, " asc"); + columns.append(", ").append(column.trim()); + } + } + + return columns.toString(); + } }
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java+3 −39 modified@@ -38,7 +38,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -101,6 +100,7 @@ import com.xpn.xwiki.doc.XWikiLock; import com.xpn.xwiki.doc.XWikiSpace; import com.xpn.xwiki.internal.store.hibernate.legacy.LegacySessionImplementor; +import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils; import com.xpn.xwiki.monitor.api.MonitorPlugin; import com.xpn.xwiki.objects.BaseCollection; import com.xpn.xwiki.objects.BaseElement; @@ -2902,28 +2902,7 @@ public List<XWikiDocument> searchDocuments(String wheresql, boolean distinctbyla */ protected String createSQLQuery(String queryPrefix, String whereSQL) { - StringBuilder sql = new StringBuilder(queryPrefix); - - String normalizedWhereSQL; - if (StringUtils.isBlank(whereSQL)) { - normalizedWhereSQL = ""; - } else { - normalizedWhereSQL = whereSQL.trim(); - } - - sql.append(getColumnsForSelectStatement(normalizedWhereSQL)); - sql.append(" from XWikiDocument as doc"); - - if (!normalizedWhereSQL.equals("")) { - if ((!normalizedWhereSQL.startsWith("where")) && (!normalizedWhereSQL.startsWith(","))) { - sql.append(" where "); - } else { - sql.append(" "); - } - sql.append(normalizedWhereSQL); - } - - return sql.toString(); + return HqlQueryUtils.createLegacySQLQuery(queryPrefix, whereSQL); } /** @@ -2935,22 +2914,7 @@ protected String createSQLQuery(String queryPrefix, String whereSQL) */ protected String getColumnsForSelectStatement(String whereSQL) { - StringBuilder columns = new StringBuilder(); - - int orderByPos = whereSQL.toLowerCase().indexOf("order by"); - if (orderByPos >= 0) { - String orderByStatement = whereSQL.substring(orderByPos + "order by".length() + 1); - StringTokenizer tokenizer = new StringTokenizer(orderByStatement, ","); - while (tokenizer.hasMoreTokens()) { - String column = tokenizer.nextToken().trim(); - // Remove "desc" or "asc" from the column found - column = StringUtils.removeEndIgnoreCase(column, " desc"); - column = StringUtils.removeEndIgnoreCase(column, " asc"); - columns.append(", ").append(column.trim()); - } - } - - return columns.toString(); + return HqlQueryUtils.getColumnsForSelectStatement(whereSQL); } @Override
7313dc9b533cXWIKI-22728: Improve search query validation
3 files changed · +137 −49
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/api/XWiki.java+59 −9 modified@@ -44,6 +44,7 @@ import org.xwiki.model.reference.PageReference; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; +import org.xwiki.query.hql.internal.HQLStatementValidator; import org.xwiki.rendering.renderer.PrintRendererFactory; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.security.authorization.AuthorizationException; @@ -58,6 +59,7 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.internal.XWikiInitializerJob; import com.xpn.xwiki.internal.XWikiInitializerJobStatus; +import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils; import com.xpn.xwiki.objects.meta.MetaClass; import com.xpn.xwiki.user.api.XWikiUser; import com.xpn.xwiki.util.Programming; @@ -105,6 +107,8 @@ public class XWiki extends Api private ContextualAuthorizationManager contextualAuthorizationManager; + private HQLStatementValidator hqlValidator; + /** * XWiki API Constructor * @@ -167,6 +171,15 @@ private DocumentRevisionProvider getDocumentRevisionProvider() return this.documentRevisionProvider; } + private HQLStatementValidator getHQLStatementValidator() + { + if (this.hqlValidator == null) { + this.hqlValidator = Utils.getComponent(HQLStatementValidator.class); + } + + return this.hqlValidator; + } + /** * Privileged API allowing to access the underlying main XWiki Object * @@ -710,6 +723,23 @@ public MetaClass getMetaclass() return this.xwiki.getMetaclass(); } + private void checkSearchQueryAllowed(String whereSQL) throws XWikiException + { + if (!hasProgrammingRights()) { + try { + if (!getHQLStatementValidator() + .isSafe(HqlQueryUtils.createLegacySQLQuery("select distinct doc.fullName", whereSQL))) { + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, + XWikiException.ERROR_XWIKI_ACCESS_DENIED, + "The query [" + whereSQL + "] requires programming right"); + } + } catch (Exception e) { + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_ACCESS_DENIED, + "Failed to validate the query [" + whereSQL + "], requiring programming right", e); + } + } + } + /** * API allowing to search for document names matching a query. Examples: * <ul> @@ -742,6 +772,8 @@ public MetaClass getMetaclass() @Deprecated public List<String> searchDocuments(String wheresql) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return this.xwiki.getStore().searchDocumentsNames(wheresql, getXWikiContext()); } @@ -760,6 +792,8 @@ public List<String> searchDocuments(String wheresql) throws XWikiException @Deprecated public List<String> searchDocuments(String wheresql, int nb, int start) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return this.xwiki.getStore().searchDocumentsNames(wheresql, nb, start, getXWikiContext()); } @@ -796,6 +830,8 @@ public List<String> searchDocuments(String wheresql, int nb, int start, String s */ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return convert(this.xwiki.getStore().searchDocuments(wheresql, distinctbylocale, getXWikiContext())); } @@ -812,6 +848,8 @@ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale) public List<Document> searchDocuments(String wheresql, boolean distinctbylocale, int nb, int start) throws XWikiException { + checkSearchQueryAllowed(wheresql); + return convert(this.xwiki.getStore().searchDocuments(wheresql, distinctbylocale, nb, start, getXWikiContext())); } @@ -845,6 +883,8 @@ public List<Document> searchDocuments(String wheresql, boolean distinctbylocale, public List<String> searchDocuments(String parameterizedWhereClause, int maxResults, int startOffset, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedWhereClause); + return this.xwiki.getStore().searchDocumentsNames(parameterizedWhereClause, maxResults, startOffset, parameterValues, getXWikiContext()); } @@ -858,6 +898,8 @@ public List<String> searchDocuments(String parameterizedWhereClause, int maxResu @Deprecated public List<String> searchDocuments(String parameterizedWhereClause, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedWhereClause); + return this.xwiki.getStore().searchDocumentsNames(parameterizedWhereClause, parameterValues, getXWikiContext()); } @@ -885,6 +927,8 @@ public List<String> searchDocumentsNames(String wikiName, String parameterizedWh try { this.context.setWikiId(wikiName); + checkSearchQueryAllowed(parameterizedWhereClause); + return searchDocuments(parameterizedWhereClause, maxResults, startOffset, parameterValues); } finally { this.context.setWikiId(database); @@ -895,18 +939,20 @@ public List<String> searchDocumentsNames(String wikiName, String parameterizedWh * Search spaces by passing HQL where clause values as parameters. See * {@link #searchDocuments(String, int, int, List)} for more about parameterized hql clauses. * - * @param parametrizedSqlClause the HQL where clause. For example + * @param parameterizedSqlClause the HQL where clause. For example * {@code where doc.fullName <> ?1 and (doc.parent = ?2 or (doc.parent = ?3 and doc.space = ?4))} * @param nb the number of rows to return. If 0 then all rows are returned * @param start the number of rows to skip. If 0 don't skip any row * @param parameterValues the where clause values that replace the question marks (?) * @return a list of spaces names. * @throws XWikiException in case of error while performing the query */ - public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int start, List<?> parameterValues) + public List<String> searchSpacesNames(String parameterizedSqlClause, int nb, int start, List<?> parameterValues) throws XWikiException { - return this.xwiki.getStore().search("select distinct doc.space from XWikiDocument doc " + parametrizedSqlClause, + checkSearchQueryAllowed(parameterizedSqlClause); + + return this.xwiki.getStore().search("select distinct doc.space from XWikiDocument doc " + parameterizedSqlClause, nb, start, parameterValues, this.context); } @@ -915,7 +961,7 @@ public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int * {@link #searchDocuments(String, int, int, List)} for more about parameterized hql clauses. You can specify * properties of attach (the attachment) or doc (the document it is attached to) * - * @param parametrizedSqlClause The HQL where clause. For example + * @param parameterizedSqlClause The HQL where clause. For example * {@code where doc.fullName <> ?1 and (doc.parent = ?2 or (doc.parent = ?3 and doc.space = ?4))} * @param nb The number of rows to return. If 0 then all rows are returned * @param start The number of rows to skip at the beginning. @@ -924,27 +970,31 @@ public List<String> searchSpacesNames(String parametrizedSqlClause, int nb, int * @throws XWikiException in case of error while performing the query * @since 5.0M2 */ - public List<Attachment> searchAttachments(String parametrizedSqlClause, int nb, int start, List<?> parameterValues) + public List<Attachment> searchAttachments(String parameterizedSqlClause, int nb, int start, List<?> parameterValues) throws XWikiException { + checkSearchQueryAllowed(parameterizedSqlClause); + return convertAttachments( - this.xwiki.searchAttachments(parametrizedSqlClause, true, nb, start, parameterValues, this.context)); + this.xwiki.searchAttachments(parameterizedSqlClause, true, nb, start, parameterValues, this.context)); } /** * Count attachments returned by a given parameterized query * - * @param parametrizedSqlClause Everything which would follow the "WHERE" in HQL see: + * @param parameterizedSqlClause Everything which would follow the "WHERE" in HQL see: * {@link #searchDocuments(String, int, int, List)} * @param parameterValues A {@link java.util.List} of the where clause values that replace the question marks (?) * @return int number of attachments found. * @throws XWikiException * @see #searchAttachments(String, int, int, List) * @since 5.0M2 */ - public int countAttachments(String parametrizedSqlClause, List<?> parameterValues) throws XWikiException + public int countAttachments(String parameterizedSqlClause, List<?> parameterValues) throws XWikiException { - return this.xwiki.countAttachments(parametrizedSqlClause, parameterValues, this.context); + checkSearchQueryAllowed(parameterizedSqlClause); + + return this.xwiki.countAttachments(parameterizedSqlClause, parameterValues, this.context); } /**
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/store/hibernate/query/HqlQueryUtils.java+75 −1 modified@@ -19,6 +19,7 @@ */ package com.xpn.xwiki.internal.store.hibernate.query; +import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,6 +35,16 @@ */ public final class HqlQueryUtils { + private static final String FROM = "from"; + + private static final String WHERE = "where "; + + private static final String ORDER = "order"; + + private static final String ORDER_BY = "order by"; + + private static final String COMMA = ","; + private static final Pattern LEGACY_ORDINAL_PARAMS_PATTERN = Pattern.compile("([=\\s,\\(<>])\\?([=\\s,\\)<>]|$)"); private HqlQueryUtils() @@ -47,7 +58,7 @@ private HqlQueryUtils() */ public static boolean isShortFormStatement(String statement) { - return StringUtils.startsWithAny(statement.trim().toLowerCase(), ",", "where ", "order by"); + return StringUtils.startsWithAny(statement.trim().toLowerCase(), COMMA, WHERE, ORDER_BY); } /** @@ -129,4 +140,67 @@ public String getStatement() return completeQuery; } + + /** + * @param queryPrefix the start of the SQL query (for example "select distinct doc.space, doc.name") + * @param whereSQL the where clause to append + * @return the full formed SQL query, to which the order by columns have been added as returned columns (this is + * required for example for HSQLDB). + * @since 17.0.3RC1 + * @since 16.10.6 + */ + public static String createLegacySQLQuery(String queryPrefix, String whereSQL) + { + StringBuilder sql = new StringBuilder(queryPrefix); + + String normalizedWhereSQL; + if (StringUtils.isBlank(whereSQL)) { + normalizedWhereSQL = ""; + } else { + normalizedWhereSQL = whereSQL.trim(); + } + + sql.append(getColumnsForSelectStatement(normalizedWhereSQL)); + sql.append(" from XWikiDocument as doc"); + + if (!normalizedWhereSQL.equals("")) { + if ((!normalizedWhereSQL.startsWith(WHERE)) && (normalizedWhereSQL.charAt(0) != ',')) { + sql.append(" where "); + } else { + sql.append(" "); + } + sql.append(normalizedWhereSQL); + } + + return sql.toString(); + } + + /** + * @param whereSQL the SQL where clause + * @return the list of columns to return in the select clause as a string starting with ", " if there are columns or + * an empty string otherwise. The returned columns are extracted from the where clause. One reason for doing + * so is because HSQLDB only support SELECT DISTINCT SQL statements where the columns operated on are + * returned from the query. + * @since 17.0.3RC1 + * @since 16.10.6 + */ + public static String getColumnsForSelectStatement(String whereSQL) + { + StringBuilder columns = new StringBuilder(); + + int orderByPos = whereSQL.toLowerCase().indexOf(ORDER_BY); + if (orderByPos >= 0) { + String orderByStatement = whereSQL.substring(orderByPos + ORDER_BY.length() + 1); + StringTokenizer tokenizer = new StringTokenizer(orderByStatement, COMMA); + while (tokenizer.hasMoreTokens()) { + String column = tokenizer.nextToken().trim(); + // Remove "desc" or "asc" from the column found + column = StringUtils.removeEndIgnoreCase(column, " desc"); + column = StringUtils.removeEndIgnoreCase(column, " asc"); + columns.append(", ").append(column.trim()); + } + } + + return columns.toString(); + } }
xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java+3 −39 modified@@ -38,7 +38,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -101,6 +100,7 @@ import com.xpn.xwiki.doc.XWikiLock; import com.xpn.xwiki.doc.XWikiSpace; import com.xpn.xwiki.internal.store.hibernate.legacy.LegacySessionImplementor; +import com.xpn.xwiki.internal.store.hibernate.query.HqlQueryUtils; import com.xpn.xwiki.monitor.api.MonitorPlugin; import com.xpn.xwiki.objects.BaseCollection; import com.xpn.xwiki.objects.BaseElement; @@ -2916,28 +2916,7 @@ public List<XWikiDocument> searchDocuments(String wheresql, boolean distinctbyla */ protected String createSQLQuery(String queryPrefix, String whereSQL) { - StringBuilder sql = new StringBuilder(queryPrefix); - - String normalizedWhereSQL; - if (StringUtils.isBlank(whereSQL)) { - normalizedWhereSQL = ""; - } else { - normalizedWhereSQL = whereSQL.trim(); - } - - sql.append(getColumnsForSelectStatement(normalizedWhereSQL)); - sql.append(" from XWikiDocument as doc"); - - if (!normalizedWhereSQL.equals("")) { - if ((!normalizedWhereSQL.startsWith("where")) && (!normalizedWhereSQL.startsWith(","))) { - sql.append(" where "); - } else { - sql.append(" "); - } - sql.append(normalizedWhereSQL); - } - - return sql.toString(); + return HqlQueryUtils.createLegacySQLQuery(queryPrefix, whereSQL); } /** @@ -2949,22 +2928,7 @@ protected String createSQLQuery(String queryPrefix, String whereSQL) */ protected String getColumnsForSelectStatement(String whereSQL) { - StringBuilder columns = new StringBuilder(); - - int orderByPos = whereSQL.toLowerCase().indexOf("order by"); - if (orderByPos >= 0) { - String orderByStatement = whereSQL.substring(orderByPos + "order by".length() + 1); - StringTokenizer tokenizer = new StringTokenizer(orderByStatement, ","); - while (tokenizer.hasMoreTokens()) { - String column = tokenizer.nextToken().trim(); - // Remove "desc" or "asc" from the column found - column = StringUtils.removeEndIgnoreCase(column, " desc"); - column = StringUtils.removeEndIgnoreCase(column, " asc"); - columns.append(", ").append(column.trim()); - } - } - - return columns.toString(); + return HqlQueryUtils.getColumnsForSelectStatement(whereSQL); } @Override
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
8- github.com/advisories/GHSA-p9qm-p942-q3w5ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-54385ghsaADVISORY
- docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_XMLGEN.htmlghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/commit/7313dc9b533c70f14b7672379c8b3b63d1fd8f51ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/commit/7c4087d44ac550610b2fa413dd4f5375409265a5ghsax_refsource_MISCWEB
- github.com/xwiki/xwiki-platform/security/advisories/GHSA-p9qm-p942-q3w5ghsax_refsource_CONFIRMWEB
- jira.xwiki.org/browse/XWIKI-22728ghsax_refsource_MISCWEB
- www.xwiki.org/xwiki/bin/view/ReleaseNotes/Data/XWiki/16.10.6ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.