https://en.wikipedia.org/wiki/Man-in-the-middle_attack

中间人攻击(Man-in-the-middle attack,缩写:MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

HTTP(S)中间人代理

HTTP

HTTP 是超文本传输协议,数据在客户端(比如浏览器)和服务器之间传输时是明文的。想要监听 HTTP 流量实现起来非常简单,只要充当 Proxy Server 拦截网络流量,接受并解析 HTTP 请求转发给目标服务器,把真正的服务器响应内容转发给客户端,或者伪造的响应给客户端。

下面以 mitmproxy 为例子:

HTTPS

HTTPS 是基于 HTTP 的安全版本,加了 SSL/TLS 加密层:

SSL/TLS 的功能实现主要依赖于三类基本算法,『散列函数』、『对称加密』和『非对称加密』,其利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。

(from: https://heptaluan.github.io/2020/08/09/HTTP/09/

HTTPS 是如何请求的

HTTPS 的请求过程比 HTTP 多了一些步骤,主要包括建立安全连接和数据传输两部分:

数字证书链

补充说明:实际部署中通常由“中间 CA”签发服务器证书。客户端会构建从服务器证书到根 CA 的完整链并进行主机名校验(优先检查证书的 SAN),还可能进行 OCSP 或 CRL 状态检查以确认证书未被吊销。

数字证书链的核心是证书中心(certificate authority,简称CA),合法CA的公钥是预存在操作系统和浏览器里的,只有通过了CA认证的服务器公钥才被浏览器客户端认为是可信的公钥。认证的原理很简单,依然是公私钥原理。CA拿自己的私钥去给需要认证的服务器公钥签名,生成一个“数字证书”。数字证书是包含了CA的签名,服务器自身公钥等等信息的集合体。浏览器拿着CA的公钥去验证该签名。只有被CA公钥验证通过的证书才是可信任的证书。

(from https://github.com/wuchangming/https-mitm-proxy-handbook/blob/master/doc/Chapter3.md

伪造并信任CA证书

🔒安全提示:生成的根 CA 私钥仅用于本地调试,务必妥善保管并限制文件权限。不要在生产或不受控环境中安装或分发自签 CA。滥用可能带来严重安全与合规风险。

让自定义的CA证书得到了客户端的信任,就能用CA证书签发各种“伪造”的服务器证书。

使用 github.com/google/martian 库来生成CA根证书:

package cert

import (
	"crypto/x509"
	"encoding/pem"
	"os"
	"testing"
	"time"

	"github.com/google/martian/v3/mitm"
)

func TestGenCA(t *testing.T) {
	x509c, priv, err := mitm.NewAuthority("zznq.mitm", "ZZNQ MITM", 10*365*24*time.Hour)
	if err != nil {
		t.Fatal(err)
	}

	certOut, err := os.Create("./ca.pem")
	if err != nil {
		t.Fatal(err)
	}
	defer certOut.Close()
	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: x509c.Raw})

	keyOut, err := os.Create("./ca.key")
	if err != nil {
		t.Fatal(err)
	}
	defer keyOut.Close()

	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
}

在对应操作系统上安装并信任证书:

如何劫持HTTPS流量

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

以 mitmproxy 为例:

  1. 客户端与 mitmproxy 建立连接,并发出 HTTP CONNECT 请求。
  2. Mitmproxy 以 200 Connection Established 进行响应,就像它已经设置了 CONNECT 管道一样。客户端认为它正在与远程服务器通信,并启动 TLS 连接。它使用 SNI 来指示它要连接到的主机名。
  3. Mitmproxy 连接到服务器,并使用客户端指示的 SNI 主机名建立 TLS 连接。
  4. 服务器使用匹配的证书进行响应,该证书包含生成拦截证书所需的 CN 和 SAN 值。
  5. Mitmproxy 生成拦截证书,并继续在步骤 3 中暂停的客户端 TLS 握手。
  6. 客户端通过已建立的 TLS 连接发送请求。
  7. Mitmproxy 通过步骤 4 中启动的 TLS 连接将请求传递到服务器。

使用martian实现

package main

import (
	"crypto/tls"
	"crypto/x509"
	"log"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/google/martian/v3"
	"github.com/google/martian/v3/mitm"
)

var defaultTimeout = 5 * time.Second

func main() {
	skipTLSVerify := true
	parentProxy := ""
	certFile := "./ca.pem"
	keyFile := "./ca.key"

	proxy := martian.NewProxy()
	proxy.SetRoundTripper(&http.Transport{
		MaxIdleConns:          100,
		TLSHandshakeTimeout:   defaultTimeout,
		ExpectContinueTimeout: defaultTimeout,
		ResponseHeaderTimeout: defaultTimeout,
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: skipTLSVerify,
		},
	})

	if parentProxy != "" {
		proxyURL, err := url.Parse(parentProxy)
		if err != nil {
			log.Fatal(err)
		}
		proxy.SetDownstreamProxy(proxyURL)
	}

	// config mitm cert file
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		log.Fatal(err)
	}
	x509c, err := x509.ParseCertificate(cert.Certificate[0])
	if err != nil {
		log.Fatal(err)
	}
	tlscnf, err := mitm.NewConfig(x509c, cert.PrivateKey)
	if err != nil {
		log.Fatal(err)
	}
	tlscnf.SkipTLSVerify(skipTLSVerify)
	proxy.SetMITM(tlscnf)

	// set request, response modifier
	proxy.SetRequestModifier(martian.RequestModifierFunc(
		func(req *http.Request) error {
			log.Printf("[mitm] modify request - method: %s url: %s", req.Method, req.URL.String())
			return nil
		}))
	proxy.SetResponseModifier(martian.ResponseModifierFunc(
		func(res *http.Response) error {
			log.Printf("[mitm] modify response - method: %s url: %s status: %d", res.Request.Method, res.Request.URL.String(), res.StatusCode)
			return nil
		}))

	// start proxy server
	listener, err := net.Listen("tcp", ":8081")
	if err != nil {
		log.Fatalf("listen error: %v", err)
	}
	defer listener.Close()

	log.Printf("proxy server listen on %s", listener.Addr().String())
	if err := proxy.Serve(listener); err != nil {
		log.Fatalf("proxy serve error: %v", err)
	}
}

