mitm

package module
v0.0.0-...-fdcf39d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 19, 2024 License: MIT Imports: 21 Imported by: 0

README

mitm-go

mitm-go is a library to implement an MITM (Man-in-the-Middle) server.
When you can intercept TLS communications, it provides abstractions for decrypting them, and performing man-in-the-middle attacks.

Overview

The main APIs are NewTLSServer and NewTLSListener.

When you call NewTLSServer on a net.Conn corresponding to the intercepted communication from a client, it returns a tls.Conn. Normally, the communication using such a tls.Conn would fail because it lacks a valid certificate. However, by generating a self-signed certificate corresponding to the actual server and performing a handshake, the communication succeeds (provided that the client trusts the CA certificate used for signing).

l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 443})
if err != nil {
  panic(err)
}
defer l.Close()

for {
  conn, err := l.Accept()
  if err != nil {
    break
  }

  go func() {
    defer conn.Close()

    tlsConn, err := mitm.NewTLSServer(conn, &mitm.TLSConfig{
      RootCertificate: mitmCACert, // self-signed certificate
      GetDestination: func(conn net.Conn, serverName string) net.Addr {
        return conn.LocalAddr() // e.g. [TPROXY](https://docs.kernel.org/networking/tproxy.html)
      },
    })
    if err != nil {
      return
    }
    defer tlsConn.Close()

    // ...
  }()
}

NewTLSListener is the API used for net.Listener. The above code can be rewritten as follows:

l, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 443})
if err != nil {
  panic(err)
}

l, err = mitm.NewTLSListener(l, &mitm.TLSConfig{
  RootCertificate: mitmCACert, // self-signed certificate
  GetDestination: func(conn net.Conn, serverName string) net.Addr {
    return conn.LocalAddr() // e.g. [TPROXY](https://docs.kernel.org/networking/tproxy.html)
  },
})
if err != nil {
  panic(err)
}
defer l.Close()

for {
  conn, err := l.Accept()
  if err != nil {
    break
  }

  go func() {
    defer conn.Close()

    // ...
  }()
}

Technically, this library implements the same methods described in the mitmproxy documentation, so please refer to it as well.

The abstractions provided by tls.Conn and net.Listener are powerful enough to allow you to write various types of proxies using these APIs. Currently, the following HTTP proxies are implemented:

  • HTTP(S) proxy using HTTP/1.1 CONNECT
  • HTTP(S) proxy using HTTP/2 CONNECT
  • Transparent HTTP(S) proxy

TODO

  • QUIC support
  • HTTP/3 proxy
  • transparent HTTP proxy sample on Windows/macOS
  • SOCKS5 proxy

LICENSE

MIT

Documentation

Overview

Example (MemorizingReader_Memorized)
mr := mitm.NewMemorizingReader(strings.NewReader("Hello, World!"), nil)
io.ReadAll(mr)
mr.Seek(0, io.SeekStart)
fmt.Println(string(mr.Memorized()))
Output:

Hello, World!
Example (MemorizingReader_OneTimeReader)
mr := mitm.NewMemorizingReader(strings.NewReader("Hello, World!"), nil)
bs, err := mr.Next(5)
if err != nil {
	log.Fatal(err)
}
fmt.Println(string(bs))

// read from the beginning again without memorizing.
if _, err = mr.Seek(0, io.SeekStart); err != nil {
	log.Fatal(err)
}
if _, err := io.Copy(os.Stdout, mr.OneTimeReader()); err != nil {
	log.Fatal(err)
}
Output:

Hello
Hello, World!

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultGetTLSClientConfig = func(serverName string, alpnProtocols []string) *tls.Config {
	return &tls.Config{
		ServerName: serverName,
		NextProtos: alpnProtocols,
	}
}
View Source
var DefaultGetTLSServerConfig = func(certificate *tls.Certificate, negotiatedProtocol string, err error) *tls.Config {
	config := &tls.Config{}

	if err != nil {
		config.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
			return nil, err
		}
		return config
	}

	if negotiatedProtocol != "" {
		config.NextProtos = []string{negotiatedProtocol}
	}

	if certificate != nil {
		config.Certificates = []tls.Certificate{*certificate}
	}

	return config
}
View Source
var (
	ErrHandshakeWithServer = errors.New("mitm: upstream certificate sniffing failed")
)
View Source
var (
	ErrInvalidQUICConfig = errors.New("invalid mitm.QUICConfig")
)
View Source
var (
	ErrInvalidTLSConfig = errors.New("invalid mitm.TLSConfig")
)

Functions

func CreateCACert

func CreateCACert(subject pkix.Name, duration time.Duration) (cert tls.Certificate, err error)

func CreateCACertPEM

func CreateCACertPEM(subject pkix.Name, duration time.Duration) (*bytes.Buffer, *bytes.Buffer, error)

