获取http请求者ip(nodejs-express)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37263637/article/details/83831247

今天想找一个安全生成sessionid的方案,随后想到应该检测请求方ip,这时才发现这里是一片知识的盲区。随即整理学习。


本文测试服务器均为linux环境
网站访问并不是简单地从用户的浏览器直达服务器,中间可能部署有CDN、WAF、高防。例如,采用这样的架构:用户 > CDN/WAF/高防 > 源站服务器。那么,在经过多层加速后,服务器如何获取发起请求的真实客户端 IP 呢?

1 原理

http请求会在headers中携带相关信息。
其中主要涉及到两个参数:

  • Remote Address
  • X-Forwarded-For

1.1 Remote Address

我们知道 HTTP 连接基于 TCP 连接,HTTP 协议中没有 IP的概念,但Remote Address是来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP。
Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。不同语言获取 Remote Address 的方式不一样,Node.js 是 req.connection.remoteAddress。
如果在没有代理的情况下,我们得到Remote Address即为请求者真实ip地址。

1.2 存在代理 X-Forwarded-For

1.2.1 实现逻辑

这个问题最关键的就是http header 中字段:X-Forwarded-For
一个透明的代理服务器在把用户的请求转到下一环节的服务器时,会在HTTP的头中加入一条X-Forwarded-For记录,用来记录用户的真实IP,其形式为X-Forwarded-For:用户IP。如果中间经历了多个代理服务器,那么X-Forwarded-For会表现为以下形式:X-Forwarded-For:用户IP, 代理服务器1-IP, 代理服务器2-IP, 代理服务器3-IP, ……。
常见的应用服务器可以使用X-Forwarded-For的方式获取访问者真实IP。如果使用nginx,添加X-Forwarded-For支持。

1.2.2 X-Forwarded-For

X-Forwarded-For 基础概念:
X-Forwarded-For 是一个 HTTP 扩展头部,X-Forwarded-For 请求头格式非常简单,就这样:
X-Forwarded-For: client, proxy1, proxy2
可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。所以在有代理的情况下,请求真实ip即为X-Forwarded-For最左侧ip。

1.3 确保请求者ip是真实的而不是伪造的

因为X-Forwarded-For 可以自行在headers中伪造,后续的代理服务器只会在X-Forwarded-For 原有内容中添加。所以我们不能完全相信X-Forwarded-For中的内容。里面数据甚至可能是SQL注入攻击语句。

1.3.1 对于安全性要求较高的行为

1 直接对外提供服务的 Web 应用,在进行与安全有关的操作时,只能通过 Remote Address 获取 IP,不能相信任何请求头(因为x-forwarded-for可直接伪造)
2 使用 Nginx 等 Web Server 进行反向代理的 Web 应用,在配置正确的前提下,要用 X-Forwarded-For 最后一节来获取 IP(因为 Remote Address 得到的是 Nginx 所在服务器的内网 IP),同时还应该禁止 Web 应用直接对外提供服务。
以上两种方案均无法解决如何安全的得到经过多重代理的原请求者ip。
第一种情况,只能直连到服务器的请求ip。
第二种情况,只能得到server之间代理前最后一级ip。

1.3.2 对于安全要求较低的行为

比如像天气之列的行为,通过ip获取来传递信息的接口。被攻击耶没什么用,则我们可以取x-forwarded-for中左侧内容ip作为请求者ip。但我们仍然需要对该内容进行检查防范注入攻击。

2 获取请求者ip方法

2.1 express框架实现(nodejs)

文档地址:http://www.expressjs.com.cn/guide/behind-proxies.html
express提供了trust proxy参数可以解析获取经过代理后的x-forwarded-for中原请求者ip

该值可以传入几种参数
1 boolean
配置为true,存在代理且x-forwarded-for headers中最左侧条目为req.ip的值,即请求者ip
配置为false,req.ip的值即为req.connection.remoteAddress中的值,即为没有代理,与服务器建立tcp的ip即为请求者ip

app.set('trust proxy', true);

2 配置为受信任地址(ip 地址)
上面我们说到了,我们可能会自行架设多级代理。配置该参数后,参数中地址就会x-forwarded-for地址确认中被排除。

app.set('trust proxy', 'loopback, 123.123.123.123') // specify a subnet and an address

指定后,IP地址或子网将从地址确定过程中排除,并且最靠近应用程序服务器的不受信任的IP地址将被确定为客户端的IP地址。即自行搭建的代理服务器上一个地址即为req.ip地址。(即1.3.1 中第二种情况,访问待见最外层代理服务器的ip,被认为为客户端ip)

3 数字

app.set('trust proxy', 1) // specify a subnet and an address

信任n来自前置代理服务器的第二跳作为客户端。即我们搭建了几层代理即可设置为几。

所以以上参数:
配置为true,则适用于1.3.2
配置后false,受信任地址,代理服务器层级 则适用1.3.1 这种对安全要求较高的情况

2.2 sample code(获取x-forwarded-for最左侧条目作为请求者ip)

在express 中app.js加入一下语句

app.set('trust proxy', true);// 设置以后,req.ips是ip数组;如果未经过代理,则为[]. 若不设置,则req.ips恒为[]
app.get('/ip', function(req, res){
  console.log("x-forwarded-for    = " + req.header('x-forwarded-for'));// 各阶段ip的CSV, 最左侧的是原始ip
  console.log("ips                          = " + JSON.stringify(req.ips));// 相当于(req.header('x-forwarded-for') || '').split(',')
  console.log("remote Address     = " + req.connection.remoteAddress);// 未发生代理时,请求的ip
  console.log("ip                            = " + req.ip);// 同req.connection.remoteAddress, 但是格式要好一些
  res.send('Hello World');
});

