diff --git a/modules/dns_spoof/dns_spoof.go b/modules/dns_spoof/dns_spoof.go index 8302474c..14d132e8 100644 --- a/modules/dns_spoof/dns_spoof.go +++ b/modules/dns_spoof/dns_spoof.go @@ -24,6 +24,7 @@ type DNSSpoofer struct { Hosts Hosts TTL uint32 All bool + ResolveAll bool waitGroup *sync.WaitGroup pktSourceChan chan gopacket.Packet } @@ -33,6 +34,7 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer { SessionModule: session.NewSessionModule("dns.spoof", s), Handle: nil, All: false, + ResolveAll: true, Hosts: Hosts{}, TTL: 1024, waitGroup: &sync.WaitGroup{}, @@ -64,6 +66,10 @@ func NewDNSSpoofer(s *session.Session) *DNSSpoofer { "^[0-9]+$", "TTL of spoofed DNS replies.")) + mod.AddParam(session.NewBoolParameter("dns.spoof.resolve_all", + "true", + "If true the module will resolve every DNS request, even if it is not covered by spoof list.")) + mod.AddHandler(session.NewModuleHandler("dns.spoof on", "", "Start the DNS spoofer in the background.", func(args []string) error { @@ -106,6 +112,8 @@ func (mod *DNSSpoofer) Configure() error { return err } else if err, mod.All = mod.BoolParam("dns.spoof.all"); err != nil { return err + } else if err, mod.ResolveAll = mod.BoolParam("dns.spoof.resolve_all"); err != nil { + return err } else if err, address = mod.IPParam("dns.spoof.address"); err != nil { return err } else if err, domains = mod.ListParam("dns.spoof.domains"); err != nil { @@ -287,6 +295,16 @@ func (mod *DNSSpoofer) onPacket(pkt gopacket.Packet) { } break } else { + if mod.ResolveAll { + ips, err := net.LookupIP(qName) + if err == nil && len(ips) > 0 { + redir, who := DnsReply(mod.Session, mod.TTL, pkt, eth, udp, qName, ips[0], dns, eth.SrcMAC) + if redir != "" && who != "" { + mod.Info("sending forward DNS reply for %s %s to %s.", tui.Red(qName), tui.Dim(redir), tui.Bold(who)) + } + break + } + } mod.Debug("skipping domain %s", qName) } } @@ -306,7 +324,7 @@ func (mod *DNSSpoofer) Start() error { src := gopacket.NewPacketSource(mod.Handle, mod.Handle.LinkType()) mod.pktSourceChan = src.Packets() for packet := range mod.pktSourceChan { - if !mod.Running() { + if !mod.Running() || packet == nil { break } diff --git a/modules/http_proxy/http_proxy.go b/modules/http_proxy/http_proxy.go index 150d9c34..b4a35102 100644 --- a/modules/http_proxy/http_proxy.go +++ b/modules/http_proxy/http_proxy.go @@ -2,6 +2,7 @@ package http_proxy import ( "github.com/bettercap/bettercap/session" + "github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/str" ) @@ -44,6 +45,11 @@ func NewHttpProxy(s *session.Session) *HttpProxy { "", "URL, path or javascript code to inject into every HTML page.")) + mod.AddParam(session.NewStringParameter("http.proxy.certificate", + "~/.bettercap-ca.cert.pem", + "", + "HTTP proxy certification authority TLS certificate file.")) + mod.AddParam(session.NewStringParameter("http.proxy.blacklist", "", "", "Comma separated list of hostnames to skip while proxying (wildcard expressions can be used).")) @@ -70,7 +76,7 @@ func NewHttpProxy(s *session.Session) *HttpProxy { return mod.Stop() })) - mod.InitState("stripper") + mod.InitState("stripper") return mod } @@ -94,6 +100,7 @@ func (mod *HttpProxy) Configure() error { var httpPort int var doRedirect bool var scriptPath string + var certFile string var stripSSL bool var useIDN bool var jsToInject string @@ -118,6 +125,10 @@ func (mod *HttpProxy) Configure() error { return err } else if err, jsToInject = mod.StringParam("http.proxy.injectjs"); err != nil { return err + } else if err, certFile = mod.StringParam("http.proxy.certificate"); err != nil { + return err + } else if certFile, err = fs.Expand(certFile); err != nil { + return err } else if err, blacklist = mod.StringParam("http.proxy.blacklist"); err != nil { return err } else if err, whitelist = mod.StringParam("http.proxy.whitelist"); err != nil { @@ -127,7 +138,7 @@ func (mod *HttpProxy) Configure() error { mod.proxy.Blacklist = str.Comma(blacklist) mod.proxy.Whitelist = str.Comma(whitelist) - error := mod.proxy.Configure(address, proxyPort, httpPort, doRedirect, scriptPath, jsToInject, stripSSL, useIDN) + error := mod.proxy.Configure(address, proxyPort, httpPort, doRedirect, scriptPath, jsToInject, stripSSL, useIDN, certFile) // save stripper to share it with other http(s) proxies mod.State.Store("stripper", mod.proxy.Stripper) diff --git a/modules/http_proxy/http_proxy_base.go b/modules/http_proxy/http_proxy_base.go index b810f864..43d34a64 100644 --- a/modules/http_proxy/http_proxy_base.go +++ b/modules/http_proxy/http_proxy_base.go @@ -94,8 +94,13 @@ func NewHTTPProxy(s *session.Session, tag string) *HTTPProxy { if !p.isTLS { req.URL.Scheme = "http" } + // Clone it. req.URL.Host = req.Host - p.Proxy.ServeHTTP(w, req) + if req.Host == "mitm.it" { + p.GetCert(w, req) + } else { + p.Proxy.ServeHTTP(w, req) + } } }) @@ -106,6 +111,24 @@ func NewHTTPProxy(s *session.Session, tag string) *HTTPProxy { return p } +func (p *HTTPProxy) GetCert(w http.ResponseWriter, req *http.Request) { + if p.CertFile != "" { + respHeader := w.Header() + respHeader.Set("Content-Type", "application/x-x509-ca-cert") + if strings.Index(req.UserAgent(), "Windows NT") != -1 { + // Return cer certificate format. + respHeader.Set("Content-Disposition", "inline; filename=ca-cert.cer") + } else { + // Return pem certificate format. + respHeader.Set("Content-Disposition", "inline; filename=ca-cert.pem") + } + rawCert, _ := ioutil.ReadFile(p.CertFile) + _, _ = w.Write(rawCert) + } else { + http.NotFound(w, req) + } +} + func (p *HTTPProxy) Debug(format string, args ...interface{}) { p.Sess.Events.Log(log.DEBUG, p.tag+format, args...) } @@ -170,12 +193,12 @@ func (p *HTTPProxy) shouldProxy(req *http.Request) bool { } func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, doRedirect bool, scriptPath string, - jsToInject string, stripSSL bool, useIDN bool) error { + jsToInject string, stripSSL bool, useIDN bool, certFile string) error { var err error // check if another http(s) proxy is using sslstrip and merge strippers if stripSSL { - for _, mname := range []string{"http.proxy", "https.proxy"}{ + for _, mname := range []string{"http.proxy", "https.proxy"} { err, m := p.Sess.Module(mname) if err == nil && m.Running() { var mextra interface{} @@ -197,6 +220,8 @@ func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, doRed p.doRedirect = doRedirect p.jsHook = "" + p.CertFile = certFile + if strings.HasPrefix(jsToInject, "http://") || strings.HasPrefix(jsToInject, "https://") { p.jsHook = fmt.Sprintf("", jsToInject) } else if fs.Exists(jsToInject) { @@ -298,7 +323,7 @@ func (p *HTTPProxy) TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx * func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, doRedirect bool, scriptPath string, certFile string, keyFile string, jsToInject string, stripSSL bool, useIDN bool) (err error) { - if err = p.Configure(address, proxyPort, httpPort, doRedirect, scriptPath, jsToInject, stripSSL, useIDN); err != nil { + if err = p.Configure(address, proxyPort, httpPort, doRedirect, scriptPath, jsToInject, stripSSL, useIDN, certFile); err != nil { return err } diff --git a/network/net.go b/network/net.go index ef964f33..a576402a 100644 --- a/network/net.go +++ b/network/net.go @@ -303,7 +303,7 @@ func SetInterfaceTxPower(name string, txpower int) error { } func GatewayProvidedByUser(iface *Endpoint, gateway string) (*Endpoint, error) { - Debug("GatewayProvidedByUser(%s) [cmd=%v opts=%v parser=%v]", gateway, IPv4RouteCmd, IPv4RouteCmdOpts, IPv4RouteParser) + Debug("GatewayProvidedByUser(%s) [cmd=%v opts=%v]", gateway, IPv4RouteCmd, IPv4RouteCmdOpts) if IPv4Validator.MatchString(gateway) { Debug("valid gateway ip %s", gateway) // we have the address, now we need its mac diff --git a/network/net_gateway.go b/network/net_gateway.go index 1d7b6042..89bb8526 100644 --- a/network/net_gateway.go +++ b/network/net_gateway.go @@ -3,17 +3,17 @@ package network import ( + "bufio" + "fmt" "strings" "github.com/bettercap/bettercap/core" - - "github.com/evilsocket/islazy/str" ) func FindGateway(iface *Endpoint) (*Endpoint, error) { - Debug("FindGateway(%s) [cmd=%v opts=%v parser=%v]", iface.Name(), IPv4RouteCmd, IPv4RouteCmdOpts, IPv4RouteParser) + Debug("FindGateway(%s) [cmd=%v opts=%v]", iface.Name(), IPv4RouteCmd, IPv4RouteCmdOpts) - output, err := core.Exec(IPv4RouteCmd, IPv4RouteCmdOpts) + output, err := core.ExecInEnglish(IPv4RouteCmd, append(IPv4RouteCmdOpts, fmt.Sprintf("%d", iface.Index))) if err != nil { Debug("FindGateway(%s): core.Exec failed with %s", err) return nil, err @@ -22,25 +22,24 @@ func FindGateway(iface *Endpoint) (*Endpoint, error) { Debug("FindGateway(%s) output:\n%s", iface.Name(), output) ifName := iface.Name() - for _, line := range strings.Split(output, "\n") { - if line = str.Trim(line); strings.Contains(line, ifName) { - m := IPv4RouteParser.FindStringSubmatch(line) - if len(m) >= IPv4RouteTokens { - Debug("FindGateway(%s) line '%s' matched with %v", iface.Name(), line, m) - return IPv4RouteIsGateway(ifName, m, func(gateway string) (*Endpoint, error) { - if gateway == iface.IpAddress { - Debug("gateway is the interface") - return iface, nil - } else { - // we have the address, now we need its mac - mac, err := ArpLookup(ifName, gateway, false) - if err != nil { - return nil, err - } - Debug("gateway is %s[%s]", gateway, mac) - return NewEndpoint(gateway, mac), nil - } - }) + scanner := bufio.NewScanner(strings.NewReader(output)) + for scanner.Scan() { + keyPair := strings.Split(scanner.Text(), ":") + if len(keyPair) != 2 { + continue + } + key, value := strings.TrimSpace(keyPair[0]), strings.TrimSpace(keyPair[1]) + if key == "Default Gateway" { + if value == iface.IpAddress { + return iface, nil + } else { + // we have the address, now we need its mac + mac, err := ArpLookup(ifName, value, false) + if err != nil { + return nil, err + } + Debug("gateway is %s[%s]", value, mac) + return NewEndpoint(value, mac), nil } } } diff --git a/network/net_windows.go b/network/net_windows.go index 5fe544c8..e0bda49d 100644 --- a/network/net_windows.go +++ b/network/net_windows.go @@ -3,17 +3,14 @@ package network import ( "fmt" "net" - "regexp" "strings" "github.com/google/gopacket/pcap" ) // only matches gateway lines -var IPv4RouteParser = regexp.MustCompile("^.+\\s+.+\\s+\\d+\\s+([0-9\\.]+/\\d+)\\s+\\d+\\s+([0-9\\.]+).*$") -var IPv4RouteTokens = 3 var IPv4RouteCmd = "netsh" -var IPv4RouteCmdOpts = []string{"interface", "ipv4", "show", "route"} +var IPv4RouteCmdOpts = []string{"interface", "ipv4", "show", "address"} func IPv4RouteIsGateway(ifname string, tokens []string, f func(gateway string) (*Endpoint, error)) (*Endpoint, error) { // TODO check if the subnet is the same as iface ? diff --git a/tls/cert.go b/tls/cert.go index acb585e8..1e69207f 100644 --- a/tls/cert.go +++ b/tls/cert.go @@ -3,8 +3,10 @@ package tls import ( "crypto/rand" "crypto/rsa" + "crypto/sha1" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/pem" "math/big" "os" @@ -74,6 +76,17 @@ func CertConfigFromModule(prefix string, m session.SessionModule) (cfg CertConfi return cfg, err } +var oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} +var oidNetscapeCertType = asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 1, 1} + +var netscapeCetSSLCA = []byte{3, 2, 2, 4} + +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + func CreateCertificate(cfg CertConfig, ca bool) (*rsa.PrivateKey, []byte, error) { priv, err := rsa.GenerateKey(rand.Reader, cfg.Bits) if err != nil { @@ -100,11 +113,41 @@ func CreateCertificate(cfg CertConfig, ca bool) (*rsa.PrivateKey, []byte, error) }, NotBefore: notBefore, NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageTimeStamping, x509.ExtKeyUsageMicrosoftCommercialCodeSigning, x509.ExtKeyUsageMicrosoftServerGatedCrypto, x509.ExtKeyUsageNetscapeServerGatedCrypto}, BasicConstraintsValid: true, IsCA: ca, } + // We can only remove this once we move to go 1.15. + if ca && len(template.SubjectKeyId) == 0 { + // SubjectKeyId generated using method 1 in RFC 5280, Section 4.2.1.2 + publicKeyBytes, err := asn1.Marshal(priv.PublicKey) + if err != nil { + return nil, nil, err + } + // This is a NULL parameters value which is required by + // RFC 3279, Section 2.3.1. + publicKeyAlgorithm := pkix.AlgorithmIdentifier{ + Algorithm: oidPublicKeyRSA, + Parameters: asn1.NullRawValue, + } + encodedPublicKey := asn1.BitString{BitLength: len(publicKeyBytes) * 8, Bytes: publicKeyBytes} + pki := publicKeyInfo{nil, publicKeyAlgorithm, encodedPublicKey} + b, err := asn1.Marshal(pki) + if err != nil { + return nil, nil, err + } + h := sha1.Sum(b) + template.SubjectKeyId = h[:] + } + + if ca { + template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{ + Id: oidNetscapeCertType, + Critical: false, + Value: netscapeCetSSLCA, + }) + } cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { diff --git a/tls/sign.go b/tls/sign.go index c2cdbe74..24f09a71 100644 --- a/tls/sign.go +++ b/tls/sign.go @@ -63,7 +63,7 @@ func SignCertificateForHost(ca *tls.Certificate, host string, port int) (cert *t }, NotBefore: notBefore, NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } @@ -90,7 +90,7 @@ func SignCertificateForHost(ca *tls.Certificate, host string, port int) (cert *t } var certpriv *rsa.PrivateKey - if certpriv, err = rsa.GenerateKey(rand.Reader, 1024); err != nil { + if certpriv, err = rsa.GenerateKey(rand.Reader, 2048); err != nil { return }