1. 程式人生 > >獲取http請求者ip(nodejs-express)

獲取http請求者ip(nodejs-express)

今天想找一個安全生成sessionid的方案,隨後想到應該檢測請求方ip,這時才發現這裡是一片知識的盲區。隨即整理學習。


本文測試伺服器均為linux環境
網站訪問並不是簡單地從使用者的瀏覽器直達伺服器,中間可能部署有CDN、WAF、高防。例如,採用這樣的架構:使用者 > CDN/WAF/高防 > 源站伺服器。那麼,在經過多層加速後,伺服器如何獲取發起請求的真實客戶端 IP 呢?

1 原理

http請求會在headers中攜帶相關資訊。
其中主要涉及到兩個引數:

  • Remote Address
  • X-Forwarded-For

1.1 Remote Address

我們知道 HTTP 連線基於 TCP 連線,HTTP 協議中沒有 IP的概念,但Remote Address是來自 TCP 連線,表示與服務端建立 TCP 連線的裝置 IP。
Remote Address 無法偽造,因為建立 TCP 連線需要三次握手,如果偽造了源 IP,無法建立 TCP 連線,更不會有後面的 HTTP 請求。不同語言獲取 Remote Address 的方式不一樣,Node.js 是 req.connection.remoteAddress。
如果在沒有代理的情況下,我們得到Remote Address即為請求者真實ip地址。

1.2 存在代理 X-Forwarded-For

1.2.1 實現邏輯

這個問題最關鍵的就是http header 中欄位:X-Forwarded-For
一個透明的代理伺服器在把使用者的請求轉到下一環節的伺服器時,會在HTTP的頭中加入一條X-Forwarded-For記錄,用來記錄使用者的真實IP,其形式為X-Forwarded-For:使用者IP。如果中間經歷了多個代理伺服器,那麼X-Forwarded-For會表現為以下形式:X-Forwarded-For:使用者IP, 代理伺服器1-IP, 代理伺服器2-IP, 代理伺服器3-IP, ……。
常見的應用伺服器可以使用X-Forwarded-For的方式獲取訪問者真實IP。如果使用nginx,新增X-Forwarded-For支援。

1.2.2 X-Forwarded-For

X-Forwarded-For 基礎概念:
X-Forwarded-For 是一個 HTTP 擴充套件頭部,X-Forwarded-For 請求頭格式非常簡單,就這樣:
X-Forwarded-For: client, proxy1, proxy2
可以看到,XFF 的內容由「英文逗號 + 空格」隔開的多個部分組成,最開始的是離服務端最遠的裝置 IP,然後是每一級代理裝置的 IP。所以在有代理的情況下,請求真實ip即為X-Forwarded-For最左側ip。

1.3 確保請求者ip是真實的而不是偽造的

因為X-Forwarded-For 可以自行在headers中偽造,後續的代理伺服器只會在X-Forwarded-For 原有內容中新增。所以我們不能完全相信X-Forwarded-For中的內容。裡面資料甚至可能是SQL注入攻擊語句。

1.3.1 對於安全性要求較高的行為

1 直接對外提供服務的 Web 應用,在進行與安全有關的操作時,只能通過 Remote Address 獲取 IP,不能相信任何請求頭(因為x-forwarded-for可直接偽造)
2 使用 Nginx 等 Web Server 進行反向代理的 Web 應用,在配置正確的前提下,要用 X-Forwarded-For 最後一節來獲取 IP(因為 Remote Address 得到的是 Nginx 所在伺服器的內網 IP),同時還應該禁止 Web 應用直接對外提供服務。
以上兩種方案均無法解決如何安全的得到經過多重代理的原請求者ip。
第一種情況,只能直連到伺服器的請求ip。
第二種情況,只能得到server之間代理前最後一級ip。

1.3.2 對於安全要求較低的行為

比如像天氣之列的行為,通過ip獲取來傳遞資訊的介面。被攻擊耶沒什麼用,則我們可以取x-forwarded-for中左側內容ip作為請求者ip。但我們仍然需要對該內容進行檢查防範注入攻擊。

2 獲取請求者ip方法

2.1 express框架實現(nodejs)

該值可以傳入幾種引數
1 boolean
配置為true,存在代理且x-forwarded-for headers中最左側條目為req.ip的值,即請求者ip
配置為false,req.ip的值即為req.connection.remoteAddress中的值,即為沒有代理,與伺服器建立tcp的ip即為請求者ip

app.set('trust proxy', true);

2 配置為受信任地址(ip 地址)
上面我們說到了,我們可能會自行架設多級代理。配置該引數後,引數中地址就會x-forwarded-for地址確認中被排除。

app.set('trust proxy', 'loopback, 123.123.123.123') // specify a subnet and an address

指定後,IP地址或子網將從地址確定過程中排除,並且最靠近應用程式伺服器的不受信任的IP地址將被確定為客戶端的IP地址。即自行搭建的代理伺服器上一個地址即為req.ip地址。(即1.3.1 中第二種情況,訪問待見最外層代理伺服器的ip,被認為為客戶端ip)

3 數字

app.set('trust proxy', 1) // specify a subnet and an address

信任n來自前置代理伺服器的第二跳作為客戶端。即我們搭建了幾層代理即可設定為幾。

所以以上引數:
配置為true,則適用於1.3.2
配置後false,受信任地址,代理伺服器層級 則適用1.3.1 這種對安全要求較高的情況

2.2 sample code(獲取x-forwarded-for最左側條目作為請求者ip)

在express 中app.js加入一下語句

