Enforce the body size limit before CSRF parses the form (closes #90)
All checks were successful
check / check (push) Successful in 3m6s
All checks were successful
check / check (push) Successful in 3m6s
chi runs Use middleware in registration order, and every form route
group registered CSRF() before MaxBodySize(). gorilla/csrf calls
r.PostFormValue, so the form was parsed under net/http's 10 MB default
and the intended 1 MB cap never applied to form fields. The
/user/{username} group, which carries POST /password, had no
MaxBodySize registration at all.
- Register MaxBodySize ahead of CSRF in /pages, /sources, and
/source/{sourceID}, and add it to /user/{username}.
- Reject a declared-oversize body up front with 413. Reordering alone
cannot produce one: http.MaxBytesReader surfaces its error on Read,
so the form parse fails and gorilla/csrf answers 403 "no token" for
what is really an oversized body. MaxBytesReader is still installed
afterwards so chunked or length-lying clients stay hard-capped.
- Drop the handler-local MaxBytesReader calls in auth.go, profile.go,
and source_management.go now that the middleware is the single
enforcement point. maxBodyShift stays; webhook.go still uses it.
The /webhook/{uuid} receiver is untouched: it bounds itself with
io.LimitReader in readWebhookBody and is neither CSRF-protected nor
form-parsed.
Tests cover the middleware in isolation (declared oversize is rejected
without reaching a sentinel handler; at-limit and under-limit bodies
pass through intact; GET is unaffected; an undeclared oversize body is
truncated at the cap) and the real router built by SetupRoutes, so the
registration order itself is guarded: an oversized POST to
/pages/login returns 413 with no gorilla/csrf cookie issued, an
oversized POST /password with a valid session and CSRF token returns
413 and leaves the stored hash unchanged, and under-limit requests
still complete through the normal CSRF path.
This commit is contained in:
@@ -285,10 +285,36 @@ func (s *Middleware) NoCache() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// MaxBodySize returns middleware that limits the request body size
|
||||
// for POST requests. If the body exceeds the given limit in
|
||||
// bytes, the server returns 413 Request Entity Too Large. This
|
||||
// prevents clients from sending arbitrarily large form bodies.
|
||||
// bodyLimitedMethod reports whether the request method carries a
|
||||
// body that the MaxBodySize middleware should cap.
|
||||
func bodyLimitedMethod(method string) bool {
|
||||
return method == http.MethodPost ||
|
||||
method == http.MethodPut ||
|
||||
method == http.MethodPatch
|
||||
}
|
||||
|
||||
// MaxBodySize returns middleware that limits the size of
|
||||
// POST/PUT/PATCH request bodies to maxBytes. It must be registered
|
||||
// before any middleware that parses the body — notably CSRF, which
|
||||
// calls r.PostFormValue — so that form parsing happens under this
|
||||
// cap rather than net/http's 10 MB default.
|
||||
//
|
||||
// Two enforcement paths exist, because http.MaxBytesReader alone
|
||||
// cannot produce a 413: it reports the overflow as an error from
|
||||
// Read, by which point the body parser downstream has already
|
||||
// converted that error into its own response.
|
||||
//
|
||||
// - Declared oversize: the request announces a Content-Length
|
||||
// greater than maxBytes. The middleware answers 413 Request
|
||||
// Entity Too Large immediately and does not call the next
|
||||
// handler, so neither CSRF nor the endpoint handler runs.
|
||||
// - Undeclared oversize: the request is chunked (Content-Length
|
||||
// of -1) or lies about its Content-Length. There is nothing to
|
||||
// check up front, so http.MaxBytesReader hard-caps the body at
|
||||
// maxBytes and the request fails downstream — the form parse
|
||||
// errors out and CSRF rejects it with 403. The response is less
|
||||
// precise than a 413, but the body is still never buffered
|
||||
// beyond the cap, which is the property that matters.
|
||||
func (s *Middleware) MaxBodySize(
|
||||
maxBytes int64,
|
||||
) func(http.Handler) http.Handler {
|
||||
@@ -297,14 +323,31 @@ func (s *Middleware) MaxBodySize(
|
||||
w http.ResponseWriter,
|
||||
r *http.Request,
|
||||
) {
|
||||
if r.Method == http.MethodPost ||
|
||||
r.Method == http.MethodPut ||
|
||||
r.Method == http.MethodPatch {
|
||||
r.Body = http.MaxBytesReader(
|
||||
w, r.Body, maxBytes,
|
||||
)
|
||||
if !bodyLimitedMethod(r.Method) {
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if r.ContentLength > maxBytes {
|
||||
s.log.Warn(
|
||||
"request body exceeds limit",
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"content_length", r.ContentLength,
|
||||
"limit", maxBytes,
|
||||
)
|
||||
http.Error(
|
||||
w,
|
||||
"Request Entity Too Large",
|
||||
http.StatusRequestEntityTooLarge,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxBytes)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user