diff --git a/modules/api_rest.go b/modules/api_rest.go index 90904256..3b56ebc4 100644 --- a/modules/api_rest.go +++ b/modules/api_rest.go @@ -10,13 +10,10 @@ import ( "github.com/evilsocket/bettercap-ng/log" "github.com/evilsocket/bettercap-ng/session" "github.com/evilsocket/bettercap-ng/tls" - - "github.com/gin-gonic/gin" ) type RestAPI struct { session.SessionModule - router *gin.Engine server *http.Server certFile string keyFile string @@ -94,8 +91,6 @@ func (api *RestAPI) Author() string { func (api *RestAPI) Configure() error { var err error - var username string - var password string var ip string var port int @@ -103,30 +98,19 @@ func (api *RestAPI) Configure() error { return err } else if err, port = api.IntParam("api.rest.port"); err != nil { return err - } - api.server.Addr = fmt.Sprintf("%s:%d", ip, port) - - if err, api.certFile = api.StringParam("api.rest.certificate"); err != nil { + } else if err, api.certFile = api.StringParam("api.rest.certificate"); err != nil { return err } else if api.certFile, err = core.ExpandPath(api.certFile); err != nil { return err - } - - if err, api.keyFile = api.StringParam("api.rest.key"); err != nil { + } else if err, api.keyFile = api.StringParam("api.rest.key"); err != nil { return err } else if api.keyFile, err = core.ExpandPath(api.keyFile); err != nil { return err - } - - if err, username = api.StringParam("api.rest.username"); err != nil { + } else if err, ApiUsername = api.StringParam("api.rest.username"); err != nil { return err - } - - if err, password = api.StringParam("api.rest.password"); err != nil { + } else if err, ApiPassword = api.StringParam("api.rest.password"); err != nil { return err - } - - if core.Exists(api.certFile) == false || core.Exists(api.keyFile) == false { + } else if core.Exists(api.certFile) == false || core.Exists(api.keyFile) == false { log.Info("Generating TLS key to %s", api.keyFile) log.Info("Generating TLS certificate to %s", api.certFile) if err := tls.Generate(api.certFile, api.keyFile); err != nil { @@ -137,19 +121,14 @@ func (api *RestAPI) Configure() error { log.Info("Loading TLS certificate from %s", api.certFile) } - gin.SetMode(gin.ReleaseMode) + api.server.Addr = fmt.Sprintf("%s:%d", ip, port) - api.router = gin.New() - api.router.Use(SecurityMiddleware()) - api.router.Use(gin.BasicAuth(gin.Accounts{username: password})) + router := http.NewServeMux() - group := api.router.Group("/api") - group.GET("/session", ShowRestSession) - group.POST("/session", RunRestCommand) - group.GET("/events", ShowRestEvents) - group.DELETE("/events", ClearRestEvents) + router.HandleFunc("/api/session", SessionRoute) + router.HandleFunc("/api/events", EventsRoute) - api.server.Handler = api.router + api.server.Handler = router return nil } diff --git a/modules/api_rest_controller.go b/modules/api_rest_controller.go index 625696bc..a73af485 100644 --- a/modules/api_rest_controller.go +++ b/modules/api_rest_controller.go @@ -1,41 +1,84 @@ package modules import ( + "crypto/subtle" + "encoding/json" + "net/http" "strconv" "github.com/evilsocket/bettercap-ng/session" - - "github.com/gin-gonic/gin" ) -func ShowRestSession(c *gin.Context) { - c.JSON(200, session.I) +var ( + ApiUsername = "" + ApiPassword = "" +) + +type CommandRequest struct { + Command string `json:"cmd"` } -func RunRestCommand(c *gin.Context) { +type APIResponse struct { + Success bool `json:"success"` + Message string `json:"msg"` +} + +func checkAuth(r *http.Request) bool { + user, pass, _ := r.BasicAuth() + // timing attack my ass + if subtle.ConstantTimeCompare([]byte(user), []byte(ApiUsername)) != 1 { + return false + } else if subtle.ConstantTimeCompare([]byte(pass), []byte(ApiPassword)) != 1 { + return false + } + return true +} + +func setAuthFailed(w http.ResponseWriter) { + w.Header().Set("WWW-Authenticate", `Basic realm="auth"`) + w.WriteHeader(401) + w.Write([]byte("Unauthorized")) +} + +func setSecurityHeaders(w http.ResponseWriter) { + w.Header().Add("X-Frame-Options", "DENY") + w.Header().Add("X-Content-Type-Options", "nosniff") + w.Header().Add("X-XSS-Protection", "1; mode=block") + w.Header().Add("Referrer-Policy", "same-origin") +} + +func toJSON(w http.ResponseWriter, o interface{}) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(o) +} + +func showSession(w http.ResponseWriter, r *http.Request) { + toJSON(w, session.I) +} + +func runSessionCommand(w http.ResponseWriter, r *http.Request) { var err error var cmd CommandRequest - if err = SafeBind(c, &cmd); err != nil { - BadRequest(c) - } - - err = session.I.Run(cmd.Command) - if err != nil { - BadRequest(c, err.Error()) + if r.Body == nil { + http.Error(w, "Bad Request", 400) + } else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil { + http.Error(w, "Bad Request", 400) + } else if err = session.I.Run(cmd.Command); err != nil { + http.Error(w, err.Error(), 400) } else { - c.JSON(200, APIResponse{Success: true}) + toJSON(w, APIResponse{Success: true}) } } -func ShowRestEvents(c *gin.Context) { +func showEvents(w http.ResponseWriter, r *http.Request) { var err error events := session.I.Events.Sorted() nmax := len(events) n := nmax - q := c.Request.URL.Query() + q := r.URL.Query() vals := q["n"] if len(vals) > 0 { n, err = strconv.Atoi(q["n"][0]) @@ -48,10 +91,37 @@ func ShowRestEvents(c *gin.Context) { } } - c.JSON(200, events[0:n]) + toJSON(w, events[0:n]) } -func ClearRestEvents(c *gin.Context) { +func clearEvents(w http.ResponseWriter, r *http.Request) { session.I.Events.Clear() - c.JSON(200, gin.H{"success": true}) +} + +func SessionRoute(w http.ResponseWriter, r *http.Request) { + setSecurityHeaders(w) + + if checkAuth(r) == false { + setAuthFailed(w) + } else if r.Method == "GET" { + showSession(w, r) + } else if r.Method == "POST" { + runSessionCommand(w, r) + } else { + http.Error(w, "Bad Request", 400) + } +} + +func EventsRoute(w http.ResponseWriter, r *http.Request) { + setSecurityHeaders(w) + + if checkAuth(r) == false { + setAuthFailed(w) + } else if r.Method == "GET" { + showEvents(w, r) + } else if r.Method == "DELETE" { + clearEvents(w, r) + } else { + http.Error(w, "Bad Request", 400) + } } diff --git a/modules/api_rest_security.go b/modules/api_rest_security.go deleted file mode 100644 index 9373b3ce..00000000 --- a/modules/api_rest_security.go +++ /dev/null @@ -1,35 +0,0 @@ -package modules - -import ( - "fmt" - "strings" - - "github.com/evilsocket/bettercap-ng/log" - - "github.com/gin-gonic/gin" - "gopkg.in/unrolled/secure.v1" -) - -func SecurityMiddleware() gin.HandlerFunc { - rules := secure.New(secure.Options{ - FrameDeny: true, - ContentTypeNosniff: true, - BrowserXssFilter: true, - ReferrerPolicy: "same-origin", - }) - - return func(c *gin.Context) { - err := rules.Process(c.Writer, c.Request) - if err != nil { - who := strings.Split(c.Request.RemoteAddr, ":")[0] - req := fmt.Sprintf("%s %s", c.Request.Method, c.Request.URL.Path) - log.Warning("%s > %s | Security exception: %s", who, req, err) - c.Abort() - return - } - - if status := c.Writer.Status(); status > 300 && status < 399 { - c.Abort() - } - } -} diff --git a/modules/api_rest_utils.go b/modules/api_rest_utils.go deleted file mode 100644 index fc280664..00000000 --- a/modules/api_rest_utils.go +++ /dev/null @@ -1,42 +0,0 @@ -package modules - -import ( - "encoding/json" - "io" - - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" -) - -type CommandRequest struct { - Command string `json:"cmd"` -} - -type APIResponse struct { - Success bool `json:"success"` - Message string `json:"msg"` -} - -func SafeBind(c *gin.Context, obj interface{}) error { - decoder := json.NewDecoder(io.LimitReader(c.Request.Body, 100*1024)) - if err := decoder.Decode(obj); err != nil { - return err - } - - if binding.Validator == nil { - return nil - } - return binding.Validator.ValidateStruct(obj) -} - -func BadRequest(c *gin.Context, optMsg ...string) { - msg := "Bad Request" - if len(optMsg) > 0 { - msg = optMsg[0] - } - c.JSON(400, APIResponse{ - Success: false, - Message: msg, - }) - c.Abort() -}