自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

漫談:Http網(wǎng)絡(luò)協(xié)議中的X-Forwarded-For

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
我一直認(rèn)為,對(duì)于從事 Web 前端開(kāi)發(fā)的同學(xué)來(lái)說(shuō),HTTP 協(xié)議以及其他常見(jiàn)的網(wǎng)絡(luò)知識(shí)屬于必備項(xiàng)。

我一直認(rèn)為,對(duì)于從事 Web 前端開(kāi)發(fā)的同學(xué)來(lái)說(shuō),HTTP 協(xié)議以及其他常見(jiàn)的網(wǎng)絡(luò)知識(shí)屬于必備項(xiàng)。一方面,前端很多工作如 Web 性能優(yōu)化,大部分規(guī)則都跟 HTTP、HTTPS、SPDY 和 TCP 等協(xié)議的特點(diǎn)直接對(duì)應(yīng),如果不從協(xié)議本身出發(fā)而是一味地照辦教條,很可能適得其反。另一方面,隨著 Node 的發(fā)展壯大,越來(lái)越多的前端同學(xué)開(kāi)始寫服務(wù)端程序,甚至是框架(ThinkJS 就是這樣由前端工程師開(kāi)發(fā),并有著眾多前端工程師用戶的 Node 框架),掌握必要的網(wǎng)絡(luò)知識(shí),對(duì)于服務(wù)端程序安全、部署、運(yùn)維等工作來(lái)說(shuō)至關(guān)重要。

背景

通過(guò)名字就知道,X-Forwarded-For 是一個(gè)擴(kuò)展頭。HTTP/1.1(RFC 2616)協(xié)議并沒(méi)有對(duì)它的定義,它最開(kāi)始是由 Squid 這個(gè)緩存代理軟件引入,用來(lái)表示 HTTP 請(qǐng)求端真實(shí) IP,現(xiàn)在已經(jīng)成為事實(shí)上的標(biāo)準(zhǔn),被各大 HTTP 代理、負(fù)載均衡等轉(zhuǎn)發(fā)服務(wù)廣泛使用,并被寫入 RFC 7239(Forwarded HTTP Extension)標(biāo)準(zhǔn)之中。

X-Forwarded-For 請(qǐng)求頭格式非常簡(jiǎn)單,就這樣:

X-Forwarded-For: client, proxy1, proxy2

可以看到,XFF 的內(nèi)容由「英文逗號(hào) + 空格」隔開(kāi)的多個(gè)部分組成,最開(kāi)始的是離服務(wù)端最遠(yuǎn)的設(shè)備 IP,然后是每一級(jí)代理設(shè)備的 IP。

如果一個(gè) HTTP 請(qǐng)求到達(dá)服務(wù)器之前,經(jīng)過(guò)了三個(gè)代理 Proxy1、Proxy2、Proxy3,IP 分別為 IP1、IP2、IP3,用戶真實(shí) IP 為 IP0,那么按照 XFF 標(biāo)準(zhǔn),服務(wù)端最終會(huì)收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直連服務(wù)器,它會(huì)給 XFF 追加 IP2,表示它是在幫 Proxy2 轉(zhuǎn)發(fā)請(qǐng)求。列表中并沒(méi)有 IP3,IP3 可以通過(guò)服務(wù)端的 Remote Address 字段獲得。我們知道 HTTP 連接基于 TCP 連接,HTTP 協(xié)議中沒(méi)有 IP 的概念,Remote Address 來(lái)自 TCP 連接,表示與服務(wù)端建立 TCP 連接的設(shè)備 IP,在這個(gè)例子里就是 IP3。

Remote Address 無(wú)法偽造,因?yàn)榻?TCP 連接需要三次握手,如果偽造了源 IP,無(wú)法建立 TCP 連接,更不會(huì)有后面的 HTTP 請(qǐng)求。不同語(yǔ)言獲取 Remote Address 的方式不一樣,例如 php 是 $_SERVER["REMOTE_ADDR"],Node 是 req.connection.remoteAddress,但原理都一樣。

