diff --git a/modules/custom_http_proxy.go b/modules/custom_http_proxy.go new file mode 100644 index 00000000..f850f1e2 --- /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.NewIntParameter("custom.http.port", + "80", + "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 int + 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.IntParam("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..96460b65 --- /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 int, 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_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) + } +}