1. 程式人生 > >Nginx網站使用CDN之後禁止使用者真實IP訪問的方法

Nginx網站使用CDN之後禁止使用者真實IP訪問的方法

做過面向公網WEB的運維人員經常會遇見惡意掃描、拉取、注入等圖謀不軌的行為,對於直接對外的WEB伺服器,我們可以直接通過 iptables 、Nginx 的deny指令或是程式來ban掉這些惡意請求。

而對於套了一層 CDN 或代理的網站,這些方法可能就失效了。尤其是個人網站,可能就一臺VPS,然後套一個免費的CDN就行走在網際網路了。並不是每個CDN都能精準的攔截各種惡意請求的,更鬧心的是很多CDN還不支援使用者在CDN上新增BAN規則,比如騰訊雲CDN。

因此,就有了本文的折騰分享。

一、真假難辨

如何禁止訪問,我們先了解下常見的3種網站訪問模式:

  1. 使用者直接訪問對外服務的普通網站
    瀏覽器 –> DNS解析 –> WEB資料處理 –> 資料吐到瀏覽器渲染展示

  2. 使用者訪問使用了CDN的網站
    瀏覽器 –> DNS解析 –> CDN節點 –> WEB資料處理 –> 資料吐到瀏覽器渲染展示

  3. 使用者通過代理上網訪問了我們的網站
    瀏覽器 –> 代理上網 –> DNS解析 –> 上述2種模式均可能

對於第一種模式,我要禁止這個使用者的訪問很簡單,可以直接通過 iptables 或者 Nginx的deny指令來禁止均可:

iptabels:
iptables -I INPUT -s 使用者ip -j DROP
Nginx的deny指令:
語 法: deny address | CIDR | unix: | all;
預設值: —
配置段: http, server, location, limit_except
順 序:從上往下
Demo:
location / {
deny 使用者IP或IP段;
}

但對於後面2種模式就無能為力了,因為iptables 和 deny 都只能針對直連IP,而後面2種模式中,WEB伺服器直連IP是CDN節點或者代理伺服器,此時使用 iptable 或 deny 就只能把 CDN節點 或代理IP給封了,可能誤殺一大片正常使用者了,而真正的罪魁禍首輕輕鬆鬆換一個代理IP又能繼續請求了。

那我們可以通過什麼途徑去解決以上問題呢?

二、火眼金睛

如果長期關注張戈部落格的朋友,應該還記得之前轉載過一篇分享Nginx在CDN加速之後,獲取使用者真實IP做併發訪問限制的方法。說明Nginx還是可以實實在在的拿到使用者真實IP地址的,那麼事情就好辦了。

要拿到使用者真實IP,只要在Nginx的http模組內加入如下配置:

#獲取使用者真實IP,並賦值給變數$clientRealIP
map $http_x_forwarded_for  $clientRealIp {
        ""      $remote_addr;
        ~^(?P<firstAddr>[0-9\.]+),?.*$  $firstAddr;
}

那麼,$clientRealIP就是使用者真實IP了,其實就是匹配了 $http_x_forwarded_for 的第一個值,具體原理前文也簡單分享過:

其實,當一個 CDN 或者透明代理伺服器把使用者的請求轉到後面伺服器的時候,這個 CDN 伺服器會在 Http 的頭中加入一個記錄
X-Forwarded-For : 使用者IP, 代理伺服器IP
如果中間經歷了不止一個代理伺服器,這個記錄會是這樣
X-Forwarded-For : 使用者IP, 代理伺服器1-IP, 代理伺服器2-IP, 代理伺服器3-IP, ….
可以看到經過好多層代理之後, 使用者的真實IP 在第一個位置, 後面會跟一串中間代理伺服器的IP地址,從這裡取到使用者真實的IP地址,針對這個 IP 地址做限制就可以了。

而且程式碼中還配合使用了 $remote_addr,因此$clientRealIP 還能相容上文中第1種直接訪問模式,不像 $http_x_forwarded_for 在直接訪問模式中將會是空值!

