diff --git a/caplets/http-req-dump.cap b/caplets/http-req-dump.cap index e2d21e71..7e98ce7b 100644 --- a/caplets/http-req-dump.cap +++ b/caplets/http-req-dump.cap @@ -2,21 +2,22 @@ # # sudo ./bettercap-ng -caplet caplets/http-req-dump.cap -eval "set arp.spoof.targets 192.168.1.64" -events.stream off +# events.stream off net.recon on net.probe on sleep 1 net.probe off -set net.sniff.verbose false -set net.sniff.local true -set net.sniff.filter tcp port 443 -net.sniff on +# set net.sniff.verbose false +# set net.sniff.local true +# set net.sniff.filter tcp port 443 +# net.sniff on set https.proxy.script caplets/http-req-dump.js set http.proxy.script caplets/http-req-dump.js clear + http.proxy on https.proxy on arp.spoof on diff --git a/modules/arp_spoof.go b/modules/arp_spoof.go index 56021b93..88ce2ce5 100644 --- a/modules/arp_spoof.go +++ b/modules/arp_spoof.go @@ -103,7 +103,9 @@ func (p *ArpSpoofer) getMAC(ip net.IP, probe bool) (net.HardwareAddr, error) { func (p *ArpSpoofer) sendArp(saddr net.IP, smac net.HardwareAddr, check_running bool, probe bool) { for _, ip := range p.addresses { - if p.shouldSpoof(ip) == false { + if check_running && p.Running() == false { + return + } else if p.shouldSpoof(ip) == false { log.Debug("Skipping address %s from ARP spoofing.", ip) continue } @@ -121,10 +123,6 @@ func (p *ArpSpoofer) sendArp(saddr net.IP, smac net.HardwareAddr, check_running log.Debug("Sending %d bytes of ARP packet to %s:%s.", len(pkt), ip.String(), hw.String()) p.Session.Queue.Send(pkt) } - - if check_running && p.Running() == false { - return - } } } diff --git a/modules/http_proxy_base.go b/modules/http_proxy_base.go index 0b386733..a4a9f942 100644 --- a/modules/http_proxy_base.go +++ b/modules/http_proxy_base.go @@ -1,20 +1,33 @@ package modules import ( + "bufio" + "bytes" "context" + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" + "net" "net/http" + "net/url" + "strconv" "strings" + "sync" "time" + "github.com/evilsocket/bettercap-ng/core" "github.com/evilsocket/bettercap-ng/firewall" "github.com/evilsocket/bettercap-ng/log" "github.com/evilsocket/bettercap-ng/session" + btls "github.com/evilsocket/bettercap-ng/tls" "github.com/elazarl/goproxy" + "github.com/inconshreveable/go-vhost" ) type HTTPProxy struct { + Name string Address string Server http.Server Redirection *firewall.Redirection @@ -23,12 +36,23 @@ type HTTPProxy struct { CertFile string KeyFile string - isTLS bool - sess *session.Session + isTLS bool + isRunning bool + sniListener net.Listener + sess *session.Session +} + +func stripPort(s string) string { + ix := strings.IndexRune(s, ':') + if ix == -1 { + return s + } + return s[:ix] } func NewHTTPProxy(s *session.Session) *HTTPProxy { p := &HTTPProxy{ + Name: "http.proxy", Proxy: goproxy.NewProxyHttpServer(), sess: s, isTLS: false, @@ -43,6 +67,7 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy { p.Proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) p.Proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { + log.Debug("(%s) < %s %s %s%s", core.Green(p.Name), req.RemoteAddr, req.Method, req.Host, req.URL.Path) if p.Script != nil { jsres := p.Script.OnRequest(req) if jsres != nil { @@ -54,6 +79,8 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy { }) p.Proxy.OnResponse().DoFunc(func(res *http.Response, ctx *goproxy.ProxyCtx) *http.Response { + req := res.Request + log.Debug("(%s) > %s %s %s%s", core.Green(p.Name), req.RemoteAddr, req.Method, req.Host, req.URL.Path) if p.Script != nil { jsres := p.Script.OnResponse(res) if jsres != nil { @@ -68,7 +95,7 @@ func NewHTTPProxy(s *session.Session) *HTTPProxy { } func (p *HTTPProxy) logAction(req *http.Request, jsres *JSResponse) { - p.sess.Events.Add("http.proxy.spoofed-response", struct { + p.sess.Events.Add(p.Name+".spoofed-response", struct { To string Method string Host string @@ -142,6 +169,65 @@ func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, scrip return nil } +var ( + certCache = make(map[string]*tls.Certificate) + certLock = &sync.Mutex{} +) + +func getCachedCert(domain string, port int) *tls.Certificate { + key := fmt.Sprintf("%s:%d", domain, port) + + certLock.Lock() + defer certLock.Unlock() + + if cert, found := certCache[key]; found == true { + return cert + } + return nil +} + +func setCachedCert(domain string, port int, cert *tls.Certificate) { + key := fmt.Sprintf("%s:%d", domain, port) + + certLock.Lock() + defer certLock.Unlock() + + certCache[key] = cert +} + +func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) { + return func(host string, ctx *goproxy.ProxyCtx) (c *tls.Config, err error) { + parts := strings.SplitN(host, ":", 2) + hostname := parts[0] + port := 443 + if len(parts) > 1 { + port, err = strconv.Atoi(parts[1]) + if err != nil { + port = 443 + } + } + + cert := getCachedCert(hostname, port) + if cert == nil { + log.Info("Creating spoofed certificate for %s:%d", core.Yellow(hostname), port) + cert, err = btls.SignCertificateForHost(ca, hostname) + if err != nil { + log.Warning("Cannot sign host certificate with provided CA: %s", err) + return nil, err + } + + setCachedCert(hostname, port, cert) + } + + config := tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{*cert}, + } + + return &config, nil + } +} + func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, scriptPath string, certFile string, keyFile string) error { err := p.Configure(address, proxyPort, httpPort, scriptPath) if err != nil { @@ -149,9 +235,105 @@ func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, sc } p.isTLS = true + p.Name = "https.proxy" p.CertFile = certFile p.KeyFile = keyFile + rawCert, _ := ioutil.ReadFile(p.CertFile) + rawKey, _ := ioutil.ReadFile(p.KeyFile) + + ourCa, err := tls.X509KeyPair(rawCert, rawKey) + if err != nil { + return err + } + + if ourCa.Leaf, err = x509.ParseCertificate(ourCa.Certificate[0]); err != nil { + return err + } + + goproxy.GoproxyCa = ourCa + goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: TLSConfigFromCA(&ourCa)} + goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: TLSConfigFromCA(&ourCa)} + goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&ourCa)} + goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: TLSConfigFromCA(&ourCa)} + + return nil +} + +func (p *HTTPProxy) httpWorker() error { + p.isRunning = true + return p.Server.ListenAndServe() +} + +type dumbResponseWriter struct { + net.Conn +} + +func (dumb dumbResponseWriter) Header() http.Header { + panic("Header() should not be called on this ResponseWriter") +} + +func (dumb dumbResponseWriter) Write(buf []byte) (int, error) { + if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) { + return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request + } + return dumb.Conn.Write(buf) +} + +func (dumb dumbResponseWriter) WriteHeader(code int) { + panic("WriteHeader() should not be called on this ResponseWriter") +} + +func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil +} + +func (p *HTTPProxy) httpsWorker() error { + var err error + + // listen to the TLS ClientHello but make it a CONNECT request instead + p.sniListener, err = net.Listen("tcp", p.Server.Addr) + if err != nil { + return err + } + + p.isRunning = true + for p.isRunning { + c, err := p.sniListener.Accept() + if err != nil { + log.Warning("Error accepting connection: %s.", err) + continue + } + + go func(c net.Conn) { + tlsConn, err := vhost.TLS(c) + if err != nil { + log.Warning("Error reading SNI: %s.", err) + return + } + + hostname := tlsConn.Host() + if hostname == "" { + log.Warning("Client does not support SNI.") + return + } + + log.Debug("Got new SNI from %s for %s", core.Bold(stripPort(c.RemoteAddr().String())), core.Yellow(hostname)) + + req := &http.Request{ + Method: "CONNECT", + URL: &url.URL{ + Opaque: hostname, + Host: net.JoinHostPort(hostname, "443"), + }, + Host: hostname, + Header: make(http.Header), + } + resp := dumbResponseWriter{tlsConn} + p.Proxy.ServeHTTP(resp, req) + }(c) + } + return nil } @@ -160,9 +342,9 @@ func (p *HTTPProxy) Start() { var err error if p.isTLS == true { - err = p.Server.ListenAndServeTLS(p.CertFile, p.KeyFile) + err = p.httpsWorker() } else { - err = p.Server.ListenAndServe() + err = p.httpWorker() } if err != nil { @@ -180,7 +362,13 @@ func (p *HTTPProxy) Stop() error { p.Redirection = nil } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - return p.Server.Shutdown(ctx) + if p.isTLS == true { + p.isRunning = false + p.sniListener.Close() + return nil + } else { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return p.Server.Shutdown(ctx) + } } diff --git a/modules/https_proxy.go b/modules/https_proxy.go index 1133a368..8240c6cb 100644 --- a/modules/https_proxy.go +++ b/modules/https_proxy.go @@ -32,14 +32,14 @@ func NewHttpsProxy(s *session.Session) *HttpsProxy { "Port to bind the HTTPS proxy to.")) p.AddParam(session.NewStringParameter("https.proxy.certificate", - "~/.bcap-https.proxy.certificate.pem", + "~/.bcap-https.proxy-ca.certificate.pem", "", - "HTTPS proxy TLS certificate.")) + "HTTPS proxy certification authority TLS certificate file.")) p.AddParam(session.NewStringParameter("https.proxy.key", - "~/.bcap-https.proxy.key.pem", + "~/.bcap-https.proxy-ca.key.pem", "", - "HTTPS proxy TLS key")) + "HTTPS proxy certification authority TLS key file.")) p.AddParam(session.NewStringParameter("https.proxy.script", "", @@ -111,14 +111,14 @@ func (p *HttpsProxy) Configure() error { } if core.Exists(certFile) == false || core.Exists(keyFile) == false { - log.Info("Generating proxy TLS key to %s", keyFile) - log.Info("Generating proxy TLS certificate to %s", certFile) + log.Info("Generating proxy certification authority TLS key to %s", keyFile) + log.Info("Generating proxy certification authority TLS certificate to %s", certFile) if err := tls.Generate(certFile, keyFile); err != nil { return err } } else { - log.Info("Loading proxy TLS key from %s", keyFile) - log.Info("Loading proxy TLS certificate from %s", certFile) + log.Info("Loading proxy certification authority TLS key from %s", keyFile) + log.Info("Loading proxy certification authority TLS certificate from %s", certFile) } return p.proxy.ConfigureTLS(address, proxyPort, httpPort, scriptPath, certFile, keyFile) diff --git a/modules/net_probe.go b/modules/net_probe.go index e2b0d30c..d61f6039 100644 --- a/modules/net_probe.go +++ b/modules/net_probe.go @@ -73,7 +73,7 @@ func (p *Prober) sendProbe(from net.IP, from_hw net.HardwareAddr, ip net.IP) { } else if con, err := net.DialUDP("udp", nil, addr); err != nil { log.Error("Could not dial %s.", name) } else { - log.Debug("UDP connection to %s enstablished.", name) + // log.Debug("UDP connection to %s enstablished.", name) defer con.Close() con.Write([]byte{0xde, 0xad, 0xbe, 0xef}) } diff --git a/packets/queue.go b/packets/queue.go index 731ddd2e..a26ce2d0 100644 --- a/packets/queue.go +++ b/packets/queue.go @@ -140,7 +140,7 @@ func (q *Queue) Send(raw []byte) error { if q.active { err := q.handle.WritePacketData(raw) - if err != nil { + if err == nil { q.Sent += uint64(len(raw)) } else { q.Errors += 1 diff --git a/session/session_core_handlers.go b/session/session_core_handlers.go index b4ac27e6..f2be1e09 100644 --- a/session/session_core_handlers.go +++ b/session/session_core_handlers.go @@ -81,7 +81,9 @@ func (s *Session) activeHandler(args []string, sess *Session) error { if len(params) > 0 { fmt.Println() for _, p := range params { - fmt.Printf(p.Dump(s.HelpPadding)) + _, val := s.Env.Get(p.Name) + fmt.Printf(" "+core.YELLOW+"%"+strconv.Itoa(s.HelpPadding)+"s"+core.RESET+ + " : %s\n", p.Name, val) } } diff --git a/tls/cert.go b/tls/cert.go index 990bab7a..cda2c127 100644 --- a/tls/cert.go +++ b/tls/cert.go @@ -50,6 +50,7 @@ func Generate(certPath string, keyPath string) error { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, + IsCA: true, } cert_raw, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) diff --git a/tls/sign.go b/tls/sign.go new file mode 100644 index 00000000..fff4a6fa --- /dev/null +++ b/tls/sign.go @@ -0,0 +1,89 @@ +package tls + +import ( + "crypto/rsa" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "net" + "sort" + "time" + + "github.com/elazarl/goproxy" +) + +func hashSorted(lst []string) []byte { + c := make([]string, len(lst)) + copy(c, lst) + sort.Strings(c) + h := sha1.New() + for _, s := range c { + h.Write([]byte(s + ",")) + } + return h.Sum(nil) +} + +func hashSortedBigInt(lst []string) *big.Int { + rv := new(big.Int) + rv.SetBytes(hashSorted(lst)) + return rv +} + +func SignCertificateForHost(ca *tls.Certificate, host string) (cert *tls.Certificate, err error) { + var x509ca *x509.Certificate + + // TODO: read actual fields from the host + + if x509ca, err = x509.ParseCertificate(ca.Certificate[0]); err != nil { + return + } + start := time.Unix(0, 0) + end, err := time.Parse("2006-01-02", "2049-12-31") + if err != nil { + panic(err) + } + + hosts := []string{host} + hash := hashSorted(hosts) + serial := new(big.Int) + serial.SetBytes(hash) + template := x509.Certificate{ + SerialNumber: serial, + Issuer: x509ca.Subject, + Subject: pkix.Name{ + Organization: []string{"Cisco Systems, Inc."}, + }, + NotBefore: start, + NotAfter: end, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + var csprng goproxy.CounterEncryptorRand + if csprng, err = goproxy.NewCounterEncryptorRandFromKey(ca.PrivateKey, hash); err != nil { + return + } + var certpriv *rsa.PrivateKey + if certpriv, err = rsa.GenerateKey(&csprng, 1024); err != nil { + return + } + var derBytes []byte + if derBytes, err = x509.CreateCertificate(&csprng, &template, x509ca, &certpriv.PublicKey, ca.PrivateKey); err != nil { + return + } + return &tls.Certificate{ + Certificate: [][]byte{derBytes, ca.Certificate[0]}, + PrivateKey: certpriv, + }, nil +}