問(wèn)題

有了上面的背景知識(shí),開(kāi)始說(shuō)問(wèn)題。我用 Node 寫了一個(gè)最簡(jiǎn)單的 Web Server 用于測(cè)試。HTTP 協(xié)議跟語(yǔ)言無(wú)關(guān),這里用 Node 只是為了方便演示,換成任何其他語(yǔ)言都可以得到相同結(jié)論。另外本文用 Nginx 也是一樣的道理,如果有興趣,換成 Apache 或其他 Web Server 也一樣。

下面這段代碼會(huì)監(jiān)聽(tīng) 9009 端口,并在收到 HTTP 請(qǐng)求后,輸出一些信息:

var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('remoteAddress: ' + req.connection.remoteAddress + '\n');
res.write('x-forwarded-for: ' + req.headers['x-forwarded-for'] + '\n');
res.write('x-real-ip: ' + req.headers['x-real-ip'] + '\n');
res.end();
}).listen(9009, '0.0.0.0');

這段代碼除了前面介紹過(guò)的 Remote Address 和 X-Forwarded-For,還有一個(gè) X-Real-Ip,這又是一個(gè)自定義頭。X-Real-Ip 通常被 HTTP 代理用來(lái)表示與它產(chǎn)生 TCP 連接的設(shè)備 IP,這個(gè)設(shè)備可能是其他代理,也可能是真正的請(qǐng)求端。需要注意的是,X-Real-Ip 目前并不屬于任何標(biāo)準(zhǔn),代理和 Web 應(yīng)用之間可以約定用任何自定義頭來(lái)傳遞這個(gè)信息。

現(xiàn)在可以用域名 + 端口號(hào)直接訪問(wèn)這個(gè) Node 服務(wù),再配一個(gè) Nginx 反向代理:

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:9009/;
proxy_redirect off;
}

我的 Nginx 監(jiān)聽(tīng) 80 端口,所以不帶端口就可以訪問(wèn) Nginx 轉(zhuǎn)發(fā)過(guò)的服務(wù)。

測(cè)試直接訪問(wèn) Node 服務(wù):

curl http://t1.imququ.com:9009/
remoteAddress: 114.248.238.236
x-forwarded-for: undefined
x-real-ip: undefined

由于我的電腦直接連接了 Node 服務(wù),Remote Address 就是我的 IP。同時(shí)我并未指定額外的自定義頭,所以后兩個(gè)字段都是 undefined。

再來(lái)訪問(wèn) Nginx 轉(zhuǎn)發(fā)過(guò)的服務(wù):

curl http://t1.imququ.com/
remoteAddress: 127.0.0.1
x-forwarded-for: 114.248.238.236
x-real-ip: 114.248.238.236

這一次,我的電腦是通過(guò) Nginx 訪問(wèn) Node 服務(wù),得到的 Remote Address 實(shí)際上是 Nginx 的本地 IP。而前面 Nginx 配置中的這兩行起作用了,為請(qǐng)求額外增加了兩個(gè)自定義頭:

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

實(shí)際上,在生產(chǎn)環(huán)境中部署 Web 應(yīng)用,一般都采用上面第二種方式,好處多多,具體是哪些不是本文重點(diǎn)不寫了。這就引入一個(gè)隱患:很多 Web 應(yīng)用為了獲取用戶真正的 IP,從 HTTP 請(qǐng)求頭中獲取 IP。

HTTP 請(qǐng)求頭可以隨意構(gòu)造,我們通過(guò) curl 的 -H 參數(shù)構(gòu)造 X-Forwarded-Fox 和 X-Real-Ip,再來(lái)測(cè)試一把。

直接訪問(wèn) Node 服務(wù):

curl http://t1.imququ.com:9009/ -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-Ip: 2.2.2.2'
remoteAddress: 114.248.238.236
x-forwarded-for: 1.1.1.1
x-real-ip: 2.2.2.2