所以,$clientRealIP 還能配置到 Nginx 日誌格式中,替代傳統的 $remote_addr 使用,推薦!

三、隔山打牛

既然已經拿到了真實IP,卻不能使用 iptables 和 deny 指令,是否無力感油然而生?

哈哈,在強大的 Nginx 面前只要想得到,你就做得到!通過對 $clientRealIP 這個變數的判斷,Nginx就能實現隔山打牛的目的,而且規則簡單易懂:

#如果真實IP為 121.42.0.18、121.42.0.19,那麼返回403
if ($clientRealIp ~* "121.42.0.18|121.42.0.19") {
        #如果你的nginx安裝了echo模組,還能如下輸出語言,狠狠的發洩你的不滿(但不相容返回403,試試200吧)!
        #add_header Content-Type text/plain;
        #echo "son of a bitch,you mother fucker,go fuck yourself!";
        return 403;
        break;
}

把這個儲存為 deny_ip.conf ,上傳到 Nginx 的 conf 資料夾,然後在要生效的網站 server 模組中引入這個配置檔案,並 Reload 過載 Nginx 即可生效:

#禁止某些使用者訪問
include deny_ip.conf;

如果再想新增其他要禁止的IP,只需要編輯這個檔案,插入要禁止的IP,使用分隔符 | 隔開即可,記得每次修改都需要 reload 過載 Nginx才能生效。

四、奇淫巧計

為了更方便的新增和刪除這些黑名單IP,昨晚熬夜寫了一個小指令碼,一鍵新增和刪除,懶人有福了!

#!/bin/bash
###################################################################
#  Deny Real IP for Nginx;  Author: Jager <[email protected]>        #
# For more information please visit https://zhangge.net/5096.html #
#-----------------------------------------------------------------#
#  Copyright ©2016 zhangge.net. All rights reserved.              #
###################################################################

NGINX_BIN=/usr/local/nginx/sbin/nginx
DENY_CONF=/usr/local/nginx/conf/deny_ip.conf

COLOR_RED=$(    echo -e "\e[31;49m" )
COLOR_GREEN=$(  echo -e "\e[32;49m" )
COLOR_RESET=$(  echo -e "\e[0m"     )

rep_info() { echo;echo -e "${COLOR_GREEN}$*${COLOR_RESET}";echo; }
rep_error(){ echo;echo -e "${COLOR_RED}$*${COLOR_RESET}";echo;exit 1; }

show_help()
{
printf "
###################################################################
#  Deny Real IP for Nginx;  Author: Jager <[email protected]>        #
# For more information please visit https://zhangge.net/5096.html #
#-----------------------------------------------------------------#
#  Copyright ©2016 zhangge.net. All rights reserved.              #
###################################################################

Usage: $0 [OPTIONS]

OPTIONS:
-h | --help   : Show help of this script
-a | --add    : Add a deny ip to nginx, for example: ./$0 -a 192.168.1.1
-c | --create : Create deny config file($DENY_CONF) for Nginx
-d | --del    : Delete a ip from deny list, for example: ./$0 -d 192.168.1.1
-s | --show   : Show current deny list

"
}

reload_nginx()
{
    $NGINX_BIN -t >/dev/null 2>&1 && \
    $NGINX_BIN -s reload && \
    return 0
}

show_list()
{
   awk -F '["){|]' '/if/ {for(i=2;i<=NF;i++) if ($i!="") printf $i"\n"}' $DENY_CONF 
}

pre_check()
{
    test -f $NGINX_BIN || rep_error "$NGINX_BIN not found,Plz check and edit."
    test -f $DENY_CONF || rep_error "$DENY_CONF not found,Plz check and edit." 
    MATCH_COUNT=$(show_list | grep -w $1 | wc -l)
    return $MATCH_COUNT
}

