Search code examples
amazon-web-servicesamazon-s3amazon-cloudfronthotlinkingamazon-waf

Prevent image hotlinking with S3 and CloudFront


I have a website with many pages where each page has many images (dozens or even hundreds).

I'm trying to avoid image hotlinking, but without increasing AWS costs too much.

So far I found 3 options:

Option 1: Use WAF to prevent hotlinking, by creating a rule to block based on referer header.

https://aws.amazon.com/pt/blogs/security/how-to-prevent-hotlinking-by-using-aws-waf-amazon-cloudfront-and-referer-checking/

The problem of this solution is that if you have many images loaded on each page, the cost will increase too much, bacause beyond paying S3 and CloudFront, you will need also to pay WAF, for every image request (imagine if you have 100 images on each page, for example).

Option 2: Configure CloudFront to fire a Lambda@Edge Viewer Request trigger that will inspect each request as it comes in the front door, and block requests based on referer header.

https://stackoverflow.com/a/46044606/2444386

This is similar to option above. The problem is that you add an overhead to EVERY request, even if the image is already in CloudFront cache. The Lambda@Edge will always be called and will also increase too much the cost if you have many images on each page of your website.

Option 3: Configure S3 bucket policy to block requests based on referer header, and whitelist the referer header at CloudFront.

So far, this seems to me the option with the best cost benefit, because if the image is already in CloudFront cache, you don't have any overhead, and also the cost is the cheapest because you don't need to pay WAF nor Lambda@Edge.

The problem is that the cache hit ratio will be much smaller, because CloudFront will not serve a response from cache unless the incoming request's Referer header matches exactly one from an already-cached request.

I tried to use the "origin" request header to avoid this problem, but it seems browser don't send this header for image GET requests.

Is there a better option?


Solution

  • I ended up using option 3 (S3 bucket policy) and additionally using the free plan of CloudFlare. CloudFlare has "Hotlink Protection" in the "Scrape Shield" even in the free plan.

    S3 bucket policy to prevent hotlinking:

    {
        "Version": "2012-10-17",
        "Id": "http referer policy",
        "Statement": [
            {
                "Sid": "Allow access only from my website",
                "Effect": "Deny",
                "Principal": {
                    "AWS": "*"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::resources.mywebsite.com/*",
                "Condition": {
                    "StringNotLike": {
                        "aws:Referer": [
                            "https://dev.mywebsite.com:8080/*",
                            "https://staging.mywebsite.com/*",
                            "https://www.mywebsite.com/*"
                        ]
                    }
                }
            }
        ]
    }
    

    In CloudFront I whitelisted the Referer header:

    enter image description here

    And in CloudFlare you can enable the "Hotlink Protection" option in the "Scrape Shield" menu:

    enter image description here

    enter image description here