1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > html获取当前ip地址_科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?

html获取当前ip地址_科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?

时间:2018-11-02 21:14:45

相关推荐

html获取当前ip地址_科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?

## 背景

我们的 Web 服务,往往需要获取用户的真实 IP,譬如防刷、API 限流等等场景。

这似乎是一个显而易见的问题。

以 Node.js 来说,每一个 TCP 连接都有 remoteAddress 属性,通过它可以直接获取到请求的 IP 地址。而在 HTTP 请求中,我们可以通过request.socket.remoteAddress访问到这个属性。

可是事情真的有这么简单吗?

一般来说我们的应用服务都不会直接接收外部的请求,而会将服务部署在接入层之后,从而实现多台机器的负载均衡和服务的平滑发布,保证高可用。如阿里云 SLB 或 Nginx 反向代理。

此时,我们通过remoteAddress获取到的就是代理服务器的 IP 而不是用户的真实 IP。

## 常规解决方案

最常见的 Web 服务架构

既然这是一个通用的部署架构,那肯定有通用的解决方案来实现获取用户 IP 地址吧?

这个通用的解决方案就是 X-Forwarded-For 请求头。

简单的来说,就是所有的反向代理都实现一个统一的约定,在转发请求给下游服务之前,把请求代理的 IP 地址写入到X-Forwarded-For头中,形成了一个 IP 地址列:

X-Forwarded-For: client, proxy1, proxy2

这个方案虽然不是正式的 HTTP 协议,但已经成为了一个事实标准,基本上所有的反向代理服务都实现了这个功能,以确保下游的服务可以感知到经过的反向代理,并从中获取到用户的 IP 地址。

经查阅,发现有个 RFC 7239 规定了 Forwarded 请求头。(不过 Koa 没支持)Forwarded: for=127.0.0.1; proto=https, for=1.2.3.4; proto=https

看起来问题似乎完美解决。

## 真的解决了吗?

永远不要相信用户侧的输入。

可是总有一些“聪明”的用户,在遇到服务有做 IP 限制的时候,他们灵光一闪:假如我们在请求中直接加入X-Forwarded-For的请求头,是不是就可以伪装请求的 IP 了呢?

curl -H 'X-Forwarded-For: 1.2.3.4' /api/resources

按照X-Forwarded-For的工作原理,收到它的第一个反向代理会就会认为原始的请求地址为1.2.3.4,然后将真实的用户 IP 地址当做“第一个代理”加入到X-Forwarded-For中,最终在应用层的结果就变成了:

X-Forwarded-For: 1.2.3.4, client, proxy1, proxy2

如果我们的服务只按照之前的约定,将第一个 IP 地址当做用户的真实 IP,就被这些“聪明”的用户绕过了。甚至他们还有 Chrome 插件,直接把浏览器所有的请求都带上伪造的 IP 地址。

麻麻呀,这世界真危险,人与人最基础的信任呢?

## 解决方案

为了避免获取到伪造的用户 IP 地址,我们只能够更进一步,确定我们的部署架构上到底有多少个反向代理服务,从而在从X-Forwarded-For请求头中获取请求的真实 IP 时,过滤掉用户伪造的 IP 地址。

// 伪代码// [ illegalIp, clientRealIp, proxyIp1, proxyIp2 ...]const val = ctx.get('X-Forwarded-For');let ips = val ? val.split(/s*,s*/) : [];ips = ips.slice(-(maxProxyCount + 1));

很笨很挫的方式,不是么?但安全解决方案往往都是这样的 Dirty 和追求实用。

同时,作为企业级的 Node.js 框架,Egg.js 直接内置提供了对应的解决方案。

仅需简单的配置:

// config/config.default.jsconfig.proxy = true;config.maxProxyCount = 1;

应用开发者即可无感知获取到正确的信息:

ctx.ip // 获取用户的 IP 地址ctx.host// 获取用户请求的域名ctx.protocol // 获取用户请求的协议

详细的方案,有兴趣的同学可以扩展阅读以下文档,其中也介绍了一些注意事项。

Egg 文档:前置代理模式​

有的同学可能会问了:是否可以直接在前面的接入层配置一个特殊的头,或者直接让它们覆盖掉X-Forwarded-For就可以了呢?

proxy_set_header X-Forwarded-For $remote_addr;

当然这也不失为一个解决方案,但是这个方案有它自己的问题,Egg.js 需要做兜底处理:

并不是所有的统一接入层都受用户控制,例如阿里云 SLB 等商业负载均衡服务上,可能你就没法定制化这些需求了。框架更希望通过标准(事实标准)的解决方案来收敛功能,只要上层统一接入遵循标准,框架就可以直接接入,统一配置,而不需要用户根据不同的接入层来不同实现。两者并不冲突,框架兜底实现,default to good。

## 关于本文

本方案源自于 语雀 的真实安全攻防案例,作者为蚂蚁金服的 ​ @死马 。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。