func ForgeCertificate

func ForgeCertificate(root *tls.Certificate, orig *x509.Certificate) (tls.Certificate, error)

ForgeCertificate creates a new tls.Certificate that looks like the original certificate but signed by the specified root.

func ListenTCPTProxy

func ListenTCPTProxy(network string, laddr *net.TCPAddr) (*net.TCPListener, error)

func ListenUDPTProxy

func ListenUDPTProxy(network string, laddr *net.UDPAddr) (*net.UDPConn, error)

func LoadCertificate

func LoadCertificate(certPath, keyPath string) (cert tls.Certificate, err error)

LoadCertificate loads a certificate from the specified files. The returned certificate has the Leaf field set.

func NewMemorizingReader

func NewMemorizingReader(r io.Reader, buf []byte) *memorizingReader

NewMemorizingReader returns a new memorizingReader.

func NewQUICListener

func NewQUICListener(conn net.PacketConn, config *QUICConfig) (*quicListener, error)

func NewTLSListener

func NewTLSListener(l net.Listener, config *TLSConfig) (net.Listener, error)

NewTLSListener returns a new net.Listener that listens for incoming TLS connections on l.

Example
rootCACert := mustLoadCACert("root")
mitmCACert := mustLoadCACert("mitm")

defaultPool := x509.NewCertPool()
defaultPool.AddCert(rootCACert.Leaf)

mitmPool := x509.NewCertPool()
mitmPool.AddCert(mitmCACert.Leaf)

// true server
l1, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
	panic(err)
}
trueCert := mustIssueLocalCert(rootCACert, "echo")
l1 = tls.NewListener(l1, &tls.Config{
	Certificates: []tls.Certificate{*trueCert},
})
defer l1.Close()

go func() {
	for {
		conn, err := l1.Accept()
		if err != nil {
			break
		}

		go func() {
			defer conn.Close()
			io.Copy(conn, conn) // echo
		}()
	}
}()

// MITM server
l2, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
	panic(err)
}

l2, err = mitm.NewTLSListener(l2, &mitm.TLSConfig{
	RootCertificate: mitmCACert, // self-signed certificate
	GetClientConfig: func(serverName string, alpnProtocols []string) *tls.Config {
		c := mitm.DefaultGetTLSClientConfig(serverName, alpnProtocols)
		c.RootCAs = defaultPool
		return c
	},
	GetDestination: func(conn net.Conn, serverName string) net.Addr {
		return l1.Addr()
	},
})
if err != nil {
	panic(err)
}
defer l2.Close()

go func() {
	for {
		conn, err := l2.Accept()
		if err != nil {
			break
		}

		go func() {
			defer conn.Close()

			io.Copy(os.Stdout, conn)
			conn.Write([]byte("Bye!\n"))
		}()
	}
}()

clientConn, err := tls.Dial(l2.Addr().Network(), l2.Addr().String(), &tls.Config{
	ServerName: "echo",
	RootCAs:    mitmPool,
})
if err != nil {
	panic(err)
}
defer clientConn.Close()

clientConn.Write([]byte("Hello, World!\n"))
clientConn.CloseWrite()
io.Copy(os.Stdout, clientConn)
Output:

Hello, World!
Bye!

func NewTLSServer

func NewTLSServer(conn net.Conn, config *TLSConfig) (*tls.Conn, error)

NewTLSServer returns a new TLS server connection. Unlike tls.Server, this will use a forged certificate to handle the connection as follows:

  1. Peek the ClientHello message from the connection.
  2. Sniff the certificate of the upstream server.
  3. Forge a certificate and returns tls.Server, which will reread the message from the beginning.
Example
rootCACert := mustLoadCACert("root")
mitmCACert := mustLoadCACert("mitm")

defaultPool := x509.NewCertPool()
defaultPool.AddCert(rootCACert.Leaf)

mitmPool := x509.NewCertPool()
mitmPool.AddCert(mitmCACert.Leaf)

// true server
l1, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
	panic(err)
}
trueCert := mustIssueLocalCert(rootCACert, "echo")
l1 = tls.NewListener(l1, &tls.Config{
	Certificates: []tls.Certificate{*trueCert},
})
defer l1.Close()

go func() {
	for {
		conn, err := l1.Accept()
		if err != nil {
			break
		}

		go func() {
			defer conn.Close()
			io.Copy(conn, conn) // echo
		}()
	}
}()

// MITM server
l2, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
	panic(err)
}
defer l2.Close()

