X-Forward-For 看破紅塵,代理 IP 無所遁形

在開始瞭解 X-Forward-For 以前,咱們先來假設一個場景。你是一名爬蟲工程師,如今要爬取目標網站 xxx.com 上面的內容。在編碼的時候,你發現單位時間內請求頻率太高時會被限制,猜想應該是目標網站針對 IP 地址作了限制。如今你有兩種選擇:html

  • 單機,用 IP 代理解決頻率高被限制的問題。
  • 多機,用分佈式爬蟲解決單機 IP 被限制的問題。

因爲目標網站只須要爬取一次,單機+IP 代理這種組合的成本更低,因此你選擇了它。從 IP 代理服務商 xx 處購買了代理服務後,你進行了新一輪的測試,代碼片斷 Forwarded-Test 爲測試代碼。python

import requests

# 請求地址
targetUrl = "http://111.231.93.117/"

# 代理服務器
proxyHost = "220.185.128.170"
proxyPort = "9999"

proxyMeta = "http://%(host)s:%(port)s" % {

    "host": proxyHost,
    "port": proxyPort,
}


proxies = {

    "http": proxyMeta,
}
# 設定一個 Referer
header = {
    "Referer": "http://www.sfhfpc.com",
}
resp = requests.get(targetUrl, proxies=proxies, headers=header)
print(resp.status_code)
print(resp.text)
複製代碼

代碼片斷 Forwarded-Testnginx

代碼運行後,你發現你仍然被限制!web

頓時感到頭大,因而在各大搜索引擎尋找相關資料,例如:算法

ip 代理無效瀏覽器

識別 ip 代理bash

ip 代理被發現服務器

你發現不少文章中都提到一個東西 X-Forward-For,你們都說它可以看破 IP 代理。併發

那麼問題來了:負載均衡

  • X-Forward-For 究竟是什麼呢?
  • 爲何 X-Forward-For 可以發現咱們使用了 IP 代理
  • 它怎麼能找到原始 IP 呢?
  • 有什麼方法能夠騙過 X-Forward-For 呢?

帶着這些問題,咱們就來研究一下 X-Forward-For。

X-Forward-For 是什麼

