(5-2) 代理 (proxy)、隧道 (tunnel) 與 閘道 (gateway):
正常情況下,HTTP 的請求、回應訊息,
將經歷 連接鍊 (chain of connection) 中的連線,
最終抵達 使用者代理 (User Agent, UA) 或 源伺服器 (Origin Server, O)。
連接鍊 不一定 為 線性 (linear) 的!
每個 HTTP 參與者,皆可能同時進行多個通訊。
而獲取 請求鍊狀態 與 User Agent 的身份,則是一重要課題。
目錄
Via
Via 有 通過、路 的意思,
每當訊息「通過」一個 代理 (proxy) 或 閘道 (gateway),
得添加基本資訊 至 Via 表頭欄位,
已告知 Client 或 Server,訊息 (message) 是經由哪些 中介 (intermediary) 傳遞:
格式為:
Via: 上游協定 代理名稱(or IP 位址) [註解資訊]
為求簡潔,若 協定 為 HTTP 則僅顯示 協定版本,
以常見的 (正/反向) 代理伺服器 — — squid 舉例:
Via: 1.1 JasonProxy (squid/3.5.15)
Via 表頭欄位 可用於 追蹤訊息轉發、避免請求迴圈,
或讓下游端點 能根據 上游的協定版本 提供支援的功能。
多值欄位
多個 中介 添加欄位值時,以「,」為分隔符號:
Via: 1.0 ricky, 1.1 ethel, 1.1 fred, 1.0 lucy
承上例,若有多個相同協定版本的條目,
且有安全隱私的考量 (e.g., 防火牆後的 主機 IP 位址、port),允許以 假名 (pseudonym) 合併:
Via: 1.0 ricky, 1.1 mertz, 1.0 lucy
可靠性
由上節可知,via 表頭欄位 僅能做為參考,
並非可靠的中介資訊,
例如,許多 代理 (proxy) 可透過設定,不添加 via 表頭欄位:
X-Forwarded-For
明顯地,上述的 Via 欄位,
對於訊息發起者 — — 使用者代理 (User Agent) 的追蹤,幫助不大。
於是 Squid 開發人員,引進了表頭欄位 — — X-Forwarded-For (XFF)。
X-Forwarded-For 是 「對…轉發」之意,
可說是最普遍的 客製表頭欄位 之一,其中 "X-" 是指 Custom (客製的)。
與 Via 類似,每通過一個 代理 (proxy) 或 閘道 (gateway),
便在 XFF 欄位中新增 來源的 IP 位址 (by TCP/IP 協定)。
(Via 欄位實務上不常置放 IP 資訊)
例如:
GET / HTTP/1.1
Host: echo.paw.cloud
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
X-Forwarded-For: 8.8.8.8, 8.8.6.6, 1.2.3.4
代表 User Agent IP 是 8.8.8.8 !
維基百科:
如果沒有 X-Forwarded-For 或者另外一種相似的技術,
所有通過代理伺服器的連線只會顯示代理伺服器的IP位址,而非連線發起的原始IP位址,
這樣的代理伺服器實際上充當了匿名服務提供者的角色,
如果連線的原始IP位址不可得,惡意存取的檢測與預防的難度將大大增加。
取得 IP 位址
有了 X-Forwarded-For 欄位
便可透過著名的 程式片段 取得 User Agent IP !
(以 PHP 為例)
// Function to get the client IP address
function get_client_ip()
{
$ip = getenv('HTTP_CLIENT_IP') ?:
getenv('HTTP_X_FORWARDED_FOR') ?:
getenv('HTTP_X_FORWARDED') ?:
getenv('HTTP_FORWARDED_FOR') ?:
getenv('HTTP_FORWARDED') ?:
getenv('REMOTE_ADDR');
return $ip;
}
原理是先看查看 傳統的 CLIENT_IP 表頭是否有值,
沒有的話則查看 X-Forwarded-For 欄位,
還是沒有的話,就直接以 TCP/IP 層,取得連線 IP 位址 (REMOTE_ADDR)。
可靠性
如此獲取 IP 位址的方式:
一點都不可靠 !
一點都不可靠 !
一點都不可靠 !
鑑於資安的重要原則: 不信任。
要知道偽造表頭是相當容易的,
且偽造者不限於 User Agent、也可能是 中介 (intermediary)。
較可信的反而只有 TCP/IP 的連線 IP (REMOTE_ADDR),
然而,這只能取得最近的連線者資訊 (可能是 代理 proxy)…。
普遍的做法,是以 過濾 (filter) 的方式,排除不信任的部分 (e.g., 127.0.0.1、部分區域 ip),
並將相關欄位 全部儲存,必要之時逐層對 代理 做追蹤。
維基百科:
鑑於偽造這一欄位非常容易,應該謹慎使用X-Forwarded-For欄位。
正常情況下 XFF 中最後一個 IP 位址,是最後一個代理伺服器的 IP 位址,
這通常是一個比較可靠的資訊來源。
DNT
說到 追蹤 (Track),就得提到 DNT (Do Not Track) 表頭欄位,
顧名思義「不要追蹤」😂,使用方式非常簡單,設值 0 或 1,
以設置成 1 居多 (啟用 不要追蹤)。
用於告知廣告網與網站和應用程式,您拒絕受到行為廣告等機制的追蹤,
遵守該規則的網站,便不會追蹤使用者的訊息,以提供更精卻的廣告。
類似的還有 TK 表頭欄位 (草案) :
Tk: N
詳情請見 Firefox DNT,與 MDN TK。
另外,也可透過著名的 Disconnect.me 提供的封鎖清單,
防止惡意網站的追蹤、視覺化消除頁面追蹤廣告,以大幅增加讀取速度 !
Photo by Disconnect.
這些技術也時常應用於 瀏覽器的「隱私視窗/模式」:
Referer
Referer,是史上最智障的表頭欄位 😂。
因為正確的拼法,應該是 Referrer (參照/參考),
這源於早期 HTTP 規範的拼寫錯誤,礙於相容性,將錯就錯了…😐。
無關鍵字
Referer 表頭欄位 簡單說就是:
你從哪個網站來的
例如,從 Google 搜尋進入本站:
GET / HTTP/1.1
Host: echo.paw.cloud
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
referer: https://www.google.com.tw/
dnt: 1
需注意的是:
你無法以 Referer 欄位,應用於關鍵字優化。
大部分搜尋引擎,皆因安全性考量,取消了 Referer 中的關鍵字。
但 Referer 欄位 允許 Server 生成與其他資源的反向鏈接 (上一頁),
以進行簡單的分析、記錄、優化的快取等,或用於維護過期或錯誤的連結。
跨站請求偽造 (CSRF)
跨站請求偽造 (CSRF) 是一種惡意攻擊,
能在使用者 不知情的狀況下,送出請求訊息,
並誤導 Server 為「使用者本人」發出。 (詳見 維基)
例如,使用者 Sandra 👱🏻♀️ 登入 example.com 後,
可透過該站的連結,購買衣服、或任意商品,
使用者代理 (e.g.,瀏覽器) 可能自動 送出授權憑證 (e.g., cookie、token),確保使用者已登入 !
<a href="/buy?product=clothes">購買</a>
而攻擊者可在 另一個頁面 ,放置該連結 :
<img src="https://example.com/buy?product=clothes">
Sandra 👱🏻♀️ 一旦進入該頁面,img 標籤將送出請求,
且瀏覽器也 貼心地送出了憑證,Server 認證無誤後便下單,
Sandra 就在 不知不覺中 買了好多衣服 😲,
更可怕的是,Sandra 取貨時還不確定是否自己買的 😂。
跨站請求偽造 (CSRF) 根本無需竊取帳密,
就能送出惡意的「偽造」請求 😱。
某些 Server 則利用 Referer 欄位,做為限制 跨站請求偽造 (CSRF) 的方法,
或 拒絕來自其他站點 (深度連結)。
因為,若是 CSRF 攻擊發送的請求,Referer 欄位 會包含 惡意站點的網址,
而非原站 URI (範例的 example.com) ,此時 Server 便能識別出惡意的請求。
CSRF Token
然而,並非所有請求都包含 Referer 欄位,
例如,使用者直接在網址列中輸入網址、選取書籤…,
此時 使用者代理 (User Agent) 不會發送 Referer 欄位、或發送欄位值 "about:blank"。
並且,如 X-Forwarded-For 節所述: 偽造表頭相當容易。
因此,較佳的做法是使用 CSRF Token (以攻擊者未知的資訊,進行額外驗證),
例如 PHP 框架 — Laravel 的 CSRF Token :
<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
或 M$ ASP.NET MVC 框架 的 Anti-Forgery Tokens :
@using (Html.BeginForm())
{
// 產生隱藏的表單欄位 (防偽語彙基元),
// 表單送出時會驗證這個欄位。
@Html.AntiForgeryToken()
}
更多表單用法,詳見 (4-2) GET vs. POST。
TRACE
最後介紹的是 TRACE 請求方法 !
(Trace vs. Track 含義的不同,可參閱 此篇)
沿著 目標資源 的 路徑 執行 訊息 loop-back 測試。
Perform a message loop-back test along the path to the target resource.
顧名思義,TRACE 主要用於測試,是類似 回聲 (Echo) 的概念,
請求的 最終接收者 (注意此處),必須將收到的請求訊息以 "message/http" 的媒體類型,傳回至 User Agent。
請求訊息範例:
TRACE https://example.com/?id=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
Max-Forwards: 4
回應訊息範例:
HTTP/1.1 200 OK
Content-Type: message/http
Date: Wed, 14 Jun 2017 17:20:15 GMT
Server: nginx/1.11.8
TRACE /?id=1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
Max-Forwards: 1
Via: 1.0 ricky, 1.1 mertz, 1.0 lucy
哪泥 !? 為何回應酬載 與 請求訊息 不同 !?
因為存在 代理 (Proxy)。
從 請求訊息的 請求目標 為 絕對形式 (https://example.com/?id=1),
可合理懷疑 User Agent 配置了 代理伺服器 (proxy),
且收到擁有 3 個值的 Via 欄位,可猜測 連接鏈 至少 存在 3 個以上的中介 (可能合併)。
Max-Forwards (轉發上限)
其中,一有趣的表頭欄位: Max-Forwards,
從字面翻譯不難理解,是 (訊息) 最大的轉發次數,
這也是為何,範例中,發送請求時 值為 4,回來卻變成 1 了。
每個接收到 TRACE 或 OPTIONS 請求訊息的 中介 (intermediary),
必須再轉發訊息之前,檢查 並 更新 (減一) Max-Forwards 欄位。
如果接收到的值為零 (0),則中介不得轉發該請求,
並且,中介需做為 最終接收者 發送回應,
這也是為何上方是使用 「最終接收者」而非「源伺服器 (Origin Server)」。
Max-Forwards 主要用於 TRACE 與 OPTIONS 請求,
允許 Client 限制請求鏈的長度,這對測試無窮迴圈的 代理鏈 相當實用,
若使用於其他請求方法,則 可能 被 中介 忽略。
屬性 (property)
安全性: Yes
冪等性: Yes
可快取性: No
2014 年,HTTP/1.1 修訂中提及:
Client 不得在包含敏感資料的 TRACE 請求中,生成表頭欄位。
例如,使用者代理 (User Agent) 在 TRACE 請求中,
發送 用戶憑證 或 狀態資料 (e.g., cookies) 是愚不可及的,
且 (訊息) 最終接收者,應在生成回應時,
排除掉任何包含敏感資料的部分。
然而,一切都馬後炮了,
過去十幾年,許多 使用者代理 不良地實作了 TRACE 方法,
使 TRACE 已成為 不安全 的象徵,直至今日。
相同的功能,可使用簡易的 Echo Server 與 OPTIONS 方法取代。