2015年9月29日 星期二

觀察 HTTP/HTTPS 傳輸的內容以及防範 man-in-the-middle attack

看到許多強者推薦 mitmproxy (MITM 是 man-in-the-middle 的意思), 試用了一下的確超好用的。它用 man-in-the-middle attack 的方式觀察 (甚至更改) HTTP/HTTPS 傳輸的內容。

How To: Use mitmproxy to read and modify HTTPS traffic 介紹原理和用法, 設定很簡單。

官方文件也很清楚, 可交叉參考:

這裡摘要一下 mitmproxy 作為 Transparent Proxy 的方式和原理:

  1. 備好一台機器 R 作為觀察目標裝置 D (如手機) 的 gateway, 這樣就完成 man-in-the-middle 的設置。
  2. 設定 R 的 firewall, 重導往外連往 port 80 和 443 的連線到在 R 上執行的 mitmproxy。
  3. 在 D 上安裝 mitmproxy 的 certificate, 這樣 D 會相信 mitmproxy 幫自己簽的 certificate。

測試 Android 手機在 MITM 情況下 App 的反應

我是用 ASUS Zenfone 2, 不過 App 的測試結果應該和手機無關。

結果如下:

  • Android OS 有出現訊息說網路可能被監控了, 但清掉訊息後就沒再出現了, 使用者應該不會注意到。
  • 沒裝 mitmproxy certificate 的情況, Google Play 會出現連線錯誤; 裝 certificate 後可以用 Google Play 安裝 app。
  • 沒裝 mitmproxy certificate 的情況, Chrome 會警告有問題; 有裝 certificate 會放行。
  • 沒裝 mitmproxy certificate 的情況, Gmail 會如常運作, 看不到 HTTPS 傳的內容。裝 certificate 的後可以看到 HTTPS 傳的內容。雖然 Gmail 用自己訂的資料格式, 在搜尋信件時, 可看到搜尋字、回傳信件的標題、內文前面幾句的文字。
  • 另外在有裝 mitmproxy certificate 的情況測了 Hangouts, Facebook, Messenger, Skype, Line。它們的主要傳輸內容都不是用 HTTPS, 改天再用 SSLsplit 看看它們傳什麼。
    • Line 會說 Wi-Fi 有問題無法用需要連線的功能 (訊息、電話、桌機 QR Code 登入), 剩下的 App 都可以用。
    • Facebook 有部份內容用 HTTPS 傳, 並且用 JSON 作為內文編碼。
    • Facebook 和 Messenger 有時候會顯示連線問題, 但放一陣子又通了, 也許它們有偵測網路情況用不同方式連線吧。
    • Hangouts 意外地都沒有安全警告, 有沒有裝 mitmproxy certificate 都一樣, 可以正常使用。

其它 Web Proxy

這些也是很有名的 Web Proxy, 有看到朋友推薦。不確定作為 transparent proxy 的效果如何, 至少監控本機的訊息應該滿方便的, 留著備忘。

防範 man-in-the-middle attack

Scott 提到可用 Certificate Pinning 防 MITM attack。原理是只相信自己指定的 certificate (或幫你簽 certificate 的 certificate), 這樣就會忽視攻擊者裝到作業系統的 root CA。當 SSL handshake 的 certificate 被替換的時候, 會變成 handshake error。Certificate Pinning 似乎有些不同的作法, 先記住關鍵字, 之後再來研究。

另一個好像可行但可能有問題的解法是不用 OS 提供的 certificate, 直接用自己包在 app 裡的 certificate (放 server 用的 certificate 或簽它的 CA 的 certificate, 意思一樣)。雖然這樣不用擔心裝置被裝了有問題的 root certificate, 但和 self-signed certificate 一樣, 需要處理另一個問題 (原本的問題): 如何確定 app 內裝了正確的 certificate? 這個難題原本是由 OS 處理, 假設 OS 沒安裝有問題的 root certificate, 所以可以用它的 root certificate。

2015-10-01 更新

Victor 告知, 發現 Gmail 應該有檢查 certificate, 我又測了一下, 並更正內文。

Gmail 和 Google Play 有檢查 certificate, 只是使用者從系統刪除信任的 certificate 時, app 沒有同步更新, 仍會相信這個 certificate。另一方面, Chrome 有同步更新。

HTTP library 支援 gzip response body 的情況

為了節省 HTTP response 的傳輸量, HTTP 有提供 request header Accept-Encoding: gzip 要求 server 「可以的話, 用 gzip 壓縮 body 再傳回來」, 還有 response header Content-Encoding: gzip 告知 client 「response body 有用 gzip 壓縮」。