X-Forward-For 跟 Referer 和 User-Agent 同樣,都是 HTTP 中的頭域。HTTP/1.1 的 RFC 文檔編號爲 2616,在 2616 中並未說起 X-Forward-For,也就是說 HTTP/1.1 出現的時候 X-Forward-For 還沒出生。真正提出 X-Forward-For 的是2014 年的 RFC7239(詳見 www.rfc-editor.org/rfc/rfc7239… X-Forward-For 做爲HTTP 擴展出現。

RFC: 全稱 Request For Comments,是一系列以編號排定的文件。它收集了互聯網相關的協議信息,你能夠抽象地將 RFC2616 理解爲 HTTP/1.1 的協議規範。Websocket 協議規範的詳細解讀可參考《Python3 反爬蟲原理與繞過實戰》一書。

關於 X-Forward-For 的全部正確描述都寫在了 RFC7239 中,全部符合規範的 HTTP 也會遵照 RFC7239。固然,你也能夠選擇不遵照

不遵照: 實際上,RFC 只是一種規範、約定,做爲你們統一行徑的參考,並未強制實現。不少反爬蟲手段就是另闢蹊徑,採用了與 RFC 約定不一樣的策略,具體反爬蟲思路和案例可參考《Python3 反爬蟲原理與繞過實戰》一書。

RFC7239 很長,咱們沒必要逐一閱讀。實際上跟咱們相關的只有幾個部分,例如:

1.Abstract
7.5. Example Usage
複製代碼

Abstract 是本文章的摘要,它描述了 RFC7239 的做用:

This document defines an HTTP extension header field that allows proxy components to disclose information lost in the proxying process, for example, the originating IP address of a request or IP address of the proxy on the user-agent-facing interface. In a path of proxying components, this makes it possible to arrange it so that each subsequent component will have access to, for example, all IP addresses used in the chain of proxied HTTP requests.

This document also specifies guidelines for a proxy administrator to anonymize the origin of a request.

大致意思爲本文的定義(擴展)了一個 HTTP 頭域,這個字段容許代理組件披露原始 IP 地址。

從這裏咱們瞭解到 X-Forward-For 的正向用途是便於服務端識別原始 IP,並根據原始 IP 做出動態處理。例如服務端按照 IP 地址進行負載均衡時,若是可以看破 IP 代理,取得原始 IP 地址,那麼就可以做出有效的負載。不然有可能形成資源分配不均,致使假負載均衡的狀況出現。

Example Usage 給出了 X-Forward-For 的使用示例:

A request from a client with IP address 192.0.2.43 passes through a proxy with IP address 198.51.100.17, then through another proxy with IP address 203.0.113.60 before reaching an origin server. This could, for example, be an office client behind a corporate malware filter talking to a origin server through a reverse proxy.

o The HTTP request between the client and the first proxy has no "Forwarded" header field.

o The HTTP request between the first and second proxy has a "Forwarded: for=192.0.2.43" header field.

o The HTTP request between the second proxy and the origin server has a "Forwarded: for=192.0.2.43, for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com" header field.

假設原始 IP 爲192.0.2.43,它的請求使用了地址爲 198.51.100.17 的代理,在到達目標服務器 203.0.113.60 以前還使用了另一個代理(文章假設另一個代理爲 222.111.222.111)。

這種狀況下

  • 客戶端和第一個代理之間的 HTTP 請求中沒有 Forwarded 頭域。
  • 第一個代理和第二個代理之間的 HTTP 請求中有 Forwarded 頭域,頭域及值爲 Forwarded: for=192.0.2.43 。
  • 第二個代理和服務器之間的 HTTP 請求中有 Forwarded 頭域,頭域及值爲 Forwarded: for=192.0.2.43, for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com"

圖 forwarded-client-server 描述了上述情景。

圖 forwarded-client-server

因爲客戶端到代理 1 的請求沒有使用代理,因此值爲空或短橫線。到代理 2 時,中間通過了代理 1,因此值爲原始 IP。到服務端時,中間通過了代理 1 和代理2 ,因此值爲原始 IP 和代理 1 IP。

上面就是關於 RFC7239 中部份內容的解讀。看到這裏,想必你已有絲絲頭緒,接下來咱們再捋一捋。

IP 代理實驗

首先我在本身的測試服務器上安裝並啓動了 Nginx,它的默認日誌格式以下:

log_format  main  
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
複製代碼

即 access.log 文件中會記錄客戶端 IP 地址、客戶端時間、請求方式、響應狀態碼、響應正文大小、Referer、User-Agent 和代理清單。

提示:Nginx 中 $http_x_forwarded_for 對應的值這裏稱爲代理清單,它與 RFC7239 中的 Forwarded 含義相同。

當我使用計算機終端瀏覽器訪問測試服務器地址時,對應的日誌記錄以下:

180.137.156.168 - - [24/Nov/2019:12:41:19 +0800] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Safari/605.1.15" "-"
複製代碼

服務器記錄到的信息含義以下:

  • 客戶端 IP 爲 180.137.156.168
  • 客戶端時間爲 [24/Nov/2019:12:41:19 +0800]
  • 請求方式爲 GET / HTTP/1.1
  • 響應狀態碼爲 200
  • 響應正文大小爲 612
  • Referer 爲短橫線,即爲空
  • User-Agent 顯示瀏覽器品牌爲 Safari
  • 代理清單爲短橫線,即爲空。

因爲本次並未使用 IP 代理,那麼代理清單天然就是短橫線。接着咱們用 Python 代碼測試一下,代碼片斷 Python-Request 爲測試代碼。

import requests
resp = requests.get("http://111.231.93.117/")
print(resp.status_code)
複製代碼

代碼片斷 Python-Request

代碼運行結果爲 200,即目標服務器正確響應了本次請求。對應的日誌記錄以下:

180.137.156.168 - - [24/Nov/2019:12:49:41 +0800] "GET / HTTP/1.1" 200 612 "-" "python-requests/2.21.0" "-"
複製代碼

此次也沒有使用 IP 代理,因此代理清單依舊是短橫線。如今用代理 IP 測試一下,代碼片斷 Forwarded-Test 中使用了 IP 代理,咱們就用它進行測試便可。這裏的代理服務器 IP 地址爲 220.185.128.170,根據以前對 RFC7239 的瞭解,猜想本次請求對應的 Forwarded 記錄的會是原始 IP,而客戶端 IP 則是代理服務器的 IP。

代碼運行後,服務器記錄到對應的日誌信息以下:

220.185.128.170 - - [24/Nov/2019:12:52:58 +0800] "GET / HTTP/1.1" 200 612 "http://www.sfhfpc.com" "python-requests/2.21.0" "180.137.156.168"
複製代碼

果真,記錄中客戶端 IP 對應的是 220.185.128.170,即代理服務器的 IP。Forwarded 中記錄的 180.137.156.168 是 Python 程序所在的計算機 IP 地址,即原始 IP。

這與 RFC7239 的描述徹底相符,服務端能夠經過 Forwarded 找到原始 IP,甚至是使用過的代理服務器 IP。

調皮的 IP 代理商

剛纔咱們用的是普通 IP 代理,因爲它很容易被識別,達不到隱匿的目的,因此 IP 代理商又推出了高匿代理

高匿代理: 相對於普通 IP 代理而言,使用高匿代理後,原始 IP 會被隱藏得更好,服務端更難發現。

這裏我使用了 芝麻代理 服務商提供的免費高匿 IP,註冊後就能夠領取免費 IP,簡直就是開箱即用。

將代碼片斷 Forwarded-Test 中用於設置代理服務器 IP 和端口號的字段值改成高匿 IP 及對應的端口號便可,例如:

# 代理服務器
proxyHost = "58.218.92.132"  # "220.185.128.170"
proxyPort = "2390"  # "9999"
複製代碼

保存更改後運行代碼,對應的日誌記錄以下:

125.82.188.4 - - [24/Nov/2019:13:05:07 +0800] "GET / HTTP/1.1" 200 612 "http://www.sfhfpc.com" "python-requests/2.21.0" "-"
複製代碼

原始 IP 爲 125.82.188.4,代理清單爲短橫線。細心的你可能會有疑問,爲何填寫的代理 IP 是 58.218.92.132,而日誌中的卻不是呢?

這是代理服務商作了多一層的轉移,58.218.92.132 是給用戶的入口,代理商的服務端會將入口爲 58.218.92.132 的請求轉給地址爲 125.82.188.4。其中過程咱們不用深究,高匿代理和普通代理的原理會再開一篇文章進行討論。

日誌記錄說明高匿 IP 可以幫助咱們實現隱匿的目的。說到這裏不得不提一下,芝麻代理高匿 IP 的質量真的好,據說他們的 IP 還支持高併發調用,有需求的朋友不妨去試試。

機智的你和想固然的開發者

難道普通代理就必定會被 X-Forward-For 發現嗎?

辦法老是會有的,翻一下 www.sfhfpc.com 或者公衆號韋世東學算法和反爬蟲說不定靈感就來了!在解讀 RFC7239 - Example Usage 時,咱們瞭解到 X-Forward-For 會記錄原始 IP,在使用多層 IP 代理的狀況下記錄的是上層 IP。利用這個特色,是否是能夠僞造一下呢?

既然 X-Forward-For 和 Referer 同樣是頭域,那麼就說明它能夠被人爲改變。咱們只須要在請求時加上 X-Forward-For 請求頭和對應的值便可。代碼片斷 Python-Request-CustomHeader 實現了這樣的需求。

import requests

# 請求地址
targetUrl = "http://111.231.93.117/"

# 代理服務器
proxyHost = "220.185.128.170"
proxyPort = "9999"

proxyMeta = "http://%(host)s:%(port)s" % {

    "host": proxyHost,
    "port": proxyPort,
}


proxies = {
    "http": proxyMeta,
}
header = {
    "Referer": "http://www.sfhfpc.com",
    "X-Forwarded-For": "_",
}
resp = requests.get(targetUrl, proxies=proxies, headers=header)
print(resp.status_code)
print(resp.text)
複製代碼

代碼片斷 Python-Request-CustomHeader

代碼運行後,控制檯結果以下:

200
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
複製代碼

響應狀態碼是 200,而且返回了 Welcome to nginx 等字樣,這說明請求成功。對應的日誌記錄爲:

220.185.128.170 - - [24/Nov/2019:14:13:24 +0800] "GET / HTTP/1.1" 200 612 "http://www.sfhfpc.com" "python-requests/2.21.0" "_, 180.137.156.168"
複製代碼

記錄顯示,原始 IP 爲 220.185.128.170、代理清單爲 "_, 180.137.156.168"。實際上原始 IP 是 180.137.156.168,而代理服務器的 IP 是 220.185.128.170。代理清單中多出來的短橫線是咱們在代碼中加上的,這裏竟然也顯示了。這說明咱們只須要在請求時附帶上 X-Forward-For 頭域就能夠達到僞造的目的。

若是我想讓服務端認爲原始 IP 爲 112.113.115.116,那麼只須要將代碼片斷 Python-Request-CustomHeader 中 header 對象中 X-Forwarded-For 鍵對應的值設置爲 112.113.115.116 便可。

保存後運行代碼,對應的日誌記錄以下:

220.185.128.170 - - [24/Nov/2019:14:28:08 +0800] "GET / HTTP/1.1" 200 612 "http://www.sfhfpc.com" "python-requests/2.21.0" "112.113.115.116, 180.137.156.168"
複製代碼

根據 RFC7239 - Example Usage,開發者會認爲代理清單中的第一組 IP 地址是原始 IP,卻不知這是咱們特地爲他準備的。

小結

X-Forward-For 是 HTTP 協議擴展的一個頭域,它能夠識別出通過多層代理後的原始 IP。搗蛋的人向來不喜歡遵照約定和規範,來了個魚目混珠。更多關於 RFC 協議解讀和經過違反約定實現的反爬蟲措施可翻閱《Python3 反爬蟲原理與繞過實戰》一書。

提示:點擊連接「免費領 IP」可前往芝麻代理領取免費 IP。

版權聲明

做者:韋世東

來源:www.sfhfpc.com

著做權歸做者全部,非商業轉載請註明出處,禁止商業轉載。

相關文章
相關標籤/搜索