VYPR
High severityNVD Advisory· Published Jan 31, 2024· Updated Aug 1, 2024

MinIO unsafe default: Access keys inherit `admin` of root user, allowing privilege escalation

CVE-2024-24747

Description

MinIO is a High Performance Object Storage. When someone creates an access key, it inherits the permissions of the parent key. Not only for s3:* actions, but also admin:* actions. Which means unless somewhere above in the access-key hierarchy, the admin rights are denied, access keys will be able to simply override their own s3 permissions to something more permissive. The vulnerability is fixed in RELEASE.2024-01-31T20-20-33Z.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/minio/minioGo
< 0.0.0-20240131185645-0ae4915a93910.0.0-20240131185645-0ae4915a9391

Affected products

1
  • Range: < RELEASE.2024-01-31T20-20-33Z

Patches

1
0ae4915a9391

fix: permission checks for editing access keys (#18928)

https://github.com/minio/minioAditya ManthramurthyJan 31, 2024via ghsa
6 files changed · +120 83
  • cmd/admin-handlers-users.go+12 9 modified
    @@ -774,6 +774,16 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
     		return
     	}
     
    +	// Permission checks:
    +	//
    +	// 1. Any type of account (i.e. access keys (previously/still called service
    +	// accounts), STS accounts, internal IDP accounts, etc) with the
    +	// policy.UpdateServiceAccountAdminAction permission can update any service
    +	// account.
    +	//
    +	// 2. We would like to let a user update their own access keys, however it
    +	// is currently blocked pending a re-design. Users are still able to delete
    +	// and re-create them.
     	if !globalIAMSys.IsAllowed(policy.Args{
     		AccountName:     cred.AccessKey,
     		Groups:          cred.Groups,
    @@ -782,15 +792,8 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
     		IsOwner:         owner,
     		Claims:          cred.Claims,
     	}) {
    -		requestUser := cred.AccessKey
    -		if cred.ParentUser != "" {
    -			requestUser = cred.ParentUser
    -		}
    -
    -		if requestUser != svcAccount.ParentUser {
    -			writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
    -			return
    -		}
    +		writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
    +		return
     	}
     
     	password := cred.SecretKey
    
  • cmd/admin-handlers-users_test.go+90 8 modified
    @@ -206,6 +206,7 @@ func TestIAMInternalIDPServerSuite(t *testing.T) {
     				suite.TestCannedPolicies(c)
     				suite.TestGroupAddRemove(c)
     				suite.TestServiceAccountOpsByAdmin(c)
    +				suite.TestServiceAccountPrivilegeEscalationBug(c)
     				suite.TestServiceAccountOpsByUser(c)
     				suite.TestAddServiceAccountPerms(c)
     				suite.TearDownSuite(c)
    @@ -896,14 +897,6 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
     	// 3. Check S3 access
     	c.assertSvcAccS3Access(ctx, s, cr, bucket)
     
    -	// 4. Check that svc account can restrict the policy, and that the
    -	// session policy can be updated.
    -	c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, accessKey, bucket)
    -
    -	// 4. Check that service account's secret key and account status can be
    -	// updated.
    -	c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket)
    -
     	// 5. Check that service account can be deleted.
     	c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
     
    @@ -979,6 +972,95 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
     	c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
     }
     
    +func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug(c *check) {
    +	ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
    +	defer cancel()
    +
    +	err := s.client.MakeBucket(ctx, "public", minio.MakeBucketOptions{})
    +	if err != nil {
    +		c.Fatalf("bucket creat error: %v", err)
    +	}
    +
    +	err = s.client.MakeBucket(ctx, "private", minio.MakeBucketOptions{})
    +	if err != nil {
    +		c.Fatalf("bucket creat error: %v", err)
    +	}
    +
    +	pubPolicyBytes := []byte(`{
    + "Version": "2012-10-17",
    + "Statement": [
    +  {
    +   "Effect": "Allow",
    +   "Action": [
    +    "s3:*"
    +   ],
    +   "Resource": [
    +    "arn:aws:s3:::public",
    +    "arn:aws:s3:::public/*"
    +   ]
    +  }
    + ]
    +}`)
    +
    +	fullS3PolicyBytes := []byte(`{
    +  "Version": "2012-10-17",
    +  "Statement": [
    +    {
    +      "Effect": "Allow",
    +      "Action": [
    +        "s3:*"
    +      ],
    +      "Resource": [
    +        "arn:aws:s3:::*"
    +      ]
    +    }
    +  ]
    +}
    +`)
    +
    +	// Create a service account for the root user.
    +	cr, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
    +		TargetUser: globalActiveCred.AccessKey,
    +		Policy:     pubPolicyBytes,
    +	})
    +	if err != nil {
    +		c.Fatalf("admin should be able to create service account for themselves %s", err)
    +	}
    +
    +	svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
    +
    +	// Check that the service account can access the public bucket.
    +	buckets, err := svcClient.ListBuckets(ctx)
    +	if err != nil {
    +		c.Fatalf("err fetching buckets %s", err)
    +	}
    +	if len(buckets) != 1 || buckets[0].Name != "public" {
    +		c.Fatalf("service account should only have access to public bucket")
    +	}
    +
    +	// Create an madmin client with the service account creds.
    +	svcAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
    +		Creds:  credentials.NewStaticV4(cr.AccessKey, cr.SecretKey, ""),
    +		Secure: s.secure,
    +	})
    +	if err != nil {
    +		c.Fatalf("Err creating svcacct admin client: %v", err)
    +	}
    +	svcAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
    +
    +	// Attempt to update the policy on the service account.
    +	err = svcAdmClient.UpdateServiceAccount(ctx, cr.AccessKey,
    +		madmin.UpdateServiceAccountReq{
    +			NewPolicy: fullS3PolicyBytes,
    +		})
    +
    +	if err == nil {
    +		c.Fatalf("service account should not be able to update policy on itself")
    +	} else if !strings.Contains(err.Error(), "Access Denied") {
    +		c.Fatalf("unexpected error: %v", err)
    +	}
    +}
    +
     func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
     	ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
     	defer cancel()
    
  • cmd/auth-handler.go+2 1 modified
    @@ -162,7 +162,8 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
     	s3Err := ErrAccessDenied
     	if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
     		getRequestAuthType(r) == authTypeSigned {
    -		// We only support admin credentials to access admin APIs.
    +
    +		// Get credential information from the request.
     		cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
     		if s3Err != ErrNone {
     			return cred, owner, s3Err
    
  • cmd/iam.go+14 31 modified
    @@ -974,7 +974,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
     		m[iamPolicyClaimNameSA()] = inheritedPolicyType
     	}
     
    -	// Add all the necessary claims for the service accounts.
    +	// Add all the necessary claims for the service account.
     	for k, v := range opts.claims {
     		_, ok := m[k]
     		if !ok {
    @@ -1848,37 +1848,14 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string)
     		return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
     	}
     
    -	// Now check if we have a sessionPolicy.
    -	spolicy, ok := args.Claims[sessionPolicyNameExtracted]
    -	if !ok {
    -		return false
    -	}
    -
    -	spolicyStr, ok := spolicy.(string)
    -	if !ok {
    -		// Sub policy if set, should be a string reject
    -		// malformed/malicious requests.
    -		return false
    -	}
    -
    -	// Check if policy is parseable.
    -	subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
    -	if err != nil {
    -		// Log any error in input session policy config.
    -		logger.LogIf(GlobalContext, err)
    -		return false
    -	}
    -
    -	// This can only happen if policy was set but with an empty JSON.
    -	if subPolicy.Version == "" && len(subPolicy.Statements) == 0 {
    -		return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
    -	}
    -
    -	if subPolicy.Version == "" {
    -		return false
    +	// 3. If an inline session-policy is present, evaluate it.
    +	hasSessionPolicy, isAllowedSP := isAllowedBySessionPolicy(args)
    +	if hasSessionPolicy {
    +		return isAllowedSP && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
     	}
     
    -	return subPolicy.IsAllowed(parentArgs) && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
    +	// Sub policy not set. Evaluate only the parent policies.
    +	return (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
     }
     
     // IsAllowedSTS is meant for STS based temporary credentials,
    @@ -2000,8 +1977,14 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
     		return
     	}
     
    +	// As the session policy exists, even if the parent is the root account, it
    +	// must be restricted by it. So, we set `.IsOwner` to false here
    +	// unconditionally.
    +	sessionPolicyArgs := args
    +	sessionPolicyArgs.IsOwner = false
    +
     	// Sub policy is set and valid.
    -	return hasSessionPolicy, subPolicy.IsAllowed(args)
    +	return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
     }
     
     // GetCombinedPolicy returns a combined policy combining all policies
    
  • cmd/signature-v4-utils.go+2 2 modified
    @@ -158,8 +158,8 @@ func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, A
     		// Check if the access key is part of users credentials.
     		u, ok := globalIAMSys.GetUser(r.Context(), accessKey)
     		if !ok {
    -			// Credentials will be invalid but and disabled
    -			// return a different error in such a scenario.
    +			// Credentials could be valid but disabled - return a different
    +			// error in such a scenario.
     			if u.Credentials.Status == auth.AccountOff {
     				return cred, false, ErrAccessKeyDisabled
     			}
    
  • cmd/sts-handlers_test.go+0 32 modified
    @@ -909,14 +909,6 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
     	// 3. Check S3 access
     	c.assertSvcAccS3Access(ctx, s, cr, bucket)
     
    -	// 4. Check that svc account can restrict the policy, and that the
    -	// session policy can be updated.
    -	c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
    -	// 4. Check that service account's secret key and account status can be
    -	// updated.
    -	c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
     	// 5. Check that service account can be deleted.
     	c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
     
    @@ -1101,14 +1093,6 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithGroups(c *check) {
     	// 3. Check S3 access
     	c.assertSvcAccS3Access(ctx, s, cr, bucket)
     
    -	// 4. Check that svc account can restrict the policy, and that the
    -	// session policy can be updated.
    -	c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
    -	// 4. Check that service account's secret key and account status can be
    -	// updated.
    -	c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
     	// 5. Check that service account can be deleted.
     	c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
     
    @@ -1358,14 +1342,6 @@ func (s *TestSuiteIAM) TestOpenIDServiceAcc(c *check) {
     	// 3. Check S3 access
     	c.assertSvcAccS3Access(ctx, s, cr, bucket)
     
    -	// 4. Check that svc account can restrict the policy, and that the
    -	// session policy can be updated.
    -	c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
    -	// 4. Check that service account's secret key and account status can be
    -	// updated.
    -	c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
     	// 5. Check that service account can be deleted.
     	c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
     
    @@ -1658,14 +1634,6 @@ func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicy(c *check) {
     	// 3. Check S3 access
     	c.assertSvcAccS3Access(ctx, s, cr, bucket)
     
    -	// 4. Check that svc account can restrict the policy, and that the
    -	// session policy can be updated.
    -	c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
    -	// 4. Check that service account's secret key and account status can be
    -	// updated.
    -	c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
    -
     	// 5. Check that service account can be deleted.
     	c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.