reject object names with '\' on windows (#16856)

This commit is contained in:
Harshavardhana 2023-03-20 13:16:00 -07:00 committed by GitHub
parent 6c11dbffd5
commit b3c54ec81e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 6 deletions

View File

@ -22,6 +22,7 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"runtime"
"strings" "strings"
"testing" "testing"
@ -32,6 +33,9 @@ import (
// Wrapper for calling NewMultipartUpload tests for both Erasure multiple disks and single node setup. // Wrapper for calling NewMultipartUpload tests for both Erasure multiple disks and single node setup.
func TestObjectNewMultipartUpload(t *testing.T) { func TestObjectNewMultipartUpload(t *testing.T) {
if runtime.GOOS == globalWindowsOSName {
t.Skip()
}
ExecObjectLayerTest(t, testObjectNewMultipartUpload) ExecObjectLayerTest(t, testObjectNewMultipartUpload)
} }
@ -110,9 +114,18 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
{"--", object, uploadID, BucketNotFound{}}, {"--", object, uploadID, BucketNotFound{}},
{"foo", object, uploadID, BucketNotFound{}}, {"foo", object, uploadID, BucketNotFound{}},
{bucket, object, "foo-foo", InvalidUploadID{}}, {bucket, object, "foo-foo", InvalidUploadID{}},
{bucket, "\\", uploadID, InvalidUploadID{}},
{bucket, object, uploadID, nil}, {bucket, object, uploadID, nil},
} }
if runtime.GOOS != globalWindowsOSName {
abortTestCases = append(abortTestCases, struct {
bucketName string
objName string
uploadID string
expectedErrType error
}{bucket, "\\", uploadID, InvalidUploadID{}})
}
// Iterating over creatPartCases to generate multipart chunks. // Iterating over creatPartCases to generate multipart chunks.
for i, testCase := range abortTestCases { for i, testCase := range abortTestCases {
err = obj.AbortMultipartUpload(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, opts) err = obj.AbortMultipartUpload(context.Background(), testCase.bucketName, testCase.objName, testCase.uploadID, opts)

View File

@ -174,10 +174,7 @@ func IsValidObjectPrefix(object string) bool {
// work with file systems, we will reject here // work with file systems, we will reject here
// to return object name invalid rather than // to return object name invalid rather than
// a cryptic error from the file system. // a cryptic error from the file system.
if strings.ContainsRune(object, 0) { return !strings.ContainsRune(object, 0)
return false
}
return true
} }
// checkObjectNameForLengthAndSlash -check for the validity of object name length and prefis as slash // checkObjectNameForLengthAndSlash -check for the validity of object name length and prefis as slash
@ -199,7 +196,7 @@ func checkObjectNameForLengthAndSlash(bucket, object string) error {
if runtime.GOOS == globalWindowsOSName { if runtime.GOOS == globalWindowsOSName {
// Explicitly disallowed characters on windows. // Explicitly disallowed characters on windows.
// Avoids most problematic names. // Avoids most problematic names.
if strings.ContainsAny(object, `:*?"|<>`) { if strings.ContainsAny(object, `\:*?"|<>`) {
return ObjectNameInvalid{ return ObjectNameInvalid{
Bucket: bucket, Bucket: bucket,
Object: object, Object: object,

View File

@ -19,19 +19,75 @@ package cmd
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/httptest"
"reflect" "reflect"
"runtime"
"strconv" "strconv"
"testing" "testing"
"github.com/klauspost/compress/s2" "github.com/klauspost/compress/s2"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config/compress" "github.com/minio/minio/internal/config/compress"
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
"github.com/minio/pkg/trie" "github.com/minio/pkg/trie"
) )
// Wrapper
func TestPathTraversalExploit(t *testing.T) {
if runtime.GOOS != globalWindowsOSName {
t.Skip()
}
defer DetectTestLeak(t)()
ExecExtendedObjectLayerAPITest(t, testPathTraversalExploit, []string{"PutObject"})
}
// testPathTraversal exploit test, exploits path traversal on windows
// with following object names "\\../.minio.sys/config/iam/${username}/identity.json"
// #16852
func testPathTraversalExploit(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials auth.Credentials, t *testing.T,
) {
if err := newTestConfig(globalMinioDefaultRegion, obj); err != nil {
t.Fatalf("Initializing config.json failed")
}
objectName := `\../.minio.sys/config/hello.txt`
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for Get Object end point.
req, err := newTestSignedRequestV4(http.MethodPut, getPutObjectURL("", bucketName, objectName),
int64(5), bytes.NewReader([]byte("hello")), credentials.AccessKey, credentials.SecretKey, map[string]string{})
if err != nil {
t.Fatalf("failed to create HTTP request for Put Object: <ERROR> %v", err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(rec, req)
ctx, cancel := context.WithCancel(GlobalContext)
defer cancel()
// Now check if we actually wrote to backend (regardless of the response
// returned by the server).
z := obj.(*erasureServerPools)
xl := z.serverPools[0].sets[0]
erasureDisks := xl.getDisks()
parts, errs := readAllFileInfo(ctx, erasureDisks, bucketName, objectName, "", false)
for i := range parts {
if errs[i] == nil {
if parts[i].Name == objectName {
t.Errorf("path traversal allowed to allow writing to minioMetaBucket: %s", instanceType)
}
}
}
}
// Tests validate bucket name. // Tests validate bucket name.
func TestIsValidBucketName(t *testing.T) { func TestIsValidBucketName(t *testing.T) {
testCases := []struct { testCases := []struct {