你确信 X-Forwarded-For 拿到的就是用户真实 IP 吗?
X-Forwarded-For 拿到的就是真实 IP 吗?
1.故事
在这个小节开始前,我先讲一个开发中的小故事,可以加深一下大家对这个字段的理解。
前段时间要做一个和风控相关的需求,需要拿到用户的 IP,开发后灰度了一小部分用户,测试发现后台日志里灰度的用户 IP 全是异常的,哪有这么巧的事情。随后测试发过来几个异常 IP:
10.148.2.122
10.135.2.38
10.149.12.33
...
一看 IP 特征我就明白了,这几个 IP 都是 10 开头的,属于 A 类 IP 的私有 IP 范围(10.0.0.0-10.255.255.255),后端拿到的肯定是代理服务器的 IP,而不是用户的真实 IP。
2.原理

现在有些规模的网站基本都不是单点 Server 了,为了应对更高的流量和更灵活的架构,应用服务一般都是隐藏在代理服务器之后的,比如说 Nginx。
加入接入层后,我们就能比较容易的实现多台服务器的负载均衡和服务升级,当然还有其他的好处,比如说更好的内容缓存和安全防护,不过这些不是本文的重点就不展开了。
网站加入代理服务器后,除了上面的几个优点,同时引入了一些新的问题。比如说之前的单点 Server,服务器是可以直接拿到用户的 IP 的,加入代理层后,如上图所示,(应用)原始服务器拿到的是代理服务器的 IP,我前面讲的故事的问题就出在这里。
Web 开发这么成熟的领域,肯定是有现成的解决办法的,那就是 X-Forwarded-For 请求头。
X-Forwarded-For 是一个事实标准,虽然没有写入 HTTP RFC 规范里,从普及程度上看其实可以算 HTTP 规范了。
这个标准是这样定义的,每次代理服务器转发请求到下一个服务器时,要把代理服务器的 IP 写入 X-Forwarded-For 中,这样在最末端的应用服务收到请求时,就会得到一个 IP 列表:
X-Forwarded-For: client, proxy1, proxy2
因为 IP 是一个一个依次 push 进去的,那么第一个 IP 就是用户的真实 IP,取来用就好了。
但是,事实有这么简单吗?
3.攻击
从安全的角度上考虑,整个系统最不安全的就是人,用户端都是最好攻破最好伪造的。有些用户就开始钻协议的漏洞:X-Forwarded-For 是代理服务器添加的,如果我一开始请求的 Header 头里就加了 X-Forwarded-For ,不就骗过服务器了吗?
1. 首先从客户端发出请求,带有 X-Forwarded-For 请求头,里面写一个伪造的 IP:
X-Forwarded-For: fakeIP
2. 服务端第一层代理服务收到请求,发现已经有 X-Forwarded-For,误把这个请求当成代理服务器,于是向这个字段追加了客户端的真实 IP:
X-Forwarded-For: fakeIP, client
3. 经过几层代理后,最终的服务器拿到的 Header 是这样的:
X-Forwarded-For: fakeIP, client, proxy1, proxy2
要是按照取 X-Forwarded-For 第一个 IP 的思路,你就着了攻击者的道了,你拿到的是 fakeIP,而不是 client IP。
4.破招
服务端如何破招?上面三个步骤:
第一步是客户端造假,服务器无法介入 第二步是代理服务器,可控,可防范 第三步是应用服务器,可控,可防范
第二步的破解我拿 Nginx 服务器举例。
我们在最外层的 Nginx 上,对 X-Forwarded-For 的配置如下:
proxy_set_header X-Forwarded-For $remote_addr;
什么意思呢?就是最外层代理服务器不信任客户端的 X-Forwarded-For 输入,直接覆盖,而不是追加。
非最外层的 Nginx 服务器,我们配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
$proxy_add_x_forwarded_for 就是追加 IP 的意思。通过这招,就可以破解用户端的伪造办法。
第三步的破解思路也很容易,正常思路我们是取X-Forwarded-For 最左侧的 IP,这次我们反其道而行之,从右边数,减去代理服务器的数目,那么剩下的 IP 里,最右边的就是真实 IP。
X-Forwarded-For: fakeIP, client, proxy1, proxy2
比如说我们已知代理服务有两层,从右向左数,把 proxy1 和 proxy2 去掉,剩下的 IP 列表最右边的就是真实 IP。
相关思路和代码实现可参考 Egg.js 前置代理模式。
5.一句话总结总结
通过 X-Forwarded-For 获取用户真实 IP 时,最好不要取第一个 IP,以防止用户伪造 IP。
文章推荐
下面我要推荐我的几篇文章:
一篇介绍了 webpack 中最易混淆的 5 个知识点,掘金快 800 赞了,一文讲清楚 Webpack 中那些长得像却意义不同的概念 一篇详细介绍了 webpack dll 是个什么东西,并且给出了 2 条最佳实践,摆脱繁琐的 dll 配置 React Native 性能优化指南从渲染层的角度分析了 RN 性能优化的 6 个点,并以图文形式讲解了 FlatList 的实现原理 Web Scraper——轻量数据爬取利器 介绍了一个小巧的浏览器爬虫插件,可以实现简单的数据爬取功能
最后推荐一下我的个人公众号:「卤蛋实验室」,平时会分享一些前端技术和数据分析的内容,大家感兴趣的话可以关注一波:

