diff --git a/firewall/firewall_linux.go b/firewall/firewall_linux.go index cd192870..b1a629be 100644 --- a/firewall/firewall_linux.go +++ b/firewall/firewall_linux.go @@ -74,7 +74,7 @@ func (f *LinuxFirewall) getCommandLine(r *Redirection, enabled bool) (cmdLine [] action, "PREROUTING", "-i", r.Interface, "-p", r.Protocol, - "--dport", fmt.Sprintf("%d", r.SrcPort), + "--dport", fmt.Sprintf("%s", r.SrcPort), "-j", "DNAT", "--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort), } @@ -85,7 +85,7 @@ func (f *LinuxFirewall) getCommandLine(r *Redirection, enabled bool) (cmdLine [] "-i", r.Interface, "-p", r.Protocol, "-d", r.SrcAddress, - "--dport", fmt.Sprintf("%d", r.SrcPort), + "--dport", fmt.Sprintf("%s", r.SrcPort), "-j", "DNAT", "--to", fmt.Sprintf("%s:%d", r.DstAddress, r.DstPort), } diff --git a/firewall/redirection.go b/firewall/redirection.go index b4d9ef93..3123eeaf 100644 --- a/firewall/redirection.go +++ b/firewall/redirection.go @@ -1,17 +1,28 @@ package firewall -import "fmt" +import ( + "fmt" + "strconv" +) type Redirection struct { Interface string Protocol string SrcAddress string - SrcPort int + SrcPort string DstAddress string DstPort int + MultiPort bool } -func NewRedirection(iface string, proto string, port_from int, addr_to string, port_to int) *Redirection { +func NewRedirection(iface string, proto string, port_from string, addr_to string, port_to int) *Redirection { + _, err := strconv.Atoi(port_from) + multi_port := false + if err != nil { + multi_port = true + } else { + multi_port = false + } return &Redirection{ Interface: iface, Protocol: proto, @@ -19,6 +30,7 @@ func NewRedirection(iface string, proto string, port_from int, addr_to string, p SrcPort: port_from, DstAddress: addr_to, DstPort: port_to, + MultiPort: multi_port, } } diff --git a/main.go b/main.go index 015b519e..fa050e19 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ func main() { sess.Register(modules.NewTcpProxy(sess)) sess.Register(modules.NewHttpProxy(sess)) sess.Register(modules.NewHttpsProxy(sess)) + sess.Register(modules.NewCustomHttpProxy(sess)) sess.Register(modules.NewHttpServer(sess)) sess.Register(modules.NewRestAPI(sess)) sess.Register(modules.NewWOL(sess)) diff --git a/modules/custom_http_proxy.go b/modules/custom_http_proxy.go new file mode 100644 index 00000000..3b1acc66 --- /dev/null +++ b/modules/custom_http_proxy.go @@ -0,0 +1,99 @@ +package modules + +import ( + "github.com/bettercap/bettercap/session" +) + +type CustomHttpProxy struct { + session.SessionModule + proxy *CustomProxy +} + +func NewCustomHttpProxy(s *session.Session) *CustomHttpProxy { + p := &CustomHttpProxy{ + SessionModule: session.NewSessionModule("custom.http.proxy", s), + proxy: NewCustomProxy(s), + } + + p.AddParam(session.NewStringParameter("custom.http.port", + "80", session.PortsValidator, + "HTTP port to redirect when the proxy is activated.")) + + p.AddParam(session.NewStringParameter("custom.http.proxy.address", + session.ParamIfaceAddress, + session.IPv4Validator, + "Address to bind the Custom HTTP proxy to.")) + + p.AddParam(session.NewIntParameter("custom.http.proxy.port", + "8080", + "Port to bind the Custom HTTP proxy to.")) + + p.AddParam(session.NewBoolParameter("custom.http.proxy.sslstrip", + "false", + "Enable or disable SSL stripping.")) + + p.AddHandler(session.NewModuleHandler("custom.http.proxy on", "", + "Start Custom HTTP proxy.", + func(args []string) error { + return p.Start() + })) + + p.AddHandler(session.NewModuleHandler("custom.http.proxy off", "", + "Stop Custom HTTP proxy.", + func(args []string) error { + return p.Stop() + })) + + return p +} + +func (p *CustomHttpProxy) Name() string { + return "custom.http.proxy" +} + +func (p *CustomHttpProxy) Description() string { + return "Use a custom HTTP proxy server instead of the builtin one." +} + +func (p *CustomHttpProxy) Author() string { + return "Simone Margaritelli " +} + +func (p *CustomHttpProxy) Configure() error { + var err error + var address string + var proxyPort int + var httpPort string + var stripSSL bool + + if p.Running() { + return session.ErrAlreadyStarted + } else if err, address = p.StringParam("custom.http.proxy.address"); err != nil { + return err + } else if err, proxyPort = p.IntParam("custom.http.proxy.port"); err != nil { + return err + } else if err, httpPort = p.StringParam("custom.http.port"); err != nil { + return err + } else if err, stripSSL = p.BoolParam("custom.http.proxy.sslstrip"); err != nil { + return err + } + + return p.proxy.Configure(address, proxyPort, httpPort, stripSSL) +} + +func (p *CustomHttpProxy) Start() error { + if err := p.Configure(); err != nil { + return err + } + + return p.SetRunning(true, func() { + p.proxy.Start() + }) +} + +func (p *CustomHttpProxy) Stop() error { + return p.SetRunning(false, func() { + p.proxy.Stop() + }) +} + diff --git a/modules/custom_proxy_base.go b/modules/custom_proxy_base.go new file mode 100644 index 00000000..b6f4e53b --- /dev/null +++ b/modules/custom_proxy_base.go @@ -0,0 +1,114 @@ +package modules + +import ( + "net/http" + "github.com/bettercap/bettercap/firewall" + "net" + "github.com/bettercap/bettercap/session" + "strings" + "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/core" +) + +type CustomProxy struct { + Name string + Address string + Redirection *firewall.Redirection + + isTLS bool + isRunning bool + stripper *SSLStripper + sniListener net.Listener + sess *session.Session +} + +func NewCustomProxy(s *session.Session) *CustomProxy { + p := &CustomProxy{ + Name: "custom.proxy", + sess: s, + stripper: NewSSLStripper(s, false), + } + return p +} + +func (p *CustomProxy) doProxy(req *http.Request) bool { + blacklist := []string{ + "localhost", + "127.0.0.1", + } + + if req.Host == "" { + log.Error("Got request with empty host: %v", req) + return false + } + + for _, blacklisted := range blacklist { + if strings.Split(req.Host, ":")[0] == blacklisted { + log.Error("Got request with blacklisted host: %s", req.Host) + return false + } + } + + return true +} + +func (p *CustomProxy) stripPort(s string) string { + ix := strings.IndexRune(s, ':') + if ix == -1 { + return s + } + return s[:ix] +} + +func (p *CustomProxy) Configure(proxyAddress string, proxyPort int, srcPort string, stripSSL bool) error { + + p.stripper.Enable(stripSSL) + p.Address = proxyAddress + + if !p.sess.Firewall.IsForwardingEnabled() { + log.Info("Enabling forwarding.") + p.sess.Firewall.EnableForwarding(true) + } + + p.Redirection = firewall.NewRedirection(p.sess.Interface.Name(), + "TCP", + srcPort, + p.Address, + proxyPort) + + if err := p.sess.Firewall.EnableRedirection(p.Redirection, true); err != nil { + return err + } + log.Debug("Applied redirection %s", p.Redirection.String()) + + + return nil +} + +func (p *CustomProxy) Start() { + go func() { + var err error + + strip := core.Yellow("enabled") + if !p.stripper.Enabled() { + strip = core.Dim("disabled") + } + + log.Info("%s started on %s (sslstrip %s)", core.Green(p.Name), p.Address, strip) + + if err != nil && err.Error() != "http: Server closed" { + log.Fatal("%s", err) + } + }() +} + +func (p *CustomProxy) Stop() error { + if p.Redirection != nil { + log.Debug("Disabling redirection %s", p.Redirection.String()) + if err := p.sess.Firewall.EnableRedirection(p.Redirection, false); err != nil { + return err + } + p.Redirection = nil + } + return nil +} \ No newline at end of file diff --git a/modules/http_proxy.go b/modules/http_proxy.go index 876d3665..401133da 100644 --- a/modules/http_proxy.go +++ b/modules/http_proxy.go @@ -15,8 +15,8 @@ func NewHttpProxy(s *session.Session) *HttpProxy { proxy: NewHTTPProxy(s), } - p.AddParam(session.NewIntParameter("http.port", - "80", + p.AddParam(session.NewStringParameter("http.port", + "80", session.PortsValidator, "HTTP port to redirect when the proxy is activated.")) p.AddParam(session.NewStringParameter("http.proxy.address", @@ -68,7 +68,7 @@ func (p *HttpProxy) Configure() error { var err error var address string var proxyPort int - var httpPort int + var httpPort string var scriptPath string var stripSSL bool @@ -78,7 +78,7 @@ func (p *HttpProxy) Configure() error { return err } else if err, proxyPort = p.IntParam("http.proxy.port"); err != nil { return err - } else if err, httpPort = p.IntParam("http.port"); err != nil { + } else if err, httpPort = p.StringParam("http.port"); err != nil { return err } else if err, scriptPath = p.StringParam("http.proxy.script"); err != nil { return err diff --git a/modules/http_proxy_base.go b/modules/http_proxy_base.go index 86816471..4c648590 100644 --- a/modules/http_proxy_base.go +++ b/modules/http_proxy_base.go @@ -106,7 +106,7 @@ func (p *HTTPProxy) doProxy(req *http.Request) bool { return true } -func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, scriptPath string, stripSSL bool) error { +func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort string, scriptPath string, stripSSL bool) error { var err error p.stripper.Enable(stripSSL) @@ -141,7 +141,6 @@ func (p *HTTPProxy) Configure(address string, proxyPort int, httpPort int, scrip if err := p.sess.Firewall.EnableRedirection(p.Redirection, true); err != nil { return err } - log.Debug("Applied redirection %s", p.Redirection.String()) p.sess.UnkCmdCallback = func(cmd string) bool { @@ -187,7 +186,7 @@ func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *goproxy.ProxyCt } } -func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort int, scriptPath string, certFile string, keyFile string, stripSSL bool) (err error) { +func (p *HTTPProxy) ConfigureTLS(address string, proxyPort int, httpPort string, scriptPath string, certFile string, keyFile string, stripSSL bool) (err error) { if p.Configure(address, proxyPort, httpPort, scriptPath, stripSSL); err != nil { return err } diff --git a/modules/http_proxy_script_test.go b/modules/http_proxy_script_test.go new file mode 100644 index 00000000..98e1d4ae --- /dev/null +++ b/modules/http_proxy_script_test.go @@ -0,0 +1,37 @@ +package modules + +import ( + "net/http" + "testing" + + "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/session" +) + +func getScript(src string) *HttpProxyScript { + sess := session.Session{} + sess.Env = session.NewEnvironment(&sess, "") + + err, script := LoadHttpProxyScriptSource("", src, &sess) + if err != nil { + log.Fatal("%s", err) + } + return script +} + +func getRequest() *http.Request { + req, err := http.NewRequest("GET", "http://www.google.com/", nil) + if err != nil { + log.Fatal("%s", err) + } + return req +} + +func BenchmarkOnRequest(b *testing.B) { + script := getScript("function onRequest(req,res){}") + req := getRequest() + + for n := 0; n < b.N; n++ { + script.OnRequest(req) + } +} diff --git a/modules/https_proxy.go b/modules/https_proxy.go index b8ee65f8..503dad3a 100644 --- a/modules/https_proxy.go +++ b/modules/https_proxy.go @@ -18,8 +18,8 @@ func NewHttpsProxy(s *session.Session) *HttpsProxy { proxy: NewHTTPProxy(s), } - p.AddParam(session.NewIntParameter("https.port", - "443", + p.AddParam(session.NewStringParameter("https.port", + "443", session.PortsValidator, "HTTPS port to redirect when the proxy is activated.")) p.AddParam(session.NewStringParameter("https.proxy.address", @@ -81,7 +81,7 @@ func (p *HttpsProxy) Configure() error { var err error var address string var proxyPort int - var httpPort int + var httpsPort string var scriptPath string var certFile string var keyFile string @@ -93,7 +93,7 @@ func (p *HttpsProxy) Configure() error { return err } else if err, proxyPort = p.IntParam("https.proxy.port"); err != nil { return err - } else if err, httpPort = p.IntParam("https.port"); err != nil { + } else if err, httpsPort = p.StringParam("https.port"); err != nil { return err } else if err, stripSSL = p.BoolParam("https.proxy.sslstrip"); err != nil { return err @@ -120,7 +120,7 @@ func (p *HttpsProxy) Configure() error { log.Info("Loading proxy certification authority TLS certificate from %s", certFile) } - return p.proxy.ConfigureTLS(address, proxyPort, httpPort, scriptPath, certFile, keyFile, stripSSL) + return p.proxy.ConfigureTLS(address, proxyPort, httpsPort, scriptPath, certFile, keyFile, stripSSL) } func (p *HttpsProxy) Start() error { diff --git a/modules/tcp_proxy.go b/modules/tcp_proxy.go index d3be611a..e7c59e76 100644 --- a/modules/tcp_proxy.go +++ b/modules/tcp_proxy.go @@ -87,7 +87,7 @@ func (p *TcpProxy) Author() string { func (p *TcpProxy) Configure() error { var err error - var port int + var port string var proxyPort int var address string var proxyAddress string @@ -103,7 +103,7 @@ func (p *TcpProxy) Configure() error { return err } else if err, proxyPort = p.IntParam("tcp.proxy.port"); err != nil { return err - } else if err, port = p.IntParam("tcp.port"); err != nil { + } else if err, port = p.StringParam("tcp.port"); err != nil { return err } else if err, tunnelAddress = p.StringParam("tcp.tunnel.address"); err != nil { return err @@ -113,7 +113,7 @@ func (p *TcpProxy) Configure() error { return err } else if p.localAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", proxyAddress, proxyPort)); err != nil { return err - } else if p.remoteAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", address, port)); err != nil { + } else if p.remoteAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%s", address, port)); err != nil { return err } else if p.tunnelAddr, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", tunnelAddress, tunnelPort)); err != nil { return err diff --git a/session/module_handler.go b/session/module_handler.go index 35b18027..19e95b3b 100644 --- a/session/module_handler.go +++ b/session/module_handler.go @@ -9,6 +9,7 @@ import ( ) const IPv4Validator = `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$` +const PortsValidator = `^(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])+[,]+)*(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])+)$|^(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])+):(([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])+)$` type ModuleHandler struct { Name string