在Heroku上获取客户端的真实IP地址

caw*_*caw 31 http ip-address heroku

在Heroku Cedar上,我想获得客户端的IP.第一次尝试:

ENV['REMOTE_ADDR']
Run Code Online (Sandbox Code Playgroud)

当然,这不起作用,因为所有请求都通过代理传递.所以替代方案是使用:

ENV['HTTP_X_FORWARDED_FOR']
Run Code Online (Sandbox Code Playgroud)

但这不太安全,是吗?

如果它只包含一个值,我就拿这个.如果它包含多个值(以逗号分隔),我可以选择第一个值.

但是,如果有人操纵这个价值呢?我不能相信ENV['HTTP_X_FORWARDED_FOR'],我可以用ENV['REMOTE_ADDR'].并且我也没有可以使用的可信代理列表.

但总是必须有一些方法来可靠地获取客户端的IP地址.你知道吗?

他们的文档中,Heroku描述了X-Forwarded-For"连接到Heroku路由器的客户端的原始IP地址".

听起来好像Heroku可能会X-Forwarded-For用原始的远程IP 覆盖它.这样可以防止欺骗,对吧?有人可以验证吗?

caw*_*caw 40

来自Heroku当时的安全总监雅各布:

路由器不会覆盖X-Forwarded-For,但它确保真正的原点始终是列表中的最后一项.

这意味着,如果您以正常方式访问Heroku应用程序,您将只在X-Forwarded-For标题中看到您的IP地址:

$ curl http://httpbin.org/ip
{
  "origin": "123.124.125.126",
}
Run Code Online (Sandbox Code Playgroud)

如果你试图欺骗知识产权,你的所谓来源就会被反映出来,但是 - 关键 - 你的真实IP也是如此.显然,这就是我们所需要的,因此有一个清晰安全的解决方案可以在Heroku上获取客户端的IP地址:

$ curl -H"X-Forwarded-For: 8.8.8.8" http://httpbin.org/ip
{
  "origin": "8.8.8.8, 123.124.125.126"
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,这与维基百科上描述的情况正好相反.

PHP实现:

function getIpAddress() {
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ipAddresses = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        return trim(end($ipAddresses));
    }
    else {
        return $_SERVER['REMOTE_ADDR'];
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 两者都是正确的.'真正的起源'_应该是最左边的IP,但它可以被欺骗.只有_rightmost_ IP由Heroku保证,因为连接到Heroku的IP,但可能是代理.有关更多信息,请参阅Joel Watson的详细答案. (4认同)

Joe*_*son 27

我在Heroku的支持部门工作,并花了一些时间与我们的路由工程师讨论这个问题.我想发布一些额外的信息来澄清一些关于这里发生了什么的事情.

上面答案中提供的示例只是巧妙地显示了客户端IP,并且不能保证.它不是第一个的原因是因为原始请求声称它正在转发X-Forwarded-For标头中指定的IP .当Heroku路由器收到请求时,它只是X-Forwarded-For在注入请求之后附加了直接连接到列表的IP .我们的路由器始终将连接到我们平台前面的AWS ELB的IP添加为列表中的最后一个IP.这个IP可能是原始IP(并且在只有一个IP的情况下,几乎可以肯定),但是当有多个IP链接的瞬间,所有的赌注都关闭.惯例总是将链中的最新IP添加到列表的末尾(这就是我们所做的),但是在链的任何一点,链可以被改变并且可以插入不同的IP.因此,唯一可靠的IP(从我们平台的角度来看)是列表中的最后一个IP.

为了说明,假设有人发起请求并随意向X-Forwarded-For标头添加3个额外的IP:

curl -H "X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4" http://www.google.com
Run Code Online (Sandbox Code Playgroud)

想象一下,这台机器的IP是9.9.9.9并且它必须通过代理(例如,大学的校园代理).假设代理的IP为2.2.2.2.假设它没有被配置为剥离X-Forwarded-For标题(它可能不会),它只会将9.9.9.9 IP添加到列表的末尾并将请求传递给Google.此时,标题看起来像这样:

X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4,9.9.9.9
Run Code Online (Sandbox Code Playgroud)

然后该请求将通过Google的端点,该端点将附加大学代理的IP 2.2.2.2,因此标题最终将在Google的日志中显示如下:

X-Forwarded-For: 12.12.12.12,15.15.15.15,4.4.4.4,9.9.9.9,2.2.2.2
Run Code Online (Sandbox Code Playgroud)

那么,哪个是客户端IP?从谷歌的角度来看,这是不可能的.实际上,客户端IP是9.9.9.9.列出的最后一个IP是2.2.2.2,第一个是12.12.12.12.所有Google都知道2.2.2.2 IP肯定是正确的,因为那是实际连接到他们服务的IP - 但是他们不知道这是否是请求的初始客户端,而不是来自可用的数据.同样,当此标题中只有一个IP时 - 即直接连接到我们服务的IP,因此我们知道它是可靠的.

从实际的角度来看,这个IP在大多数情况下可能是可靠(因为大多数人都不会费心去欺骗他们的IP).不幸的是,不可能阻止这种欺骗,当请求到达Heroku路由器时,我们无法判断X-Forwarded-For链中的IP 是否已被篡改.

除了所有可靠性问题之外,应始终从左到右读取这些IP链.客户端IP 始终是最左侧的IP.

  • 我跟进到最后一段.那应该说IP链应该从右向左读取,客户端IP应该是最右边的IP吗? (2认同)
  • 客户端 IP 是最左边的 IP。约定是在遇到额外的 IP 时将它们附加到列表中,因此在大多数情况下,列表中的第一个 IP 应该是您想要的实际客户端 IP。列表中的其他 IP 是中间件。请记住,列表中的 IP 可以在请求链中的任何点任意修改,因此您不能_保证_IP 是正确的。 (2认同)
  • 经过测试,我可以验证客户端IP是否是第一个而不是最后一个-很高兴在部署= p之前进行了测试 (2认同)