Feb 22, 2026·7 min read·11 visits
The XWiki Full Calendar Macro (< 2.4.5) contains a trivial HQL injection. Attackers can pass a full database query via the URL parameters, executing it with the privileges of the script author (often Admin). This grants read access to all data, including credentials.
A 10.0 Critical vulnerability in the XWiki Full Calendar Macro allows unauthenticated attackers to execute arbitrary Hibernate Query Language (HQL) commands. By manipulating the 'sql', 'fromsql', or 'wheresql' parameters in the Calendar.JSONService page, attackers can bypass security controls, exfiltrate the entire wiki database, or potentially deny service to the host. The flaw stems from direct concatenation of user input into database queries, essentially providing a remote database shell to anyone who asks nicely.
We often ignore the little things. The plugins, the macros, the 'quality of life' add-ons that make enterprise software bearable. In the XWiki ecosystem, the Full Calendar Macro is one such tool—a simple utility to display events. But as with many innocent-looking features, it harbored a dark secret. It didn't just display events; it acted as an open gateway to the underlying database.
This vulnerability, tracked as CVE-2025-65091, is a perfect example of what happens when convenience overrides security. It isn't a complex memory corruption bug or a subtle race condition. It is a logic flaw so glaring that it feels almost polite. The application essentially asked the user, "Hey, what database query would you like me to run for you today?" and then dutifully executed it.
Because XWiki relies heavily on HQL (Hibernate Query Language), this isn't just about reading calendar entries. An injection here allows an attacker to traverse the object graph, accessing user profiles, password hashes (if stored), and proprietary documentation. Worse, it was exploitable by unauthenticated users, earning it the rare and terrifying CVSS 10.0 score. If you had this macro installed, your wiki was effectively public.
The root cause of this vulnerability lies in the file Calendar.JSONService.xml. This script is responsible for fetching event data to populate the calendar UI. To do this, it constructs a database query. In a secure application, this would be done using a fixed query with parameterized inputs. In the vulnerable version, it was done using string concatenation of the worst kind.
The developers implemented a feature that allowed the frontend to specify exactly how to filter data. They exposed parameters like request.sql, request.fromsql, and request.wheresql. If the sql parameter was present, the code would simply take that string and treat it as the entire HQL query.
Think about that for a second. The code explicitly checked: "Did the user provide a full SQL command? If yes, let's run that instead of our logic." It is the web application equivalent of leaving the keys in the ignition with the engine running and a sign on the dashboard that says "Free Car."
Even if the sql parameter wasn't used, the code blindly concatenated fromsql and wheresql into the query string. This meant that even without replacing the whole query, an attacker could inject JOIN statements to link sensitive tables or modify the WHERE clause to bypass visibility checks. It is a textbook Injection flaw, mapped to CWE-89, but operating at the ORM level.
Let's look at the vulnerable Velocity code. This is where the magic (and the horror) happens.
The Vulnerable Code (Pre-Patch):
#if ("$!\{request.sql\}" != '')
## The developer trusts the user implicitly here.
#set ($hql = $request.sql)
#else
## Even the fallback is dangerous.
#set ($hql =", BaseObject as obj $!\{request.fromsql\} where doc.fullName=obj.name and obj.className='$\{request.classname\}' $!\{request.wheresql\}")
#end
...#foreach ($item in $services.query.hql($hql).execute())
The variable `$request.sql` is taken directly from the URL query string. If I send `?sql=select...`, the variable `$hql` becomes my query. The `$services.query.hql($hql).execute()` line then runs it against the database.
**The Fix (Version 2.4.5):**
The developers stripped out the direct override and added a critical permission drop.
```velocity
#set ($hqlStatement =", BaseObject as obj $!{request.fromsql} where doc.fullName=obj.name and obj.className=:classname $!{request.wheresql}") ...
#set ($discard = $xcontext.dropPermissions())
#foreach ($item in $services.query.hql($hqlStatement).bindValue("classname", "$!{request.classname}").addFilter('currentlanguage').execute())
Notice two things: First, the ability to completely replace the query via `sql` is gone. Second, and arguably more importantly, `$xcontext.dropPermissions()` is called. In XWiki, scripts often run with the permissions of the *author* (usually an Admin who installed the extension). `dropPermissions()` forces the script to run with the permissions of the *requestor*. If you are an unauthenticated attacker, you are now just "Guest," and you can't query sensitive tables even if you manage to inject SQL.
Exploiting this is trivially easy. You don't need Burp Suite; you just need a web browser. The attack vector targets the JSONService which is publicly accessible.
Scenario 1: The Full Dump
An attacker wants to list all documents in the wiki to map out the internal structure.
URL:
http://target-wiki/xwiki/bin/view/Calendar/JSONService?sql=select+doc.fullName,+doc.title+from+XWikiDocument+doc
The server sees the sql parameter, ignores its internal logic, and executes the HQL: select doc.fullName, doc.title from XWikiDocument doc. The JSON response will kindly return every document title in the system.
Scenario 2: Credential Hunting
XWiki stores user data in objects. While password hashes might be salted, getting the list of usernames and their metadata is a goldmine for social engineering or brute force.
URL:
http://target-wiki/xwiki/bin/view/Calendar/JSONService?sql=select+obj.name+from+BaseObject+obj+where+obj.className='XWiki.XWikiUsers'
Scenario 3: The 'Re-Exploit' (Post-Patch Bypass Attempt)
Look closely at the patched code. The developer still includes $!{request.fromsql} and $!{request.wheresql} in the query string!
#set ($hqlStatement =", BaseObject as obj $!{request.fromsql} ... $!{request.wheresql}")While they bound :classname and removed request.sql, injection is technically still possible in the FROM and WHERE clauses. However, because of $xcontext.dropPermissions(), the query runs as 'Guest'. So, while I can still inject HQL syntax (e.g., ?fromsql=, XWikiDocument as doc), I can only retrieve data that 'Guest' is allowed to see. The exploit is neutered not by fixing the injection fully, but by stripping the privileges required to make the injection dangerous. It's a functional fix, but a dirty one from a code purity standpoint.
A CVSS score of 10.0 is reserved for the absolute worst-case scenarios. Why did a calendar plugin merit this?
sql could lock up the database, causing a Denial of Service.Essentially, this flaw turns a collaboration tool into a data exfiltration pipe. For organizations using XWiki as an internal knowledge base, this exposes everything from network diagrams to HR policies to the public internet.
If you are running org.xwiki.contrib:macro-fullcalendar versions older than 2.4.5, you are vulnerable. The remediation is straightforward: Update the extension immediately.
If you cannot update for some reason (perhaps you've forked the code), you must manually patch Calendar.JSONService.xml. The most critical change is ensuring that the script drops permissions before executing any query that touches user input.
Remediation Checklist:
macro-fullcalendar to 2.4.5+ via the Extension Manager.Calendar.JSONService source code for $xcontext.dropPermissions().JSONService containing query parameters like select, from, or join. If you see these, you have likely already been scanned or compromised.Since this exploit leaves no binary artifacts (it's just a web request), log analysis is your only hope for forensic confirmation.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
macro-fullcalendar XWiki Contrib | < 2.4.5 | 2.4.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 (SQL Injection) |
| CVSS v3.1 | 10.0 (Critical) |
| Attack Vector | Network (HQL Injection via GET parameters) |
| Exploit Status | PoC Available / Trivial |
| Privileges | None (Unauthenticated) |
| Patch Commit | 5fdcf06a05015786492fda69b4d9dea5460cc994 |