用浏览器或POSTman 测试 http://172.28.xx.xx/ip 适用一下语句访问

2.3 nginx配置传递 x-forwarded-for参数

Nginx 默认是不会传递x-forwarded-for参数的,需要server块中进行配置才会传递该参数。
添加一下参数:

 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

如图:
在这里插入图片描述

3 测试

本文代理均指nginx

3.1 server 与 client均为本地同一服务器

server所在ip:172.28.28.17
打印结果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:172.28.28.17
ip                 = ::ffff:172.28.28.17

可以看到没有代理则x-forwarded-for为undefined, remote Address 为本机ip。

3.2 server与client均为本地同一服务器且架设nginx反向代理,client通过代理访问

打印结果:

x-forwarded-for    = 172.28.28.17
ips                = ["172.28.28.17"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.28.17

可以看到通过代理访问则x-forwarded-for有值, remote Address 为代理服务器地址。

3.3 server与client在同一局域网且架设nginx反向代理,但不配置x-forwarded-for

打印结果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:127.0.0.1
ip                 = ::ffff:127.0.0.1

x-forwarded-for为undefined,且remote Address 为代理服务器地址,且最终结果也为代理服务器地址

3.4 server与client在同一局域网且架设nginx反向代理

clientip:172.28.52.15
打印结果:

x-forwarded-for    = 172.28.52.15
ips                = ["172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.52.15

可以看到通过代理访问则x-forwarded-for值为172.28.52.15, remote Address 为127.0.01 为nginx代理服务器地址。

3.5 server与client在同一局域网且架设nginx反向代理,client伪造x-forwarded-for(重要)

在postman中手动添加x-forwarded-for值且为172.28.28.111
打印结果:

x-forwarded-for    = 172.28.28.111, 172.28.52.15
ips                = ["172.28.28.111","172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.28.111

可以看到手动添加172.28.28.111出现在了x-forwarded-for,且最终结果ip为伪造的ip。这就是使用x-forwarded-for 的安全风险。

高级风险SQL注入风险
在postman中手动添加x-forwarded-for值且为mysql语句:select * from table1

x-forwarded-for    = select * from table1, 172.28.52.15
ips                = ["select * from table1","172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = select * from table1

可以看到手动添加select * from table1 出现在了x-forwarded-for中最左侧。且最终结果也为select * from table1 。所以要求安全的情况下是不能直接使用最左侧项的。

3.6 server在公网,client访问

使用手机4G 查的手机公网ip:139.207.57.70
打印结果

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:139.207.57.70
ip                 = ::ffff:139.207.57.70

可以看到没有代理则x-forwarded-for为undefined,且remote Address直接为手机公网ip。这种情况即为没有代理直接访问。

3.7 server在公网,client使用海外代理访问(科学上网真有趣)

使用手机4G 查的手机公网ip:139.207.57.70
科学上网服务器ip:104.236.xx4.1xx
打印结果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:104.236.xx4.1xx
ip                 = ::ffff:104.236.xx4.1xx

可以看到没有代理则x-forwarded-for为undefined,且remote Address直接为科学上网服务器ip,科学上网大法好,把自己ip成功藏起来了。

3.8 server在公网且架设nginx反向代理,client访问

使用手机4G 查的手机公网ip:139.207.57.70
打印结果:

x-forwarded-for    = 139.207.57.70
ips                = ["139.207.57.70"]
remote Address     = ::ffff:127.0.0.1
ip                 = 139.207.57.70

可以看到没有代理则x-forwarded-for为undefined,且remote Address直接为代理服务器ip。

3.9 server在公网且架设nginx反向代理,client且伪造x-forwarded-for

本地服务器 101.204.240.1xx
打印结果

x-forwarded-for    = select * from table1, 101.204.240.1xx
ips                = ["select * from table1","101.204.240.1xx"]
remote Address     = ::ffff:127.0.0.1
ip                 = select * from table1

可以看到手动添加select * from table1 出现在了x-forwarded-for中最左侧。且最终结果也为select * from table1 。所以要求安全的情况下是不能直接使用最左侧项的。

4 如何在广袤互联网找到你

了解上诉方案之后,自然就会产生一个问题,如何在互联网找到你?
基于上诉测试结果,如果你使用的网站经过的代理(CDN)均为透明代理,即代理都会传递http headers参数中的X-Forwarded-For。
意味着你ip是会随着X-Forwarded-For 传递到网站服务器上。所以网站服务器后台是会有相应访问记录的ip。
比如 你在某时某刻 在某个ip在该网站上留下了某条评论。

现在如果需要找到你,有以下几步:

  1. 网站后台上拿到ip和访问时间。
  2. 找到该ip对应ISP(网络接入商 电信 联通 找到该ip对应ISP(网络接入商 电信 联通 移动),并要求通过提供该ip在该时段时与网站服务器(网站ip)通信的用户账户信息(NAT协议的转发记录可以找到具体使用者)
  3. 获取到账户信息后,就可以找到你家庭住址 GG(开网肯定要提供相关信息)

5 参考链接

https://www.cnblogs.com/duhuo/p/5700900.html
HTTP 请求头中的 X-Forwarded-For: https://imququ.com/post/x-forwarded-for-header-in-http.html

猜你喜欢

转载自blog.csdn.net/m0_37263637/article/details/83831247