app.set('trust proxy', true);// 設定以後,req.ips是ip陣列;如果未經過代理,則為[]. 若不設定,則req.ips恆為[]
app.get('/ip', function(req, res){
  console.log("x-forwarded-for    = " + req.header('x-forwarded-for'));// 各階段ip的CSV, 最左側的是原始ip
  console.log("ips                          = " + JSON.stringify(req.ips));// 相當於(req.header('x-forwarded-for') || '').split(',')
  console.log("remote Address     = " + req.connection.remoteAddress);// 未發生代理時,請求的ip
  console.log("ip                            = " + req.ip);// 同req.connection.remoteAddress, 但是格式要好一些
  res.send('Hello World');
});

2.3 nginx配置傳遞 x-forwarded-for引數

Nginx 預設是不會傳遞x-forwarded-for引數的,需要server塊中進行配置才會傳遞該引數。
新增一下引數:

 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

如圖:
在這裡插入圖片描述

3 測試

本文代理均指nginx

3.1 server 與 client均為本地同一伺服器

server所在ip:172.28.28.17
列印結果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:172.28.28.17
ip                 = ::ffff:172.28.28.17

可以看到沒有代理則x-forwarded-for為undefined, remote Address 為本機ip。

3.2 server與client均為本地同一伺服器且架設nginx反向代理,client通過代理訪問

列印結果:

x-forwarded-for    = 172.28.28.17
ips                = ["172.28.28.17"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.28.17

可以看到通過代理訪問則x-forwarded-for有值, remote Address 為代理伺服器地址。

3.3 server與client在同一區域網且架設nginx反向代理,但不配置x-forwarded-for

列印結果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:127.0.0.1
ip                 = ::ffff:127.0.0.1

x-forwarded-for為undefined,且remote Address 為代理伺服器地址,且最終結果也為代理伺服器地址

3.4 server與client在同一區域網且架設nginx反向代理

clientip:172.28.52.15
列印結果:

x-forwarded-for    = 172.28.52.15
ips                = ["172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.52.15

可以看到通過代理訪問則x-forwarded-for值為172.28.52.15, remote Address 為127.0.01 為nginx代理伺服器地址。

3.5 server與client在同一區域網且架設nginx反向代理,client偽造x-forwarded-for(重要)

在postman中手動新增x-forwarded-for值且為172.28.28.111
列印結果:

x-forwarded-for    = 172.28.28.111, 172.28.52.15
ips                = ["172.28.28.111","172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = 172.28.28.111

可以看到手動新增172.28.28.111出現在了x-forwarded-for,且最終結果ip為偽造的ip。這就是使用x-forwarded-for 的安全風險。

高階風險SQL注入風險
在postman中手動新增x-forwarded-for值且為mysql語句:select * from table1

x-forwarded-for    = select * from table1, 172.28.52.15
ips                = ["select * from table1","172.28.52.15"]
remote Address     = ::ffff:127.0.0.1
ip                 = select * from table1

可以看到手動新增select * from table1 出現在了x-forwarded-for中最左側。且最終結果也為select * from table1 。所以要求安全的情況下是不能直接使用最左側項的。

3.6 server在公網,client訪問

使用手機4G 查的手機公網ip:139.207.57.70
列印結果

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:139.207.57.70
ip                 = ::ffff:139.207.57.70

可以看到沒有代理則x-forwarded-for為undefined,且remote Address直接為手機公網ip。這種情況即為沒有代理直接訪問。

3.7 server在公網,client使用海外代理訪問(科學上網真有趣)

使用手機4G 查的手機公網ip:139.207.57.70
科學上網伺服器ip:104.236.xx4.1xx
列印結果:

x-forwarded-for    = undefined
ips                = []
remote Address     = ::ffff:104.236.xx4.1xx
ip                 = ::ffff:104.236.xx4.1xx

可以看到沒有代理則x-forwarded-for為undefined,且remote Address直接為科學上網伺服器ip,科學上網大法好,把自己ip成功藏起來了。

3.8 server在公網且架設nginx反向代理,client訪問

使用手機4G 查的手機公網ip:139.207.57.70
列印結果:

x-forwarded-for    = 139.207.57.70
ips                = ["139.207.57.70"]
remote Address     = ::ffff:127.0.0.1
ip                 = 139.207.57.70

可以看到沒有代理則x-forwarded-for為undefined,且remote Address直接為代理伺服器ip。

3.9 server在公網且架設nginx反向代理,client且偽造x-forwarded-for

本地伺服器 101.204.240.1xx
列印結果

x-forwarded-for    = select * from table1, 101.204.240.1xx
ips                = ["select * from table1","101.204.240.1xx"]
remote Address     = ::ffff:127.0.0.1
ip                 = select * from table1

可以看到手動新增select * from table1 出現在了x-forwarded-for中最左側。且最終結果也為select * from table1 。所以要求安全的情況下是不能直接使用最左側項的。

4 如何在廣袤網際網路找到你

瞭解上訴方案之後,自然就會產生一個問題,如何在網際網路找到你?
基於上訴測試結果,如果你使用的網站經過的代理(CDN)均為透明代理,即代理都會傳遞http headers引數中的X-Forwarded-For。
意味著你ip是會隨著X-Forwarded-For 傳遞到網站伺服器上。所以網站伺服器後臺是會有相應訪問記錄的ip。
比如 你在某時某刻 在某個ip在該網站上留下了某條評論。

現在如果需要找到你,有以下幾步:

  1. 網站後臺上拿到ip和訪問時間。
  2. 找到該ip對應ISP(網路接入商 電信 聯通 找到該ip對應ISP(網路接入商 電信 聯通 移動),並要求通過提供該ip在該時段時與網站伺服器(網站ip)通訊的使用者賬戶資訊(NAT協議的轉發記錄可以找到具體使用者)
  3. 獲取到賬戶資訊後,就可以找到你家庭住址 GG(開網肯定要提供相關資訊)

5 參考連結