為什麼用代理#
對網路流量發動中間人攻擊,最常見的做法就是強迫應用程式透過代理服務(proxy service)通訊。本節說明幾種常見代理型態的相對優缺點,以及如何把典型用戶端應用程式的流量導進代理。
本節的範例實作都使用 Canape Core 函式庫,程式碼放進 C# 腳本檔(.csx)即可執行。
連接埠轉發代理#
連接埠轉發(port forwarding)是最簡單的代理方式:架一個監聽伺服器(TCP 或 UDP)等待新連線;連線進來後,代理再對真正的服務開一條轉發連線,把兩端邏輯上接起來。

圖表 2-8:TCP 連接埠轉發代理總覽
實作:簡單的 TCP 連接埠轉發代理
把下列程式碼放進 C# 腳本檔,並把 LOCALPORT、REMOTEHOST、REMOTEPORT 換成適合你網路環境的值:
// PortFormatProxy.csx – Simple TCP port-forwarding proxy
// Expose methods like WriteLine and WritePackets
using static System.Console;
using static CANAPE.Cli.ConsoleUtils;
// Create proxy template
var template = new FixedProxyTemplate();
template.LocalPort = LOCALPORT;
template.Host = "REMOTEHOST";
template.Port = REMOTEPORT;
// Create proxy instance and start
var service = template.Create();
service.Start();
WriteLine("Created {0}", service);
WriteLine("Press Enter to exit...");
ReadLine();
service.Stop();
// Dump packets
var packets = service.Packets;
WriteLine("Captured {0} packets:", packets.Count);
WritePackets(packets);腳本先建立一個 FixedProxyTemplate 實例。Canape Core 採範本(template)模型運作,必要時你仍可深入操作底層網路設定。範本設定好本地與遠端網路資訊後,用它建立服務實例——你可以把框架中的文件想成服務的範本。服務啟動時網路連線隨之設定完成;等待按鍵後停止服務,最後用 WritePackets() 把所有擷取到的封包寫到主控台。
執行後,轉發代理會只綁定在 localhost 介面的 LOCALPORT 上。當有新的 TCP 連線進來,代理就會對 REMOTEHOST 的 REMOTEPORT 建立新連線,並把兩條連線串起來。
把代理綁定到所有網路位址在安全上是有風險的:為測試協定而寫的代理鮮少實作健全的安全機制。除非你完全掌控所連接的網路、或別無選擇,只把代理綁在本機回送介面(local loopback)上。上述範例預設即為
LOCALHOST;要綁定所有介面則需將AnyBind屬性設為true。
把流量導進代理#
代理寫好之後,接下來要讓應用程式的流量流經它。
- 瀏覽器最簡單:把
http://www.domain.com/resource改成http://localhost:localport/resource,請求就會被推進你的連接埠轉發代理。 - 其他應用程式較麻煩:可能得翻找應用程式的設定。有時候唯一能改的只有目的 IP 位址,這會造成雞生蛋、蛋生雞的窘境——你不知道它會用哪些 TCP 或 UDP 埠,尤其當應用程式的複雜功能跑在多條不同的服務連線上時。遠端程序呼叫(RPC)協定如 CORBA(Common Object Request Broker Architecture)就是這樣:它先連到一個作為服務目錄的代理者(broker),再對請求的服務用該實例專屬的 TCP 埠建立第二條連線。
遇到這種情況,好的做法是盡量操作應用程式所有會連網的功能,同時用被動擷取技術監看。這樣就能挖出應用程式通常會建立哪些連線,再用轉發代理逐一複製出來。
如果應用程式根本不支援更改目的地,就得更有創意一點。若它是透過主機名稱解析伺服器位址,你的選項就多了:可以架一台自訂 DNS 伺服器,對名稱查詢回應你的代理 IP;也可以用幾乎所有作業系統都有的 hosts 檔(前提是你能控制該裝置的系統檔案)。
hosts 檔的位置與寫法
主機名稱解析時,作業系統(或解析函式庫)會先查 hosts 檔看有沒有本地紀錄,找不到才發出 DNS 請求。下面這份 hosts 檔把 www.badgers.com 與 www.domain.com 都導向 localhost:
# Standard Localhost addresses
127.0.0.1 localhost
::1 localhost
# Following are dummy entries to redirect traffic through the proxy
127.0.0.1 www.badgers.com
127.0.0.1 www.domain.comhosts 檔的標準位置:
- 類 Unix 系統:
/etc/hosts - Windows:
C:\Windows\System32\Drivers\etc\hosts(Windows 資料夾路徑請依環境自行替換)
部分防毒與資安產品會追蹤系統 hosts 檔的變更,因為這種變更是惡意軟體的徵兆。若要修改 hosts 檔,你可能得先停用該產品的防護。
優點與缺點#
優點
- 簡單:等連線、對原目的地開新連線、來回傳遞流量,如此而已。代理本身沒有協定要處理,被擷取的應用程式也不需要任何特殊支援。
- 它是代理 UDP 流量的主要方式——UDP 不是連線導向的,轉發器的實作因此簡單許多。
缺點
- 由於只是把單一監聽連線的流量轉給單一目的地,若應用程式在不同連接埠上使用多種協定,就需要多個代理實例。例如某應用程式只有單一主機名稱或 IP 可設定(或你用主機名稱欺騙來控制),但它會連到 TCP 443 與 1234 兩個埠——因為你能控制的是位址而非埠,即使你只在意 1234 的流量,兩個埠都得架轉發代理。
- 難以處理同一個知名埠上的多條連線。若代理監聽 1234 埠並連往
www.domain.com:1234,就只有原網域的重導流量會如預期運作;想同時重導www.badgers.com就麻煩了。緩解方式是靠應用程式支援指定目的位址與埠,或使用 DNAT(Destination Network Address Translation)之類的技術,把特定連線導到各自專屬的轉發代理(第 5 章會有更多 DNAT 及進階擷取技術的細節)。 - 協定本身可能會使用目的位址。例如 HTTP 的
Host標頭可用於虛擬主機(Virtual Host)判斷,這會讓被連接埠轉發的協定行為和直接重導不同,甚至完全失效。至少對 HTTP 而言,反向 HTTP 代理提供了繞過這個限制的辦法。
SOCKS 代理#
可以把 SOCKS 代理想成「強化版的連接埠轉發代理」。它不只把 TCP 連線轉往目標位置,而且每條新連線都以一次簡單的交握(handshake)開場,由用戶端告訴代理最終目的地,而不是把目的地寫死。它也能支援監聽連線,這對像 FTP 這種需要開新本地埠讓伺服器送資料過來的協定很重要。