對(duì)于 Web 應(yīng)用來(lái)說(shuō),X-Forwarded-Fox 和 X-Real-Ip 就是兩個(gè)普通的請(qǐng)求頭,自然就不做任何處理原樣輸出了。這說(shuō)明,對(duì)于直連部署方式,除了從 TCP 連接中得到的 Remote Address 之外,請(qǐng)求頭中攜帶的 IP 信息都不能信。

訪問(wèn) Nginx 轉(zhuǎn)發(fā)過(guò)的服務(wù):

curl http://t1.imququ.com/ -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-Ip: 2.2.2.2'
remoteAddress: 127.0.0.1
x-forwarded-for: 1.1.1.1, 114.248.238.236
x-real-ip: 114.248.238.236

這一次,Nginx 會(huì)在 X-Forwarded-For 后追加我的 IP;并用我的 IP 覆蓋 X-Real-Ip 請(qǐng)求頭。這說(shuō)明,有了 Nginx 的加工,X-Forwarded-For ***一節(jié)以及 X-Real-Ip 整個(gè)內(nèi)容無(wú)法構(gòu)造,可以用于獲取用戶 IP。

用戶 IP 往往被使用在跟 Web 安全有關(guān)的場(chǎng)景上,例如檢查用戶登錄地區(qū),基于 IP 做訪問(wèn)頻率控制等等。這種場(chǎng)景下,確保 IP 無(wú)法構(gòu)造更重要。經(jīng)過(guò)前面的測(cè)試和分析,對(duì)于直接面向用戶部署的 Web 應(yīng)用,必須使用從 TCP 連接中得到的 Remote Address;對(duì)于部署了 Nginx 這樣反向代理的 Web 應(yīng)用,在正確配置了 Set Header 行為后,可以使用 Nginx 傳過(guò)來(lái)的 X-Real-Ip 或 X-Forwarded-Ip ***一節(jié)(實(shí)際上它們一定等價(jià))。

那么,Web 應(yīng)用自身如何判斷請(qǐng)求是直接過(guò)來(lái),還是由可控的代理轉(zhuǎn)發(fā)來(lái)的呢?在代理轉(zhuǎn)發(fā)時(shí)增加額外的請(qǐng)求頭是一個(gè)辦法,但是不怎么保險(xiǎn),因?yàn)檎?qǐng)求頭太容易構(gòu)造了。如果一定要這么用,這個(gè)自定義頭要夠長(zhǎng)夠罕見(jiàn),還要保管好不能泄露出去。

判斷 Remote Address 是不是本地 IP 也是一種辦法,不過(guò)也不完善,因?yàn)樵?Nginx 所處服務(wù)器上訪問(wèn),無(wú)論直連還是走 Nginx 代理,Remote Address 都是 127.0.0.1。這個(gè)問(wèn)題還好通??梢院雎?,更麻煩的是,反向代理服務(wù)器和實(shí)際的 Web 應(yīng)用不一定部署在同一臺(tái)服務(wù)器上。所以更合理的做法是收集所有代理服務(wù)器 IP 列表,Web 應(yīng)用拿到 Remote Address 后逐一比對(duì)來(lái)判斷是以何種方式訪問(wèn)。

通常,為了簡(jiǎn)化邏輯,生產(chǎn)環(huán)境會(huì)封掉通過(guò)帶端口直接訪問(wèn) Web 應(yīng)用的形式,只允許通過(guò) Nginx 來(lái)訪問(wèn)。那是不是這樣就沒(méi)問(wèn)題了呢?也不見(jiàn)得。

首先,如果用戶真的是通過(guò)代理訪問(wèn) Nginx,X-Forwarded-For ***一節(jié)以及 X-Real-Ip 得到的是代理的 IP,安全相關(guān)的場(chǎng)景只能用這個(gè),但有些場(chǎng)景如根據(jù) IP 顯示所在地天氣,就需要盡可能獲得用戶真實(shí) IP,這時(shí)候 X-Forwarded-For 中***個(gè) IP 就可以排上用場(chǎng)了。這時(shí)候需要注意一個(gè)問(wèn)題,還是拿之前的例子做測(cè)試:

