Replace CSRF token checking with Referer checking

main
Brian Picciano 2 years ago
parent 159638084e
commit 08811a6da7
  1. 5
      srv/src/http/api.go
  2. 64
      srv/src/http/csrf.go
  3. 18
      srv/src/http/tpl.go
  4. 6
      srv/src/http/tpl/assets.html
  5. 4
      srv/src/http/tpl/edit-post.html
  6. 13
      srv/src/http/tpl/load-csrf.html
  7. 5
      srv/src/http/tpl/posts.html

@ -164,8 +164,6 @@ func (a *api) Shutdown(ctx context.Context) error {
func (a *api) apiHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/csrf", a.getCSRFTokenHandler())
mux.Handle("/pow/challenge", a.newPowChallengeHandler())
mux.Handle("/pow/check",
a.requirePowMiddleware(
@ -250,11 +248,10 @@ func (a *api) handler() http.Handler {
h := apiutil.MethodMux(map[string]http.Handler{
"GET": applyMiddlewares(
mux,
setCSRFMiddleware,
),
"*": applyMiddlewares(
mux,
checkCSRFMiddleware,
a.checkCSRFMiddleware,
addResponseHeadersMiddleware(map[string]string{
"Cache-Control": "no-store, max-age=0",
"Pragma": "no-cache",

@ -3,76 +3,26 @@ package http
import (
"errors"
"net/http"
"net/url"
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
)
const (
csrfTokenCookieName = "csrf_token"
csrfTokenHeaderName = "X-CSRF-Token"
csrfTokenFormName = "csrfToken"
)
func setCSRFMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
csrfTok, err := apiutil.GetCookie(r, csrfTokenCookieName, "")
if err != nil {
apiutil.InternalServerError(rw, r, err)
return
} else if csrfTok == "" {
http.SetCookie(rw, &http.Cookie{
Name: csrfTokenCookieName,
Value: apiutil.RandStr(32),
Secure: true,
})
}
h.ServeHTTP(rw, r)
})
}
func checkCSRFMiddleware(h http.Handler) http.Handler {
func (a *api) checkCSRFMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
csrfTok, err := apiutil.GetCookie(r, csrfTokenCookieName, "")
refererURL, err := url.Parse(r.Referer())
if err != nil {
apiutil.InternalServerError(rw, r, err)
apiutil.BadRequest(rw, r, errors.New("invalid Referer"))
return
}
givenCSRFTok := r.Header.Get(csrfTokenHeaderName)
if givenCSRFTok == "" {
givenCSRFTok = r.FormValue(csrfTokenFormName)
}
if csrfTok == "" || givenCSRFTok != csrfTok {
apiutil.BadRequest(rw, r, errors.New("invalid CSRF token"))
if refererURL.Scheme != a.params.PublicURL.Scheme ||
refererURL.Host != a.params.PublicURL.Host {
apiutil.BadRequest(rw, r, errors.New("invalid Referer"))
return
}
h.ServeHTTP(rw, r)
})
}
func (a *api) getCSRFTokenHandler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
csrfTok, err := apiutil.GetCookie(r, csrfTokenCookieName, "")
if err != nil {
apiutil.InternalServerError(rw, r, err)
return
}
apiutil.JSONResult(rw, r, struct {
CSRFToken string
}{
CSRFToken: csrfTok,
})
})
}

@ -5,7 +5,6 @@ import (
"fmt"
"html/template"
"io/fs"
"log"
"net/http"
"path/filepath"
"strings"
@ -100,21 +99,12 @@ func (a *api) mustParseTpl(name string) *template.Template {
func (a *api) mustParseBasedTpl(name string) *template.Template {
tpl := a.mustParseTpl(name)
tpl = template.Must(tpl.New("load-csrf.html").Parse(mustReadTplFile("load-csrf.html")))
tpl = template.Must(tpl.New("base.html").Parse(mustReadTplFile("base.html")))
return tpl
}
type tplData struct {
Payload interface{}
CSRFToken string
}
func (t tplData) CSRFFormInput() template.HTML {
return template.HTML(fmt.Sprintf(
`<input type="hidden" name="%s" class="csrfHiddenInput" />`,
csrfTokenFormName,
))
Payload interface{}
}
// executeTemplate expects to be the final action in an http.Handler
@ -123,11 +113,8 @@ func executeTemplate(
tpl *template.Template, payload interface{},
) {
csrfToken, _ := apiutil.GetCookie(r, csrfTokenCookieName, "")
tplData := tplData{
Payload: payload,
CSRFToken: csrfToken,
Payload: payload,
}
if err := tpl.Execute(rw, tplData); err != nil {
@ -141,7 +128,6 @@ func executeTemplate(
func (a *api) executeRedirectTpl(
rw http.ResponseWriter, r *http.Request, url string,
) {
log.Printf("here url:%q", url)
executeTemplate(rw, r, a.redirectTpl, struct {
URL string
}{

@ -1,7 +1,5 @@
{{ define "body" }}
{{ $csrfFormInput := .CSRFFormInput }}
<h2>Upload Asset</h2>
<p>
@ -10,7 +8,6 @@
</p>
<form action="{{ BlogURL "assets/" }}" method="POST" enctype="multipart/form-data">
{{ $csrfFormInput }}
<div class="row">
<div class="four columns">
<input type="text" placeholder="Unique ID" name="id" />
@ -37,7 +34,6 @@
method="POST"
style="margin-bottom: 0;"
>
{{ $csrfFormInput }}
<input type="submit" value="Delete" />
</form>
</td>
@ -46,8 +42,6 @@
</table>
{{ template "load-csrf.html" . }}
{{ end }}
{{ template "base.html" . }}

@ -2,8 +2,6 @@
<form method="POST" action="{{ BlogURL "posts/" }}">
{{ .CSRFFormInput }}
<div class="row">
<div class="columns six">
@ -99,8 +97,6 @@
</form>
{{ template "load-csrf.html" . }}
{{ end }}
{{ template "base.html" . }}

@ -1,13 +0,0 @@
<script async type="module" src="{{ StaticURL "api.js" }}"></script>
<script type="text/javascript">
(async () => {
const api = await import("{{ StaticURL "api.js" }}");
const res = await api.call("/api/csrf");
const els = document.getElementsByClassName("csrfHiddenInput");
for (let i = 0; i < els.length; i++) {
els[i].value = res.CSRFToken;
}
})();
</script>

@ -18,8 +18,6 @@
{{ define "body" }}
{{ $csrfFormInput := .CSRFFormInput }}
<p style="text-align: center;">
<a href="{{ BlogURL "posts/" }}?edit">
<button>New Post</button>
@ -44,7 +42,6 @@
action="{{ PostURL .ID }}?method=delete"
method="POST"
>
{{ $csrfFormInput }}
<input type="submit" value="Delete" />
</form>
</td>
@ -55,8 +52,6 @@
{{ template "posts-nextprev" . }}
{{ template "load-csrf.html" . }}
{{ end }}
{{ template "base.html" . }}

Loading…
Cancel
Save