create_rule()
{
test -f $DENY_CONF && \
rep_error "$DENY_CONF already exist!."
cat >$DENY_CONF<<EOF
if (\$clientRealIp ~* "8.8.8.8") {
    #add_header Content-Type text/plain;
    #echo "son of a bitch,you mother fucker,go fuck yourself!"; 
    return 403;
    break;
}
EOF
test -f $DENY_CONF && \
rep_info "$DENY_CONF create success!" && \
cat $DENY_CONF && \
exit 0

rep_error "$DENY_CONF create failed!" && \
exit 1

}

add_ip()
{
    pre_check $1
    if [[ $? -eq 0 ]];then
        sed -i "s/\")/|$1&/g" $DENY_CONF && \
        reload_nginx && \
        rep_info "add $1 to deny_list success." || \
        rep_error "add $1 to deny_list failed."
    else
        rep_error "$1 has been in deny list!"
        exit
    fi
}

del_ip()
{
    pre_check $1
    if [[ $? -ne 0 ]];then
        sed -ie "s/\(|$1\|$1|\)//g" $DENY_CONF && \
        reload_nginx && \
        rep_info "del $1 from deny_list success." || \
        rep_error "del $1 from deny_list failed."
    else
        rep_error "$1 not found in deny list!"
        exit
    fi
}

case $1 in
    "-s"|"--show" )
        show_list
        exit
        ;;
    "-h"|"--help" )
        show_help
        exit
        ;;
    "-c"|"--create" )
        create_rule
    ;;
esac

while [ $2 ];do
    case $1 in
        "-a"|"--add" )
            add_ip $2;
            ;;
        "-d"|"--del" )
            del_ip $2
            ;;
        * )
            show_help
            ;; 
    esac
    exit
done
show_help

使用方法:

  1. 根據實際情況修改第9、10行 Nginx 二進位制檔案及其deny配置檔案路徑

  2. 然後將此指令碼儲存為 deny_ctrl.sh 上傳到伺服器任意目錄,比如放到 /root

  3. 給指令碼賦予可執行許可權:chmod +x deny_ctrl.sh 即可使用

  4. 使用引數:

    Usage: deny_ctrl.sh [OPTIONS]
    OPTIONS:
    -h | –help : 顯示幫助資訊
    -a | –add : 新增一個黑名單IP, 例如: ./deny_ctrl.sh -a 192.168.1.1
    -c | –create : 初始化建立一個禁止IP的配置檔案,需要自行include 到需要的網站server模組
    -d | –del : 刪除一個黑名單IP,例如: ./deny_ctrl.sh -d 192.168.1.1
    -s | –show : 顯示當前已拉黑IP清單

初次使用,先執行 ./deny_ctrl.sh -c 建立一下 Nginx 相關配置檔案:deny_ip.conf,預設內容如下:

if ($clientRealIp ~* "8.8.8.8") {
    #add_header Content-Type text/plain;
    #echo "son of a bitch,you mother fucker,go fuck yourself!"; 
    return 403;
    break;
}

8.8.8.8 是為了佔位,規避為空的情況,實際使用中也請注意,必須要有一個IP佔位,否則可能導致誤殺哦!

生成這個檔案之後,編輯網站對應的配置檔案,比如 zhangge.net.conf

在 server {}模組內部插入 include deny_ip.conf; (注意有英文分號)即可。

比如:

server
    {
        listen 80;   
        server_name zhangge.net;
        index index.html index.htm index.php default.html default.htm default.php;
        root  /home/wwwroot/zhangge.net;

        include agent_deny.conf;  #新增此行
        #其他略 ...

最後,使用nginx -s reload 過載nginx即可。

後面需要新增黑名單或刪除黑名單都可以使用 deny_ctrl.sh 指令碼來操作了!

最後,順便說明一下,本文分享的方法僅作為使用CDN網站遇到惡意IP的一種手工拉黑方案。而自動化限制的方案可以參考部落格之前的分享:

Nginx在CDN加速之後,獲取使用者真實IP做併發訪問限制的方法

好了,本文分享到此,希望對你有所幫助。