圖表 2-9:SOCKS 代理總覽
三個版本#
- SOCKS 4:支援度最高的版本,但只支援 IPv4,目的位址必須以 32 位元 IP 位址指定。
- SOCKS 4a:版本 4 的更新,允許以主機名稱連線(在你沒有能解析 IP 的 DNS 伺服器時很有用)。
- SOCKS 5:引入主機名稱支援、IPv6、UDP 轉發與更好的認證機制,也是唯一有 RFC 規範的版本(RFC 1928)。
SOCKS 4 的請求與回應格式
用戶端要對 IP 10.0.0.1 的 12345 埠建立 SOCKS 連線時,送出的請求包含這些欄位:
VER:版本號,此例為 4。CMD:表示要連出(往外連線);綁定位址則是CMD 2。- TCP 連接埠與位址:以二進位形式指定。
USERNAME:這是 SOCKS 版本 4 中唯一的認證方式(確實不怎麼安全)。

圖表 2-10:SOCKS 版本 4 的請求
連線成功後回應包含:
RESP:回應狀態。- TCP 連接埠與位址欄位:只有在綁定請求時才有意義。

圖表 2-11:SOCKS 版本 4 的成功回應
之後連線即變為透明(transparent),用戶端與伺服器直接協商,代理伺服器只負責雙向轉發流量。
實作:簡單的 SOCKS 代理
Canape Core 內建支援 SOCKS 4、4a 與 5。把 LOCALPORT 換成你要監聽的本地 TCP 埠:
// SocksProxy.csx – Simple SOCKS proxy
// Expose methods like WriteLine and WritePackets
using static System.Console;
using static CANAPE.Cli.ConsoleUtils;
// Create the SOCKS proxy template
var template = new SocksProxyTemplate();
template.LocalPort = LOCALPORT;
// Create proxy instance and start
var service = template.Create();
service.Start();
WriteLine("Created {0}", service);
WriteLine("Press Enter to exit...");
ReadLine();
service.Stop();
// Dump packets
var packets = service.Packets;
WriteLine("Captured {0} packets:", packets.Count);
WritePackets(packets);這段程式碼與連接埠轉發代理的模式完全相同,差別只在於建立的是 SocksProxyTemplate,其餘一模一樣。
把流量導進代理#
找出把應用程式流量推進 SOCKS 代理的方法時,先看應用程式本身——例如 Mozilla Firefox 的代理設定對話框就能直接指定 SOCKS 代理。

