Nginx 獲取真實 IP 方案


問題根源:

基於七層的負載均衡系統,獲取IP的原理都是通過XRI和XFF進行處理,從中選出“正常情況下”的源頭IP,然而這兩個Header都是普通的HTTP頭,任何代理程序都可以輕易修改偽造它們,使得獲取IP的邏輯失效。

解決依據:

TCP協議需要建立真實的網絡鏈路,因此其信息可以認為是真實可靠難以偽造的。根據阿里SLB文檔中獲取真實IP的方法( https://help.aliyun.com/document_detail/slb/best-practice/get-real-ipaddress.html)得知,如果采用四層負載均衡,則SLB的后端系統可直接通過 remote address 獲取到IP,如果采用七層負載均衡,后續系統需配合 http_realip_module 使用。

設置步驟:

1、在阿里SLB中將負載均衡模式設置為四層,並且打開獲取真實IP的選項(默認打開);
2、在SLB后端的轉發 nginx 中使用 $remote_addr 參數填寫 XRI 和 XFF;
3、在應用中即可可通過 XRI 或 XFF 獲取真實 IP;

試驗環境:

客戶端:Chrome + Postman
本地模擬:本地 nginx + 測試環境 nginx + 測試環境 app
四層負載均衡:阿里 SLB + 測試環境 nginx + 測試環境 app
七層負載均衡:阿里 SLB + release nginx + release app

試驗步驟:

1、在測試環境 customer 應用中部署 test.jsp,內容為獲取 request 信息;
2、雙 nginx 模擬
2.1、在測試環境 nginx 中設置 XFF 規則如下:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2.2、在本地 nginx 中設置負載均衡到測試環境 nginx 地址,XFF 規則同上;
2.3、使用 Postman 直接發送請求:
可以發現XFF的第一個IP是正確的地址(由於本試驗中本地 nginx 是系統的一部分,所以127.0.0.1算正確IP);
2.4、在請求中加入偽造的 XFF:
發現此時獲取到的 XFF 已經被污染。
3、四層SLB測試
3.1、將測試環境 nginx 中的 XFF 規則設置為:
proxy_set_header X-Forwarded-For $remote_addr;
3.2、正常發送請求
發現獲取了正確的 IP;
3.3、偽造 XFF 請求:
發現依然可以獲取到正確的 IP;
4、七層SLB + http_realip_module 模塊測試
4.1、將 release 環境 nginx 相關配置修改為(其中100.97.0.0/16為SLB所在網段):
set_real_ip_from 100.97.0.0/16;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
4.2、偽造XFF請求:
可以發現:HTTP頭中多了一個 remoteip 的字段,其值始終為正確的客戶 IP;同時,XFF 字段保留了所有代理鏈路信息(包括偽造的部分)。

試驗總結:

1、無論哪種方案其實核心原理大同小異,都是利用四層TCP的連接信息獲取實際IP參數,區別只在於:獲取到的這個IP在何時、用何種方式傳遞到后面系統,以及后面系統如何接收該參數。
2、雙 nginx 只是為了在可控環境模擬 HTTP IP 欺騙的原理;
3、四層 SLB 負載均衡方案思路是在整個系統入口(即SLB的四層處)覆蓋掉原始的 XRI 或 XFF,在系統的后面部分便可充分信任這些參數;
4、七層 SLB 負載均衡方案思路是把系統入口處拿到的真實IP放在獨立的 remoteip 參數中(當然如果需要也可在后續nginx中用該參數覆寫 XRI 或 XFF,和上一個方案相同);

參考資料:

阿里雲SLB獲取真實IP的配置方法:
http_realip_module官方文檔:
http_realip_module實現代碼:
阿里SLB原理:
SLB官負載均衡配置:
 
 

測試用頁面代碼:

復制代碼
<%@page contentType="text/html" pageEncoding="GBK"%>
<%@page import="java.util.*"%><!--使用Enumeration導入此包-->
<html>
<head>
    <title>接收全部請求參數的名稱及對應的內容</title>
</head>
<body>
<%
    Enumeration enu=request.getHeaderNames();//取得全部頭信息
    while(enu.hasMoreElements()){//以此取出頭信息
        String headerName=(String)enu.nextElement();
        String headerValue=request.getHeader(headerName);//取出頭信息內容
%>
        <h5><%=headerName%><font color="red">--></font>
        <font color="blue"><%=headerValue%></font></h5>
<%
    }
%>
</body>
</html>
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM