成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

HTTP被動掃描代理的那些事

安全 應用安全 網絡管理
這篇文章我們從小白的角度粗略的聊聊 HTTP 代理到底是如何工作的,在實現被動掃描功能時有哪些細節需要注意以及如何科學的處理這些細節。

HTTP 代理這個名詞對于安全從業人員應該都是熟知的,我們常用的抓包工具 burp 就是通過配置 HTTP 代理來實現請求的截獲修改等。然而國內對這一功能的原理類文章很少,有的甚至有錯誤。筆者在做 xray 被動代理時研究了一下這部分內容,并整理成了這篇文章,這篇文章我們從小白的角度粗略的聊聊 HTTP 代理到底是如何工作的,在實現被動掃描功能時有哪些細節需要注意以及如何科學的處理這些細節。

[[276450]]

開始之前我先來一波靈魂6問,讀者可以先自行思考下,這些問題將是本文的關鍵點,并將在文章中一一解答:

  • http_proxy 和 https_proxy 有什么區別?
  • 為什么需要信任證書才能掃描 HTTPS 的站點?
  • 代理 HTTPS 的站點一定需要信任證書嗎?
  • 代理的隧道模式下如何區分是不是 TLS 的流量?
  • 代理應如何處理 Websocket 和 HTTP2 的流量?
  • 是否應該復用連接以及如何復用連接?

知識儲備

我們在本地做開發時,有時會需要啟動一個 HTTPS 的服務,通常使用 OpenSSL 自行簽發證書并在系統中信任該證書,然后就可以正常使用這個 TLS 服務了。如果沒有信任,瀏覽器就會提示證書不信任而無法訪問,簡言之,我們需要手動信任自行簽發的證書才可以正常訪問配置了該證書的網站。那么問題來了,為什么平日訪問的那些網站都不需要信任證書呢?打開 baidu.com 查看其證書發現這里其實是一個證書鏈:

 

最頂層的 Global Sign RootCA 是一個根證書,第二個是一個中間證書,最后一個才是 baidu 的頒發證書,這三種證書的效力是:

  1. RootCA >  Intermediates CA > End-User Cert 

而且只要信任了 RootCA 由 RootCA 簽發的包括其下級簽發的證書都會被信任。而 Global Sign RootCA等是一些默認安裝在系統和瀏覽器中的根證書。這些證書由一些權威機構來維護,可以確保證書的安全和有效性。而內置的這些根證書就允許我們訪問一些公共的網站而無需手動信任證書了。

再來說下與 HTTP 代理相關的兩個環境變量: HTTP_PROXY 和 HTTPS_PROXY,有的程序使用的是小寫的,比如 curl。對于這兩個變量,約定俗稱的規則如下:

  • 如果目標是 HTTP 的,則使用 HTTP_PROXY 中的地址
  • 如果目標是 HTTPS 的,則使用 HTTPS_PROXY 中的地址
  • 如果對應的環境變量為空,則不使用代理

這兩個環境變量的值是一個 URI,常見的有如下三種形式:

  • http://127.0.0.1:7777
  • https://127.0.0.1:7777
  • socks5://127.0.0.1:7777

拋開與主題無關的 socks 不管,這里又有一個 http 和 https,別暈,這里的 http 和 https 指的是代理服務器的類型,類似 http://baidu.comhttps://baidu.com 一個是裸的 HTTP 服務,一個套了一層 TLS 而已。那么組合一下就有 4 種情況了:

  • http_proxy=http://127.0.0.1:7777
  • https_proxy=http://127.0.0.1:7777
  • http_proxy=https://127.0.0.1:7777
  • https_proxy=https://127.0.0.1:7777

這四種情況都是合法的,也是代理實現時應該考慮的。但是如上面所說,這只是約定俗稱的,沒有哪個 RFC 規定必須這樣做,導致上面四種情況在常見的工具中被實現的五花八門,為了避免把大家繞暈,我直接說結論:很多工具對后面兩種不支持,比如 wget, python requests, 也就是說 https://還是被當成了 http://,因此我們這里只討論前兩種情況的實現。

代理中的 MITM

HTTP 代理的協議基于 HTTP,因此 HTTP 代理本身就是一個 HTTP 的服務,而其工作原理本質上就是中間人(MITM) ,即讀取當前客戶端的 HTTP 請求,從代理發送出去并獲得響應,然后將響應返回給客戶端。其過程類似下面的流程:

為了更直觀的感受下,可以用 nc 監聽 127.0.0.1:7777 然后使用:

  1. httphttp_proxy=http://127.0.0.1:7777 curl http://example.com 