在這樣的規定下, 推測 HTTP library 可能會有兩種實作方式:

  • 不處理。使用者自己加 request header "Accept-Encoding: gzip", 然後自己解 response body
  • 有個 flag 開啟後會自動處理。自動加上 "Accept-Encoding: gzip", 然後自動解 response body 並且移掉 response header "Content-Encoding: gzip"。另外要註明 API 取得的 content length 是否會受 gzip 影響, 結果如何變化。

不過實際情況是:

  • iOS NSURLConnection 文件沒有提到這件事, 但是會自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref., 實測結果確實如此, 不需手動加 Accept-Encoding: gzip 就有作用)
  • Android HttpURLConnection 指明預設就會作, 並會清掉 Content-Encoding 和 Content-Length。可以用 Accept-Encoding: identity 覆寫預設值 (Ref.)。
  • curl 開啟 flag CURLOPT_ACCEPT_ENCODING 後會支援, 也是自動加 request header 並自動解 response body, 不過沒有移掉 response header "Content-Encoding: gzip" (Ref.)

結果只有 Android 的實作符合預期。使用前還是要先測過才準啊。

附帶一提, http://httpbin.org/ 會回傳 request/response header, 用來測試 HTTP library 很方便。或是用 mitmproxy 作 transparent proxy 也可以, 相對之下費工一點, 不過看得最清楚。

2015年9月21日 星期一

Android 線上原始碼

偵測網路環境是否有 Captive Portal (使用 Wi-Fi 時會先導到登入網頁)

Captive Portal 是指在可以正常用網路前, 先導到一個登入網頁, 登入後才可以正常上網。通常用在需要付費上網的地方, 或是飯店提供免費但需帳號登入的 Wi-Fi。不過不限於 Wi-Fi 連線

這樣作的好處是使用者可以透過網頁看到比較詳細的說明, 像是「使用者同意書」、「使用限制」、「收費標準」等。對於 Wi-Fi 連線的供應者, 在連上 Wi-Fi 時登入 Wi-Fi 帳號和密碼, 就無法提供這些資訊。

但缺點是使用裝置會誤以為有網路可用 (因為 Wi-Fi 層級有通), 但其實還不能用。如果用的 app 沒有作進一步的偵測, 不會開啟登入網頁, 就無法上網了。使用者會覺得「Wi-Fi 有通 app 卻不能用」, 而覺得是 app 的問題。

Android 4.0.1 開始有內建一套偵測機制 [*1], 發現有 Captive Portal 時會通知使用者:

