diff --git a/s3api/controllers/bucket-post.go b/s3api/controllers/bucket-post.go index e22c420c..1fd0a825 100644 --- a/s3api/controllers/bucket-post.go +++ b/s3api/controllers/bucket-post.go @@ -112,15 +112,7 @@ func (c S3ApiController) POSTObject(ctx *fiber.Ctx) (*Response, error) { cacheControl := parsed.Fields["cache-control"] expires := parsed.Fields["expires"] - key, ok := parsed.Fields["key"] - if !ok || key == "" { - debuglogger.Logf("missing object key") - return &Response{ - MetaOpts: &MetaOptions{ - BucketOwner: parsedAcl.Owner, - }, - }, s3err.PostAuth.MissingField("key") - } + key := parsed.Fields["key"] err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ diff --git a/s3api/controllers/bucket-post_test.go b/s3api/controllers/bucket-post_test.go index 0788c04f..4d4aa968 100644 --- a/s3api/controllers/bucket-post_test.go +++ b/s3api/controllers/bucket-post_test.go @@ -254,28 +254,6 @@ func TestS3ApiController_POSTObject(t *testing.T) { input testInput output testOutput }{ - { - name: "missing key", - input: testInput{ - locals: postObjectLocalsForTest(middlewares.PostObjectResult{ - Fields: map[string]string{ - "policy": basePolicy, - "file": "ignored", - "x-amz-signature": "ignored", - }, - FileRdr: newMockFileReader("payload"), - ContentLength: int64(len("payload")), - }), - }, - output: testOutput{ - response: &Response{ - MetaOpts: &MetaOptions{ - BucketOwner: "root", - }, - }, - err: s3err.PostAuth.MissingField("key"), - }, - }, { name: "verify access fails", input: testInput{ diff --git a/s3api/middlewares/object-post-auth.go b/s3api/middlewares/object-post-auth.go index 10cabdef..7567c885 100644 --- a/s3api/middlewares/object-post-auth.go +++ b/s3api/middlewares/object-post-auth.go @@ -86,6 +86,16 @@ func AuthorizePostObject(root RootUserConfig, iam auth.IAMService, region string fields := result.Fields + if fields["key"] == "" { + debuglogger.Logf("missing object key") + return s3err.PostAuth.MissingField("key") + } + + if !utils.IsObjectNameValid(fields["key"]) { + debuglogger.Logf("invalid POST object key: %q", fields["key"]) + return s3err.GetAPIError(s3err.ErrBadRequest) + } + policyB64 := fields[formFieldPolicy] algorithm := fields[formFieldAlgorithm] credentialStr := fields[formFieldCredential] diff --git a/tests/integration/PostObject.go b/tests/integration/PostObject.go index 962a2443..d020ed6b 100644 --- a/tests/integration/PostObject.go +++ b/tests/integration/PostObject.go @@ -309,6 +309,44 @@ func PostObject_access_denied(s *S3Conf) error { }) } +func PostObject_invalid_object_names(s *S3Conf) error { + testName := "PostObject_invalid_object_names" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + for _, obj := range []string{ + ".", + "..", + "./", + "/.", + "//", + "../", + "/..", + "../.", + "../../../.", + "../../../etc/passwd", + "../../../../tmp/foo", + "for/../../bar/", + "a/a/a/../../../../../etc/passwd", + "/a/../../b/../../c/../../../etc/passwd", + } { + resp, err := sendPostObject(PostRequestConfig{ + bucket: bucket, + key: obj, + s3Conf: s, + fileContent: []byte("data"), + }) + if err != nil { + return err + } + + if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrBadRequest)); err != nil { + return err + } + } + + return nil + }) +} + func PostObject_policy_access_control(s *S3Conf) error { testName := "PostObject_policy_access_control" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 24c29d44..0bd7254b 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -1196,6 +1196,7 @@ func TestPostObject(ts *TestState) { ts.Run(PostObject_signature_mismatch) ts.Run(PostObject_expired_due_to_date) ts.Run(PostObject_access_denied) + ts.Run(PostObject_invalid_object_names) ts.Run(PostObject_policy_access_control) ts.Run(PostObject_policy_expired) ts.Run(PostObject_invalid_policy_document) @@ -2038,6 +2039,7 @@ func GetIntTests() IntTests { "PostObject_signature_mismatch": PostObject_signature_mismatch, "PostObject_expired_due_to_date": PostObject_expired_due_to_date, "PostObject_access_denied": PostObject_access_denied, + "PostObject_invalid_object_names": PostObject_invalid_object_names, "PostObject_policy_access_control": PostObject_policy_access_control, "PostObject_policy_expired": PostObject_policy_expired, "PostObject_invalid_policy_document": PostObject_invalid_policy_document,