Limit request body size. (#4866)

Normally we do this with a reverse proxy in front of Boulder, but it's
nice to have an additional layer of protection in case someone deploys
Boulder without a reverse proxy.

Fixes #4730.
This commit is contained in:
Jacob Hoffman-Andrews 2020-06-12 12:02:49 -07:00 committed by GitHub
parent 3d9c31580a
commit 065cfd502f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 39 additions and 4 deletions

View File

@ -57,6 +57,8 @@ const (
issuerPath = "/acme/issuer-cert"
buildIDPath = "/build"
rolloverPath = "/acme/key-change"
maxRequestSize = 50000
)
// WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
@ -500,8 +502,11 @@ func (wfe *WebFrontEndImpl) verifyPOST(ctx context.Context, logEvent *web.Reques
return nil, nil, reg, probs.Malformed("No body on POST")
}
bodyBytes, err := ioutil.ReadAll(request.Body)
bodyBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, request.Body, maxRequestSize))
if err != nil {
if err.Error() == "http: request body too large" {
return nil, nil, reg, probs.Unauthorized("request body too large")
}
wfe.httpErrorCounter.WithLabelValues("UnableToReadReqBody").Inc()
return nil, nil, reg, probs.ServerInternal("unable to read request body")
}

View File

@ -2191,6 +2191,18 @@ func TestLengthRequired(t *testing.T) {
test.AssertEquals(t, http.StatusLengthRequired, prob.HTTPStatus)
}
func TestRequestTooLong(t *testing.T) {
wfe, _ := setupWFE(t)
payload := fmt.Sprintf(`{"a":"%s"}`, strings.Repeat("a", 50000))
_, _, _, prob := wfe.verifyPOST(ctx, newRequestEvent(), makePostRequest(signRequest(t,
payload, wfe.nonceService)), false, "n/a")
test.Assert(t, prob != nil, "No error returned for too-long request body.")
test.AssertEquals(t, probs.UnauthorizedProblem, prob.Type)
test.AssertEquals(t, "request body too large", prob.Detail)
test.AssertEquals(t, http.StatusForbidden, prob.HTTPStatus)
}
type mockSAGetRegByKeyFails struct {
core.StorageGetter
}

View File

@ -23,8 +23,12 @@ import (
"github.com/letsencrypt/boulder/web"
)
// POST requests with a JWS body must have the following Content-Type header
const expectedJWSContentType = "application/jose+json"
const (
// POST requests with a JWS body must have the following Content-Type header
expectedJWSContentType = "application/jose+json"
maxRequestSize = 50000
)
func sigAlgorithmForKey(key *jose.JSONWebKey) (jose.SignatureAlgorithm, error) {
switch k := key.Key.(type) {
@ -349,8 +353,11 @@ func (wfe *WebFrontEndImpl) parseJWSRequest(request *http.Request) (*jose.JSONWe
// Read the POST request body's bytes. validPOSTRequest has already checked
// that the body is non-nil
bodyBytes, err := ioutil.ReadAll(request.Body)
bodyBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, request.Body, maxRequestSize))
if err != nil {
if err.Error() == "http: request body too large" {
return nil, probs.Unauthorized("request body too large")
}
wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "UnableToReadReqBody"}).Inc()
return nil, probs.ServerInternal("unable to read request body")
}

View File

@ -9,6 +9,7 @@ import (
"crypto/rsa"
"fmt"
"net/http"
"strings"
"testing"
"github.com/letsencrypt/boulder/core"
@ -881,6 +882,16 @@ func TestParseJWSRequest(t *testing.T) {
Request: validJWSRequest,
ExpectedProblem: nil,
},
{
Name: "POST body too large",
Request: makePostRequestWithPath("test-path",
fmt.Sprintf(`{"a":"%s"}`, strings.Repeat("a", 50000))),
ExpectedProblem: &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: "request body too large",
HTTPStatus: http.StatusForbidden,
},
},
}
for _, tc := range testCases {