从下游代理解析用户IP

Resolve user IP from downstream proxies

我们以前 运行 互联网和我们的微服务之间的单个 Nginx 反向代理,配置如下:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

其中的请求通过 header 管道通过,例如:

User        -> ALB [nginx]            -> App Servers
IP: 1.2.3.4    IP: 172.31.1.1            IP: n/a
               Forwarded-For: 1.2.3.4    Real-IP: 1.2.3.4
                                         Forwarded-For: 1.2.3.4, 172.31.1.1

但是现在我们需要扩展弹性 LB 后面的 ALB,我们发现额外的代理层存在问题,例如:

User        -> ELB                    -> ALB [nginx]                        -> App Servers
IP: 1.2.3.4    IP: 172.31.1.2            IP: 172.31.1.1                        IP: n/a
               Forwarded-For: 1.2.3.4    Forwarded-For: 1.2.3.4, 172.31.1.2    Real-IP: 172.31.1.2
                                                                               Forwarded-For: 1.2.3.4, 172.31.1.2

但如您所见,目前只是将 X-Real-IP: 设置为 ELB 的 IP。

我们需要能够剥离受信任的代理并在 X-Real-IP header 中发送正确的用户 IP,并记录用户 IP 而不是 ELB IP。

GeoIP 模块具有 geoip_proxy 指令,这些指令定义了在确定“真实”IP 时要忽略的受信任代理,我想知道 nginx 中是否有类似的东西或其他一些方法来完成这个?

TIA

简短的回答是,没有一个简单的配置指令可以做到这一点,也没有 100% 可靠的方法来只信任某些代理。

让我们来构建一个例子。我的 IP 是 1.2.3.4,但我是恶意的,我想假装我是 5.6.7.8。我通过浏览器扩展注入我自己的 X-Forwarded-For: header 并且 nginx 框得到:

X-Forwarded-For: 5.6.7.8, 1.2.3.4, 172.31.1.1, 172.31.1.2

1。简单,但有缺陷

map $proxy_add_x_forwarded_for $x_real_ip {
  "~^([^,]+).*" ;
  default $remote_addr;
}

所有这一切只是剥离 X-Forwarded-For: header 的第一个 IP 地址,如果您不介意用户通过 [=73= 欺骗其他 IP 地址,这一切都很好】 注射。在上面的例子中,这个方法将 return 欺骗 5.6.7.8.

2。有点复杂,但大多数情况下可以接受

理想情况下,我们只想剥离两个受信任的代理并使用第一个不受信任的 IP。为此,我们将不得不对正则表达式进行一些创意:

map $proxy_add_x_forwarded_for $x_real_ip {
  "~(?:^|, )([^,]+), (?:10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.).*" ;
  default $remote_addr;
}

大约一半的正则表达式只是处理 172.16.0.0/12 网络没有在八位字节边界上干净地分割这一事实,但它确实起到了作用。对于上面的例子,它正确 returns 1.2.3.4.

但是,如果外部攻击者不知何故知道这个 kludgey 配置正在使用 并且 也知道可信网络是什么,他们可以设置以下 header 来绕过它:

X-Forwarded-For: 5.6.7.8, 172.16.0.1

最终在代理处为:

X-Forwarded-For: 5.6.7.8, 172.16.0.1, 1.2.3.4, 172.31.1.1, 172.31.1.2

由于正则表达式本质上是读取 left-to-right 和 return 将 IP 发送到第一个受信任 IP 的左侧,在这种特定情况下,它将 return 欺骗 5.6.7.8 知识产权。但是,这是一个极端情况,对于我的特定用途 YMMV 来说是可以接受的。

警告: 您可能会收到一条错误消息,提示“您应该增加 map_hash_bucket_size”,这意味着您需要增加该值以适应那个 bigass 正则表达式。但是,the docs on that 有点繁琐,并且谈论“对齐”很重要,所以如果您没有在其他地方设置该值,我建议将消息​​中引用的值加倍。在我的例子中,我将它从 64 翻倍到 128。

3。一个真正合适的、防弹的解决方案

恕我直言,这需要实际解析 header 并应用真正的逻辑,因此需要将其修补到 nginx 中或写入模块中。基本上移植 GeoIP 模块用于 geoip_proxygeoip_proxy_recursive.

的相同逻辑

或者,您可以制作您的应用程序 proxy-aware 并在那里实现逻辑。如果您知道如何正确处理 IP 和子网,那应该是小菜一碟。不幸的是,在这种情况下我没有可用的选项。


谢谢: 如果不是 IRC 上的 membear 提醒我正则表达式捕获组在 map{} 块内有效,我可能仍在旋转我的轮子.

不要脸的外挂:我原来写过a blog post这个,才想起来在这里问过。它大部分是相同的,但也有那个大正则表达式的更详细的细分。