Fixing context issues with Go 1.7.

pull/382/head
Jordan Wright 2016-09-14 22:24:51 -05:00
parent d687872462
commit 103fd72cc8
9 changed files with 152 additions and 47 deletions

View File

@ -3,6 +3,8 @@ sudo: false
go: go:
- 1.5 - 1.5
- 1.6
- 1.7
- tip - tip
install: install:

View File

@ -9,8 +9,8 @@ import (
"crypto/rand" "crypto/rand"
ctx "github.com/gophish/gophish/context"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
ctx "github.com/gorilla/context"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -39,23 +39,19 @@ var ErrEmptyPassword = errors.New("Password cannot be blank")
var ErrPasswordMismatch = errors.New("Passwords must match") var ErrPasswordMismatch = errors.New("Passwords must match")
// Login attempts to login the user given a request. // Login attempts to login the user given a request.
func Login(r *http.Request) (bool, error) { func Login(r *http.Request) (bool, models.User, error) {
username, password := r.FormValue("username"), r.FormValue("password") username, password := r.FormValue("username"), r.FormValue("password")
session, _ := Store.Get(r, "gophish")
u, err := models.GetUserByUsername(username) u, err := models.GetUserByUsername(username)
if err != nil && err != models.ErrUsernameTaken { if err != nil && err != models.ErrUsernameTaken {
return false, err return false, models.User{}, err
} }
//If we've made it here, we should have a valid user stored in u //If we've made it here, we should have a valid user stored in u
//Let's check the password //Let's check the password
err = bcrypt.CompareHashAndPassword([]byte(u.Hash), []byte(password)) err = bcrypt.CompareHashAndPassword([]byte(u.Hash), []byte(password))
if err != nil { if err != nil {
ctx.Set(r, "user", nil) return false, models.User{}, ErrInvalidPassword
return false, ErrInvalidPassword
} }
ctx.Set(r, "user", u) return true, u, nil
session.Values["id"] = u.Id
return true, nil
} }
// Register attempts to register the user given a request. // Register attempts to register the user given a request.

26
context/context-legacy.go Normal file
View File

@ -0,0 +1,26 @@
// +build !go1.7
package context
import (
"net/http"
"github.com/gorilla/context"
)
func Get(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func Set(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
context.Set(r, key, val)
return r
}
func Clear(r *http.Request) {
context.Clear(r)
}

25
context/context.go Normal file
View File

@ -0,0 +1,25 @@
// +build go1.7
package context
import (
"net/http"
"context"
)
func Get(r *http.Request, key interface{}) interface{} {
return r.Context().Value(key)
}
func Set(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), key, val))
}
func Clear(r *http.Request) {
return
}

29
context/doc.go Normal file
View File

