From f3f72d81117a66416f6299785cf83fa0822e01de Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Sat, 5 Nov 2022 21:17:31 +0100 Subject: [PATCH] CSRF --- cmd/owl/reset_password.go | 10 +--------- cmd/owl/web/auth_test.go | 9 +++++++++ cmd/owl/web/handler.go | 27 +++++++++++++++++++++++++++ embed/auth.html | 1 + renderer.go | 1 + user.go | 8 +------- utils.go | 16 ++++++++++++++++ 7 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 utils.go diff --git a/cmd/owl/reset_password.go b/cmd/owl/reset_password.go index 6c9506c..78179cf 100644 --- a/cmd/owl/reset_password.go +++ b/cmd/owl/reset_password.go @@ -3,7 +3,6 @@ package main import ( "fmt" "h4kor/owl-blogs" - "math/rand" "github.com/spf13/cobra" ) @@ -35,14 +34,7 @@ var resetPasswordCmd = &cobra.Command{ } // generate a random password and print it - const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - - b := make([]byte, 16) - for i := range b { - b[i] = chars[rand.Intn(len(chars))] - } - password := string(b) - + password := owl.GenerateRandomString(16) user.ResetPassword(password) fmt.Println("User: ", user.Name()) diff --git a/cmd/owl/web/auth_test.go b/cmd/owl/web/auth_test.go index a7485fe..fa8ed54 100644 --- a/cmd/owl/web/auth_test.go +++ b/cmd/owl/web/auth_test.go @@ -16,6 +16,8 @@ func TestAuthPostWrongPassword(t *testing.T) { repo, user := getSingleUserTestRepo() user.ResetPassword("testpassword") + csrfToken := "test_csrf_token" + // Create Request and Response form := url.Values{} form.Add("password", "wrongpassword") @@ -23,9 +25,12 @@ func TestAuthPostWrongPassword(t *testing.T) { form.Add("redirect_uri", "http://example.com/response") form.Add("response_type", "code") form.Add("state", "test_state") + form.Add("csrf_token", csrfToken) + req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken}) assertions.AssertNoError(t, err, "Error creating request") rr := httptest.NewRecorder() router := main.SingleUserRouter(&repo) @@ -39,6 +44,8 @@ func TestAuthPostCorrectPassword(t *testing.T) { repo, user := getSingleUserTestRepo() user.ResetPassword("testpassword") + csrfToken := "test_csrf_token" + // Create Request and Response form := url.Values{} form.Add("password", "testpassword") @@ -46,9 +53,11 @@ func TestAuthPostCorrectPassword(t *testing.T) { form.Add("redirect_uri", "http://example.com/response") form.Add("response_type", "code") form.Add("state", "test_state") + form.Add("csrf_token", csrfToken) req, err := http.NewRequest("POST", user.AuthUrl()+"verify/", strings.NewReader(form.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) + req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken}) assertions.AssertNoError(t, err, "Error creating request") rr := httptest.NewRecorder() router := main.SingleUserRouter(&repo) diff --git a/cmd/owl/web/handler.go b/cmd/owl/web/handler.go index 0d100aa..8a7ead3 100644 --- a/cmd/owl/web/handler.go +++ b/cmd/owl/web/handler.go @@ -100,6 +100,15 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque return } + // Double Submit Cookie Pattern + // https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie + csrfToken := owl.GenerateRandomString(32) + cookie := http.Cookie{ + Name: "csrf_token", + Value: csrfToken, + } + http.SetCookie(w, &cookie) + reqData := owl.AuthRequestData{ Me: me, ClientId: clientId, @@ -107,6 +116,7 @@ func userAuthHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Reque State: state, ResponseType: responseType, User: user, + CsrfToken: csrfToken, } html, err := owl.RenderUserAuthPage(reqData) @@ -204,6 +214,23 @@ func userAuthVerifyHandler(repo *owl.Repository) func(http.ResponseWriter, *http response_type := r.FormValue("response_type") state := r.FormValue("state") + // CSRF check + formCsrfToken := r.FormValue("csrf_token") + cookieCsrfToken, err := r.Cookie("csrf_token") + + if err != nil { + println("Error getting csrf token from cookie: ", err.Error()) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Error getting csrf token from cookie")) + return + } + if formCsrfToken != cookieCsrfToken.Value { + println("Invalid csrf token") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Invalid csrf token")) + return + } + password_valid := user.VerifyPassword(password) if !password_valid { http.Redirect(w, r, diff --git a/embed/auth.html b/embed/auth.html index 9ed5df9..03884ce 100644 --- a/embed/auth.html +++ b/embed/auth.html @@ -7,5 +7,6 @@ + \ No newline at end of file diff --git a/renderer.go b/renderer.go index bd455d0..03e5540 100644 --- a/renderer.go +++ b/renderer.go @@ -27,6 +27,7 @@ type AuthRequestData struct { State string ResponseType string User User + CsrfToken string } func renderEmbedTemplate(templateFile string, data interface{}) (string, error) { diff --git a/user.go b/user.go index 2c58b84..4141814 100644 --- a/user.go +++ b/user.go @@ -2,7 +2,6 @@ package owl import ( "fmt" - "math/rand" "net/url" "os" "path" @@ -287,12 +286,7 @@ func (user User) addAuthCode(code AuthCode) error { func (user User) GenerateAuthCode(client_id string, redirect_uri string) (string, error) { // generate code - const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - b := make([]byte, 32) - for i := range b { - b[i] = chars[rand.Intn(len(chars))] - } - code := string(b) + code := GenerateRandomString(32) return code, user.addAuthCode(AuthCode{ Code: code, ClientId: client_id, diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..27083a2 --- /dev/null +++ b/utils.go @@ -0,0 +1,16 @@ +package owl + +import ( + "crypto/rand" + "math/big" +) + +func GenerateRandomString(length int) string { + chars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + b := make([]rune, length) + for i := range b { + k, _ := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) + b[i] = chars[k.Int64()] + } + return string(b) +}