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 }