WenD1l
该文章首发在i春秋论坛,欢迎各位师傅完成专业爱好者认证,可第一时间获取最新技术资讯和实战技能分享。
(识别二维码,快速完成认证)
Linux作为一款流行的操作系统,提供了多种远程管理工具,例如SSH、VNC和RDP等。然而,在某些情况下,这些标准的远程管理工具可能会遇到一些问题,例如需要穿透防火墙、网络性能下降、安全性问题等。为了解决这些问题,我们可以利用Linux的端口复用功能,提高远程管理的效率。
端口复用是指一个应用程序可以在同一时间使用多个网络端口的能力。在Linux中,端口复用通过SO_REUSEADDR选项实现。当启用SO_REUSEADDR选项时,系统将允许应用程序重新使用已经关闭的端口。这样,应用程序可以在不需要等待端口释放时间的情况下,立即绑定到新端口上,从而提高了应用程序的启动速度和响应时间。
这篇文章的写作初衷源于作者在实际工作中所遇到的问题。为了解决目标环境中出现的问题,作者开发了一个工具,并通过进一步利用得到了解决方案,同时还将自己探索的两个解决思路和处理方法记录下来,希望可以为大家提供有益的启示和帮助,提升解决问题的能力。
场景:机器只有一个5236端口可以进行tcp通信,并且有dubbo服务监听占用端口,机器可以命令执行。
思路1
dubbo是为是系统间的rpc服务框架,所以监听地址自然是0.0.0.0,思考远控是否有思路去与其他机器通信。
做个实验:
dubbo已经占用5236端口,用nc模拟占用:
nc -l 5236
看一下进程:
可以看到nc进程已经绑定了端口
写一个占用端口的demo:
func main() {
lis, err ꞉= net.Listen("tcp", "0.0.0.0꞉523 6")
if err != nil {
log.Println(err)
}
buf ꞉= make([]byte, 1024)
for {
conn, err ꞉= lis.Accept()
if err != nil {
log.Println(err)
continue
}
for {
n, err ꞉= conn.Read(buf)
if err != nil {
fmt.Println("read failed", err)
break
}
fmt.Println(string(buf[꞉n]))
}
}
}
运行:
可以看到端口被占用,不能监听起来。这里去思考Linux基于socket端口复用的特性。
socket端口复用
默认的情况下,如果一个网络应用程序的一个套接字绑定了一个端口( 占用了8000),这时候,别的套接字就无法使用这个端口,端口复用允许在一个应用程序可以把n个套接字绑在一个端口上而不出错。
设置socket的SO_REUSEADDR选项,即可实现端口复用。
SO_REUSEADDR可以用在以下四种情况:
-
当有一个相同本地地址和端口的socket1处于TIME_WAIT状态时,而启动程序的socket2要占用该地址和端口,程序就要用到该选项。
-
SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。
-
SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的IP地址不同。这和2很相似,区别请看UNPv1。
-
SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
所以这里可以利用上面第二点绕过0.0.0.0的绑定,利用设置SO_REUSEADDR绑定回环地址+port。
import (
"fmt"
"github.com/gogf/greuse"
"net/http"
"os"
)
func main() {
fmt.Println("run")
listener, err ꞉= greuse.Listen("tcp", "127.0.0.1꞉5237") //greuse库端口封装了socket端口复用的代码,直接调用即可
if err != nil {
panic(err)
}
conn, err ꞉= listener.Accept()
buf ꞉= make([]byte, 1024)
for {
n, err ꞉= conn.Read(buf)
if err != nil {
fmt.Println("read failed", err)
break
}
fmt.Println(string(buf[꞉n]))
}
defer listener.Close()
server ꞉= &http.Server{}
http.HandleFunc("/", func(w http.ResponseW riter, r *http.Request) {
fmt.Fprintf(w, "gid꞉ %d, pid꞉ %dn", o s.Getgid(), os.Getpid())
})
panic(server.Serve(listener))
}
nc监听5237模拟dubbo占用:
可以看到在nc的端口占用下依然监听了127.0.0.1,并且可以正常收到回环的请求。
那如果监听内网ip+port,是否可以做到对外通信?
可以看到fuyong进程监听到了内网ip+port,此时再去从外部机器去验证连通性。
使用同网段的另一台机器发起请求:
收到请求:
编写远控也可以用这个思路去写,变相等于抢占了原服务的通信去做远控通信,但是具体还是要看linux的版本(与发行版无关)以及内核特性,换了一台centos7测试就失败了。
思路2
源于红队实战的技巧,基于iptables的端口转发。
这里在虚拟测试机启动一个nginx,去模拟dubbo占用端口监听http服务宿主机,模拟内网另外一台机器。
模拟目标机:192.168.31.66
模拟内网一台机器1:192.168.31.179
模拟内网一台机器2:192.168.31.157
iptables转发命令:
iptables -t nat -A PREROUTING -p tcp -d 本机ip--dport 服务占用端口 -j REDIRECT --to-port 转发指向端口
iptables的端口转发可以将指定的传输层协议,由一个端口转发到另外一个端口,我们先去做一个示例,把80端口的tcp流量转发到7878端口:
iptables -t nat -A PREROUTING -p tcp -d 本机ip--dport 80 -j REDIRECT --to-port 7878
然后监听本机的7878端口
内网一台机器1向目标机器的80端口发起tcp请求
可以看到80端口的流量已经转发到了7878
所以现在的思路是本地起一个端口监听,然后将可通信的端口的tcp流量转发到该端口,可实现远控与C2之间的通信。
此时需要思考一个问题,会影响原服务吗?
答案是会的,因为iptables把所有的tcp流量全部转发到了指定端口,为了解决这个问题,这里提出一个限制源的策略:iptables的-s参数可以指定来源, 不过需要有内网的另一台不影响业务的机器作为C2。
iptables -t nat -A PREROUTING -p tcp -d 本机ip-s C2ip --dport 服务占用端口 -j REDIRECT --to-p ort 转发指向端口
所以这里把192.168.31.179作为C2
iptables -t nat -A PREROUTING -p tcp -d 192.16 8.31.66 -s 192.168.31.179 --dport 80 -j REDIRECT --to-port 7878
此时在目标机器监听7878端口
在192.168.31.179上发起基于tcp的请求
目标机器收到192.168.31.179请求
用192.168.31.157检查业务是否受到影响
说在最后
在Linux远控中,我们可以利用端口复用实现以下目标:
1.增加安全性:通过绑定到不同的端口,可以将不同的服务分散到不同的端口上,从而增加系统的安全性。
2.穿透防火墙:在某些情况下,防火墙可能会阻止某些端口上的通信。通过将同一个服务绑定到多个端口上,可以增加服务穿过防火墙的机会。
3.提高网络性能:当某个端口上的服务因为网络问题停止响应时,应用程序可以快速地切换到另一个端口上,从而避免因为单个端口的故障导致整个服务不可用的情况。
通过阅读本篇文章,大家可以拓宽思维,尝试自己编写工具,解决实际问题。
原文始发于微信公众号(i春秋):Linux远控的端口复用