diff --git a/Makefile b/Makefile index fe434e4f..111ac714 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,6 @@ deps: @go get github.com/op/go-logging @go get github.com/robertkrimen/otto - clean: @rm -rf $(TARGET) net/oui_compiled.go diff --git a/caplets/rest-api.cap b/caplets/rest-api.cap new file mode 100644 index 00000000..17c4704e --- /dev/null +++ b/caplets/rest-api.cap @@ -0,0 +1,8 @@ +# change these! +set api.rest.username bcap +set api.rest.password bcap + +net.probe on +net.recon on +api.rest on + diff --git a/main.go b/main.go index c1e23566..6d5daef9 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ func main() { sess.Register(session_modules.NewArpSpoofer(sess)) sess.Register(session_modules.NewSniffer(sess)) sess.Register(session_modules.NewHttpProxy(sess)) + sess.Register(session_modules.NewRestAPI(sess)) if err = sess.Start(); err != nil { log.Fatal(err) diff --git a/session/environment.go b/session/environment.go index afee43ca..522d2b48 100644 --- a/session/environment.go +++ b/session/environment.go @@ -8,30 +8,26 @@ import ( ) type Environment struct { - Padding int - storage map[string]string + Padding int `json:"-"` + Storage map[string]string `json:"storage"` lock *sync.Mutex } func NewEnvironment() *Environment { env := &Environment{ Padding: 0, - storage: make(map[string]string), + Storage: make(map[string]string), lock: &sync.Mutex{}, } return env } -func (env *Environment) Storage() *map[string]string { - return &env.storage -} - func (env *Environment) Has(name string) bool { env.lock.Lock() defer env.lock.Unlock() - _, found := env.storage[name] + _, found := env.Storage[name] return found } @@ -40,8 +36,8 @@ func (env *Environment) Set(name, value string) string { env.lock.Lock() defer env.lock.Unlock() - old, _ := env.storage[name] - env.storage[name] = value + old, _ := env.Storage[name] + env.Storage[name] = value width := len(name) if width > env.Padding { @@ -55,7 +51,7 @@ func (env *Environment) Get(name string) (bool, string) { env.lock.Lock() defer env.lock.Unlock() - if value, found := env.storage[name]; found == true { + if value, found := env.Storage[name]; found == true { return true, value } @@ -79,7 +75,7 @@ func (env *Environment) Sorted() []string { defer env.lock.Unlock() var keys []string - for k := range env.storage { + for k := range env.Storage { keys = append(keys, k) } sort.Strings(keys) diff --git a/session/modules/api_rest.go b/session/modules/api_rest.go new file mode 100644 index 00000000..a894e8c6 --- /dev/null +++ b/session/modules/api_rest.go @@ -0,0 +1,197 @@ +package session_modules + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/evilsocket/bettercap-ng/core" + "github.com/evilsocket/bettercap-ng/session" +) + +type RestAPI struct { + session.SessionModule + server *http.Server + username string + password string +} + +func NewRestAPI(s *session.Session) *RestAPI { + api := &RestAPI{ + SessionModule: session.NewSessionModule(s), + server: &http.Server{}, + username: "", + password: "", + } + + api.AddParam(session.NewStringParameter("api.rest.address", + "", + `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`, + "Address to bind the API REST server to.")) + + api.AddParam(session.NewIntParameter("api.rest.port", + "8081", + "Port to bind the API REST server to.")) + + api.AddParam(session.NewStringParameter("api.rest.username", + "", + "", + "API authentication username.")) + + api.AddParam(session.NewStringParameter("api.rest.password", + "", + "", + "API authentication password.")) + + api.AddHandler(session.NewModuleHandler("api.rest on", "", + "Start REST API server.", + func(args []string) error { + return api.Start() + })) + + api.AddHandler(session.NewModuleHandler("api.rest off", "", + "Stop REST API server.", + func(args []string) error { + return api.Stop() + })) + + http.HandleFunc("/api/session", api.getSession) + + return api +} + +func (api *RestAPI) getSession(w http.ResponseWriter, r *http.Request) { + if api.checkAuth(w, r) == false { + return + } + + js, err := json.Marshal(api.Session) + if err != nil { + log.Errorf("Error while returning session: %s", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(js) +} + +func (api RestAPI) checkAuth(w http.ResponseWriter, r *http.Request) bool { + if api.Authenticated(w, r) == false { + log.Warningf("Unauthenticated access!") + http.Error(w, "Not authorized", 401) + return false + } + return true +} + +func (api RestAPI) Authenticated(w http.ResponseWriter, r *http.Request) bool { + w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) + + parts := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + if len(parts) != 2 { + return false + } + + b, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return false + } + + pair := strings.SplitN(string(b), ":", 2) + if len(pair) != 2 { + return false + } + + if pair[0] != api.username || pair[1] != api.password { + return false + } + + return true +} + +func (api RestAPI) Name() string { + return "REST API" +} + +func (api RestAPI) Description() string { + return "Expose a RESTful API." +} + +func (api RestAPI) Author() string { + return "Simone Margaritelli " +} + +func (api RestAPI) OnSessionStarted(s *session.Session) { + // refresh the address after session has been created + s.Env.Set("api.rest.address", s.Interface.IpAddress) +} + +func (api RestAPI) OnSessionEnded(s *session.Session) { + if api.Running() { + api.Stop() + } +} + +func (api *RestAPI) Start() error { + var address string + var port int + + if err, v := api.Param("api.rest.address").Get(api.Session); err != nil { + return err + } else { + address = v.(string) + } + + if err, v := api.Param("api.rest.port").Get(api.Session); err != nil { + return err + } else { + port = v.(int) + } + + if err, v := api.Param("api.rest.username").Get(api.Session); err != nil { + return err + } else { + api.username = v.(string) + if api.username == "" { + return fmt.Errorf("api.rest.username is empty.") + } + } + + if err, v := api.Param("api.rest.password").Get(api.Session); err != nil { + return err + } else { + api.password = v.(string) + if api.password == "" { + return fmt.Errorf("api.rest.password is empty.") + } + } + + if api.Running() == false { + api.SetRunning(true) + + api.server.Addr = fmt.Sprintf("%s:%d", address, port) + go func() { + fmt.Printf("[%s] starting on http://%s/ ...\n", core.Green("api"), api.server.Addr) + err := api.server.ListenAndServe() + if err != nil { + fmt.Printf("[%s] %s\n", core.Green("api"), err) + } + }() + + return nil + } else { + return fmt.Errorf("REST API server already started.") + } +} + +func (api *RestAPI) Stop() error { + if api.Running() == true { + api.SetRunning(false) + return api.server.Shutdown(nil) + } else { + return fmt.Errorf("REST API server already stopped.") + } +} diff --git a/session/modules/http_proxy.go b/session/modules/http_proxy.go index 96cc31eb..43a56a30 100644 --- a/session/modules/http_proxy.go +++ b/session/modules/http_proxy.go @@ -49,15 +49,15 @@ func NewHttpProxy(s *session.Session) *HttpProxy { "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.NewIntParameter("http.proxy.port", + "8080", + "Port to bind the HTTP proxy to.")) + p.AddParam(session.NewStringParameter("http.proxy.script", "", "", diff --git a/session/modules/http_proxy_script.go b/session/modules/http_proxy_script.go index f482f874..6aec3498 100644 --- a/session/modules/http_proxy_script.go +++ b/session/modules/http_proxy_script.go @@ -49,7 +49,7 @@ func LoadProxyScript(path string, sess *session.Session) (err error, s *ProxyScr } // define session pointer - err = s.VM.Set("env", *sess.Env.Storage()) + err = s.VM.Set("env", sess.Env.Storage) if err != nil { log.Errorf("Error while defining environment: %s", err) return diff --git a/session/session.go b/session/session.go index db5b2769..8e870749 100644 --- a/session/session.go +++ b/session/session.go @@ -22,20 +22,20 @@ import ( ) type Session struct { - Options core.Options - Interface *net.Endpoint - Gateway *net.Endpoint - Firewall firewall.FirewallManager - Env *Environment - Targets *Targets - Queue *packets.Queue - Input *readline.Instance - Active bool + Options core.Options `json:"options"` + Interface *net.Endpoint `json:"interface"` + Gateway *net.Endpoint `json:"gateway"` + Firewall firewall.FirewallManager `json:"-"` + Env *Environment `json:"env"` + Targets *Targets `json:"targets"` + Queue *packets.Queue `json:"-"` + Input *readline.Instance `json:"-"` + Active bool `json:"active"` // Watcher *discovery.Watcher - CoreHandlers []CommandHandler - Modules []Module - HelpPadding int + CoreHandlers []CommandHandler `json:"-"` + Modules []Module `json:"-"` + HelpPadding int `json:"-"` } func New() (*Session, error) { @@ -175,7 +175,7 @@ func (s *Session) registerCoreHandlers() { prev_ns = ns } - fmt.Printf(" %"+strconv.Itoa(s.Env.Padding)+"s: '%s'\n", k, s.Env.storage[k]) + fmt.Printf(" %"+strconv.Itoa(s.Env.Padding)+"s: '%s'\n", k, s.Env.Storage[k]) } fmt.Println() } else if found, value := s.Env.Get(key); found == true {