Browse Source

add strict referrer check for POST uploads

This should protect against cross-site request forgery without the need
for cookies. It continues to allow requests with Linx-Delete-Key,
Linx-Expiry, or Linx-Randomize headers as these will not be set in the
case of cross-site requests.
pull/49/head
mutantmonkey 9 years ago
parent
commit
6ff181facb
  1. 24
      csrf.go
  2. 5
      pages.go
  3. 86
      server_test.go
  4. 5
      upload.go

24
csrf.go

@ -0,0 +1,24 @@
package main
import (
"net/http"
"strings"
)
func strictReferrerCheck(r *http.Request, prefix string, whitelistHeaders []string) bool {
for _, header := range whitelistHeaders {
if r.Header.Get(header) != "" {
return true
}
}
if referrer := r.Header.Get("Referer"); !strings.HasPrefix(referrer, prefix) {
return false
}
if origin := r.Header.Get("Origin"); origin != "" && !strings.HasPrefix(origin, prefix) {
return false
}
return true
}

5
pages.go

@ -75,6 +75,11 @@ func oopsHandler(c web.C, w http.ResponseWriter, r *http.Request, rt RespType, m
} }
} }
func badRequestHandler(c web.C, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
}
func unauthorizedHandler(c web.C, w http.ResponseWriter, r *http.Request) { func unauthorizedHandler(c web.C, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(401) w.WriteHeader(401)
err := Templates["401.html"].ExecuteWriter(pongo2.Context{}, w) err := Templates["401.html"].ExecuteWriter(pongo2.Context{}, w)

86
server_test.go

@ -119,6 +119,7 @@ func TestPostCodeUpload(t *testing.T) {
} }
req.PostForm = form req.PostForm = form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", Config.siteURL)
goji.DefaultMux.ServeHTTP(w, req) goji.DefaultMux.ServeHTTP(w, req)
@ -131,6 +132,84 @@ func TestPostCodeUpload(t *testing.T) {
} }
} }
func TestPostCodeUploadWhitelistedHeader(t *testing.T) {
w := httptest.NewRecorder()
filename := generateBarename()
extension := "txt"
form := url.Values{}
form.Add("content", "File content")
form.Add("filename", filename)
form.Add("extension", extension)
req, err := http.NewRequest("POST", "/upload/", nil)
if err != nil {
t.Fatal(err)
}
req.PostForm = form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Linx-Expiry", "0")
goji.DefaultMux.ServeHTTP(w, req)
if w.Code != 301 {
t.Fatalf("Status code is not 301, but %d", w.Code)
}
}
func TestPostCodeUploadNoReferrer(t *testing.T) {
w := httptest.NewRecorder()
filename := generateBarename()
extension := "txt"
form := url.Values{}
form.Add("content", "File content")
form.Add("filename", filename)
form.Add("extension", extension)
req, err := http.NewRequest("POST", "/upload/", nil)
if err != nil {
t.Fatal(err)
}
req.PostForm = form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
goji.DefaultMux.ServeHTTP(w, req)
if w.Code != 400 {
t.Fatalf("Status code is not 400, but %d", w.Code)
}
}
func TestPostCodeUploadBadOrigin(t *testing.T) {
w := httptest.NewRecorder()
filename := generateBarename()
extension := "txt"
form := url.Values{}
form.Add("content", "File content")
form.Add("filename", filename)
form.Add("extension", extension)
req, err := http.NewRequest("POST", "/upload/", nil)
if err != nil {
t.Fatal(err)
}
req.PostForm = form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", Config.siteURL)
req.Header.Set("Origin", "http://example.com/")
goji.DefaultMux.ServeHTTP(w, req)
if w.Code != 400 {
t.Fatalf("Status code is not 400, but %d", w.Code)
}
}
func TestPostCodeExpiryJSONUpload(t *testing.T) { func TestPostCodeExpiryJSONUpload(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
@ -146,6 +225,7 @@ func TestPostCodeExpiryJSONUpload(t *testing.T) {
req.PostForm = form req.PostForm = form
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", Config.siteURL)
goji.DefaultMux.ServeHTTP(w, req) goji.DefaultMux.ServeHTTP(w, req)
@ -192,6 +272,7 @@ func TestPostUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -225,6 +306,7 @@ func TestPostJSONUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -279,6 +361,7 @@ func TestPostExpiresJSONUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -339,6 +422,7 @@ func TestPostRandomizeJSONUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -382,6 +466,7 @@ func TestPostEmptyUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -416,6 +501,7 @@ func TestPostEmptyJSONUpload(t *testing.T) {
req, err := http.NewRequest("POST", "/upload/", &b) req, err := http.NewRequest("POST", "/upload/", &b)
req.Header.Set("Content-Type", mw.FormDataContentType()) req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", Config.siteURL)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

5
upload.go

@ -45,6 +45,11 @@ type Upload struct {
} }
func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) { func uploadPostHandler(c web.C, w http.ResponseWriter, r *http.Request) {
if !strictReferrerCheck(r, Config.siteURL, []string{"Linx-Delete-Key", "Linx-Expiry", "Linx-Randomize"}) {
badRequestHandler(c, w, r)
return
}
upReq := UploadRequest{} upReq := UploadRequest{}
uploadHeaderProcess(r, &upReq) uploadHeaderProcess(r, &upReq)

Loading…
Cancel
Save