refact: we got rid of huge GIN dependency by using core golang libs for api.rest module

This commit is contained in:
evilsocket 2018-02-22 16:59:23 +01:00
parent bec98e3d1f
commit 1728ed63f1
4 changed files with 98 additions and 126 deletions

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}