XWiki exposed a REST endpoint that accepts HQL queries. Due to a flaw in how 'short-form' queries were validated versus how they were executed, attackers can escape the HQL context. This results in unauthenticated Blind SQL Injection, leading to full database compromise and potential RCE. Rated CVSS 9.8.
A critical unauthenticated HQL injection vulnerability in XWiki's REST API allows attackers to break out of the Hibernate Query Language abstraction and execute raw SQL on the underlying database.
We all love ORMs (Object-Relational Mappers). They are the warm, fuzzy blanket that protects developers from the cold, hard reality of raw SQL. They promise us that if we just stick to their objects and methods, SQL injection will be a thing of the past. But here's the dirty little secret of the Java ecosystem: Hibernate Query Language (HQL) is just another language, and like any language, if you let user input dictate the syntax, you are going to have a bad time.
In this episode of "Why did you expose that endpoint?", we are looking at XWiki, a popular open-source enterprise wiki. XWiki is complex, powerful, and apparently, very trusting of anonymous internet strangers. The vulnerability lies within xwiki-platform-rest-server, specifically in how it handles search queries via the REST API.
The developers provided a handy feature: a REST endpoint (/rest/wikis/xwiki/query) that allows you to search the wiki using HQL. To make life easier, they support "short-form" queries. Instead of writing a full SELECT statement, you can just write where doc.title like '%search%', and XWiki kindly prepends the SELECT clause for you. It's syntactic sugar. And like actual sugar, too much of it will rot your teeth—or in this case, your database security posture.
The root cause of CVE-2025-32969 is a classic case of "Check-Then-Act" disparity. The vulnerability exists because the validation logic checked one string, but the execution engine ran another.
Here is the logic flow that doomed the implementation:
where doc.name = 'foo'.HqlQueryExecutor#isSafeSelect method inspects this string. It looks for dangerous keywords like UPDATE or DELETE at the start of the query. Since the user's string starts with where, it passes the check.select doc.fullName from XWikiDocument doc.The flaw is obvious in hindsight: The security check was performed on the partial query, not the final query. This gap allowed attackers to construct a payload that looked innocent to the validator but became a monster once expanded.
Furthermore, there was a secondary failure in the authorization logic. The checkAllowed(query) permission check was performed before the context switch to the target wiki. This means the application checked if Anonymous had permission to search the current context, rather than the target wiki defined in the query. This Race Condition-esque logic allowed unauthenticated users to bypass view restrictions even when "Prevent unregistered users from viewing pages" was strictly enabled.
Let's dissect the patch. The fix was pushed in commit 5c11a874bd24a581f534d283186e209bbccd8113. It reveals exactly where the logic failed.
HqlQueryExecutor.javaThe developers realized that validating the raw input string was insufficient. The patch introduces a crucial step: normalizing the query before validation.
// BEFORE (Vulnerable Logic)
// The code checked 'statement' directly, which might be just "where ..."
if (!isSafeSelect(statement)) {
throw new IllegalArgumentException("Illegal query");
}
// AFTER (Fixed Logic)
// Expand the short-form query FIRST, then validate the result
String completeStatement = toCompleteShortForm(statement);
if (!isSafeSelect(completeStatement)) {
throw new IllegalArgumentException("Illegal query");
}By converting where ... into select doc.fullName from ... where ... before the check, the validator now sees the full picture.
They also moved the permission check inside the wiki context block. Previously, the check happened too early:
// Vulnerable Flow
checkAllowed(query); // Checked against wrong context
try {
getContext().setWikiId(query.getWiki());
// execute query
}
// Fixed Flow
try {
getContext().setWikiId(query.getWiki());
checkAllowed(query); // Checked against the ACTUAL target wiki context
// execute query
}This subtle shift ensures that even if you bypass the HQL check, you still need valid permissions on the target wiki data to see the result. But since this is a blind SQLi, the permission check is largely a secondary defense layer.
So, how do we weaponize this? We need to trick the HQL parser into thinking we are still writing HQL, while simultaneously tricking the underlying SQL engine into executing a new command. This is achieved through specific character escaping.
The attack vector targets the /rest/wikis/xwiki/query endpoint. We inject a payload into the q parameter that uses a backslash and single quote combination (\') to desynchronize the HQL parser and the SQL parser.
Consider this payload found in the wild:
GET /rest/wikis/xwiki/query?type=hql&distinct=0&q=where%20doc.name=length(%27a%27)*org.apache.logging.log4j.util.Chars.SPACE%20or%201%3C%3E%271%5C%27%27%20union%20select%201,2,3,sleep(7)%20%23%27 HTTP/1.1Let's break down the URL-decoded HQL injection:
where doc.name=...: Satisfies the "short-form" requirement. It looks like a valid property check.1<>'1\'': This is the magic. The backslash escapes the quote in a way that Hibernate processes differently than the database. To Hibernate, it might look like a string literal. To the database, it closes the string and opens up the command line.union select 1,2,3,sleep(7): The standard SQL injection payload. We use UNION to append our own result set. Since we can't easily see the output (blind), we use sleep(7) to pause the database execution. If the server hangs for 7 seconds, we have a hit.#: The comment character kills whatever valid SQL XWiki tries to append after our injection.This is a Time-Based Blind SQL Injection. It's slow, it's noisy, but it works without authentication.
This is rated CVSS 9.8 (Critical) for a reason. It requires zero authentication (PR:N), zero user interaction (UI:N), and can be launched remotely (AV:N).
1. Data Exfiltration
Even though the injection is blind, an attacker can extract data bit-by-bit. They can dump the xwikidoc table, extract user password hashes, or read confidential wiki pages. In a corporate environment, a wiki often holds the "keys to the kingdom"—network diagrams, credentials, and architectural secrets.
2. Lateral Movement By extracting the hashed credentials of the XWiki administrator, an attacker can crack the hash (or pass-the-hash if the architecture allows) to log in as an admin. From there, XWiki allows execution of scripts (Groovy, Python, Velocity) in wiki pages. This turns SQL Injection into Remote Code Execution (RCE).
3. Database Manipulation
Depending on the database user's privileges, the attacker could theoretically UPDATE records to inject XSS payloads into pages served to other users, or DELETE entire tables to cause a Denial of Service.
If you are running XWiki, stop reading and start patching. The vulnerability affects a massive range of versions, effectively everything from version 1.8 up to recently patched releases.
There is no viable configuration workaround. You cannot simply "turn off" the REST API without breaking significant functionality of the XWiki platform. The fix requires updating the JARs.
If immediate patching is impossible, you should block access to /rest/wikis/*/query at your WAF or reverse proxy level for all unauthenticated IP addresses. However, given the query endpoint is fundamental to how XWiki front-ends often fetch data, this might break public-facing features.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
XWiki Platform XWiki | 1.8 - < 15.10.16 | 15.10.16 |
XWiki Platform XWiki | 16.0.0 - < 16.4.6 | 16.4.6 |
XWiki Platform XWiki | 16.5.0 - < 16.10.1 | 16.10.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 (SQL Injection) |
| Attack Vector | Network (REST API) |
| CVSS v3.1 | 9.8 (Critical) |
| CVSS v4.0 | 9.3 (Critical) |
| EPSS Score | 25.20% |
| EPSS Percentile | 96th Percentile |
| Exploit Status | PoC Available |
| Impact | Confidentiality, Integrity, Availability |
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Get the latest CVE analysis reports delivered to your inbox.