會發現 nc 的數據包為:

  1. GET http://example.com/ HTTP/1.1 
  2. Host: example.com 
  3. Proxy-Connection: keep-alive 
  4. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4)  
  5. Accept: text/html 
  6. Accept-Encoding: gzip, deflate 
  7. Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 

看起來和 HTTP 的請求非常像,唯一的區別就是 GET 后的是一個完整的 URI,而不是 path,這主要是方便代理得到客戶端的原始請求,如果不用完整的 URI,請求的 Scheme 將無從得知,端口號有時也可能是不知道的。

在 Go 中我們可以用幾行簡單的代碼實現這種場景下的代理。

  1. package main 
  2. import ( 
  3.    "bufio"    
  4.     "log"    
  5.     "net"    
  6.     "net/http" 
  7. var client = http.Client{} 
  8. func main() { 
  9.    listener, err :net.Listen("tcp", "127.0.0.1:7777") 
  10.    if err != nil { 
  11.       log.Fatal(err) 
  12.    } 
  13.    for { 
  14.       conn, err :listener.Accept() 
  15.       if err != nil { 
  16.          log.Fatal(err) 
  17.       } 
  18.       go handleConn(conn) 
  19.    } 
  20. func handleConn(conn net.Conn) { 
  21.    // 讀取代理中的請求   req, err :http.ReadRequest(bufio.NewReader(conn)) 
  22.    if err != nil { 
  23.       log.Println(err) 
  24.       return   } 
  25.    req.RequestURI = ""   // 發送請求獲取響應   resp, err :client.Do(req) 
  26.    if err != nil { 
  27.       log.Println(err) 
  28.       return   } 
  29.    // 將響應返還給客戶端   _ = resp.Write(conn) 
  30.    _ = conn.Close() 

編譯運行這段代碼,然后使用 curl 測試下:

  1. httphttp_proxy=http://127.0.0.1:7777  curl -v http://example.com 

代理看起來工作正常,我們使用不到 40 行代碼就實現了一個簡易的 HTTP 代理!代碼中的 req 就是做被動代理掃描需要用到的請求,把請求復制一份扔給掃描器就可以了。這也就是上面說的第一種情況, 即http_proxy=http://。那么如果直接使用上述實現訪問 https 的站點會發生什么呢?

TLS 與隧道代理

  1. httphttps_proxy=http://127.0.0.1:7777 curl -v https://baidu.com 

使用上面的方式訪問 baidu 時,出現了比較奇怪的事情——通過代理讀到的客戶端請求不是原來的請求,而是一個 CONNECT 請求:

  1. CONNECT baidu.com:443 HTTP/1.1 
  2. Host: baidu.com:443 
  3. User-Agent: curl/7.54.0 
  4. Proxy-Connection: Keep-Alive 

這是 HTTP 代理的另一種形式,稱為隧道代理。隧道代理的過程如下:

隧道代理的出現是為了能在 HTTP 協議基礎上傳輸非 HTTP 的內容。如果你用過 websocket,一定對 Connection: Upgrade 這個頭不陌生。這個頭是用來告訴 server,客戶端想把當前的 HTTP 的連接轉為 Websocket 協議通信的連接。類似的,這里的 CONNECT是一種協議轉換的請求,但這種轉換更像是一種 Degrade,因為握手完成后,這個鏈接將退化為原始的 Socket Connection,可以在其中傳輸任意數據。用文字描述下整個過程如下:

  • 客戶端想通過代理訪問https://baidu.com,向代理發送 Connect 請求。
  • 代理嘗試連接 baidu.com:443,如果連接成功返回一個 200 響應,連接控制權轉交個客戶端;如果連接失敗返回一個 502,連接中止。
  • 客戶端收到 200 后,在這個連接中進行 TLS 握手,握手成功后進行正常的 HTTP 傳輸。

有個點需要注意下,轉換后的連接是可以傳輸任意數據的,并非只是 HTTPS 流量,可以是普通的 HTTP流量,也可以是其他的應用層的協議流量。那么我們回到被動代理掃描這個話題,如何獲取隧道代理中的請求并用來掃描?

這是一個比較棘手的問題,正是由于隧道中的流量可以是任意應用層協議的數據,我們無法確切知道隧道中流量用的哪種協議,所以只能猜一下。查看 TLS 的 RFC 可以發現,TLS 協議開始于一個字節 0×16,這個字節在協議中被稱為 ClientHello,那么我們其實就可以根據這第一個字節將協議簡單區分為 TLS 流量和非 TLS 流量。對于被動掃描器而言,為了簡單起見,我們認為 TLS 的流量就是 HTTPS 流量,非 TLS 流量就是 HTTP 流量。后者和普通代理下的 MITM 一致,可以直接復用代碼,而 HTTPS 的情況需要多一個 TLS 握手的過程。用偽代碼表示就是:

  1. b = conn.Read(1) 
  2. if b == "0x16" { 
  3.     tlsHandShake(conn) 
  4. }  
  5. req = readRequest(conn) 
  6. handleReq(conn, req) 

這里有個細節是讀出的這一個字節不要忘記“塞回去”,因為少了一個字節,后面的會操作會失敗。

這里我們需要重點關注下 TLS 握手過程。在 TLS 握手過程中會進行證書校驗,如果客戶端訪問的是 baidu.com,server 需要有 baidu.com 這個域的公鑰和私鑰才能完成握手,可是我們手里哪能有 baidu.com的證書(私鑰),那個在文件在 baidu 的服務器上呢!

解決辦法就是文章最開始說到的信任根證書。信任根證書后,我們可以在 TLS 握手之前直接簽發一個對應域的證書來進行 TLS 握手,這就是包括 burp 在內的所有需要截獲 HTTPS 數據包的軟件都需要信任一個根證書的原因!有了被系統信任的根證書,我們就可以簽出任意的被客戶系統信任的具體域的證書,然后就可以剝開 TLS 拿到被動掃描需要的請求了。這里還有一個小問題是簽發的證書的域該使用哪個,簡單起見我們可以直接使用 CONNECT 過程中的地址,更科學的方法我們后面說。簽完證書就可以完成 TLS 握手,然后就又和第一節的情況類似了。

有個點需要提一下,如果不需要進行中間人獲取客戶端請求,是不需要信任證書的,因為這種情況下的是真正的隧道,像是客戶端與服務器的直接通信,代理服務器僅僅在做二進制的數據轉發。

至此,被動代理的核心實現已經完成了,接下來是一些瑣碎的細節,這些細節同樣值得注意。

代理的認證

一個公網的代理如果沒有加認證是比較危險的,因為代理本身就相當于開放了某個網絡的使用權限,而且由于隧道模式的存在,代理的支持的協議理論上拓寬到了任何基于 TCP 的協議,如果可以和傳統的 redis 未授權,SSRF DNS rebinding 等結合一下就是一個簡單的 CTF 題。所以給代理加上鑒權是很有必要的。

代理的認證和正常的 HTTP Basic Auth 很像,只是相關頭加了一個 Proxy- 的前綴,可以參考 《HTTP 權威指南》中的一個圖學習一下:

點對點的修正

根據 RFC,HTTP 中的下列頭被稱為單跳頭(Hop-By-Hop header),這些 Header 應該只作用于單個 TCP 連接的兩端,HTTP 代理在請求中如果遇到了,應當刪掉這些頭。

  1. "Proxy-Authenticate", 
  2. "Proxy-Authorization", 
  3. "Connection", 
  4. "Keep-Alive", 
  5. "Proxy-Connection",  
  6. "Te", 
  7. "Trailer", 
  8. "Transfer-Encoding", 
  9. "Upgrade", 

至于這些頭要刪掉的原因,這里按我的理解簡單說下。前兩個是和認證相關的,每個代理的認證是獨立的,所以認證成功應該刪掉當前代理的認證信息。

中間的三個是用于控制連接狀態的,TCP 連接是端到端的,連接狀態的維護也應該是針對兩端的,即客戶端與代理服務器, 代理服務器與目的服務器應該是分別維護各自狀態的。Proxy-Connection 類似 Connection,是用來指定客戶端和代理之間的連接是不是 KeepAlive 的,代理實現時應該兼顧這個要求。對于連接的狀態管理,我認為比較科學的方式是分拆而后串聯。分拆是說 client->proxy 和 proxy -> server 這兩個過程分開處理, client->proxy 的過程每次開啟新的 TCP 連接,不做連接復用;而 proxy->server 的過程本質上就是一個普通的 http 請求,所以可以套一個連接池,借助連接池可以復用 TCP 連接。兩部分的連接都撥通后,可以將其串聯起來,最終效果上就是在遵循 Proxy-Connection 的前提下連接的狀態最終與代理無關,而是由 client 和 server 共同控制。串聯過程在 Go 中可以用兩行代碼簡單搞定:

  1. go io.Copy(conn1, conn2) 
  2. io.Copy(conn2, conn1) 

TE Trailer Transfer-Encoding和請求傳輸的方式有關。代理在讀取客戶端請求時應該確保正確處理了 chunked 的傳輸方式后再刪除這幾個頭,由代理自行決定在發往目的服務器時要不要使用分塊傳輸。類似的還有 Content-Encoding,這個決定的是請求的壓縮方式,也應該在代理端被科學的處理掉。好在傳輸方式這幾個頭在 Go 的標準庫中都有實現,對開發者基本都是透明的,開發者可以直接使用而無需關心具體的邏輯。

Websocket 與 HTTP2

前面提到過 Upgrade,這里再簡單說說。這個頭常用于從 HTTP 轉換到 Websocket 或 HTTP2 協議。對于 Websocket,被動掃描時可以不關注,所以可以直接放行。這里放行的意思是不再去解析,而是類似 Tunnel 那種,單純的進行數據轉發。對于 HTTP2 ,我們可以拒絕這一轉換,使得數據協議始終用 HTTP,也算是一個偷懶的捷徑。

當然,如果想要做的完善些,就需要套用一下這兩種協議的解析,偽裝成 Websocket server 或 HTTP2 server,然后做中間人去獲取傳輸數據,有興趣可以看一下 Python 的 MitmProxy 的實現。

離完美的差距

回顧剛才說的一些要點,這里的被動代理實現其實并不完美,主要有這兩點:

第一點是隧道模式下,我們強行判定了以 0×16 開頭的就是 TLS 流量,協議千千萬,這種可能有誤判的。其次我們認為 TLS 層下的應用協議一定是 HTTP,這也是不妥的,但對于被動掃描這種場景是足夠了。

另一點是隧道模式下證書的簽發流程不夠完美。如果你用過虛擬主機,或者嘗試過在同一地址同一端口上運行多個 HTTP 服務,那一定知道 nginx 中的 server_name 或是 apache 的 VirtualHost。服務器收到 HTTP 請求后會去查看請求的 Host 字段,以此決定使用哪個服務。TLS 模式下有所不同,因為 TLS 握手時服務器沒法讀取請求,為此 TLS 有個叫 SNI(Server Name Indication)的拓展解決了這個問題,即在 TLS 握手時發送客戶端請求的域給服務器,使得在同一 ip 同一端口上運行多個 TLS 服務成為了可能。回到被動代理這,之前我們簽證書用的域是從 CONNECT 的 HOST 中獲取的,其實更好的辦法是從 TLS 的握手中讀取,這樣就需要自行實現 TLS 的握手過程了,具體可以參考下 MitmProxy 的實現。

https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/

責任編輯:趙寧寧 來源: FreeBuf
相關推薦

2021-10-26 11:42:51

系統

2014-06-06 16:08:17

初志科技

2011-09-19 15:40:35

2020-07-29 08:14:59

云計算云遷移IT

2011-05-19 16:47:50

軟件測試

2012-05-01 08:06:49

手機

2017-05-15 21:50:54

Linux引號

2024-02-04 17:03:30

2012-05-31 09:53:38

IT風云15年

2015-08-20 09:17:36

Java線程池

2015-09-14 09:28:47

2009-02-19 10:21:00

路由多WAN口

2017-03-08 08:53:44

Git命令 GitHub

2021-08-11 21:46:47

MySQL索引join

2021-03-18 16:05:20

SSD存儲故障

2012-10-08 11:55:05

2015-08-13 10:54:46

2010-07-27 11:29:43

Flex

2012-07-13 00:03:08

WEB前端開發WEB開發

2017-11-28 15:24:14

ETA配送構造
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 精品国产鲁一鲁一区二区张丽 | 久久视频免费观看 | 最新中文字幕在线 | 在线免费小视频 | 久久一区二区三区四区五区 | 久久精品小视频 | 国产一区二区高清在线 | 亚洲一区二区av | 精品在线一区二区三区 | 欧美三级不卡 | 亚洲欧美视频 | 欧洲成人 | 欧美日韩综合一区 | 欧美一区二区大片 | 国产精品波多野结衣 | 成人av在线播放 | 天天操,夜夜爽 | 大象一区 | 国产91亚洲精品一区二区三区 | 久久草在线视频 | 成人免费淫片aa视频免费 | 亚洲精品美女 | 国产精品一区一区三区 | 日韩视频中文字幕 | 日一日操一操 | 三极网站 | www国产成人免费观看视频,深夜成人网 | 精品免费av| 欧美日韩一区二区在线观看 | www.99热.com| 成人国产精品久久久 | 一区二区视频在线 | 日韩在线国产 | 国产精品视频导航 | 亚洲欧美激情精品一区二区 | 精品99久久| 91精品午夜窝窝看片 | 亚洲精品久久久一区二区三区 | 久久99国产精品久久99果冻传媒 | 欧美精品一区二区免费视频 | 国产区视频在线观看 |