在Heroku Cedar上,我想获得客户端的IP。第一次尝试:
ENV['REMOTE_ADDR']
当然,这不起作用,因为所有请求都通过代理传递。所以替代方案是使用:
ENV['HTTP_X_FORWARDED_FOR']
但这不太安全,是吗?
如果它只包含一个值,我就拿这个。如果它包含多个值(以逗号分隔),我可以选择第一个值。
但是,如果有人操纵这个值怎么办?我无法信任ENV['HTTP_X_FORWARDED_FOR']
ENV['REMOTE_ADDR']
。并且我也没有可以使用的可信代理列表。
但必须有某种方式来始终可靠地获取客户端的IP地址。你知道吗?
在their docs中,Heroku描述X-Forwarded-For
是“连接到Heroku路由器的客户端的原始IP地址”。
这听起来好像Heroku可能会用原始远程IP覆盖X-Forwarded-For
。这样可以防止欺骗,对吧?有人可以验证这个吗?
答案 0 :(得分:33)
来自Heroku当时的安全总监雅各布:
路由器不会覆盖
X-Forwarded-For
,但它确实保证真实来源始终是列表中的 last 项目。
这意味着,如果您以正常方式访问Heroku应用,您只会在X-Forwarded-For
标题中看到您的IP地址:
$ curl http://httpbin.org/ip
{
"origin": "123.124.125.126",
}
如果您尝试欺骗IP,您的所谓来源 会被反映出来,但是 - 关键 - 您的真实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"
}
顺便说一句,这与what is described on Wikipedia相反。
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'];
}
}
答案 1 :(得分:24)
我在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
想象一下,这台机器的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
该请求将通过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
那么,哪个是客户端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。
答案 2 :(得分:3)
您永远不会真正信任来自客户的任何信息。这更像是一个问题,你信任谁,你如何验证它。即使Heroku可能会受到影响,如果他们的代码中有错误,或者他们会以某种方式被黑客攻击,那么它会提供错误的HTTP_X_FORWARDED_FOR
值。另一个选择是其他一些Heroku机器在内部连接到您的服务器并在伪造REMOTE_ADDR
和/或HTTP_X_FORWARDED_FOR
时完全绕过他们的代理。
这里最好的答案取决于你想要做什么。如果您尝试验证客户端,则客户端证书可能是更合适的解决方案。如果您只需要IP地理位置,那么信任输入可能就足够了。最糟糕的情况是,有人会伪造该位置并获取错误的内容......如果您有不同的用例,那么在这两个极端之间还有许多其他解决方案。