圖表 2-12:Firefox 的代理設定
但有時 SOCKS 支援藏得比較深:
以系統屬性讓 Java 應用程式走 SOCKS
Java Runtime 接受命令列參數,可為任何對外 TCP 連線啟用 SOCKS。以下是一支連到 192.168.10.1 的 5555 埠的簡單 Java 程式:
// SocketClient.java – A simple Java TCP socket client
import java.io.PrintWriter;
import java.net.Socket;
public class SocketClient {
public static void main(String[] args) {
try {
Socket s = new Socket("192.168.10.1", 5555);
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
out.println("Hello World!");
s.close();
} catch(Exception e) {
}
}
}正常執行時它就照原意運作。但若在命令列傳入 socksProxyHost 與 socksProxyPort 這兩個特殊系統屬性,就能為任何 TCP 連線指定 SOCKS 代理:
java -DsocksProxyHost=localhost -DsocksProxyPort=1080 SocketClient這會讓 TCP 連線改走 localhost 1080 埠上的 SOCKS 代理。
另外兩個著手點:
作業系統的預設代理設定。macOS 在 System Preferences ▸ Network ▸ Advanced ▸ Proxies,可設定系統層級的 SOCKS 代理或其他協定的一般代理。不一定每次都管用,但這是值得一試的簡單選項。

