mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-17 04:36:50 +00:00
fix(s3api): reject empty v4 signed header names (#9910)
Problem: Signature V4 SignedHeaders parsing accepted empty header name segments such as host; or ;host. Malformed Authorization headers could continue into signature verification instead of failing during header parsing. Root cause: parseSignedHeader only checked that the SignedHeaders value was non-empty, then split it on semicolons without validating each element. Fix: reject empty or whitespace-only signed header elements with ErrMissingFields before returning the parsed header list. Reproduction: go test ./weed/s3api -run TestParseSignedHeaderRejectsEmptyHeaderNames -count=1 failed before the fix because SignedHeaders=host; returned ErrNone. Validation: gofmt -w weed/s3api/auth_signature_v4.go weed/s3api/auth_signature_v4_test.go; git diff --check; go test ./weed/s3api -run TestParseSignedHeaderRejectsEmptyHeaderNames -count=1; go test ./weed/s3api -count=1 Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -559,6 +559,10 @@ func extractV4AuthInfoFromQuery(r *http.Request) (*v4AuthInfo, s3err.ErrorCode)
|
||||
if query.Get("X-Amz-Expires") == "" {
|
||||
return nil, s3err.ErrInvalidQueryParams
|
||||
}
|
||||
signedHeaders, errCode := parseSignedHeaderList(query.Get("X-Amz-SignedHeaders"))
|
||||
if errCode != s3err.ErrNone {
|
||||
return nil, errCode
|
||||
}
|
||||
|
||||
// Parse date
|
||||
dateStr := query.Get("X-Amz-Date")
|
||||
@@ -584,7 +588,7 @@ func extractV4AuthInfoFromQuery(r *http.Request) (*v4AuthInfo, s3err.ErrorCode)
|
||||
return &v4AuthInfo{
|
||||
Signature: query.Get("X-Amz-Signature"),
|
||||
AccessKey: credHeader.accessKey,
|
||||
SignedHeaders: strings.Split(query.Get("X-Amz-SignedHeaders"), ";"),
|
||||
SignedHeaders: signedHeaders,
|
||||
Date: t,
|
||||
Region: credHeader.scope.region,
|
||||
Service: credHeader.scope.service,
|
||||
@@ -716,10 +720,19 @@ func parseSignedHeader(signedHdrElement string) ([]string, s3err.ErrorCode) {
|
||||
if signedHdrFields[0] != "SignedHeaders" {
|
||||
return nil, s3err.ErrMissingSignHeadersTag
|
||||
}
|
||||
if signedHdrFields[1] == "" {
|
||||
return parseSignedHeaderList(signedHdrFields[1])
|
||||
}
|
||||
|
||||
func parseSignedHeaderList(signedHeadersValue string) ([]string, s3err.ErrorCode) {
|
||||
if signedHeadersValue == "" {
|
||||
return nil, s3err.ErrMissingFields
|
||||
}
|
||||
signedHeaders := strings.Split(signedHdrFields[1], ";")
|
||||
signedHeaders := strings.Split(signedHeadersValue, ";")
|
||||
for _, header := range signedHeaders {
|
||||
if strings.TrimSpace(header) == "" {
|
||||
return nil, s3err.ErrMissingFields
|
||||
}
|
||||
}
|
||||
return signedHeaders, s3err.ErrNone
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,51 @@ func TestExtractV4AuthInfoFromHeader_S3Tables(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSignedHeaderRejectsEmptyHeaderNames(t *testing.T) {
|
||||
cases := []string{
|
||||
"SignedHeaders=host;",
|
||||
"SignedHeaders=;host",
|
||||
"SignedHeaders=host;;x-amz-date",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
if _, errCode := parseSignedHeader(tc); errCode != s3err.ErrMissingFields {
|
||||
t.Fatalf("parseSignedHeader(%q) errCode = %v, want %v", tc, errCode, s3err.ErrMissingFields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractV4AuthInfoFromQueryRejectsEmptySignedHeaderNames(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
cases := []string{
|
||||
"host;",
|
||||
";host",
|
||||
"host;;x-amz-date",
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc, func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost/bucket/object", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest: %v", err)
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
query.Set("X-Amz-Algorithm", signV4Algorithm)
|
||||
query.Set("X-Amz-Credential", fmt.Sprintf("AKIAIOSFODNN7EXAMPLE/%s/us-east-1/s3/aws4_request", now.Format(yyyymmdd)))
|
||||
query.Set("X-Amz-Date", now.Format(iso8601Format))
|
||||
query.Set("X-Amz-Expires", "60")
|
||||
query.Set("X-Amz-Signature", "dummy")
|
||||
query.Set("X-Amz-SignedHeaders", tc)
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
if _, errCode := extractV4AuthInfoFromQuery(req); errCode != s3err.ErrMissingFields {
|
||||
t.Fatalf("extractV4AuthInfoFromQuery(%q) errCode = %v, want %v", tc, errCode, s3err.ErrMissingFields)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPathWithForwardedPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user