运行测试HTTPS中间人代理:

⚠️该示例将上游 TLS 校验设置为 InsecureSkipVerify=true,仅用于本地调试。生产环境请关闭此选项,或配置受信任的上游 CA 与证书校验,以防止上游被劫持。

透明代理

透明代理(Transparent Proxy)是一种网络代理方式,其特点是客户端无需手动配置代理设置,网络流量就会被自动拦截并通过代理服务器处理。

透明 HTTP(S)

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

使用透明代理时,连接将重定向到网络层的代理,而无需任何客户端配置。
通过路由(routing)的机制将原始目标端口为80,443等连接重定向到Proxy server,然后按照显式 HTTPS 连接来建立 CN 和 SAN,并处理 SNI。

流量重定向

https://docs.mitmproxy.org/stable/howto-transparent/

Local capture

https://github.com/mitmproxy/mitmproxy_rs

WireGuard

https://www.wireguard.com/

WireGuard 是一种现代 VPN 协议,运行在网络层(Layer 3),通过用户空间或内核实现高效的 IP 数据包隧道传输。mitmproxy 的 WireGuard 模式利用 WireGuard 的隧道功能,将客户端的网络流量路由到 mitmproxy,然后由 mitmproxy 进行代理处理(如解密 HTTPS、记录流量等),最后转发到目标服务器。

适合对其它设备(Android, iOS)流量进行代理,如果 WireGuard 和 mitmproxy 运行在同一设备上,会导致数据包会循环路由。

如何基于TUN自己实现透明代理

TUN

TUN/TAP 是操作系统内核中的虚拟网络设备,由软件进行实现,向操作系统和应用程序提供与硬件网络设备完全相同的功能。其中 TAP 是以太网设备(二层设备),操作和封装以太网数据帧,TUN 则是网络层设备(Layer 3),操作和封装网络层数据帧。

在操作系统中,TUN 设备允许用户空间程序(如 VPN 客户端)读写网络数据包,通常以 IP 数据包的形式(区别于 TAP 设备,TAP 工作在更低的以太网帧级别)。

(from https://paper.seebug.org/1648/

什么是VPN?

VPN 全称为虚拟私人网络(Virtual Private Network),常用于连接中、大型企业或团体间私人网络的通讯方法,利用隧道协议(Tunneling Protocol)来达到发送端认证、消息保密与准确性等功能。

(from https://paper.seebug.org/1648/

VPN的核心工作原理就依赖于TUN设备,通过 TUN 你数据(Layer 3)打包(封装、加密)后通过公共网络发送到VPN服务器,再由服务器解包并转发到目标地址。

设计思路

这里要非常小心数据包循环路由,就是通过 Proxy 发出的流量又根据路由表规则再次经过 TUN 重新回到了Proxy段。

使用 wireguard-go 创建 tun 设备,基于 gvisor 的 TCP/IP 协议栈处理IP层流量,还是使用 martian 实现HTTPS中间人。区别呢就是不需要再去 Listen 一个端口,去实现一个满足 func (p *Proxy) Serve(l net.Listener) 入参的 net.Listener 接口就行。

引用资源