圖表 2-13:macOS 上的代理設定對話框
外掛工具。若應用程式原生就不支援 SOCKS,有些工具能替任意應用程式加上這項功能,從免費開源的 Dante(Linux,https://www.inet.no/dante/ ↗)到商業工具 Proxifier(Windows 與 macOS,https://www.proxifier.com/ ↗)都有。它們或多或少都是注入應用程式以加入 SOCKS 支援,並修改 socket 函式的行為。
優點與缺點#
優點
- 相較單純的連接埠轉發器,SOCKS 代理應能擷取應用程式建立的所有 TCP 連線(若使用版本 5 還可能包含部分 UDP)——前提是作業系統的 socket 層有被有效包裝,使所有連線都經過代理。
- SOCKS 代理通常會保留用戶端視角的連線目的地。因此若用戶端送出帶內(in-band)資料指涉自身端點,該端點會是伺服器預期的樣子。
缺點
- 但它不保留來源位址。某些協定(如 FTP)假設自己能要求在來源用戶端上開啟連接埠;SOCKS 協定雖提供綁定監聽連線的機制,卻增加了實作複雜度,也讓擷取與分析更困難——你必須同時考慮往返伺服器的多條資料流。
- 支援度在不同應用程式與平台間並不一致。Windows 的系統代理只支援 SOCKS 版本 4,意味著它只會解析本地主機名稱,不支援 IPv6,也沒有健全的認證機制。一般來說,用 SOCKS 工具替既有應用程式加上支援效果會好一些,但也不是永遠順利。
HTTP 代理#
HTTP 驅動了全球資訊網,以及無數的 web service 與 RESTful 協定。它也常被挪用為非 web 協定的傳輸機制——例如 Java 的 RMI(Remote Method Invocation)或 RTMP(Real Time Messaging Protocol)——因為它能穿透最嚴格的防火牆。
就算你測的不是 web 服務,理解 HTTP 代理在實務上如何運作幾乎一定用得上。既有的 web 應用程式測試工具在 HTTP 被用於原始情境之外時,往往表現不理想;有時候自己刻一個 HTTP 代理才是唯一解。
HTTP 代理主要分兩類:轉發代理(forwarding proxy) 與反向代理(reverse proxy),各有其優缺點。

圖表 2-14:HTTP 代理總覽
轉發 HTTP 代理#
HTTP 的規範分別是 RFC 1945(1.0 版)與 RFC 2616(1.1 版),兩版都提供了簡單的請求代理機制。HTTP 1.1 規定請求的第一行(request line)格式如下:
GET /image.jpg HTTP/1.1- 方法(method):以
GET、POST、HEAD等熟悉的動詞指定該請求要做什麼。在代理請求中,這部分與一般 HTTP 連線並無不同。 - 路徑(path):這才是代理請求有趣的地方。上例是絕對路徑,指出方法要作用的資源;但路徑也可以是絕對的 URI。
指定絕對 URI 後,代理伺服器就能對目的地建立新連線、把流量往前送、再把資料帶回給用戶端。代理甚至能在有限範圍內操弄流量:加上認證、對 1.1 用戶端隱藏 1.0 伺服器、加上傳輸壓縮等等。存取遠端伺服器上某個圖片資源的請求行會長這樣:
GET http://www.domain.com/image.jpg HTTP/1.1這份彈性是有代價的:代理伺服器必須能處理 HTTP 流量,複雜度因此大幅上升。
HTTPS 怎麼辦:CONNECT 方法#
眼尖的讀者會發現一個問題:代理既然必須存取底層的 HTTP 協定,那用 TLS 加密傳輸 HTTP 的 HTTPS 怎麼辦?你當然可以把加密流量拆開,但在正常環境下 HTTP 用戶端不太可能信任你提供的憑證;而且 TLS 就是刻意設計成幾乎不可能用其他方式做中間人攻擊。
所幸 RFC 2817 早有預備,提供兩個解法:一是把 HTTP 連線升級為加密連線;二是——對我們更重要的——規範了 CONNECT 方法,用來在 HTTP 代理上建立透明的穿隧(tunneled)連線。瀏覽器要透過代理連上 HTTPS 網站時,會對代理送出:
CONNECT www.domain.com:443 HTTP/1.1若代理接受,它會對伺服器建立新的 TCP 連線,成功時回應:
HTTP/1.1 200 Connection Established此後這條到代理的 TCP 連線就變成透明的,瀏覽器可以自行完成 TLS 協商,代理不會擋在中間。
值得注意的是,代理通常不會驗證這條連線上真的在跑 TLS——裡面可以是任何協定。有些應用程式正是利用這點,把自己的二進位協定穿隧出 HTTP 代理。也因此,實務上的 HTTP 代理部署常會把可穿隧的連接埠限制在很小的子集合內。
實作:簡單的轉發 HTTP 代理
Canape Core 同樣內建簡單的 HTTP 代理實作。可惜它不支援用 CONNECT 建立透明穿隧,但作為示範已經足夠。把 LOCALPORT 換成你要監聽的本地 TCP 埠:
// HttpProxy.csx – Simple HTTP proxy
// Expose methods like WriteLine and WritePackets
using static System.Console;
using static CANAPE.Cli.ConsoleUtils;
// Create proxy template
var template = new HttpProxyTemplate();
template.LocalPort = LOCALPORT;
// Create proxy instance and start
var service = template.Create();
service.Start();
WriteLine("Created {0}", service);
WriteLine("Press Enter to exit...");
ReadLine();
service.Stop();
// Dump packets
var packets = service.Packets;
WriteLine("Captured {0} packets:", packets.Count);
WritePackets(packets);同樣只是把範本物件換成 HttpProxyTemplate,其餘與前面的例子相同。
把流量導進代理#
和 SOCKS 一樣,第一站是應用程式本身——會用 HTTP 的應用程式很少完全沒有某種代理設定。若沒有專屬設定,就試作業系統的設定,位置和 SOCKS 代理設定相同;例如 Windows 是 Control Panel ▸ Internet Options ▸ Connections ▸ LAN Settings。
類 Unix 系統上許多命令列工具(curl、wget、apt 等)也支援用環境變數設定 HTTP 代理:
export http_proxy=http://localhost:3128
export https_proxy=http://localhost:3128把 http_proxy 設為代理的 URL,應用程式就會使用它;加密流量則用 https_proxy。部分實作允許特殊的 URL scheme,例如以 socks4:// 指定改用 SOCKS 代理。
優點與缺點#
優點
- 若應用程式專用 HTTP 協定,要加上代理支援只需把請求行中的絕對路徑改成絕對 URI,再把資料送到監聽中的代理伺服器即可。
- 以 HTTP 作為傳輸的應用程式,幾乎都已經支援代理。
缺點
- 轉發 HTTP 代理必須實作完整的 HTTP 解析器來處理協定的種種怪癖,複雜度顯著增加;這份複雜度可能帶來處理錯誤,最壞的情況是引入安全漏洞。
- 代理目的地被寫進協定裡,意味著很難用外部技術替既有應用程式補上 HTTP 代理支援,除非你把連線轉成使用
CONNECT方法(這對未加密的 HTTP 也適用)。 - 由於處理完整 HTTP 1.1 連線太複雜,代理常會在單一請求後就中斷用戶端,或把通訊降級到 1.0 版(1.0 在收完所有資料後一定關閉回應連線)。這可能破壞期望使用 1.1 版的上層協定,或破壞請求管線化(request pipelining)——也就是同時讓多個請求在途中以提升效能或狀態區域性的能力。
反向 HTTP 代理#
轉發代理常見於內部用戶端連往外部網路的環境,作為安全邊界,把對外流量限縮到少數協定類型(CONNECT 代理潛在的安全影響暫且不談)。但有時候你想代理的是進站連線,例如為了負載平衡或安全考量(避免伺服器直接暴露於外)。這時問題來了:你無法控制用戶端,用戶端甚至根本不知道自己連的是代理。反向 HTTP 代理就是為此而生。
反向代理不要求請求行裡指定目的主機,而是利用一個事實:所有符合 HTTP 1.1 的用戶端都必須在請求中送出 Host 標頭,載明請求 URI 中使用的原始主機名稱。(HTTP 1.0 沒有這項要求,但多數 1.0 用戶端仍會送出。)有了 Host 標頭,你就能推斷請求原本的目的地並代理過去:
GET /image.jpg HTTP/1.1
User-Agent: Super Funky HTTP Client v1.0
Host: www.domain.com
Accept: */*上例對應的原始 URL 是 http://www.domain.com/image.jpg,反向代理可以輕易取用 Host 資訊重建原始目的地。
同樣因為需要解析 HTTP 標頭,這種方式較難用於受 TLS 保護的 HTTPS 流量。所幸多數 TLS 實作接受萬用憑證(wildcard certificate),主體形如
*.domain.com,可比對domain.com的任何子網域。
實作:簡單的反向 HTTP 代理
Canape Core 內建反向 HTTP 代理,只要把範本物件從 HttpProxyTemplate 換成 HttpReverseProxyTemplate 即可。完整程式碼如下,把 LOCALPORT 換成要監聽的本地 TCP 埠;若 LOCALPORT 小於 1024 且在類 Unix 系統上執行,還需以 root 身分執行腳本:
// ReverseHttpProxy.csx – Simple reverse HTTP proxy
// Expose methods like WriteLine and WritePackets
using static System.Console;
using static CANAPE.Cli.ConsoleUtils;
// Create proxy template
var template = new HttpReverseProxyTemplate();
template.LocalPort = LOCALPORT;
// Create proxy instance and start
var service = template.Create();
service.Start();
WriteLine("Created {0}", service);
WriteLine("Press Enter to exit...");
ReadLine();
service.Stop();
// Dump packets
var packets = service.Packets;
WriteLine("Captured {0} packets:", packets.Count);
WritePackets(packets);把流量導進代理#
把流量導向反向 HTTP 代理的做法類似 TCP 連接埠轉發——重導連線到代理。但有一個重大差異:
如果受測應用程式跑在無法修改 hosts 檔的裝置上,那麼架一台自訂 DNS 伺服器可能是最省事的途徑——前提是你能更改 DNS 伺服器設定。你也可以架設一台功能完整的 DNS 伺服器,但這既費時又容易出錯(問問任何設定過 bind 的人就知道)。所幸現成工具就能做到我們要的事:對 DNS 請求回傳代理的 IP 位址,dnsspoof 就是一例。
實作:用 Canape 的 DNS 伺服器做欺騙
為了不用再裝一套工具,也可以直接用 Canape 的 DNS 伺服器。這台基本的 DNS 伺服器只會對所有 DNS 請求欺騙回單一 IP 位址。把 IPV4ADDRESS、IPV6ADDRESS、REVERSEDNS 換成適當的字串:
// DnsServer.csx – Simple DNS Server
// Expose console methods like WriteLine at global level.
using static System.Console;
// Create the DNS server template
var template = new DnsServerTemplate();
// Setup the response addresses
template.ResponseAddress = "IPV4ADDRESS";
template.ResponseAddress6 = "IPV6ADDRESS";
template.ReverseDns = "REVERSEDNS";
// Create DNS server instance and start
var service = template.Create();
service.Start();
WriteLine("Created {0}", service);
WriteLine("Press Enter to exit...");
ReadLine();
service.Stop();和反向 HTTP 代理一樣,在類 Unix 系統上需以 root 執行,因為它會嘗試綁定 53 埠,一般使用者通常不被允許。Windows 則沒有這種綁定小於 1024 連接埠的限制。
把應用程式的 DNS 伺服器指向這台欺騙用的 DNS 伺服器後,流量就會被送過來。
優點與缺點#
優點
- 反向 HTTP 代理不需要用戶端應用程式支援典型的轉發代理設定。當用戶端不在你的直接控制之下、或設定固定無法輕易更改時特別有用。只要你能強迫原始 TCP 連線被重導到代理,就能相當輕鬆地處理送往多個不同主機的請求。
缺點
- 與轉發代理基本相同:代理必須能解析 HTTP 請求,並處理該協定的種種怪癖。
結語#
被動與主動擷取,哪一種比較好?這取決於你要測試的應用程式。
除非你只是單純監看網路流量,否則採取主動方式通常是划算的。隨著本書往後推進,你會發現主動擷取對協定分析與利用有顯著好處。而如果你的應用程式允許選擇,優先使用 SOCKS——在許多情況下它是最簡單的途徑。