背景
nginx作为反向代理的时候,是用自己的IP去和服务器建立TCP连接,导致业务服务器无法看到通过ng反代访问的客户端的真实IP
-
业务服务器
在8040端口起了一个web服务,并尝试去从请求中的X-Real-IP头中获取真实IP,如果没有X-Real-IP头的话,获取X-Forwarded-For最右边的IP作为源IP。如果两个都没有的话则使用和我们建立TCP连接的IP。并输出日志到web_service.log文件里
import http.server
import socketserver
import logging
# 配置日志
logging.basicConfig(filename='web_service.log', level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
class MyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
# 尝试从X-Real-IP和X-Forwarded-For获取IP
real_ip = self.headers.get('X-Real-IP')
forwarded_for = self.headers.get('X-Forwarded-For')
# 如果没有X-Real-IP,则尝试使用X-Forwarded-For
if not real_ip:
if forwarded_for:
# 分割字符串,取最后一个元素(即最右边的IP),并去除可能的前后空格
real_ip = forwarded_for.split(',')[-1].strip()
# 如果两者都没有,则使用远程地址(可能是代理的地址)
if not real_ip:
real_ip = self.client_address[0]
# 记录访问日志
logging.info(f'Access from IP: {real_ip}, Path: {self.path}')
# 构建响应消息
response_message = "你干嘛,哎哟! Your IP is: " + real_ip
# 发送响应
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
# 将响应消息编码为字节串并发送
self.wfile.write(response_message.encode('utf-8'))
def log_message(self, format, *args):
# 重写日志方法,因为我们已经自定义了日志记录
pass
def run(server_class=http.server.HTTPServer, handler_class=MyHTTPRequestHandler):
server_address = ('', 8040)
httpd = server_class(server_address, handler_class)
logging.info('Starting httpd...\n')
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
logging.info('Stopping httpd...\n')
if __name__ == '__main__':
run()
NG测试
不过NG测试
此时拓扑如下:
客户端----->服务器
-
不伪造XFF和x-real
可以看到,服务器获取到的是真实的客户端IP
-
伪造x-real-ip
此时服务器获取到的IP是从X-Real-Ip这个头里获取的,但是这个头是我伪造的,导致服务器没有获取到我真实的IP
-
伪造X-forwarded-for头
可以看到服务器获取到的源IP是XFF的最右边的一个,但是这个也是我伪造的
-
结论
如果业务服务器从x-real-ip或者xff头中获取,客户端一就无法获取到真实的源IP了
过NG测试
-
nginx配置文件
upstream www.server_pools {
server 192.168.1.35:8040 ;
}
server {
listen 9090;
server_name localhost;
location / {
proxy_pass http://www.server_pools;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; #将客户端的真实IP地址添加到X-Real-IP头中
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #将客户端的IP地址(以及可能经过的其他代理的IP地址)添加到X-Forwarded-For头中,形成一个由逗号分隔的IP地址列表
proxy_set_header X-Forwarded-Proto $scheme; #递原始请求使用的协议(http或https)
}
}
-
nginx不配置任何字段
upstream www.server_pools {
server 192.168.1.35:8040 ;
}
server {
listen 9090;
server_name localhost;
location / {
proxy_pass http://www.server_pools;
}
}
可以看到因为请求里面没有x-real-ip和xff头且ng也没有去set这两个头,业务服务器读不到这两个头自然而然地获取到的IP就是remote ip即ng的ip
-
nginx配置X-real-IP头
upstream www.server_pools {
server 192.168.1.35:8040 ;
}
server {
listen 9090;
server_name localhost;
location / {
proxy_pass http://www.server_pools;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
客户端不伪造的情况下
获取到的是真实的客户端IP,且此时在服务器抓包看到ng发过来的包里是有x-real-ip这个头的
客户端伪造x-real-ip
可以看到此时获取到的IP依然是客户端的IP,在服务器抓包看到的X-Real-IP也是客户端的IP,当nginx收到客户端的请求中时,会把X-Real-IP的内容用remote IP替换掉
-
nginx配置XFF头
upstream www.server_pools {
server 192.168.1.35:8040 ;
}
server {
listen 9090;
server_name localhost;
location / {
proxy_pass http://www.server_pools;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #将客户端的IP地址(以及可能经过的其他代理的IP地址)添加到X-Forwarded-For头中,形成一个由逗号分隔的IP地址列表
proxy_set_header X-Forwarded-Proto $scheme; #递原始请求使用的协议(http或https)
}
}
客户端不伪造XFF头
获取到的是真实的客户端IP,nginx在转发请求的时候把客户端的IP塞到了XFF头里转发给服务器,服务器在读不到X-Real-IP的时候会去读XFF,此时输出的IP即为XFF里的真实IP
-
客户端伪造XFF头
此时获取到的源 IP依然是客户端的真实IP,请求咋经过ng的时候,ng会把客户端的真实IP插到XFF头的最右边
结论
请求不过NG的时候,客户端伪造X-Real-IP头和XFF头如果业务服务器是从这两个头中读源IP的话很容易导致业务服务器获取不到真实的客户端IP所以只能获取remote IP作为源IP,当请求经过NG代理的话且业务服务器获取remote IP作为源IP的话会导致获取到的源IP都是NG的IP。所以需要调整NG的配置主动去set x-real和xff头并把remote IP插进去,以及调整业务服务器获取源IP的方式