在当前的网络架构中,负载均衡技术已经被广泛采用以提高系统性能和可靠性。然而,与此同时,我们也面临着一个不可避免的需求,在如防薅羊毛、防账密爆破等场景下,都需要准确地获取客户端IP。这就使得如何下准确地获取并透传客户端IP成为了一个挑战。因此,本文将深入探讨在负载均衡下如何透传客户端IP,并在这个过程中如何有效地防止IP伪造的问题。
01
负载均衡分类
1.1
数据链路层(二层)
简介:负载均衡服务器对外依然提供一个 VIP(虚拟IP),集群中不同的机器采用相同 IP地址,但机器的 MAC 地址不一样。当负载均衡服务器接受到请求之后,通过改写报文的目标 MAC 地址的方式将请求转发到目标机器实现负载均衡。
应用:交换机
1.2
网络层(三层)
简介:和二层负载均衡类似,负载均衡服务器对外依然提供一个 VIP(虚拟IP),但集群中不同的机器采用不同的 IP 地址。当负载均衡服务器接受到请求之后,根据不同的负载均衡算法,通过 IP 将请求转发至不同的真实服务器。
应用:路由器
1.3
传输层(四层)
简介:四层负载均衡工作在 OSI 模型的传输层,由于在传输层,只有 TCP/UDP 协议,这两种协议中除了包含源 IP、目标 IP 以外,还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后,以后通过修改数据包的地址信息( IP+端口号 )将流量转发到应用服务器。
应用:F5、LVS、Nginx、Haproxy
1.4
应用层(七层)
简介:七层负载均衡工作在 OSI 模型的应用层,应用层协议较多,常用HTTP、FTP、DNS等。七层负载就可以基于这些协议来负载。
应用:Nginx、Haproxy、Apache
02
四层转发模式
2.1
NAT
Network Address Translation(网络地址转换)
SNAT:
SIP(来源IP)变,DIP(目的IP)不变(常见于家庭路由器)
出现问题:
-
客户端为何将包发给路由器
解决问题
-
客户端判断目的IP跟自己是否同一个子网,不是则将目的MAC改成网关MAC发给网关
DNAT:
SIP不变,DIP变
出现问题:
-
Server直接返回给Client,暴露Server真实IP
-
Client不接受不认识的IP
解决问题:
-
Server先发给LB(负载均衡器),再由LB发给Client
出现问题:
-
Server的目的IP为客户端IP,如何将包发给LB
解决问题:
-
将DMAC改成LB MAC
存在弊端:
-
对LB本身性能要求高
-
LB和后端服务必须在同一个子网下
Full NAT:
SIP、DIP都变
存在弊端:
-
对LB本身性能要求高
-
服务端无法获取客户端真实IP
2.2
DSR
Direct Server Return(服务直接返回)
SIP和DIP都不能变,Server直接回复给Client IP
出现问题:
-
MAC不对如何发给Server
解决问题:
-
将DMAC改成Server MAC
出现问题:
-
Server发现IP非自己IP进行丢弃
解决问题:
-
将Server IP和LB IP设置一致
出现问题:
-
LB和Server有相同的IP,如何确保请求冲突问题
解决问题:
-
让Server完全忽略ARP请求(相当于只自我欺骗不丢包)
存在弊端:
-
LB和Server必须部署在同一个LAN下,LB要通过二层MAC寻址找到Server,不支持走三层
2.3
IP Tunnel
保持源IP包不变,新建一个IP数据包,将原来的IP数据包整体放进新IP数据包内,支持跨网段
存在弊端:
-
回包流量不经过LB,LB只有单边流量,导致无法获得TCP完整状态
-
封装和解封装存在额外的开销
03
透传IP
3.1
四层负载均衡
如上所述,在四层负载均衡下,如果采用DSR、IP Tunnel、DNAT的负载模式,后端服务器可直接获取到客户端IP,不需要进行IP的透传,而当使用了类似Full NAT的模式,如何透传IP成为了问题,常见有以下两种解决方案。
TOA/UOA:
Tcp Option Address/Udp Option Address
这里以TOA为例,TOA是一种内核模块,它可以在TCP选项字段中添加一个包含客户端真实IP和端口的选项。
优势:没有变更协议,不会有兼容性问题。
劣势:这需要在负载均衡器和所有后端服务器上安装TOA模块。
流程:
-
客户端用户请求数据包到达L4(四层LB)时,L4在数据包的tcp option中插入Client IP和Client Port信息
-
数据包到达后端服务器(装有TOA模块)后,应用程序正常调用getpeername系统函数来获取连接的源端IP地址
-
由于在toa代码中hook了toa_init相关函数(getpeername系统调用对应的内核处理函数),该函数会从tcp option中获取L4填充的Client信息
-
只需要在第3次握手ack数据包中插入toa选项即可,后端服务器从ack数据包中解析并获取即可
格式:
Client IP就是放在tcp option字段中。option字段最长40字节,每个选项由三部分组成:op-kind、op-length、op-data。目前 option 使用的op-kind并不多,我们只需要构建一个不冲突的op-kind就可以把Client IP填充进去。IPv4 地址占用 4 个字节,IPv6占用16字节,填充到option中是没有问题的。
以IPv4的toa格式为例:
+----------+----------+--------------------+
| opcode | opsize | port |
+----------+----------+--------------------+
| clientIP |
+------------------------------------------+
源码:
/* module init */
static int __init
toa_init(void)
{
...
/* hook funcs for parse and get toa */
hook_toa_functions();
...
}
/* replace the functions with our functions */
static inline int
hook_toa_functions(void)
{
/* hook inet_getname for ipv4 */
struct proto_ops *inet_stream_ops_p =
(struct proto_ops *)&inet_stream_ops;
/* hook tcp_v4_syn_recv_sock for ipv4 */
struct inet_connection_sock_af_ops *ipv4_specific_p =
(struct inet_connection_sock_af_ops *)&ipv4_specific;
...
inet_stream_ops_p->getname = inet_getname_toa;
...
ipv4_specific_p->syn_recv_sock = tcp_v4_syn_recv_sock_toa;
return 0;
}
PROXY Protocol:
最早于 2010 年被提出,并首先运用于 HAProxy,后续越来越多厂商进行兼容如:Nginx、CDN厂商等。
Proxy Protocol有v1和v2两个版本,v1版本以明文的字符串发送数据,v2版本以二进制格式发送,以下我们以v1为例进行讨论。
实现主要是在建立 TCP 连接后, 在发送应用数据之前先将用户的 IP 信息发送到服务端,如v1版本iPv4格式:
PROXY TCP4 [Client IP] [LB IP] [Client Port] [LB Port]rn
红圈:proxy protocol信息
绿圈:真实应用层交互
3.2
七层负载均衡
对于七层负载均衡来说,主要通过HTTP的请求头进行透传IP。
X-Forwarded-For:
当负载均衡器接收到客户端的请求时,它可以在请求的HTTP头部添加一个X-Forwarded-For字段,并将客户端的IP地址放入这个字段。后端服务器可以从X-Forwarded-For字段中读取客户端的真实IP。
X-Real-IP:
这是另一种常见的方法,类似于X-Forwarded-For。负载均衡器在转发请求时,将客户端的IP地址放入HTTP头部的X-Real-IP字段。
04
利用&防御
4.1
TOA/UOA
利用:
条件:
-
L4产品使用Full NAT模式传递客户端IP
-
服务器主机启用TOA模块获取IP
原理:L4发现存在TOA不进行覆盖直接传给后端服务
POC:https://gitee.com/vulkey/FakeToa
成功将IP伪造成8.8.8.8
防御:
TOA由L4进行写入覆盖,不信任用户传来的TOA
4.2
PROXY Protocol
利用:
echo -en "PROXY TCP4 1.2.3.4 1.2.3.4 11 11rnHello World" | nc -v
黄圈:proxy protocol信息
绿圈:伪造的信息
防御:
严格按照协议的规定,只取业务数据中最开始的字段
4.3
X-Real-IP
利用:
条件:后端服务通过请求头X-Real-IP中进行获取,而LB未配置
方式:请求时加X-Real-IP头进行伪造。
防御:
如Nginx应设置:
proxy_set_header X-Real-IP $remote_addr;
4.4
X-Forwarded-For
利用:nginx配置如下
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
原理:
-
X-Forwarded-For头格式为:
X-Forwarded-For: client, proxy1, proxy2
-
通过X-Forwarded-For中进行获取左一IP作为Client IP
方式:请求时加X-Forwarded-For头进行伪造
防御:
方式一(后端服务角度):从右向左遍历,遍历时可以根据正则表达式剔除掉内网IP和已知的代理服务器本身的IP(例如192.168开头的),那么拿到的第一个非剔除IP就会是一个可信任的客户端IP
方式二(第一层LB角度):Nginx配置为:
proxy_set_header X-Forwarded-For $remote_addr;
在本文中,我们从四层和七层负载均衡的角度深入探讨了如何透传客户端IP以及如何防止IP伪造的问题。为了确保网络环境的安全性,我们不能盲目信任客户端传过来的信息,而需加以判断,必要时进行覆盖以免影响后端服务。
参考文章:
-
https://gitee.com/vulkey/FakeToa
欢迎关注 Asimov攻防实验室
原文始发于微信公众号(Asimov攻防实验室):负载均衡下的IP伪造
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论