private boolean isCaptivePortal(InetAddress server) {
    HttpURLConnection urlConnection = null;
    if (!mIsCaptivePortalCheckEnabled) return false;

    mUrl = "http://" + server.getHostAddress() + "/generate_204";
    if (DBG) log("Checking " + mUrl);
    try {
        URL url = new URL(mUrl);
        urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setInstanceFollowRedirects(false);
        urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
        urlConnection.setUseCaches(false);
        urlConnection.getInputStream();
        // we got a valid response, but not from the real google
        return urlConnection.getResponseCode() != 204;
    } catch (IOException e) {
        if (DBG) log("Probably not a portal: exception " + e);
        return false;
    } finally {
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}

作法是送 HTTP request 到 http://clients3.google.com/generate_204 [*2], 然後看 HTTP response code 是否為 204 No Content。另一個作法是連 http://www.google.com/blank.html, 然後檢查 Content-Length 是否為 0。

不過當使用者用 HTTPS 上網時, 這招就不管用了, 連線可能出現 certificate error 或是沒反應。原因是 HTTPS 會先加密連線才傳 HTTP, 提供 Wi-Fi 的 AP 無法偵測到有 HTTP 連線, 自然也就無法重導到登入頁面。這正是 HTTPS 要的結果: 使用者送出的連線不該被任意地導到其它地方 (會讓使用者誤以為那是他連往的頁面)。所以....這招應該會在 HTTPS 普及後而愈來愈少人用吧。另一方面也沒有更好的作法可以讓 Wi-Fi 供給者先顯示訊息再讓使用者登入使用。

備註

1. 5.1.1 r6的程式碼有點不同, 變得更一般化, 可自訂測試用的網址還有將 200 + 無資料視為 204, 不過概念一樣。

2. 原始碼是用 http://clients3.google.com/generate_204, 不過用 http://www.google.com/generate_204 也通

參考資料

2015年9月18日 星期五

Web Proxy 是否能處理 HTTPS 連線?

在討論 HTTPS Proxy 前, 要先分清楚是在講那件事:

  • Proxy 和 client 之間的連線用 HTTPS
  • Proxy 允許 client 透過它往外用 HTTPS 連線

前者的好處是保護 client 和 Proxy 之間的連線, client 若是用 Wi-Fi 的話, 可藉此加密所有送出的 HTTP 連線, 防止有人偷聽 Wi-Fi 連線。

後者是本篇要討論的對象: Web Proxy 是否能支援 client 對外的 HTTPS 連線? 又是如何作到的? 畢竟 HTTPS 是基於 SSL 連線的 HTTP, 既然資料都用 SSL 加密了, Proxy 看不到 HTTP header 和 body, 要怎麼作好 Web Proxy 的角色?

用 HTTP tunnel

Proxy 接受 HTTP 1.1 的 CONNECT method, 讓 client 用 CONNECT 對外打通一個 TCP 連線 (又稱 HTTP tunnel), 然後只作 TCP packet forwarding, 於是 client 要作什麼都可以。比方說用 TCP 連線作 SSL handshake 轉成加密連線, 接著就可以走 HTTPS, SPDY 或 HTTP 2 了。

這個作法下, Proxy server 不知道 client 對外傳的內容, 只能經手封包, 不能作其它事。

置換 HTTPS 的 certificate

Proxy 一樣接受 CONNECT method 建立 HTTP tunnel, 不過在 SSL handshake 時, 換掉目的地 Web Server 回傳的 certificate, 改用 Proxy 自己的。然後分別和 client 和外部 Web Server 完成 SSL handshake, 形成這樣的連線:

  • client <-> Proxy: 用 Proxy 的 certificate 建立 SSL 連線, 使用 key K1。
  • Proxy <-> 外部 Web Server: 用 Web Server 的 certificate 建立 SSL 連線, 使用 key K2。

Proxy 收到 client 的資料先用 K1 解密, 再用 K2 加密傳出去; 資料回來的時候反過來比照辦理。這樣 Proxy 就能得知傳送的內容, 可以作 content filtering (如學校可以擋掉成年人才能看的內容)。

這個作法和 SSL man-in-the-middle attack 一樣, 瀏覽器為了避免這類攻擊, 會檢查 certificate 和對應的 public key 資訊是否一致, 不一致時會跳出警告叫使用者不要繼續使用。

不過在可控制的環境下, IT 部門可以在使用者的電腦上裝上 Proxy 的 certificate, 這樣瀏覽器就不會跳出警告。另一方面, 使用者還是可以點選 HTTPS 的圖示檢查 certificate 資訊, 確認 certificate 是否有被換過, 有的話表示傳輸資料有可能被監聽。有興趣的話可以自己裝 mitmproxy 作一個 transparent proxy 試看看

自己實作 HTTP client 時, 可以用 certificate pinning 擋掉 man-in-the-middle attack, 原理是只相信自己指定的 certificate, 這樣就會忽視攻擊者裝到作業系統的 root CA。

其它討論

藉由了解 Web Proxy 如何處理 HTTPS, 可以得知: 在沒有對使用者裝置動手腳的情況下, Web Proxy 只能放行但不知道內容, 或是依網域名稱決定是否放行 (無法細到網址, 因為那在 HTTP header 裡)。另外, Proxy 可以透過 HTTP 1.1 CONNECT method 傳的參數或是 SSL handshake 傳的 certificate 資訊得知網域名稱。藉此決定是否要放行某些網域的 HTTPS 連線。

雖然 Google 鼓勵大家多用 HTTPS 並且朝向 HTTP 2 邁進, 不過反向思考一下: 用 HTTP 然後自己在 application logic 裡加密資料, 也許可以讓 API 能用在更廣的地方。在特殊的情境下 (比方回傳無法連線的資訊), 或許值得一試?

參考資料

HTTPS Proxy 相關資料

關於 HTTP 1.1 CONNECT method

關於 Certificate Pinning

2015-09-29 更新

Scott 的建議補上 certificate pinning 和 mitmproxy 的資訊。還有刪掉部份內容

2015年9月17日 星期四

用 Ubuntu 14.04 作為 Hotspot

最近需要模擬被擋封包的網路環境, 想想用筆電作為 hotspot 再設 firewall, 應該滿容易的。然後用手機連上 hotspot, 就可以模擬出各種擋封包的情況 (如立即回傳 connection error, 或只是 drop 封包)。

實際操作後的確很容易, 裝好 Ubuntu 14.04 後, 需要的東西都齊了, 內建的 Network Manager 有圖形介面, 點一點就可以分享 Wi-Fi。iPad/iPhone 可以連得上, 但是 Android 不行, 原因是 Ubuntu 預設 hotspot 用 Wi-Fi ad hoc mode, 但是 Android 不支援, 要改用 Wi-Fi infrastructure mode 才行。作法見 How do I create hotspots in Ubuntu 14.04?

其它

  • Android 4.0 後支援 Wi-Fi P2P, 可以讓 Android 裝置互連, 不過還是不支援 Wi-Fi ad hoc mode。
  • 用 Wi-Fi ad hoc mode 的時候, iPad/iPhone 會在 Wi-Fi 列表的「裝置」看到筆電用的 SSID; 若改用 infrastructure mode 的時候, 會在 AP 列表看到筆電用的 SSID。
  • 我有試過用 Mac OS X 作一樣的事, 但是新版 (我用 10.10.4) 內建只能用 pf 作 firewall, 和這東西不熟, 看起來沒 iptables 直覺。另外開啟 Internet Sharing 後 pf 就失效了, 就沒再深入研究。想想以後還是專心用 Linux 就好, 時間有限, 搞熟兩套 OS 的成本太高了。

2015年9月16日 星期三

HTTP POST 使用的編碼格式

三不五時會用到相關的東西, 所以花了點時間查清楚, 本篇重點是參考資料。

用 HTTP 的 POST 的時候, 參數會放在 request body, 然後用 HTTP header Content-Type 決定 request body 編碼方式。

常用的格式 ( Content-Type) 有兩種:

  • application/x-www-form-urlencoded: HTML form 的預設值。和 GET 在 URL 後面帶的參數差不多, 用 URL encoding 編碼 (主要差異是空白字元用 '+' 取代), 適合用在簡單的資料。
  • multipart/form-data: 適合用在 binary data, 上傳檔案時要改用這個, 比較省空間 (檔案內容不需另外編碼)。

application/x-www-form-urlencoded 用 URL encoding 編碼, 遇到大量 binary 資料時, 資料會變大很多 (可能會到三倍)。若先用 base64 [*1] 編碼再套 URL encoding 會好一些 (變成 4/3 倍), 但還是沒有 multipart/form-data 省。

除了上述的格式也可以用 application/json 或其它格式, 不過得看 web server 是否支援。JSON 不支援 binary data, 所以有 binary data 時要先用 base64 編碼, binary 資料一樣會變 4/3 倍大。雖然 base64 會增加 1/3 倍的資料量, 但它簡單易用, 我個人滿喜歡 base64 的設計。

若目的是要壓縮上傳的文字資料, 除了 application layer 自己壓縮資料外, 也可以在 transport layer 作: 使用 HTTP header Content-Encoding: gzip 然後用 gzip 壓縮 request body [*2]。雖然 response 很常用 Content-Encoding: gzip, 但 request 很少這麼用的樣子, 需要改 web server 設定才能作用。如 apache2: 需另外設定 Input Decompression

備註

1. 嚴格來說, 要用 base64url (最後兩個 byte 不同), 對 URL encoding 比較安全。Google API 有用這個, 轉換的時候要留意。

2. 另一個選擇是用 Transfer-Encoding: gzip, 這個作法語意比較正確, 但一來實作不如 Content-Encoding: gzip 常見, 二來 Content-Encoding 是 end-to-end, 不用擔心 Proxy 改變內容, 所以還是用 Content-Encoding 較好。

參考資料

POST Content-Type 相關:

Content-EncodingTransfer-Encoding 相關:

其它:

2015年9月3日 星期四

Mac OS X 雜項常識

備忘用, 持續更新。

硬體列表 (裝置沒反應時可看硬體形號找 driver)

左上角 Apple 圖示 -> About This Mac -> System Report -> Hardware

分享無線網路 (當作 hotspot )

System Preference -> Internet -> Internet Sharing。開啟 Internet Sharing 後, 自訂的 pf rules 會失效。

備忘 nginx 設定

未來的某天應該會需要更了解 nginx, 現階段先隨便備忘用到的東西。

request route

參考資料

用 try_files 設定 URI 對到不同 URI 的順序; 用 rewrite 改寫多種組合到同一位置。兩者都可用來實現 request route。

2015年9月1日 星期二

用 mtr 找出 routing 中有問題的節點

以前只知道用 traceroute 和 ping, 但用 traceroute 看完資訊後也不太清楚情況。用 "traceroute better" 搜了一下, 找到 mtr, 試了一下挺確不錯用。這裡有篇介紹: How To Use Traceroute and MTR to Diagnose Network Issues

備忘指令

  • $ mtr HOSTNAME # 看 realtime 變化
  • $ mtr -w HOSTNAME # 搜集一段時間內的資料

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...