“是否允许出站”这件事我一直以为无需过多思考,无非限制出站协议,或者限制出站端口,对于限制端口的目标十有八九也会保留 80、443,向这两个端口反弹基本能拿到 shell,直到遇到这个目标,引发我对出站端口受限的环境下,如何成功反弹 shell 的思考。
这次遇到的目标,通过 nday 轻松拿到 webshell,打点的过程毫无波澜,甚至有一丝无聊:
webshell 虽然赋予我执行命令、管理文件的能力,但毕竟不是真正的 shell,无法执行交互式命令、无法控制进程状态、无法补全命令等等,非常不利于提权操作以及横向移动,所以,必须反弹 shell。
肌肉记忆让我没有过多思考,攻击端监听端口 12321:
目标上执行反弹命令:
等待许久,攻击端就是不见回连信息,显然,一定是目标上的某种防御在作祟。导致反弹失败的因素很多,反弹命令不存在、禁止出站 IP、流量审查等等都有可能,于是,我从积累的知识库中搜索所有可能,再逐一验证。比如,我在目标上确认 bash 命令的确存在:
在攻击端能收到目标发起的 ping 探测,攻击端能收到 icmp 记录:
用 SSL 加密回连流量仍然反弹失败:
我陷入沉思,这台内网机器,既然公网可见,说明它的边界处有个防火墙作公网 IP 映射:
一般来说,防火墙可能干两件事,一是限制出站协议、一是限制出站端口。协议方面,前面已经验证过 ICMP 可以出网而 HTTP 不行,所以,我可以尝试用 ICMP 隧道反弹,即便可行,效率肯定也不高;端口方面,目标通过防火墙配置出站端口策略,若限制端口黑名单较大或允许端口白名单较小,则攻击者很难猜测出哪些端口有效。从以往的经验判断,80、443 是两个可能性较大的允许出站的端口,于是我又依次尝试向 80、443 反弹,失败!
捋一捋,通过漏洞获取了 webshell,该主机只开放了 HTTP 服务的 80 端口,要获得 shell 有两种方式,正向连接、反向连接。正向连接,目标上用 nc 监听的端口对攻击端不可见,只得利用类似 reGeorg 建立 HTTP 隧道;反向连接,目标上用 nc 回连攻击端 IP 及端口。前者效率低下,聚焦后者,目标允许向外访问任意 IP,但严格限制只能访问外部某几个特定端口,攻击者必须找出有效端口,否则反弹 shell 的流量无法通过防火墙到达攻击端。所以,我需要猜解出站端口。
猜解出站端口,思路与 SQLi 或 CMDi 盲注的带外信道手法差不多,即,攻击端监听某个端口,目标访问攻击端的该端口,若攻击端出现端口访问记录则说明该端口是目标允许的出站端口。思路简单,但实现不一定容易,要成功猜解出有效端口,需要关注三方面事宜:目标的端口探测命令、猜测的端口范围、攻击端的端口请求记录。
目标的端口探测命令
说到端口状态探测,第一时间想到的肯定是 nmap、masscan、nc 这类专用工具。看下目标上有没有:
毫无悬念地没有;自己装个 masscan 试试:
权限又不够,即便有权限,估计也连接内网的镜像源;那我上传个与目标架构、发行套件一致的 nmap 呢:
缺失依赖出现段错误而无法运行。
没事,除了以上那些专用工具外,linux 中还有其他挺多可选的方式,可以利用 python、perl各类脚本语言探测端口,也能借助 curl、wget 这类 HTTP 客户端实现,只要能请求服务的命令均可,但是,环境就是那么苛刻,我要啥它没啥:
好嘛,这时,我觉得基本无解,不借助第三方命令不可能达到端口探测的目的。休息一会,是喝口水去打乒乓球哇,还是去打乒乓球喝口水呢。回过头,检查之前的反弹命令是否有误:
突然,我注意到 /dev/tcp 后面的 12321 端口,( ⊙ o ⊙ )#,这不就是在探测端口么。一切皆文件!linux 下探测端口状态竟如此简单,比如,探测 baidu.com 的 443 端口,一定存活:
但由于这不是专门的端口探测工具,所以存在三个问题,一是如何批量输入端口,二是如何控制任务超时,三是如何查看探测结果。于是,我开始在本地探索解决思路。
批量端口问题。这个不难,使用 bash 的 for 语句即可:
若目标过滤大括号,可考虑:
若目标过滤小于号,可考虑:
甚至无需任何特殊字符:
解决了第一个问题。
超时控制问题。当我用 /dev/tcp 去探测某个存在的端口时,命令将立即返回,而探测某个不存活端口时,命令将挂起,直到强制退出:
所以,必须得想个法子让它超时时自动结束。超时,timeout,哇,这么简单啊。比如,本来要执行 8s 的命令:
借助 timeout命令可以实现超时控制:
第二个问题搞定。
查看探测结果。你知道,在 bash 中命令执行结果可以通过环境变量 $? 查看,成功为 0、是否非 0,换言之,我需要找个方式判断 $? 是否为 0,常规方式用 if 语句,或者,更优的方式,短路运算符 && 和 ||:
第三个问题也不是问题了。
解决了以上三个问题,我在不借助任何外部命令情况下,实现了批量探测端口:
虽然这次关于出站端口探测的思考是由 linux 的目标引起的,但从知识体系完整性考虑,我应该把 windows 系统也考虑进去。windows 命令行环境条件苛刻,几乎没有专门用于端口探测的内部命令,只能借助那些访问服务的命令间接实现,另外,我仍然需要解决批量端口、超时控制等问题。
批量输入端口,cmd 中的 for 语句可以实现:
控制任务超时,linux 有 timeout 命令,本质上,一旦超时则向目标进程发送 KILL 信号,达到控制进程运行时长的目的。win 也有 timeout,但语义完全不同,类似 linux 的 sleep 命令,没关系,参考 linux 版 timeout 原理,搭配 taskkill 命令也能间接实现:其中,ping -n 32 127.0.0.1 模拟长时间运行的进程,timeout /t 1 为等待时长,一旦等待时长到期则用 taskkill /im ping.exe 杀死进程。虽然不及 linux 优雅,聊胜于无。
具体到端口探测,大概有 tnc 模块和 telnet 命令两种方式。
tnc 模块方式。tnc 也就是 powershell 的 Test-NetConnection 模块,专门用于网络连接性测试:
其中,tnc 默认先用 ICMP 探测 IP 存活性,通过指定 -InformationLevel 选项为 Quiet 参数,可忽略 IP 探测,只关注端口是否存活,类似 nmap 的 -Pn 选项,以提高端口探测效率。
telnet 命令方式。服务器上基本安装了 telnet,也可用于探测端口状态:
用 telnet 命令访问攻击端的 [440, 445] 的端口,每次访问限时 1s。
猜测的端口范围
目标既然允许访问外部的几个端口,一定是那些端口提供目标所需服务,那么,我没必要一开始就到 [1, 65535] 中去找有效端口,而是根据服务的常见程度,将所有端口划为几个层次,依次逐层猜解。
第一层,最常见端口。经验来看,DNS 的 53、HTTP 服务的默认端口 80、HTTPS 的 443 是三个最常见的出站端口。
win 下执行如下命令行确认出站端口:linux 下执行:
第二层,较常见端口。大概包括如下几类:
web 服务, HTTP 的 80、HTTPS 的 443;
中间件服务,weblogic 的 7001/7002、webshpere 的 9080/9090、jboss 的 8080;
远程管理服务,SSH 的 22、telnet 的 23、SNMP 的 161、RDP 的 3389;
数据传输服务,FTP 的 21、SCP/SFTP 的 22、SMB 的 137/138/139/445;
数据库服务,mssql 的 1433、mysql 的 3306、oracle 的 1521、LDAP 的 389;
缓存服务,redis 的 6379、memcached 的 11211;
邮件服务,SMTP 的 25、POP3 的 110、IMAP 的 143;
其他服务,DNS 的 53、NTP 的 123、kerberos 的 88;
win 下探测:linux 下探测:
第三层,top100 常见端口。nmap 默认扫描 top1000 常见端口,通过 --top-ports 选项可以指定扫描更多常见端口:也就是说,nmap 内部梳理了一份已知服务默认端口列表,并且还能按常见程度排序,简直巴适,要获取 top100 常见端口:
win 下探测:
linux 下探测:
采用相同思路,我可以持续探测 top200、top400 常见端口。
攻击端的端口请求记录
从目标发起的端口访问请求,攻击端必须得配合记录,否则即便找到有效的出站端口,我也无法获悉。
思路一,单个逐次监听端口。对于少量端口的探测,攻击端很容易记录。比如,要验证 windows 目标的 8088 端口是否为出站端口,我先在攻击端用 nc -n -v -lp 8088 监听 8088,指定 -v 选项观察实时访问记录,再在目标上用 telnet 192.168.56.8 8088 连接攻击端的 8088 端口,最后在攻击端查看端口访问记录,若有则该端口是有效出站端口:
若无则重复以上步骤继续验证其他端口。
对于 top100 甚至 top1000 这样大规模的端口探测,当前验证目标的 80 端口,那么攻击端也要联动监听 80,验证 81 则联动监听 81,手工执行 nc 不断监听不同端口是不现实的,这就需要一个脚本,控制攻击端的监听动作和目标端的探测动作,倒是可以写个这样的脚本,但通用性不强,我得找寻更加普适性的方法。
思路二,批量捆绑监听端口。试想一下,如果能够把攻击端的多个端口流量转发至单个汇聚端口,就只需监听单个汇聚端口,目标上发起多个端口探测,只要在攻击端转发的多个端口的范围内,那么,一旦找到有效出站端口,攻击端的汇聚端口一定有访问记录。说到端口转发,系统自带的 ssh、iptables,三方的 frp、nps,这些工具都能高效实现,于是,我从这四个工具中找寻具备端口捆绑能力的那位,简单查阅资料,iptables 就是我需要的。只需一条命令行即可实现端口捆绑,如下:
sudo iptables -A PREROUTING -t nat -p tcp --dport 1:65535 -j REDIRECT --to-port 4442
这样,就把 65535 个全量端口捆绑至 4442 端口。
验证下,在攻击端将全量端口捆绑至 4442 端口:
监听 10086 端口:
靶机目标取消所有出站限制,访问攻击端的 10086 端口:
显示端口不存活!这正是端口捆绑的必然结果,因为攻击端接收到的 10086 端口访问流量已被转发至 4442 端口,而 nc 并未监听 4442,所以没有访问记录。接下来,nc 监听 4442:
目标端再次访问 10086:
你看,攻击端收到访问记录了:
验证完毕,不再需要端口捆绑,恢复先前规则:
安逸哇,这样我监听单个端口即可获取全量端口的探测记录。如果再结合结合 MSF 的 reverse_tcp_allports 载荷,甚至可以省去找寻有效端口的步骤,直接反弹 shell。
windows 环境的模拟实验
简单模拟实战中反弹 shell 的场景。先看 windows 环境。
第一步,查看环境信息。靶机 IP 为 192.168.56.9,如下:
攻击端 IP 为 192.168.56.8,如下:
第二步,配置出站策略。在操作系统自带防火墙中增加出站策略,只允许 2048 端口出站,即禁止 1-2047 及 2049-65535,规则管理界面中配置即可:
攻击端监听 2047 端口:
从目标访问攻击端的 2047 端口,结果为不存活:
攻击端重新监听 2048 端口:
从目标访问攻击端的 2048 端口,结果为存活:
说明只放行 2048 端口的出站策略已生效。
第三步,攻击端端口捆绑。为了便于接收目标发起的端口探测,将攻击端的全量端口捆绑至 4442 端口:
并在攻击端监听汇聚端口 4442:
第四步,目标端猜解出站端口。现在,我的身份恢复为攻击者,假定先前通过漏洞拿到目标 192.168.56.9 的命令执行权限,且执行结果有回显。尝试在 nmap 定义的 top100 常见端口范围内找寻允许出站的端口,很快找到 2048 为有效端口:
第五步,反弹 shell。首先,攻击端取消端口捆绑:
然后,攻击端生成反弹端口为 2048 的 ps1 脚本木马:
并启动 MSF 监听 2048 端口:
接着目标上执行 ps1 木马:
最后顺利反弹 shell:
拿下真实目标
现在,我把本地推演的思路应用到目标环境中。
第一步,攻击端捆绑端口至 12321,并监听该端口:
第二步,目标上探测 top100 常见端口,执行:
啊( ⊙ o ⊙ )啊!居然报错,不应该,难道是 webshell 中无法执行复杂的 shell 语句?不急,把它写入个脚本文件中执行看看,新建脚本文件 x.sh:
写入完整命令行:
赋予执行权限后执行:
很快即可查看到结果:
无一存活!
第三步,继续尝试 top200、top300 的端口,仍然没找到;尝试 top400:
哈哈哈哈,还真找到了它,42510,唯一允许出站的那个端口!
第四步,反弹 shell。攻击端取消端口捆绑,监听 42510 端口接收 shell:
目标向攻击端的 42510 端口反弹:
攻击端成功接收 shell:
呵呵,这下心情舒畅了,晚上整火锅。
好了,以后遇到能 ping 通外网但无法反弹的目标,得多个心眼考虑是否限制了出站端口,用上述手法尝试猜解,或许,能找到唯一的答案!
*本文原创作者:yangyangwithgnu,本文属于FreeBuf原创奖励计划,未经许可禁止转载