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