常見的坑有兩個:
一、獲取的是內網的ip地址。在nginx作為反向代理層的架構中,轉發請求到php,java等應用容器上。結果php獲取的是nginx代理伺服器的ip,表現為一個內網的地址。php獲取REMOTE_ADDR就是這樣一個情況(內網地址)。
二、獲取的是攻擊者偽造的ip地址。攻擊者可以隨便偽造一個頭部資訊,隨便填寫一個ip放到頭部發過來,php獲取到HTTP_CLIENT_IP就是這樣一個情況。偽造的ip,導致我們資料庫儲存是假的ip,無從真實去判斷攻擊者的來源。比如批量註冊帳號的註冊ip,登入的ip等。
為避免偽造,不要使用discuz原來的獲取ip函式,裡面的判斷優先順序會使得攻擊者容易偽造ip。
php程式碼:
function getIP() {
if (getenv("HTTP_X_FORWARDED_FOR")) { //這個提到最前面,作為優先順序,nginx代理會獲取到使用者真實ip,發在這個環境變數上,必須要nginx配置這個環境變數HTTP_X_FORWARDED_FOR $ip = getenv("HTTP_X_FORWARDED_FOR"); } else if (getenv("REMOTE_ADDR")) { //在nginx作為反向代理的架構中,使用REMOTE_ADDR拿到的將會是反向代理的的ip,即拿到是nginx伺服器的ip地址。往往表現是一個內網ip。 $ip = getenv("REMOTE_ADDR"); } else if ($_SERVER['REMOTE_ADDR']) { $ip = $_SERVER['REMOTE_ADDR']; } else if (getenv("HTTP_CLIENT_IP")) { //HTTP_CLIENT_IP攻擊者可以偽造一個這樣的頭部資訊,導致獲取的是攻擊者隨意設定的ip地址。 $ip = getenv("HTTP_CLIENT_IP"); } else { $ip = "unknown"; } return $ip; }
|
說明
在反向代理架構中,不能通過REMOTE_ADDR來獲取使用者的真實ip!
以前的理解方式有誤(更新一下)
一般的方式是這樣:
nginx>>(fastcgi方式)>>php引擎
nginx把REMOTE_ADDR傳遞給了php。代表的是當前與nginx通訊的客戶端ip,一般情況下(非反向代理),這個客戶就是使用者的瀏覽器,所以得到的使用者的ip。
假設做了反向代理架構,是下面這樣子的:
用
戶>>>>>>>>>>>伺服器
a>>>>>>>>>>>>>>>>>>nginx>>>>>>>>>>>>>>
(fastcgi方式通訊)>>>>>>>>>>>>php引擎
使用者訪問一個域名,實際上是通過伺服器a做了轉發,轉發到nginx去(反向代理架構經常會這樣部署)
於是,當前與nginx通訊的客戶端,就是伺服器a的地址, REMOTE_ADDR就是a伺服器的地址了。
如何判斷:nginx的上一層是不是還有一層。像上面的情況就還有一層。所以得到的將會是伺服器a的地址。
總結:在nginx作為反向代理的架構中,php的REMOTE_ADDR(其他語言也是類似的名稱)拿到的將會是nginx代理的ip地址。拿不到使用者的真實ip,拿到是nginx反向代理伺服器地址。
REMOTE_ADDR本意就是遠端的地址,nginx是代理層,轉發請求到php,php獲取到的遠端地址實際上是nginx反向代理伺服器ip,這是符合協議規則的。
但是,可以讓nginx幫助我們拿到使用者的真實ip,寫到一個環境變數中,然後轉發給我們,只要按照某個約定的名稱即可,比如約定名稱為HTTP_X_FORWARD_FOR(也可以約定其他名稱,關鍵看nginx中配置,可以全公司考慮統一)。
nginx配置類似於這樣:
fastcgi_param HTTP_X_FORWARD_FOR $remote_addr;
上一句的目的是,將HTTP_X_FORWARD_FOR的值設定為$remote_addr的值。也就是將使用者真實的ip(或使用者使用代理的ip)放到HTTP_X_FORWARD_FOR中去。
$remote_addr是nginx的內建變數,這個變數它得到是使用者真實的ip地址(使用者使用了代理,則就是代理的ip地址)。
於是在php端通過getenv("HTTP_X_FORWARDED_FOR")就可以獲取到nginx傳遞過來的值,是使用者真實的ip地址。