curl http://t1.imququ.com/ -H 'X-Forwarded-For: unknown, <>"1.1.1.1'
remoteAddress: 127.0.0.1
x-forwarded-for: unknown, <>"1.1.1.1, 114.248.238.236
x-real-ip: 114.248.238.236

X-Forwarded-For ***一節(jié)是 Nginx 追加上去的,但之前部分都來(lái)自于 Nginx 收到的請(qǐng)求頭,這部分用戶輸入內(nèi)容完全不可信。使用時(shí)需要格外小心,符合 IP 格式才能使用,不然容易引發(fā) SQL 注入或 XSS 等安全漏洞。

結(jié)論

直接對(duì)外提供服務(wù)的 Web 應(yīng)用,在進(jìn)行與安全有關(guān)的操作時(shí),只能通過(guò) Remote Address 獲取 IP,不能相信任何請(qǐng)求頭;

使用 Nginx 等 Web Server 進(jìn)行反向代理的 Web 應(yīng)用,在配置正確的前提下,要用 X-Forwarded-For ***一節(jié) 或 X-Real-Ip 來(lái)獲取 IP(因?yàn)?Remote Address 得到的是 Nginx 所在服務(wù)器的內(nèi)網(wǎng) IP);同時(shí)還應(yīng)該禁止 Web 應(yīng)用直接對(duì)外提供服務(wù);

在與安全無(wú)關(guān)的場(chǎng)景,例如通過(guò) IP 顯示所在地天氣,可以從 X-Forwarded-For 靠前的位置獲取 IP,但是需要校驗(yàn) IP 格式合法性;

PS:網(wǎng)上有些文章建議這樣配置 Nginx,其實(shí)并不合理:

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

這樣配置之后,安全性確實(shí)提高了,但是也導(dǎo)致請(qǐng)求到達(dá) Nginx 之前的所有代理信息都被抹掉,無(wú)法為真正使用代理的用戶提供更好的服務(wù)。還是應(yīng)該弄明白這中間的原理,具體場(chǎng)景具體分析。

責(zé)任編輯:何妍 來(lái)源: Jerry Qu的小站
相關(guān)推薦

2015-09-16 13:11:52

Http網(wǎng)絡(luò)協(xié)議Proxy-Conne

2015-09-16 09:50:35

HTTP 網(wǎng)絡(luò)協(xié)議響應(yīng)頭

2015-09-15 13:48:01

網(wǎng)絡(luò)協(xié)議HTTP Client

2010-07-06 16:19:02

協(xié)議封裝

2011-08-24 10:31:01

網(wǎng)絡(luò)協(xié)議SLIPPPP

2021-01-11 05:40:18

HTTPHTTP 協(xié)議網(wǎng)絡(luò)技術(shù)

2011-08-24 09:46:33

NetBIOS協(xié)議NetBEUI協(xié)議

2010-06-21 15:06:45

AMF協(xié)議

2011-08-24 09:36:02

IPX協(xié)議SPX協(xié)議IPX

2015-09-14 15:31:40

HTTP網(wǎng)絡(luò)協(xié)議

2011-08-24 09:21:19

網(wǎng)絡(luò)協(xié)議IPv4IPv6

2011-08-24 10:41:04

網(wǎng)絡(luò)協(xié)議DNSARP協(xié)議

2009-07-23 16:20:48

HTTP協(xié)議ASP.NET

2011-08-24 09:56:13

網(wǎng)絡(luò)協(xié)議BOOTP協(xié)議TFTP協(xié)議

2011-08-23 14:10:09

網(wǎng)絡(luò)協(xié)議

2010-06-21 17:55:24

RIP協(xié)議

2010-07-08 13:23:14

SAN路由協(xié)議

2010-07-08 14:25:12

HART協(xié)議

2015-10-19 09:52:11

2011-08-24 10:18:56

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)