go func() {
	for {
		conn, err := l2.Accept()
		if err != nil {
			break
		}

		go func() {
			defer conn.Close()

			tlsConn, err := mitm.NewTLSServer(conn, &mitm.TLSConfig{
				RootCertificate: mitmCACert, // self-signed certificate
				GetClientConfig: func(serverName string, alpnProtocols []string) *tls.Config {
					c := mitm.DefaultGetTLSClientConfig(serverName, alpnProtocols)
					c.RootCAs = defaultPool
					return c
				},
				GetDestination: func(conn net.Conn, serverName string) net.Addr {
					return l1.Addr()
				},
			})
			if err != nil {
				return
			}
			defer tlsConn.Close()

			io.Copy(os.Stdout, tlsConn)
			tlsConn.Write([]byte("Bye!\n"))
		}()
	}
}()

clientConn, err := tls.Dial(l2.Addr().Network(), l2.Addr().String(), &tls.Config{
	ServerName: "echo",
	RootCAs:    mitmPool,
})
if err != nil {
	panic(err)
}
defer clientConn.Close()

clientConn.Write([]byte("Hello, World!\n"))
clientConn.CloseWrite()
io.Copy(os.Stdout, clientConn)
Output:

Hello, World!
Bye!

Types

type Addr

type Addr struct {
	// contains filtered or unexported fields
}

func NewAddr

func NewAddr(network, str string) *Addr

func (*Addr) Network

func (a *Addr) Network() string

func (*Addr) String

func (a *Addr) String() string

type OneTimeListener

type OneTimeListener struct {
	// contains filtered or unexported fields
}

OneTimeListener is a net.Listener that accepts only one connection. It is useful when an existing API demands a net.Listener instead of a net.Conn, but you want to serve only one connection. After the first call to [Accept], all subsequent calls will be blocked until [Close] is called.

Example
server, client := net.Pipe()

done := make(chan struct{})
sconn := mitm.NewTamperedConn(server, mitm.TamperConnClose(func() error {
	err := server.Close()
	close(done)
	return err
}))
// when an existing API demands a net.Listener instead of a net.Conn, you can use OneTimeListener.
ln := mitm.NewOneTimeListener(sconn)

var wg sync.WaitGroup
wg.Add(1)

go func() {
	processListener(ln)
	wg.Done()
}()

client.Write([]byte("Hello"))
client.Close()
<-done
ln.Close()

wg.Wait()
Output:

Hello

func NewOneTimeListener

func NewOneTimeListener(conn net.Conn) *OneTimeListener

NewOneTimeListener returns a new OneTimeListener.

func (*OneTimeListener) Accept

func (l *OneTimeListener) Accept() (net.Conn, error)

Accept returns the underlying connection if it has not been accepted yet. Otherwise, it blocks until [Close] is called.

func (*OneTimeListener) Addr

func (l *OneTimeListener) Addr() net.Addr

Addr returns the local network address of the underlying connection.

func (*OneTimeListener) Close

func (l *OneTimeListener) Close() error

Close closes the listener. Any blocked [Accept] calls will be unblocked and return an error.

type ProxyConn

type ProxyConn struct {
	*TamperedConn
	// contains filtered or unexported fields
}

ProxyConn is a TamperedConn that has a destination address.

func NewProxyConn

func NewProxyConn(conn net.Conn, dstAddr net.Addr, opts ...TamperedConnOption) *ProxyConn

NewProxyConn returns a new ProxyConn. dstAddr is the destination address of the connection. opts is used to change the behaviour of the underlying TamperedConn.

func (*ProxyConn) Destination

func (c *ProxyConn) Destination() net.Addr

Destination returns the destination address of the connection.

type QUICConfig

type QUICConfig struct {
	// RootCertificate is the root certificate to be used to forge certificates.
	RootCertificate *tls.Certificate

	// GetDestination specifies a function that returns the destination address of the connection.
	GetDestination func(conn net.Conn, serverName string) net.Addr

	// NextProtos is a list of supported ALPN protocols.
	// If it is empty, the client specified list is used to negotiate the protocol with the actual server.
	NextProtos []string

	// TLSServerConfig optionally specifies a tls.Config that is used to handle incoming connections.
	// That is, the tls.Config is used when the server is acting as a TLS server.
	TLSServerConfig *tls.Config

	// GetTLSClientConfig optionally specifies a function that returns a tls.Config that is used to dial the actual server.
	// That is, the returned tls.Config is used when the server is acting as a TLS client.
	GetTLSClientConfig func(serverName string, alpnProtocols []string) *tls.Config

	// ServerConfig optionally specifies a quic.Config that is used to handle incoming connections.
	ServerConfig *quic.Config

	// ClientConfig optionally specifies a quic.Config that is used to dial the actual server.
	ClientConfig *quic.Config

	// ServerInfoCache optionally specifies a cache that stores the information of the actual servers.
	// If not set, a new cache is created.
	ServerInfoCache ServerInfoCache
}

func (*QUICConfig) Clone

func (c *QUICConfig) Clone() *QUICConfig

type QUICListener

type QUICListener interface {
	Accept(context.Context) (quic.Connection, error)
	Close() error
	Addr() net.Addr
}

