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

42
在任何Heroku堆栈上,我想获取客户端的IP。我的第一次尝试可能是:

在任何Heroku堆栈上,我都想要获取客户端的IP地址。我的第一个尝试可能是:

request.headers['REMOTE_ADDR']

当然,这种方法行不通,因为所有请求都经过代理。所以选择使用以下替代方案:

request.headers['X-Forwarded-For']

但这不太安全,是吗?

如果它只包含一个值,我会使用这个值。如果它包含多个值(用逗号分隔),我可以使用第一个值。

但是,如果有人篡改了这个值呢?我不能像使用request.headers['REMOTE_ADDR']那样信任request.headers['X-Forwarded-For']。也没有可用的受信任代理列表。

但肯定有某种方法可以始终可靠地获取客户端的IP地址。你知道有吗?

Heroku文档中,Heroku描述X-Forwarded-For为“连接到Heroku路由器的客户端的来源IP地址”。

这听起来好像Heroku可能正在用原始远程IP覆盖X-Forwarded-For。这会防止伪造,对吗?有人可以验证一下吗?


1
对不起,这是什么语言?如果不是Python,我该如何在Python中实现这个功能? - aravk33
Heroku文档(与您提到的相同)明确指出出于安全原因不要信任X-Forwarded-For头。自2013年以来必须进行更新。 - staples
原始问题在ENV['REMOTE_ADDR']ENV['HTTP_X_FORWARDED_FOR']之间进行比较;修改问题以指定这些是请求头(request.headers)。 - Jason FB
4个回答

59

来自当时担任Heroku安全主管的Jacob:

路由器不会覆盖X-Forwarded-For,但它确保真实来源始终为列表中的最后一项

这意味着,如果你以正常方式访问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"
}

顺便提一下,这正好与维基百科上描述的相反

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
https://devcenter.heroku.com/articles/http-routing并没有像你的文章那样详细地介绍,但对于想要检查行为是否发生变化的人可能会很有趣。这里的好处是Rails RemoteIp中间件应该可以正常工作,返回最后可信的IP,这将是客户端或他们经过的任何不受信任的代理,而无需配置任何Heroku代理地址。 - nruth
1
我认为你提供的例子并不是维基百科现在所说的相反情况。客户端使用头部中的8.8.8.8连接到Heroku,实际上伪造了一个代理,转发来自8.8.8.8的请求,然后Heroku将接收到的连接的IP附加到列表中。或者按照他们的列表示例,正常请求将是[client],伪造请求将是[fakeclient,client]。没有插入代理IP,因为Heroku只使用1个代理。 - nruth
4
两种说法都是正确的。 “真实来源” 应该是最左边的IP,但它可能会被伪造。只有最右边的IP由Heroku保证是连接到Heroku的IP,但那可能是代理服务器。更多信息请参考Joel Watson的详细答案。 - wuputah
1
@wuputah,我认为你说得很对。我正在测试这个,我看到的是最左边的地址是我的IP,而Heroku添加了第二个地址...但那不是我的IP...所以针对原始问题,似乎你实际上需要从数组中获取第一个元素(例如用于GeoIP目的),而不是最后一个元素。 - Stijn de Witt
@DavidHariri 谢谢,已修复! - caw
显示剩余5条评论

52

我在Heroku的支持部门工作,已经与我们的路由工程师讨论了一段时间。我想发布一些额外的信息,以澄清这里正在发生的事情。

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

为了说明这一点,假设有人发起请求,并任意添加了3个额外的IP到X-Forwarded-For头部:

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呢?从Google的角度来看,无法确定。实际上,客户端IP是9.9.9.9。不过,最后一个列出的IP地址是2.2.2.2,而第一个地址是12.12.12.12。谷歌只知道2.2.2.2 IP地址是正确的,因为那是实际连接到他们服务的IP地址,但是他们无法从可用数据中确定是否这是请求的初始客户端。同样的,当这个头部只有一个IP地址时,那就是直接连接到我们服务的IP地址,所以我们知道它是可靠的。

从实际角度考虑,这个IP地址很可能在大多数情况下是可靠的(因为大多数人不会费心伪造IP地址)。不幸的是,无法防止这种类型的欺骗,当请求到达Heroku路由器时,我们无法确定X-Forwarded-For链中的IP地址是否被篡改。

除了所有可靠性问题之外,这些IP链应始终从左至右读取。客户端IP 应该 总是最左边的IP地址。


3
我一直跟到最后一段。那是不是应该说IP链应该从右到左读取,客户端IP应该是最右边的IP? - Aaron
2
客户端IP地址是最左边的IP。约定是当遇到附加的IP时将其追加到列表中,因此在大多数情况下,列表中的第一个IP应该是实际想要的客户端IP。列表中的其他IP地址是中间人。只需记住,在请求链的任何点上可以任意修改列表中的IP地址,因此不能保证IP地址是正确的。 - Joel Watson
在上面的例子中,即使它是 9.9.9.9,由于他们有意向标头添加了额外的IP地址,因此 12.12.12.12 是否被视为客户端的IP呢? - Aaron
2
经过测试,我可以验证客户端IP是第一个而不是最后一个 - 很高兴在部署之前进行了测试 =p - Roi

4
您永远不能真正信任来自客户端的任何信息。问题更多地是你信任谁以及如何验证它。即使Heroku可能会受到代码中的漏洞或某种方式的黑客攻击而提供错误的HTTP_X_FORWARDED_FOR值,甚至有其他的Heroku机器在内部连接到您的服务器并绕过其代理,同时伪造REMOTE_ADDR和/或HTTP_X_FORWARDED_FOR
这里的最佳答案取决于您所尝试的内容。如果您想验证客户端,则客户端证书可能是更合适的解决方案。如果您只需要IP地址进行地理位置定位,则相信输入可能已经足够了。最糟糕的情况是,有人会伪造位置并获得错误的内容...如果您有其他用例,则在这两个极端之间有许多其他解决方案。

8
谢谢!我只是在问,因为Heroku知道客户端的真实IP地址。这仅是Heroku代理收到请求的IP地址。如果那是一个代理,我们就不需要关心了。当客户端在代理后面时,这也是您使用“REMOTE_ADDR”时通常会遇到的正常情况。如果Heroku被黑客攻击或其代码存在漏洞,这是个例外,我们也不应该关注。因为我们无法对此做任何事情。但是,如果Heroku刚刚覆盖了“HTTP_X_FORWARDED_FOR”,我们将始终知道客户端的真实IP地址,这可以通过“REMOTE_ADDR”获得。对吗? - caw
是的,那是正确的。更多相关信息可以在Wikipedia上找到。 - kichik

1

如果我发送一个带有多个 X-Forwarded-For 头信息的请求:curl -s -v -H "X-Forwarded-For: 1.1.1.1, 1.1.1.2, 1.1.1.3" -H "X-Forwarded-For: 2.2.2.2" -H "X-Forwarded-For: 3.3.3.3" https://foo.herokuapp.com/

该请求将包含多个IP地址,用逗号分隔。这些IP地址可以告诉服务器请求来自哪里。
> X-Forwarded-For: 1.1.1.1, 1.1.1.2, 1.1.1.3
> X-Forwarded-For: 2.2.2.2
> X-Forwarded-For: 3.3.3.3

传递给应用程序的X-Forwarded-For头部将会是:

1.1.1.1, 1.1.1.2, 1.1.1.3, <real client IP>, 2.2.2.2, 3.3.3.3

所以从列表中选择最后一个并不能支撑起来 :/

我尝试了这个,结果也一样。现在不确定该怎么办了... - ark
我试过了,结果一样。现在不知道该怎么办了.... - undefined
Heroku 的支持团队建议我使用 Cloudflare。 - dentarg
Heroku和Cloudflare??这是我从未考虑过的事情...... - ark
Heroku和Cloudflare??这可是我从来没有考虑过的东西…… - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接