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.原理


image-20200524154345598

现在有些规模的网站基本都不是单点 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

比如说我们已知代理服务有两层,从右向左数,把 proxy1proxy2 去掉,剩下的 IP 列表最右边的就是真实 IP。

相关思路和代码实现可参考 Egg.js 前置代理模式

5.一句话总结总结

通过 X-Forwarded-For 获取用户真实 IP 时,最好不要取第一个 IP,以防止用户伪造 IP。

文章推荐

下面我要推荐我的几篇文章:


最后推荐一下我的个人公众号:「卤蛋实验室」,平时会分享一些前端技术和数据分析的内容,大家感兴趣的话可以关注一波:

你确信 X-Forwarded-For 拿到的就是用户真实 IP 吗?的更多相关文章

  1. getRemoteAddr()和getRemoteHost() 区别

    System.out.println("request.getRemoteAddr(): " + request.getRemoteAddr()); System.out.prin ...

  2. JAVA获取客户端IP地址

    在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  3. 多级反向代理下,Java获取请求客户端的真实IP地址多中方法整合

    在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  4. servlet request getHeader(“x-forwarded-for”) 获取真实IP

    request方法客户端IP: request.getRemoteAddr() 输出:192.168.0.106 客户端主机名:request.getRemoteHost()输出:abc reques ...

  5. JAVA_用Java来获取访问者真实的IP地址

    在jsp里,获取客户端的ip地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  6. Java获取IP地址:request.getRemoteAddr()注意

        在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr() ,这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户 ...

  7. Java获取客户端真实IP地址的两种方法

    在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实I ...

  8. 关于httpservletrequest的获取真实的ip

    via 值为: 下面是一些DemoWTP/1.1 GDSZ-PS-GW010-WAP05.gd.chinamobile.com (Nokia WAP Gateway 4.0 CD3/ECD13_C/N ...

  9. CXF详细介绍

    首先介绍下CXF的拦截器:简单地说,CXF使用流水线型(或者说总线型)处理机制,它的核心是一个Bus.一个客户端的请求或者一个对客户端桩代码的调用被组织成为一个Message.同时,所有的CXF功能都 ...

随机推荐

  1. C++基本知识总结

    从第一个CPP开始写起: "hello,world" #include<iostream> using namespace std;//使用所有命名空间 int mai ...

  2. Jenkins 构建 Jmeter 项目之源代码管理(SVN)

    1.查看项目创建中是否又 svn 插件,没有的话下载插件 subversion 2.配置 svn 源代码管理,如下图(testcases 目录下包含 build.xml 和脚本文件) 3.查看 Jen ...

  3. css属性、样式、边框、选择器

    CSS 层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种 样式表 语言, 用来描述 HTML或 XML(包括如 SVG.MathML.XHTML 之类的 XML 分 ...

  4. 集训模拟赛-1-T1

    最近学校网课跟得紧没时间写知识点,就拿题解凑个数(bushi 而且前两天我打着打着题解电脑就突然死机 幸运的是 我没有保存(微笑) 废话不多说 上题目! 城市攻击 (city) (256MB,1s) ...

  5. c/c++获取硬盘序列号

    最近在接触软件注册模块,需要获取硬盘序列号来生成注册码. 硬盘序列号,英文名:Hard Disk Serial Number,该号是硬盘厂家为区别产品而设置的,是唯一的.网上搜索一下,发现获取硬盘序列 ...

  6. linux进程管理相关命令

    ps ps aux ps -ef | grep -E "supervisor|PPID"  top 可以按一定规则对top的结果进行排序 # 监控单一进程 top -p pid  ...

  7. spring 事务管理配置

    本篇文章只涉及spring事务的配置,不进行事务的介绍. spring通过PlatformTransactionManager接口作为事务管理器来进行事务的管理,它本身并不进行事务的创建以及相关操作, ...

  8. LeetCode--Array--Remove Element && Search Insert Position(Easy)

    27. Remove Element (Easy)# 2019.7.7 Given an array nums and a value val, remove all instances of tha ...

  9. js基石之---es7的decorator修饰器

    es7的decorator修饰器 装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法. decorator就是给类添加或修改类的变量与方法的. 装饰器是一种函数, ...

  10. java-mysql类型对照

    java mysql 数据类型对照 类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int) 描述             VARCHAR L+N VARCHAR java.lang. ...