type ServerInfoCache

type ServerInfoCache map[string]serverInfo

type TLSConfig

type TLSConfig struct {
	// RootCertificate is the root certificate to be used to forge certificates.
	RootCertificate *tls.Certificate

	// GetDestination specifies a function that returns the destination address of the connection.
	GetDestination func(conn net.Conn, serverName string) net.Addr

	// NextProtos is a list of supported ALPN protocols.
	// If it is empty, the client specified list is used to negotiate the protocol with the actual server.
	NextProtos []string

	// GetServerConfig optionally specifies a function that returns a tls.Config that is used to handle incoming connections.
	// That is, the returned tls.Config is used when the server is acting as a TLS server.
	GetServerConfig func(certificate *tls.Certificate, negotiatedProtocol string, err error) *tls.Config

	// GetClientConfig optionally specifies a function that returns a tls.Config that is used to dial the actual server.
	// That is, the returned tls.Config is used when the server is acting as a TLS client.
	GetClientConfig func(serverName string, alpnProtocols []string) *tls.Config

	// ServerInfoCache optionally specifies a cache that stores the information of the actual servers.
	// If not set, a new cache is created.
	ServerInfoCache ServerInfoCache
}

func (*TLSConfig) Clone

func (c *TLSConfig) Clone() *TLSConfig

type TamperedConn

type TamperedConn struct {
	// contains filtered or unexported fields
}

TamperedConn is a net.Conn that can be tampered. Every net.Conn method can be replaced with a custom implementation.

func NewTamperedConn

func NewTamperedConn(conn net.Conn, opts ...TamperedConnOption) *TamperedConn

NewTamperedConn returns a new TamperedConn. opts is used to change the behaviour of the underlying net.Conn.

Example
server, client := net.Pipe()

done := make(chan struct{})
sconn := mitm.NewTamperedConn(server,
	mitm.TamperConnRead(func(bs []byte) (int, error) {
		n, err := server.Read(bs)
		// echo to stdout
		io.CopyN(os.Stdout, bytes.NewReader(bs), int64(n))
		return n, err
	}),
	mitm.TamperConnClose(func() error {
		err := server.Close()
		close(done)
		return err
	}))

go func() {
	defer sconn.Close()
	io.Copy(io.Discard, sconn)
}()

client.Write([]byte("Hello, World!\n"))
client.Close()
<-done
Output:

Hello, World!

func (*TamperedConn) Close

func (c *TamperedConn) Close() (err error)

func (*TamperedConn) LocalAddr

func (c *TamperedConn) LocalAddr() net.Addr

func (*TamperedConn) Read

func (c *TamperedConn) Read(b []byte) (int, error)

func (*TamperedConn) RemoteAddr

func (c *TamperedConn) RemoteAddr() net.Addr

func (*TamperedConn) SetDeadline

func (c *TamperedConn) SetDeadline(t time.Time) error

func (*TamperedConn) SetReadDeadline

func (c *TamperedConn) SetReadDeadline(t time.Time) error

func (*TamperedConn) SetWriteDeadline

func (c *TamperedConn) SetWriteDeadline(t time.Time) error

func (*TamperedConn) Write

func (c *TamperedConn) Write(b []byte) (int, error)

type TamperedConnOption

type TamperedConnOption func(*TamperedConn) error

func TamperConnClose

func TamperConnClose(f func() error) TamperedConnOption

TamperConnClose replaces the net.Conn.Close method with f.

func TamperConnLocalAddr

func TamperConnLocalAddr(f func() net.Addr) TamperedConnOption

TamperConnLocalAddr replaces the net.Conn.LocalAddr method with f.

func TamperConnRead

func TamperConnRead(f func(b []byte) (int, error)) TamperedConnOption

TamperConnRead replaces the net.Conn.Read method with f.

func TamperConnRemoteAddr

func TamperConnRemoteAddr(f func() net.Addr) TamperedConnOption

TamperConnRemoteAddr replaces the net.Conn.RemoteAddr method with f.

func TamperConnSetDeadline

func TamperConnSetDeadline(f func(t time.Time) error) TamperedConnOption

TamperConnSetDeadline replaces the net.Conn.SetDeadline method with f.

func TamperConnSetReadDeadline

func TamperConnSetReadDeadline(f func(t time.Time) error) TamperedConnOption

TamperConnSetReadDeadline replaces the net.Conn.SetReadDeadline method with f.

func TamperConnSetWriteDeadline

func TamperConnSetWriteDeadline(f func(t time.Time) error) TamperedConnOption

TamperConnSetWriteDeadline replaces the net.Conn.SetWriteDeadline method with f.

func TamperConnWrite

func TamperConnWrite(f func(b []byte) (int, error)) TamperedConnOption

TamperConnWrite replaces the net.Conn.Write method with f.

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL