github.com/bluestoneag/bluephish@v0.1.0/controllers/route.go (about)

     1  package controllers
     2  
     3  import (
     4  	"compress/gzip"
     5  	"context"
     6  	"crypto/tls"
     7  	"html/template"
     8  	"net/http"
     9  	"net/url"
    10  	"time"
    11  
    12  	"github.com/NYTimes/gziphandler"
    13  	"github.com/bluestoneag/bluephish/auth"
    14  	"github.com/bluestoneag/bluephish/config"
    15  	ctx "github.com/bluestoneag/bluephish/context"
    16  	"github.com/bluestoneag/bluephish/controllers/api"
    17  	log "github.com/bluestoneag/bluephish/logger"
    18  	mid "github.com/bluestoneag/bluephish/middleware"
    19  	"github.com/bluestoneag/bluephish/middleware/ratelimit"
    20  	"github.com/bluestoneag/bluephish/models"
    21  	"github.com/bluestoneag/bluephish/util"
    22  	"github.com/bluestoneag/bluephish/worker"
    23  	"github.com/gorilla/csrf"
    24  	"github.com/gorilla/handlers"
    25  	"github.com/gorilla/mux"
    26  	"github.com/gorilla/sessions"
    27  	"github.com/jordan-wright/unindexed"
    28  )
    29  
    30  // AdminServerOption is a functional option that is used to configure the
    31  // admin server
    32  type AdminServerOption func(*AdminServer)
    33  
    34  // AdminServer is an HTTP server that implements the administrative Gophish
    35  // handlers, including the dashboard and REST API.
    36  type AdminServer struct {
    37  	server  *http.Server
    38  	worker  worker.Worker
    39  	config  config.AdminServer
    40  	limiter *ratelimit.PostLimiter
    41  }
    42  
    43  var defaultTLSConfig = &tls.Config{
    44  	PreferServerCipherSuites: true,
    45  	CurvePreferences: []tls.CurveID{
    46  		tls.X25519,
    47  		tls.CurveP256,
    48  	},
    49  	MinVersion: tls.VersionTLS12,
    50  	CipherSuites: []uint16{
    51  		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    52  		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    53  		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
    54  		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
    55  		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    56  		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    57  
    58  		// Kept for backwards compatibility with some clients
    59  		tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
    60  		tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
    61  	},
    62  }
    63  
    64  // WithWorker is an option that sets the background worker.
    65  func WithWorker(w worker.Worker) AdminServerOption {
    66  	return func(as *AdminServer) {
    67  		as.worker = w
    68  	}
    69  }
    70  
    71  // NewAdminServer returns a new instance of the AdminServer with the
    72  // provided config and options applied.
    73  func NewAdminServer(config config.AdminServer, options ...AdminServerOption) *AdminServer {
    74  	defaultWorker, _ := worker.New()
    75  	defaultServer := &http.Server{
    76  		ReadTimeout: 10 * time.Second,
    77  		Addr:        config.ListenURL,
    78  	}
    79  	defaultLimiter := ratelimit.NewPostLimiter()
    80  	as := &AdminServer{
    81  		worker:  defaultWorker,
    82  		server:  defaultServer,
    83  		limiter: defaultLimiter,
    84  		config:  config,
    85  	}
    86  	for _, opt := range options {
    87  		opt(as)
    88  	}
    89  	as.registerRoutes()
    90  	return as
    91  }
    92  
    93  // Start launches the admin server, listening on the configured address.
    94  func (as *AdminServer) Start() {
    95  	if as.worker != nil {
    96  		go as.worker.Start()
    97  	}
    98  	if as.config.UseTLS {
    99  		// Only support TLS 1.2 and above - ref #1691, #1689
   100  		as.server.TLSConfig = defaultTLSConfig
   101  		err := util.CheckAndCreateSSL(as.config.CertPath, as.config.KeyPath)
   102  		if err != nil {
   103  			log.Fatal(err)
   104  		}
   105  		log.Infof("Starting admin server at https://%s", as.config.ListenURL)
   106  		log.Fatal(as.server.ListenAndServeTLS(as.config.CertPath, as.config.KeyPath))
   107  	}
   108  	// If TLS isn't configured, just listen on HTTP
   109  	log.Infof("Starting admin server at http://%s", as.config.ListenURL)
   110  	log.Fatal(as.server.ListenAndServe())
   111  }
   112  
   113  // Shutdown attempts to gracefully shutdown the server.
   114  func (as *AdminServer) Shutdown() error {
   115  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   116  	defer cancel()
   117  	return as.server.Shutdown(ctx)
   118  }
   119  
   120  // SetupAdminRoutes creates the routes for handling requests to the web interface.
   121  // This function returns an http.Handler to be used in http.ListenAndServe().
   122  func (as *AdminServer) registerRoutes() {
   123  	router := mux.NewRouter()
   124  	// Base Front-end routes
   125  	router.HandleFunc("/", mid.Use(as.Base, mid.RequireLogin))
   126  	router.HandleFunc("/login", mid.Use(as.Login, as.limiter.Limit))
   127  	router.HandleFunc("/logout", mid.Use(as.Logout, mid.RequireLogin))
   128  	router.HandleFunc("/reset_password", mid.Use(as.ResetPassword, mid.RequireLogin))
   129  	router.HandleFunc("/campaigns", mid.Use(as.Campaigns, mid.RequireLogin))
   130  	router.HandleFunc("/campaigns/{id:[0-9]+}", mid.Use(as.CampaignID, mid.RequireLogin))
   131  	router.HandleFunc("/templates", mid.Use(as.Templates, mid.RequireLogin))
   132  	router.HandleFunc("/groups", mid.Use(as.Groups, mid.RequireLogin))
   133  	router.HandleFunc("/landing_pages", mid.Use(as.LandingPages, mid.RequireLogin))
   134  	router.HandleFunc("/sending_profiles", mid.Use(as.SendingProfiles, mid.RequireLogin))
   135  	router.HandleFunc("/settings", mid.Use(as.Settings, mid.RequireLogin))
   136  	router.HandleFunc("/users", mid.Use(as.UserManagement, mid.RequirePermission(models.PermissionModifySystem), mid.RequireLogin))
   137  	router.HandleFunc("/webhooks", mid.Use(as.Webhooks, mid.RequirePermission(models.PermissionModifySystem), mid.RequireLogin))
   138  	router.HandleFunc("/impersonate", mid.Use(as.Impersonate, mid.RequirePermission(models.PermissionModifySystem), mid.RequireLogin))
   139  	// Create the API routes
   140  	api := api.NewServer(
   141  		api.WithWorker(as.worker),
   142  		api.WithLimiter(as.limiter),
   143  	)
   144  	router.PathPrefix("/api/").Handler(api)
   145  
   146  	// Setup static file serving
   147  	router.PathPrefix("/").Handler(http.FileServer(unindexed.Dir("./static/")))
   148  
   149  	// Setup CSRF Protection
   150  	csrfKey := []byte(as.config.CSRFKey)
   151  	if len(csrfKey) == 0 {
   152  		csrfKey = []byte(auth.GenerateSecureKey(auth.APIKeyLength))
   153  	}
   154  	csrfHandler := csrf.Protect(csrfKey,
   155  		csrf.FieldName("csrf_token"),
   156  		csrf.Secure(as.config.UseTLS))
   157  	adminHandler := csrfHandler(router)
   158  	adminHandler = mid.Use(adminHandler.ServeHTTP, mid.CSRFExceptions, mid.GetContext, mid.ApplySecurityHeaders)
   159  
   160  	// Setup GZIP compression
   161  	gzipWrapper, _ := gziphandler.NewGzipLevelHandler(gzip.BestCompression)
   162  	adminHandler = gzipWrapper(adminHandler)
   163  
   164  	// Respect X-Forwarded-For and X-Real-IP headers in case we're behind a
   165  	// reverse proxy.
   166  	adminHandler = handlers.ProxyHeaders(adminHandler)
   167  
   168  	// Setup logging
   169  	adminHandler = handlers.CombinedLoggingHandler(log.Writer(), adminHandler)
   170  	as.server.Handler = adminHandler
   171  }
   172  
   173  type templateParams struct {
   174  	Title        string
   175  	Flashes      []interface{}
   176  	User         models.User
   177  	Token        string
   178  	Version      string
   179  	ModifySystem bool
   180  }
   181  
   182  // newTemplateParams returns the default template parameters for a user and
   183  // the CSRF token.
   184  func newTemplateParams(r *http.Request) templateParams {
   185  	user := ctx.Get(r, "user").(models.User)
   186  	session := ctx.Get(r, "session").(*sessions.Session)
   187  	modifySystem, _ := user.HasPermission(models.PermissionModifySystem)
   188  	return templateParams{
   189  		Token:        csrf.Token(r),
   190  		User:         user,
   191  		ModifySystem: modifySystem,
   192  		Version:      config.Version,
   193  		Flashes:      session.Flashes(),
   194  	}
   195  }
   196  
   197  // Base handles the default path and template execution
   198  func (as *AdminServer) Base(w http.ResponseWriter, r *http.Request) {
   199  	params := newTemplateParams(r)
   200  	params.Title = "Dashboard"
   201  	getTemplate(w, "dashboard").ExecuteTemplate(w, "base", params)
   202  }
   203  
   204  // Campaigns handles the default path and template execution
   205  func (as *AdminServer) Campaigns(w http.ResponseWriter, r *http.Request) {
   206  	params := newTemplateParams(r)
   207  	params.Title = "Campaigns"
   208  	getTemplate(w, "campaigns").ExecuteTemplate(w, "base", params)
   209  }
   210  
   211  // CampaignID handles the default path and template execution
   212  func (as *AdminServer) CampaignID(w http.ResponseWriter, r *http.Request) {
   213  	params := newTemplateParams(r)
   214  	params.Title = "Campaign Results"
   215  	getTemplate(w, "campaign_results").ExecuteTemplate(w, "base", params)
   216  }
   217  
   218  // Templates handles the default path and template execution
   219  func (as *AdminServer) Templates(w http.ResponseWriter, r *http.Request) {
   220  	params := newTemplateParams(r)
   221  	params.Title = "Email Templates"
   222  	getTemplate(w, "templates").ExecuteTemplate(w, "base", params)
   223  }
   224  
   225  // Groups handles the default path and template execution
   226  func (as *AdminServer) Groups(w http.ResponseWriter, r *http.Request) {
   227  	params := newTemplateParams(r)
   228  	params.Title = "Users & Groups"
   229  	getTemplate(w, "groups").ExecuteTemplate(w, "base", params)
   230  }
   231  
   232  // LandingPages handles the default path and template execution
   233  func (as *AdminServer) LandingPages(w http.ResponseWriter, r *http.Request) {
   234  	params := newTemplateParams(r)
   235  	params.Title = "Landing Pages"
   236  	getTemplate(w, "landing_pages").ExecuteTemplate(w, "base", params)
   237  }
   238  
   239  // SendingProfiles handles the default path and template execution
   240  func (as *AdminServer) SendingProfiles(w http.ResponseWriter, r *http.Request) {
   241  	params := newTemplateParams(r)
   242  	params.Title = "Sending Profiles"
   243  	getTemplate(w, "sending_profiles").ExecuteTemplate(w, "base", params)
   244  }
   245  
   246  // Settings handles the changing of settings
   247  func (as *AdminServer) Settings(w http.ResponseWriter, r *http.Request) {
   248  	switch {
   249  	case r.Method == "GET":
   250  		params := newTemplateParams(r)
   251  		params.Title = "Settings"
   252  		session := ctx.Get(r, "session").(*sessions.Session)
   253  		session.Save(r, w)
   254  		getTemplate(w, "settings").ExecuteTemplate(w, "base", params)
   255  	case r.Method == "POST":
   256  		u := ctx.Get(r, "user").(models.User)
   257  		currentPw := r.FormValue("current_password")
   258  		newPassword := r.FormValue("new_password")
   259  		confirmPassword := r.FormValue("confirm_new_password")
   260  		// Check the current password
   261  		err := auth.ValidatePassword(currentPw, u.Hash)
   262  		msg := models.Response{Success: true, Message: "Settings Updated Successfully"}
   263  		if err != nil {
   264  			msg.Message = err.Error()
   265  			msg.Success = false
   266  			api.JSONResponse(w, msg, http.StatusBadRequest)
   267  			return
   268  		}
   269  		newHash, err := auth.ValidatePasswordChange(u.Hash, newPassword, confirmPassword)
   270  		if err != nil {
   271  			msg.Message = err.Error()
   272  			msg.Success = false
   273  			api.JSONResponse(w, msg, http.StatusBadRequest)
   274  			return
   275  		}
   276  		u.Hash = string(newHash)
   277  		if err = models.PutUser(&u); err != nil {
   278  			msg.Message = err.Error()
   279  			msg.Success = false
   280  			api.JSONResponse(w, msg, http.StatusInternalServerError)
   281  			return
   282  		}
   283  		api.JSONResponse(w, msg, http.StatusOK)
   284  	}
   285  }
   286  
   287  // UserManagement is an admin-only handler that allows for the registration
   288  // and management of user accounts within Gophish.
   289  func (as *AdminServer) UserManagement(w http.ResponseWriter, r *http.Request) {
   290  	params := newTemplateParams(r)
   291  	params.Title = "User Management"
   292  	getTemplate(w, "users").ExecuteTemplate(w, "base", params)
   293  }
   294  
   295  func (as *AdminServer) nextOrIndex(w http.ResponseWriter, r *http.Request) {
   296  	next := "/"
   297  	url, err := url.Parse(r.FormValue("next"))
   298  	if err == nil {
   299  		path := url.Path
   300  		if path != "" {
   301  			next = path
   302  		}
   303  	}
   304  	http.Redirect(w, r, next, http.StatusFound)
   305  }
   306  
   307  func (as *AdminServer) handleInvalidLogin(w http.ResponseWriter, r *http.Request, message string) {
   308  	session := ctx.Get(r, "session").(*sessions.Session)
   309  	Flash(w, r, "danger", message)
   310  	params := struct {
   311  		User    models.User
   312  		Title   string
   313  		Flashes []interface{}
   314  		Token   string
   315  	}{Title: "Login", Token: csrf.Token(r)}
   316  	params.Flashes = session.Flashes()
   317  	session.Save(r, w)
   318  	templates := template.New("template")
   319  	_, err := templates.ParseFiles("templates/login.html", "templates/flashes.html")
   320  	if err != nil {
   321  		log.Error(err)
   322  	}
   323  	// w.Header().Set("Content-Type", "text/html; charset=utf-8")
   324  	w.WriteHeader(http.StatusUnauthorized)
   325  	template.Must(templates, err).ExecuteTemplate(w, "base", params)
   326  }
   327  
   328  // Webhooks is an admin-only handler that handles webhooks
   329  func (as *AdminServer) Webhooks(w http.ResponseWriter, r *http.Request) {
   330  	params := newTemplateParams(r)
   331  	params.Title = "Webhooks"
   332  	getTemplate(w, "webhooks").ExecuteTemplate(w, "base", params)
   333  }
   334  
   335  // Impersonate allows an admin to login to a user account without needing the password
   336  func (as *AdminServer) Impersonate(w http.ResponseWriter, r *http.Request) {
   337  
   338  	if r.Method == "POST" {
   339  		username := r.FormValue("username")
   340  		u, err := models.GetUserByUsername(username)
   341  		if err != nil {
   342  			log.Error(err)
   343  			http.Error(w, err.Error(), http.StatusNotFound)
   344  			return
   345  		}
   346  		session := ctx.Get(r, "session").(*sessions.Session)
   347  		session.Values["id"] = u.Id
   348  		session.Save(r, w)
   349  	}
   350  	http.Redirect(w, r, "/", http.StatusFound)
   351  }
   352  
   353  // Login handles the authentication flow for a user. If credentials are valid,
   354  // a session is created
   355  func (as *AdminServer) Login(w http.ResponseWriter, r *http.Request) {
   356  	params := struct {
   357  		User    models.User
   358  		Title   string
   359  		Flashes []interface{}
   360  		Token   string
   361  	}{Title: "Login", Token: csrf.Token(r)}
   362  	session := ctx.Get(r, "session").(*sessions.Session)
   363  	switch {
   364  	case r.Method == "GET":
   365  		params.Flashes = session.Flashes()
   366  		session.Save(r, w)
   367  		templates := template.New("template")
   368  		_, err := templates.ParseFiles("templates/login.html", "templates/flashes.html")
   369  		if err != nil {
   370  			log.Error(err)
   371  		}
   372  		template.Must(templates, err).ExecuteTemplate(w, "base", params)
   373  	case r.Method == "POST":
   374  		// Find the user with the provided username
   375  		username, password := r.FormValue("username"), r.FormValue("password")
   376  		u, err := models.GetUserByUsername(username)
   377  		if err != nil {
   378  			log.Error(err)
   379  			as.handleInvalidLogin(w, r, "Invalid Username/Password")
   380  			return
   381  		}
   382  		// Validate the user's password
   383  		err = auth.ValidatePassword(password, u.Hash)
   384  		if err != nil {
   385  			log.Error(err)
   386  			as.handleInvalidLogin(w, r, "Invalid Username/Password")
   387  			return
   388  		}
   389  		if u.AccountLocked {
   390  			as.handleInvalidLogin(w, r, "Account Locked")
   391  			return
   392  		}
   393  		u.LastLogin = time.Now().UTC()
   394  		err = models.PutUser(&u)
   395  		if err != nil {
   396  			log.Error(err)
   397  		}
   398  		// If we've logged in, save the session and redirect to the dashboard
   399  		session.Values["id"] = u.Id
   400  		session.Save(r, w)
   401  		as.nextOrIndex(w, r)
   402  	}
   403  }
   404  
   405  // Logout destroys the current user session
   406  func (as *AdminServer) Logout(w http.ResponseWriter, r *http.Request) {
   407  	session := ctx.Get(r, "session").(*sessions.Session)
   408  	delete(session.Values, "id")
   409  	Flash(w, r, "success", "You have successfully logged out")
   410  	session.Save(r, w)
   411  	http.Redirect(w, r, "/login", http.StatusFound)
   412  }
   413  
   414  // ResetPassword handles the password reset flow when a password change is
   415  // required either by the Gophish system or an administrator.
   416  //
   417  // This handler is meant to be used when a user is required to reset their
   418  // password, not just when they want to.
   419  //
   420  // This is an important distinction since in this handler we don't require
   421  // the user to re-enter their current password, as opposed to the flow
   422  // through the settings handler.
   423  //
   424  // To that end, if the user doesn't require a password change, we will
   425  // redirect them to the settings page.
   426  func (as *AdminServer) ResetPassword(w http.ResponseWriter, r *http.Request) {
   427  	u := ctx.Get(r, "user").(models.User)
   428  	session := ctx.Get(r, "session").(*sessions.Session)
   429  	if !u.PasswordChangeRequired {
   430  		Flash(w, r, "info", "Please reset your password through the settings page")
   431  		session.Save(r, w)
   432  		http.Redirect(w, r, "/settings", http.StatusTemporaryRedirect)
   433  		return
   434  	}
   435  	params := newTemplateParams(r)
   436  	params.Title = "Reset Password"
   437  	switch {
   438  	case r.Method == http.MethodGet:
   439  		params.Flashes = session.Flashes()
   440  		session.Save(r, w)
   441  		getTemplate(w, "reset_password").ExecuteTemplate(w, "base", params)
   442  		return
   443  	case r.Method == http.MethodPost:
   444  		newPassword := r.FormValue("password")
   445  		confirmPassword := r.FormValue("confirm_password")
   446  		newHash, err := auth.ValidatePasswordChange(u.Hash, newPassword, confirmPassword)
   447  		if err != nil {
   448  			Flash(w, r, "danger", err.Error())
   449  			params.Flashes = session.Flashes()
   450  			session.Save(r, w)
   451  			w.WriteHeader(http.StatusBadRequest)
   452  			getTemplate(w, "reset_password").ExecuteTemplate(w, "base", params)
   453  			return
   454  		}
   455  		u.PasswordChangeRequired = false
   456  		u.Hash = newHash
   457  		if err = models.PutUser(&u); err != nil {
   458  			Flash(w, r, "danger", err.Error())
   459  			params.Flashes = session.Flashes()
   460  			session.Save(r, w)
   461  			w.WriteHeader(http.StatusInternalServerError)
   462  			getTemplate(w, "reset_password").ExecuteTemplate(w, "base", params)
   463  			return
   464  		}
   465  		// TODO: We probably want to flash a message here that the password was
   466  		// changed successfully. The problem is that when the user resets their
   467  		// password on first use, they will see two flashes on the dashboard-
   468  		// one for their password reset, and one for the "no campaigns created".
   469  		//
   470  		// The solution to this is to revamp the empty page to be more useful,
   471  		// like a wizard or something.
   472  		as.nextOrIndex(w, r)
   473  	}
   474  }
   475  
   476  // TODO: Make this execute the template, too
   477  func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
   478  	templates := template.New("template")
   479  	_, err := templates.ParseFiles("templates/base.html", "templates/nav.html", "templates/"+tmpl+".html", "templates/flashes.html")
   480  	if err != nil {
   481  		log.Error(err)
   482  	}
   483  	return template.Must(templates, err)
   484  }
   485  
   486  // Flash handles the rendering flash messages
   487  func Flash(w http.ResponseWriter, r *http.Request, t string, m string) {
   488  	session := ctx.Get(r, "session").(*sessions.Session)
   489  	session.AddFlash(models.Flash{
   490  		Type:    t,
   491  		Message: m,
   492  	})
   493  }