Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS upload file from client to S3

I know there have been some questions about this. I know how to accomplish this using AWS SDK.

However, my concern is about security. My plan is to generate a signed URL so users logged on my website can upload files to S3. I generate this url on server side (based on Express framework).

The problem: anyone with access to this URL can upload files to my bucket. Only logged users can get these urls, but anyone can use them.

Is there a way to generate a "one time use" url to authenticate with S3? If not, is there an approach I can use that does not show my credentials on client side and do not upload the files to my server ?(I know, asking too much :/)

like image 614
Bruno Henrique Avatar asked Oct 14 '25 17:10

Bruno Henrique


2 Answers

Requests require authentication

Unless you make your resource publicly writeable S3 will require upload requests to contain authentication information. Authentication ties the request to an AWS account, which is then checked to see if they have permissions to carry out the request against the resource (authorisation).

See the AWS documenation on Authenticating Requests for more information.

The latest version of the algorithm used to generate the authentication information is called 'AWS Signature Version 4'. This algorithm uses the secret key of an account and some other pieces of data to produce a signature that is sent along with the request.

AWS knows the secret key and can re-calculate the signature on their end. If the signatures match the request is authenticated as coming from that account.

The AWS SDK takes care of signing requests for you with the account credentials it is given. The docs describes how the algorithm works and tells you how you could sign the requests yourself if you really wanted to.

Sign the S3 requests on the server side

Instead of keeping a secret key on the client side, so that it can sign the requests made to S3, you can generate a signed URL securely on the server on behalf of clients, and then return it for them to call S3 directly from their machine.

This scenario is one of the specific use cases mentioned in the docs:

[P]re-signed URLs are useful if you want your user/customer to be able upload a specific object to your bucket, but you don't require them to have AWS security credentials or permissions.

When you create a pre-signed URL, you must provide your security credentials, specify a bucket name an object key, an HTTP method (PUT of uploading objects), and an expiration date and time.

The pre-signed URLs are valid only for the specified duration.

Pre-Signed Request Security

A pre-signed request has the authentication signature baked right into it. As you mentioned, anyone who obtains the URL can make the request. However, I wouldn't let that fact alone dissuade me from using them; there are several things you can do to prevent an attacker from using one.

Consider the following interaction between client and server.

Direct File Upload to S3 Sequence Diagram

Request Expiry

Expiry is controlled at URL generation time with the Expires Parameter. With the flow described above the URL could expire several seconds after generation. After this time it would simply no longer work and be rejected by S3. When setting the expiry you would need to account for the latency of requests between your server and the user (steps #5-#7), and the user and S3 (step #8).

Include File Checksum in Signature

Using the Body Parameter you can instruct S3 to only allow content with a specific MD5 checksum. Similarly, but even more secure, you can require S3 to validate the payload against the SHA256 checksum of the file.

After step #3 in the flow above calculate the SHA256 checksum of the file and pass it to the server in step #4. You could then add it to the request like so:

var req = S3.putObject({ Bucket: bucket_name, Key: file_name });
req.on('build', function() {
  req.httpRequest.headers['x-amz-content-sha256'] = file_sha256;
});
var url = req.presign(url_expiry);

(References: AWS.Request class docs, AWS S3 service source code - getSignedUrl and REST Common Request Headers)

Getting an upload URL should require authentication

Only your authorised users should be able to get a pre-signed upload URL. Anonymous requests to get an upload URL (step #4) should be denied.

Furthermore, since this is a privileged call all requests should be audited (step #6). That way should a pre-signed request leak you know who generated it and can take further action if necessary.

Use HTTPS

Two main reasons, provides protection for sensitive data, like the pre-signed URL, usernames, passwords and session cookies when in transit between client and server.

It also provides assurance to the client that you are who you say your are and not a fake site set up by an attacker.

like image 184
poida Avatar answered Oct 17 '25 05:10

poida


You can use PresignedUrlUploadObject to allow users to upload objects. This is an expiring URL and you can set the TTL

For NodeJS, it looks like this:

var s3 = new AWS.S3({computeChecksums: true}); // this is the default setting
var params = {Bucket: 'myBucket', Key: 'myKey', Body: 'EXPECTED CONTENTS'};
var url = s3.getSignedUrl('putObject', params);
console.log("The URL is", url);

From: AWS Docs

like image 43
iSkore Avatar answered Oct 17 '25 05:10

iSkore