你确信 X-Forwarded-For 拿到的就是用户真实 IP 吗?的更多相关文章
- getRemoteAddr()和getRemoteHost() 区别
System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr()); System.out.prin ...
- JAVA获取客户端IP地址
在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...
- 多级反向代理下,Java获取请求客户端的真实IP地址多中方法整合
在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...
- servlet request getHeader(“x-forwarded-for”) 获取真实IP
request方法客户端IP: request.getRemoteAddr() 输出:192.168.0.106 客户端主机名:request.getRemoteHost()输出:abc reques ...
- JAVA_用Java来获取访问者真实的IP地址
在jsp里,获取客户端的ip地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...
- Java获取IP地址:request.getRemoteAddr()注意
在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr() ,这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户 ...
- Java获取客户端真实IP地址的两种方法
在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...
- 关于httpservletrequest的获取真实的ip
via 值为: 下面是一些DemoWTP/1.1 GDSZ-PS-GW010-WAP05.gd.chinamobile.com (Nokia WAP Gateway 4.0 CD3/ECD13_C/N ...
- CXF详细介绍
首先介绍下CXF的拦截器:简单地说,CXF使用流水线型(或者说总线型)处理机制,它的核心是一个Bus.一个客户端的请求或者一个对客户端桩代码的调用被组织成为一个Message.同时,所有的CXF功能都 ...
随机推荐
- 惠普 HP Pavilion 15 Notebook PC清灰教程总结 惠普g4系列清灰加内存条教程
最近天气热的电脑都受不了,风扇总是异响,声音很大,感觉是散热不行了,就把电脑清一下灰,虽然之前也清过,但是基本都忘记了,机子比较老,找不到具体教程,清灰过程中因为不熟悉有点费劲,手动记录一下,方便下次 ...
- 【Layui__监听button】在form中监听按钮事件
1. 前言 在使用form表单的按钮时,点击按钮总是页面刷新,代码如下 <button class="layui-btn" lay-submit lay-filter=&qu ...
- commons-logging slf4j log4j 区别
日志门面 1.Apache通用日志接口(commons-logging.jar) Apache Commons包中的一个,包含了日志功能,必须使用的jar包.这个包本身包含了一个Simple Logg ...
- CODING 敏捷实战系列课第二讲:Scrum 敏捷项目管理核心要素之 3355
Scrum 是敏捷开发流派中最著名和最落地的一支,全球 70% 以上公司的敏捷转型都是以 Scrum 起步.CODING 特邀敏捷顾问.CST & CTC 认证敏捷教练申健老师将在本课程< ...
- 【Swift】获取UILabel中点击的某个功能标签文字并作出响应动作
1.需求 首先.针对UILabel中显示的多个功能标签,作出颜色标记提示. 其次.对关键字作出点击响应动作. 如图所示: 解决: 1.使用正则匹配到关键字 public static var hash ...
- RSA host key has changed
- 【大数据 Spark】利用电影观看记录数据,进行电影推荐
利用电影观看记录数据,进行电影推荐. 目录 利用电影观看记录数据,进行电影推荐. 准备 1.任务描述: 2.数据下载 3.部分数据展示 实操 1.设置输入输出路径 2.配置spark 3.读取Rati ...
- 环境篇:Kylin3.0.1集成CDH6.2.0
环境篇:Kylin3.0.1集成CDH6.2.0 Kylin是什么? Apache Kylin™是一个开源的.分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析( ...
- HMM-前向后向算法与实现
目录 基本要素 HMM三大问题 概率计算问题 前向算法 后向算法 前向-后向算法 基本要素 状态 \(N\)个 状态序列 \(S = s_1,s_2,...\) 观测序列 \(O=O_1,O_2,.. ...
- [hdu3644 A Chocolate Manufacturer's Problem]模拟退火,简单多边形内最大圆
题意:判断简单多边形内是否可以放一个半径为R的圆 思路:如果这个多边形是正多边形,令r(x,y)为圆心在(x,y)处多边形内最大圆的半径,不难发现,f(x,y)越靠近正多边形的中心,r越大,所以可以利 ...