@ -0,0 +1,29 @@
/*
gophish - Open-Source Phishing Framework
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package context provides the ability to store request-scoped values on an http.Request instance. These values are cleared after every request.
// This package was created due to Go v1.7 moving the context package into the stdlib, which had a net effect of breaking some functionality from the existing gorilla/context.
package context

View File

@ -14,10 +14,10 @@ import (
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/gophish/gophish/auth" "github.com/gophish/gophish/auth"
ctx "github.com/gophish/gophish/context"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/gophish/gophish/util" "github.com/gophish/gophish/util"
"github.com/gophish/gophish/worker" "github.com/gophish/gophish/worker"
ctx "github.com/gorilla/context"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/jordan-wright/email" "github.com/jordan-wright/email"

View File

@ -14,12 +14,12 @@ import (
"github.com/gophish/gophish/auth" "github.com/gophish/gophish/auth"
"github.com/gophish/gophish/config" "github.com/gophish/gophish/config"
ctx "github.com/gophish/gophish/context"
mid "github.com/gophish/gophish/middleware" mid "github.com/gophish/gophish/middleware"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
ctx "github.com/gorilla/context" "github.com/gorilla/csrf"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/justinas/nosurf"
) )
// Logger is used to send logging messages to stdout. // Logger is used to send logging messages to stdout.
@ -67,22 +67,11 @@ func CreateAdminRouter() http.Handler {
router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/"))) router.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))
// Setup CSRF Protection // Setup CSRF Protection
csrfHandler := nosurf.New(router) csrfHandler := csrf.Protect([]byte(auth.GenerateSecureKey()),
// Exempt API routes and Static files csrf.FieldName("csrf_token"),
csrfHandler.ExemptGlob("/api/campaigns") csrf.Secure(config.Conf.AdminConf.UseTLS))
csrfHandler.ExemptGlob("/api/campaigns/*") csrfRouter := csrfHandler(router)
csrfHandler.ExemptGlob("/api/groups") return Use(csrfRouter.ServeHTTP, mid.CSRFExceptions, mid.GetContext)
csrfHandler.ExemptGlob("/api/groups/*")
csrfHandler.ExemptGlob("/api/templates")
csrfHandler.ExemptGlob("/api/templates/*")
csrfHandler.ExemptGlob("/api/pages")
csrfHandler.ExemptGlob("/api/pages/*")
csrfHandler.ExemptGlob("/api/smtp")
csrfHandler.ExemptGlob("/api/smtp/*")
csrfHandler.ExemptGlob("/api/import/*")
csrfHandler.ExemptGlob("/api/util/*")
csrfHandler.ExemptGlob("/static/*")
return Use(csrfHandler.ServeHTTP, mid.GetContext)
} }
// CreatePhishingRouter creates the router that handles phishing connections. // CreatePhishingRouter creates the router that handles phishing connections.
@ -259,7 +248,7 @@ func Register(w http.ResponseWriter, r *http.Request) {
Flashes []interface{} Flashes []interface{}
User models.User User models.User
Token string Token string
}{Title: "Register", Token: nosurf.Token(r)} }{Title: "Register", Token: csrf.Token(r)}
session := ctx.Get(r, "session").(*sessions.Session) session := ctx.Get(r, "session").(*sessions.Session)
switch { switch {
case r.Method == "GET": case r.Method == "GET":
@ -304,7 +293,7 @@ func Base(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Dashboard", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "dashboard").ExecuteTemplate(w, "base", params) getTemplate(w, "dashboard").ExecuteTemplate(w, "base", params)
} }
@ -316,7 +305,7 @@ func Campaigns(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Campaigns", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Campaigns", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "campaigns").ExecuteTemplate(w, "base", params) getTemplate(w, "campaigns").ExecuteTemplate(w, "base", params)
} }
@ -328,7 +317,7 @@ func CampaignID(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Campaign Results", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Campaign Results", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "campaign_results").ExecuteTemplate(w, "base", params) getTemplate(w, "campaign_results").ExecuteTemplate(w, "base", params)
} }
@ -340,7 +329,7 @@ func Templates(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Email Templates", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Email Templates", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "templates").ExecuteTemplate(w, "base", params) getTemplate(w, "templates").ExecuteTemplate(w, "base", params)
} }
@ -352,7 +341,7 @@ func Users(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Users & Groups", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Users & Groups", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "users").ExecuteTemplate(w, "base", params) getTemplate(w, "users").ExecuteTemplate(w, "base", params)
} }
@ -364,7 +353,7 @@ func LandingPages(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Landing Pages", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Landing Pages", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "landing_pages").ExecuteTemplate(w, "base", params) getTemplate(w, "landing_pages").ExecuteTemplate(w, "base", params)
} }
@ -376,7 +365,7 @@ func SendingProfiles(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Sending Profiles", User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Sending Profiles", User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "sending_profiles").ExecuteTemplate(w, "base", params) getTemplate(w, "sending_profiles").ExecuteTemplate(w, "base", params)
} }
@ -390,7 +379,7 @@ func Settings(w http.ResponseWriter, r *http.Request) {
Flashes []interface{} Flashes []interface{}
Token string Token string
Version string Version string
}{Title: "Settings", Version: config.Version, User: ctx.Get(r, "user").(models.User), Token: nosurf.Token(r)} }{Title: "Settings", Version: config.Version, User: ctx.Get(r, "user").(models.User), Token: csrf.Token(r)}
getTemplate(w, "settings").ExecuteTemplate(w, "base", params) getTemplate(w, "settings").ExecuteTemplate(w, "base", params)
case r.Method == "POST": case r.Method == "POST":
err := auth.ChangePassword(r) err := auth.ChangePassword(r)
@ -419,7 +408,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
Title string Title string
Flashes []interface{} Flashes []interface{}
Token string Token string
}{Title: "Login", Token: nosurf.Token(r)} }{Title: "Login", Token: csrf.Token(r)}
session := ctx.Get(r, "session").(*sessions.Session) session := ctx.Get(r, "session").(*sessions.Session)
switch { switch {
case r.Method == "GET": case r.Method == "GET":
@ -433,12 +422,13 @@ func Login(w http.ResponseWriter, r *http.Request) {
template.Must(templates, err).ExecuteTemplate(w, "base", params) template.Must(templates, err).ExecuteTemplate(w, "base", params)
case r.Method == "POST": case r.Method == "POST":
//Attempt to login //Attempt to login
succ, err := auth.Login(r) succ, u, err := auth.Login(r)
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
//If we've logged in, save the session and redirect to the dashboard //If we've logged in, save the session and redirect to the dashboard
if succ { if succ {
session.Values["id"] = u.Id
session.Save(r, w) session.Save(r, w)
http.Redirect(w, r, "/", 302) http.Redirect(w, r, "/", 302)
} else { } else {

18
controllers/route_test.go Normal file
View File

@ -0,0 +1,18 @@
package controllers
import (
"fmt"
"net/http"
"net/url"
)
func (s *ControllersSuite) TestLoginCSRF() {
resp, err := http.PostForm(fmt.Sprintf("%s/login", as.URL),
url.Values{
"username": {"admin"},
"password": {"gophish"},
})
s.Equal(resp.StatusCode, 403)
fmt.Println(err)
}

View File

@ -4,12 +4,30 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/gophish/gophish/auth" "github.com/gophish/gophish/auth"
ctx "github.com/gophish/gophish/context"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
ctx "github.com/gorilla/context" "github.com/gorilla/csrf"
) )
var CSRFExemptPrefixes = []string{
"/api",
}
func CSRFExceptions(handler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, prefix := range CSRFExemptPrefixes {
if strings.HasPrefix(r.URL.Path, prefix) {
r = csrf.UnsafeSkipCheck(r)
break
}
}
handler.ServeHTTP(w, r)
}
}
// GetContext wraps each request in a function which fills in the context for a given request. // GetContext wraps each request in a function which fills in the context for a given request.
// This includes setting the User and Session keys and values as necessary for use in later functions. // This includes setting the User and Session keys and values as necessary for use in later functions.
func GetContext(handler http.Handler) http.HandlerFunc { func GetContext(handler http.Handler) http.HandlerFunc {
@ -23,17 +41,18 @@ func GetContext(handler http.Handler) http.HandlerFunc {
// Set the context appropriately here. // Set the context appropriately here.
// Set the session // Set the session
session, _ := auth.Store.Get(r, "gophish") session, _ := auth.Store.Get(r, "gophish")
// Put the session in the context so that // Put the session in the context so that we can
ctx.Set(r, "session", session) // reuse the values in different handlers
r = ctx.Set(r, "session", session)
if id, ok := session.Values["id"]; ok { if id, ok := session.Values["id"]; ok {
u, err := models.GetUser(id.(int64)) u, err := models.GetUser(id.(int64))
if err != nil { if err != nil {
ctx.Set(r, "user", nil) r = ctx.Set(r, "user", nil)
} else { } else {
ctx.Set(r, "user", u) r = ctx.Set(r, "user", u)
} }
} else { } else {
ctx.Set(r, "user", nil) r = ctx.Set(r, "user", nil)
} }
handler.ServeHTTP(w, r) handler.ServeHTTP(w, r)
// Remove context contents // Remove context contents
@ -60,8 +79,8 @@ func RequireAPIKey(handler http.Handler) http.HandlerFunc {
JSONError(w, 400, "Invalid API Key") JSONError(w, 400, "Invalid API Key")
return return
} }
ctx.Set(r, "user_id", u.Id) r = ctx.Set(r, "user_id", u.Id)
ctx.Set(r, "api_key", ak) r = ctx.Set(r, "api_key", ak)
handler.ServeHTTP(w, r) handler.ServeHTTP(w, r)
} }
} }