HTTP X-Forwarded-For是什麼?
X-Forwarded-For 請求頭格式非常簡單,就這樣:
X-Forwarded-For: client, proxy1, proxy2
可以看到,XFF 的內容由「英文逗號 + 空格」隔開的多個部分組成,最開始的是離服務端最遠的裝置 IP,然後是每一級代理裝置的 IP。
如果一個 HTTP 請求到達伺服器之前,經過了三個代理 Proxy1、Proxy2、Proxy3,IP 分別為 IP1、IP2、IP3,使用者真實 IP 為 IP0,那麼按照 XFF 標準,服務端最終會收到以下資訊:
X-Forwarded-For: IP0, IP1, IP2
Proxy3 直連伺服器,它會給 XFF 追加 IP2,表示它是在幫 Proxy2 轉發請求。列表中並沒有 IP3,IP3 可以在服務端透過 Remote Address 欄位獲得。我們知道 HTTP 連線基於 TCP 連線,HTTP 協議中沒有 IP 的概念,Remote Address 來自 TCP 連線,表示與服務端建立 TCP 連線的裝置 IP,在這個例子裡就是 IP3。
Remote Address 無法偽造,因為建立 TCP 連線需要三次握手,如果偽造了源 IP,無法建立 TCP 連線,更不會有後面的 HTTP 請求。不同語言獲取 Remote Address 的方式不一樣,例如 php 是 $_SERVER["REMOTE_ADDR"],Node.js 是 req.connection.remoteAddress,但原理都一樣。
有了上面的背景知識,開始說問題。我用 Node.js 寫了一個最簡單的 Web Server 用於測試。HTTP 協議跟語言無關,這裡用 Node.js 只是為了方便演示,換成任何其他語言都可以得到相同結論。另外本文用 Nginx 也是一樣的道理,如果有興趣,換成 Apache 或其他 Web Server 也一樣。
下面這段程式碼會監聽 9009 埠,並在收到 HTTP 請求後,輸出一些資訊:
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');
這段程式碼除了前面介紹過的 Remote Address 和 X-Forwarded-For,還有一個 X-Real-IP,這又是一個自定義頭部欄位。X-Real-IP 通常被 HTTP 代理用來表示與它產生 TCP 連線的裝置 IP,這個裝置可能是其他代理,也可能是真正的請求端。需要注意的是,X-Real-IP 目前並不屬於任何標準,代理和 Web 應用之間可以約定用任何自定義頭來傳遞這個資訊。
現在可以用域名 + 埠號直接訪問這個 Node.js 服務,再配一個 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 proxy_redirect off; }
我的 Nginx 監聽 80 埠,所以不帶埠就可以訪問 Nginx 轉發過的服務。
測試直接訪問 Node 服務:
curl remoteAddress: 114.248.238.236 x-forwarded-for: undefined x-real-ip: undefined
由於我的電腦直接連線了 Node.js 服務,Remote Address 就是我的 IP。同時我並未指定額外的自定義頭,所以後兩個欄位都是 undefined。
再來訪問 Nginx 轉發過的服務:
curl remoteAddress: 127.0.0.1 x-forwarded-for: 114.248.238.236 x-real-ip: 114.248.238.236
這一次,我的電腦是透過 Nginx 訪問 Node.js 服務,得到的 Remote Address 實際上是 Nginx 的本地 IP。而前面 Nginx 配置中的這兩行起作用了,為請求額外增加了兩個自定義頭:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
實際上,在生產環境中部署 Web 應用,一般都採用上面第二種方式,有很多好處。但這就引入一個隱患:很多 Web 應用為了獲取使用者真正的 IP,從 HTTP 請求頭中獲取 IP。
HTTP 請求頭可以隨意構造,我們透過 curl 的 -H 引數構造 X-Forwarded-For 和 X-Real-IP,再來測試一把。
直接訪問 Node.js 服務:
curl -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
對於 Web 應用來說,X-Forwarded-For 和 X-Real-IP 就是兩個普通的請求頭,自然就不做任何處理原樣輸出了。這說明,對於直連部署方式,除了從 TCP 連線中得到的 Remote Address 之外,請求頭中攜帶的 IP 資訊都不能信。
訪問 Nginx 轉發過的服務:
curl -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 會在 X-Forwarded-For 後追加我的 IP;並用我的 IP 覆蓋 X-Real-IP 請求頭。這說明,有了 Nginx 的加工,X-Forwarded-For 最後一節以及 X-Real-IP 整個內容無法構造,可以用於獲取使用者 IP。
使用者 IP 往往會被使用與跟 Web 安全有關的場景上,例如檢查使用者登入地區,基於 IP 做訪問頻率控制等等。這種場景下,確保 IP 無法構造更重要。經過前面的測試和分析,對於直接面向使用者部署的 Web 應用,必須使用從 TCP 連線中得到的 Remote Address;對於部署了 Nginx 這樣反向代理的 Web 應用,在正確配置了 Set Header 行為後,可以使用 Nginx 傳過來的 X-Real-IP 或 X-Forwarded-For 最後一節(實際上它們一定等價)。
那麼,Web 應用自身如何判斷請求是直接過來,還是由可控的代理轉發來的呢?在代理轉發時增加額外的請求頭是一個辦法,但是不怎麼保險,因為請求頭太容易構造了。如果一定要這麼用,這個自定義頭要夠長夠罕見,還要保管好不能洩露出去。
判斷 Remote Address 是不是本地 IP 也是一種辦法,不過也不完善,因為在 Nginx 所處伺服器上訪問,無論直連還是走 Nginx 代理,Remote Address 都是 127.0.0.1。這個問題還好通常可以忽略,更麻煩的是,反向代理伺服器和實際的 Web 應用不一定部署在同一臺伺服器上。所以更合理的做法是收集所有代理伺服器 IP 列表,Web 應用拿到 Remote Address 後逐一比對來判斷是以何種方式訪問。
通常,為了簡化邏輯,生產環境會封掉透過帶埠直接訪問 Web 應用的形式,只允許透過 Nginx 來訪問。那是不是這樣就沒問題了呢?也不見得。
首先,如果使用者真的是透過代理訪問 Nginx,X-Forwarded-For 最後一節以及 X-Real-IP 得到的是代理的 IP,安全相關的場景只能用這個,但有些場景如根據 IP 顯示所在地天氣,就需要儘可能獲得使用者真實 IP,這時候 X-Forwarded-For 中第一個 IP 就可以排上用場了。這時候需要注意一個問題,還是拿之前的例子做測試:
curl -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 最後一節是 Nginx 追加上去的,但之前部分都來自於 Nginx 收到的請求頭,這部分使用者輸入內容完全不可信。使用時需要格外小心,符合 IP 格式才能使用,不然容易引發 SQL 注入或 XSS 等安全漏洞。
- 直接對外提供服務的 Web 應用,在進行與安全有關的操作時,只能透過 Remote Address 獲取 IP,不能相信任何請求頭;
- 使用 Nginx 等 Web Server 進行反向代理的 Web 應用,在配置正確的前提下,要用 X-Forwarded-For 最後一節 或 X-Real-IP 來獲取 IP(因為 Remote Address 得到的是 Nginx 所在伺服器的內網 IP);同時還應該禁止 Web 應用直接對外提供服務;
- 在與安全無關的場景,例如透過 IP 顯示所在地天氣,可以從 X-Forwarded-For 靠前的位置獲取 IP,但是需要校驗 IP 格式合法性;
PS:網上有些文章建議這樣配置 Nginx,其實並不合理:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;
這樣配置之後,安全性確實提高了,但是也導致請求到達 Nginx 之前的所有代理資訊都被抹掉,無法為真正使用代理的使用者提供更好的服務。還是應該弄明白這中間的原理,具體場景具體分析。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2766975/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- HTTP協議是什麼?HTTP協議
- 什麼是HTTP標頭注入?HTTP
- HTTP 請求頭中的 X-Forwarded-ForHTTPForward
- HTTP協議是什麼?HTTP怎樣工作?-VeCloudHTTP協議Cloud
- 什麼是HTTP? HTTP 和 HTTPS 的區別?HTTP
- 什麼是http代理伺服器,有什麼作用?HTTP伺服器
- HTTP和HTTPS是什麼 二者區別是什麼HTTP
- HTTP狀態程式碼是什麼?HTTP
- HTTP速度慢是什麼原因?HTTP
- 什麼是HTTP快取機制?HTTP快取
- 什麼是http代理,什麼是socks5代理?兩者有什麼不同?HTTP
- 什麼是 WebSocket,它與 HTTP 有何不同?WebHTTP
- 什麼是HTTP 304狀態程式碼?HTTP
- 【Python小知識】什麼是HTTP和HTTPS?有什麼不同?PythonHTTP
- 什麼是正、反向海外HTTP代理?兩者各有什麼用處?HTTP
- 極光HTTP之什麼是網路協議HTTP協議
- 什麼是海外HTTP代理白名單?如何設定?HTTP
- 什麼是HTTP 307臨時重定向?HTTP
- http狀態碼是什麼,有什麼用,在哪裡檢視,分別代表什麼意思?HTTP
- 漫話:如何給女朋友解釋什麼是HTTPHTTP
- 使用免費http代理IP的缺點是什麼HTTP
- 什麼是HTTP代理501未實現錯誤?HTTP
- 什麼是cookie,什麼是sessionCookieSession
- 什麼是DNS,什麼是HostsDNS
- 什麼是模式? 什麼是框架?模式框架
- 這是什麼這是什麼
- http協議中,“get”和“post”的區別是什麼HTTP協議
- SSL證書是什麼?HTTP和HTTPS的區別HTTP
- 什麼是http500內部伺服器錯誤?HTTP伺服器
- 什麼是WebAuthn、FIDO 是什麼?Web
- ITIL是什麼意思?ITIL是什麼?
- 什麼是跨域,什麼是同源跨域
- 海外HTTP代理中全域性代理和區域性代理是什麼?有什麼區別?HTTP
- 什麼是HTTPS證書?HTTP與HTTPS的區別HTTP
- 用海外HTTP代理爬取海外資料的原理是什麼?HTTP
- http代理出現404錯誤是什麼原因?如何修復?HTTP
- 什麼是.NET平臺、什麼是c#、什麼是ASP.NET。C#ASP.NET
- ftp是什麼,ftp是什麼東西?FTP