Smarter Image Hotlinking Prevention

Hey! That’s mine!#section1

Most web professionals are all too aware of the problems caused by hotlinkers. Leechers. Bandwidth thieves. People who use images hosted on your web server on their own pages.

Article Continues Below

For some lucky people who don’t pay by the gigabyte for the amount of data they transfer, that’s not too big a deal. Who cares if some little-trafficked weblog uses your photograph of snow falling in New York?

For other sites, however, it’s a much bigger problem. If a 100K JPEG is hotlinked on a site that gets, say, 1,000 hits a day, that’s 100MB of data transferred from your site without a single person actually visiting your site. If you have only a few gigabytes of transfer available per month — or worse, pay money per gigabyte — this can add up. And if someone were to leech an entire gallery from your site …

The trouble is that the usual approaches for preventing hotlinking have a couple of side effects.

Quick fixes aren’t perfect#section3

The usual approach is to instruct the server to deny all requests for images where the HTTP referer header 1 is not either from your own site (or blank). Thus, only people actually browsing your web site — or those whose browsers are not passing referrer headers for whatever reason — will be able to see the image.

A second approach is to redirect off-site traffic to an alternate image — either a general “hotlinking denied” image, or (in the case of some mischievous webmasters) something more shocking.

The trouble with these techniques is that regular linking is also prevented. Since browsers also send referrer headers when someone clicks a link to one of your images, the only way for people to go directly to your pictures would be to copy and paste a link into a new browser window. Granted, some webmasters might like this — it ensures that people link to the pages that photos appear on — but others may want links to succeed. Plus, if you have a gallery page with lots of images, this method makes it difficult for someone to point directly to a particular piece of your fantastic artwork.

The solution I’m about to suggest solves this problem while giving credit to you when people link to your pictures.

Where do we go from here?#section4

With PHP and mod_rewrite, you can disallow embedding and allow linking while automatically creating gallery pages for those direct linkers. It’s the best of all worlds, and here’s how to do it.

You’ll need an Apache server capable of running PHP, with mod_rewrite enabled. If you don’t know what you have, ask your hosting company, or give it a try — if it fails, you’ll know you don’t have them.

First, create a new file called showpic.php and put this code in it:

<?php
  header("Content-type: text/html");
  header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  header("Cache-Control: no-store, no-cache,
          must-revalidate");
  header("Cache-Control: post-check=0, pre-check=0",
          false);
  header("Pragma: no-cache");
  $pic = strip_tags( $_GET['pic'] );
  if ( ! $pic ) {
    die("No picture specified.");
  }
?>

<html>
<head>
<title><?php echo($pic); ?></title>
<meta
  http-equiv="Content-Type"   c charset=iso-8859-1"
>
</head>
<body>

Image

Image from your web site.

</body> </html>

Needless to say, you should change the HTML to match your own web site.

Let’s take a look at the PHP in there. The first line is a header to make sure the Content-Type sent to the browser identifies the document as HTML. We’ll see why this is important in a moment. The second line checks that a variable $pic has been passed to the script. If not, it skips to the end and exits quite abruptly. However, since this script should never be called without that variable (again, we’ll see why later), that’s not too much of an issue.

Assuming that this variable is there, the other lines of PHP strip any tags from it (to prevent cross-site scripting exploits), output the variable in the right place to create a valid tag, and add the file name to the page <title>.

So far, this is just a simple script. Go to www.yoursite.com/showpic.php?pic=yourimage.gif and it will output a simple page showing yourname.gif and a credit.

Now it gets interesting#section5

If you’re an .htaccess neophyte, take a look at this introduction which will take you through the basics.

The next step is to add the following code to your .htaccess file:

RewriteEngine OnRewriteCond %{REQUEST_FILENAME} .*jpg$|.*gif$|.*png$ [NC]
RewriteCond %{HTTP_REFERER} !^$ 
RewriteCond %{HTTP_REFERER} !yoursite.com [NC] 
RewriteCond %{HTTP_REFERER} !friendlysite.com [NC]  
RewriteCond %{HTTP_REFERER} !google. [NC] 
RewriteCond %{HTTP_REFERER} !search?q=cache [NC]RewriteRule (.*) /showpic.php?pic=$1

Let’s go through this one line at a time. RewriteEngine On gets mod_rewrite ready to do its stuff. First come the conditions:

RewriteCond %{REQUEST_FILENAME} .*jpg$|.*gif$|.*png$ [NC]

Okay. First condition: the file name must end in .jpg, .gif, or .png. This makes sure our hotlink prevention only triggers on images. You might want to change this to include .swf, .mp3, or other similar files.

RewriteCond %{HTTP_REFERER} !^$

Second condition: the referrer must not be blank. This means that people who aren’t passing referrer headers, for whatever reason, will still be able to see your images.

RewriteCond %{HTTP_REFERER} !yoursite.com [NC]
RewriteCond %{HTTP_REFERER} !friendlysite.com [NC]

These next conditions allow linking from your own site, and any other friendly sites that you want to allow linking from. Change the sites to your own, of course. Apache isn’t psychic.

(Don’t know what the ! .*$ is all about? It’s a regular expression. If you keep the format the same, you don’t need to worry about it.)

RewriteCond %{HTTP_REFERER} !google. [NC] 
RewriteCond %{HTTP_REFERER} !search?q=cache [NC]

Okay. Finally, let’s let Google get through. These last conditions allow people using the Google cache and Google Image Search to see your pictures. (You might want to remove this if you don’t want people to find your pictures this way, but I don’t recommend it.)

All together now#section6

Now let’s hook the two together. On to the last line of the .htaccess file, which is:

RewriteRule (.*) /showpic.php?pic=$1

This last rule silently redirects the request to /showpic.php?pic=[the requested file]. Thanks to the wonder of Apache, this will automatically include all necessary slashes and path information, and not be visible to the end user.

So what happens?#section7

Now, the only way a request will have got this far is if:

  • It’s for an image file, and
  • it’s not coming from a domain that you own or are friends with.

So firstly, and most importantly, if someone tries to hotlink one of your images, it’ll fail — the browser, instead of receiving an image file, will receive the result of showpic.php, which is sent as text/html. It’ll realise it can’t display it, and produce a broken image placeholder. Bandwidth saved.

On the other hand, if someone tries to link directly to your images, they’ll get silently redirected to an HTML page with your credit on it! No red X, no silly “denied” image — just a handy page that shows them the image they want to see, and gives you credit for your work.

See it in action#section8

First of all, let’s check that the script still allows images to load for people visiting your own web site. Yes, that looks fine. Now, let’s see if A List Apart can hotlink my images. Nope, guess not. And what happens if you just link straight to the image file? Well, there’s a nicely formatted page.

Taking it further#section9

If you’re using some kind of content management system like Gallery, there might be a way to tie a script like this into a database of pictures, and automatically generate ALT tags and more information about the picture.

Of course, I’ll leave that as an exercise for the reader.


1 For some reason, the HTTP specifications misspell “referrer” as “referer.”

Editor’s Note: The PHP code example in this article has been edited to address a small potential cross-site scripting vulnerability, to work with register_globals and short_tag off, and to work with caching. Thanks to everyone who helped make it better.

About the Author

Thomas Scott

Thomas Scott is a snowed-under linguistics student at the University of York. In his spare time, he makes silly things with stickers and ninjas, although not all at the same time.

90 Reader Comments

  1. At “Nope, guess not” some browsers as Amaya or Off by One ignore it and display image

  2. Sorry, but a clarification from my previous post. In the previous post when I say that this is a great article I’m refering to this ALA article 😉

  3. I’d love to see an update to the code to include how to exclude certain directories and such. You know, a lot of sites use banners for linking back to your site and really don’t mind the leeched bandwidth in that case.

    The technique has great promise for sending files (zips, mp3s, etc) through a file download manager too, in the case that they are hotlinked or directly accessed. For those of use that pay our hosting by advertising, this is essential.

  4. Once I replaced the HTML in showpic.php and uploaded it and the changes to my htaccess file, my site failed to load its stylesheets.

    What in the world?

    I came across this article by the time the comments were 6 pages deep and the cross-scripting vulerability was addressed.

    Anyone?

  5. The thing tripping me up on adding a directory exclusion to the rewrite was Apache’s variable names that don’t necessarily mirror the CGI variables I’m used to. I got it working with this additional line, which allows hotlinking from a banners directory:

    RewriteCond %{REQUEST_FILENAME} !images/banners/

  6. Very nice method Thomas.

    Just a quick note on this:

    —–snip—–
    RewriteCond %{HTTP_REFERER} !^$
    Second condition: the referrer must not be blank. This means that people who aren’t passing referrer headers, for whatever reason, will still be able to see your images.
    —–snip—–

    The intentions (as mentioned in the article) are good, but the consequences might be less attractive as Internet user’s security awareness grows.

    Bundled security software like Norton Internet Security (with Privacy Control) and McAfee Security Center (with Privacy Service) can be found on more and more systems every day. This isn’t a bad thing, really – Internet users protecting themselves is, in general, quite sweet.

    However, once a user has this software installed, it usually stays with its initial settings. There’s a minimum risk that the user will fiddle around with the software and configure it to suit specific needs and requirements. If a user actually does fiddle around with it, he/she will most likely not disable a security feature, but rather enable more of them if possible.

    The effect is that the hot linking protection, and anything else accepting blank referrers, has no effect.

    I figured this could be worth mentioning as there’s a slight tone of “not passing referrers” = “something’s wrong” in the discussion.

    cheers
    /j.

  7. Great article, it was refreshing to see a creative way of dealing with hotlinking. I don’t think there can be a perfect solution, the blank referrer is becoming more popular in bowsers – I disable it for my own browsing in opera, i believe it is disabled by default now in Opera, also – and security solutions, thus bypassing hotlink protection. If you take out the blank referrer line in .htaccess, it just causes a mess of other problems with legitimate links to files/images – mostly for those with disabled referrers, or those behind some firewalls, etc, but also for browsers with referrers enabled, especially when it comes to plugins, and some browsers not always passing the referrer correctly! That’s been my own experience, anyway, trying to find the best solution for my needs to prevent hotlinking. Nothing has ever been perfect.

    Just my thoughts on the hotlinking problem in general ;~}

  8. The no referer pass-through is fine and the fact that some user agents will bypass the Apache rewite has little bearing at all on the usefulness of the script. The purpose of the script is to discourage webmasters from linking to your images and save your bandwidth, which this technique does more or less. What is the use of hotlinking an image when 75-90%+ of users will see a broken image in it’s place? It’s much more effective to just steal the image (which is even less preventable).

  9. I see Zeldman is still hanging onto this craptacular flat, non-threaded, discussion format. Oh. Joy. No threading topics on a single page. No preview of postings. No info blurb about permitted markup. Nice job, Zeldman.

    This article is awful. Not everybody uses PHP, and as has been mentioned, the code used in the article is flawed.

    Stick with cross-platform standards, such as…I dunno… .htaccess, maybe?

    The very concept of bandwidth theft is funny as hell to me anyway. The Internet is an open network. If you put it in a publicly-accessible area of your server, you’re serving it up for public consumption. To call such public use of a publicly-accessible file on a publicly-accessible directory on a publicly-accessible server is asinine. There are existing technologies that work on all web servers, namely .htaccess, that can be used to prevent those files, such as images, from being served to any domain other than your own. But, failure to do so IS YOUR FAULT. Blaming the user is stupid and ignorant.

  10. I had abandoned using the mod_rewrite method to block hotlinking because of the large number of referrers that are not just stripped, but replaced. By a simple dash, “-“, by “XXXX:++++++++++++”, or “Referrer blocked by Add Subtract”, to name but a few. None of those are an attempt at hotlinking, but the ReWrite Conditions published in the article will deny them images when visiting my site. And in the past, it appeared to be about 4% of visitors (could be more today, with the proliferation of privacy software). This can be avoided by adding one line, after the RewriteCond %{REQUEST_FILENAME} line:

    RewriteCond %{HTTP_REFERER} ^[http|nttp].*$

    This line looks only for referrer strings that begin with “http” or “nttp”, and the examples listed above will be passed images. I’ve been testing it today, and my access logs have yet to show a “403 – Denied” that wasn’t valid.

  11. ===snip===
    Once I replaced the HTML in showpic.php and uploaded it and the changes to my htaccess file, my site failed to load its stylesheets.

    What in the world?
    —snip—
    I’ve had similar problems with mod_rewrite. Usually the style sheets without a title load correctly, but the ones with a title do not.

  12. Interesting. Thanks for seconding, Mickey, I thought I was off-topic. I wish it had worked, I’m always on the lookout for a better way, and I suspect my mod rewrite script is not as well coded as it could be.

    Could be worse though. I could be Sparky and just be angry for anger’s sake.

    Can’t we all just get along?

  13. Thanks Thomas,
    This will come in useful.

    ..and Sparky, don’t be a twat all your life

  14. One important note that I don’t believe was mentioned but merely addressed as ‘if it doesn’t work then it isn’t supported’ is that .htaccess files only work on Unix servers. So all of us .asp people don’t have that privelage. And even on Unix servers, some are restricted so that you can’t use them.

    Great tutorial by the way, .htaccess is very useful. Damn ASP and it’s ‘I’m too kewl to be on Unix’-ness.

  15. Thanks for the great article Thomas. It’ll be very useful, and I’ll try the code after some tea 🙂

    Sparky – just as kasper mentions; .htacess is not available on Windows.

    saj

  16. Sparky:

    .htaccess is not a cross-platform standard.

    The fact that “not everybody uses PHP” doesn’t make the article “awful.”

    Bandwidth theft is a problem, and if you believe it’s your job as a web developer to prevent it, then you need access to tools or techniques that let you do so. This article offers one set of tools, and it results in something a bit more multi-dimensional than what .htaccess allows. If you use Apache and simply want to prevent people from stealing your bandwidth, then .htaccess may be enough. If you don’t use Apache, or if you want to respond to requests for images in a more complex manner than .htaccess allows, this article offers a possible solution.

    The claim that the code is “flawed” is rather silly. All code is flawed. Basically, every time ALA runs an article showing how PHP or another server-side language can be used to achieve a desired result, advanced coders who read ALA will point out weaknesses or hidden dangers in the approach or suggest alternatives. That’s programming. There’s always another way to shake the bag of marbles, and there’s always a downside or tradeoff to any approach, especially when it’s a simple approach showing non-experts how to do something fairly straightforward.

    Put another way, if ALA published articles on writing, for an audience of business professionals who occassionally need to write clear business letters and proposals, those articles wouldn’t necessarily please brilliant prose stylists like Martin Amis.

    You’ve also complained about ALA’s formatting of the discussion forum, which is off-topic so won’t be addressed except to say that we originally ran a threaded forum, which many ALA readers disliked; we switched to the current format in response to reader requests, and most people seem to prefer it. As to your comment about “no info blurb about permitted markup,” I guess you didn’t notice the info blurb about permitted markup at the top of every page of the forum. It says:

    HTML tags and entities display as source; they do not render. To create a live link, simply type the URL (including http://).

    I’m not sure how much more information you’d need than that.

    We don’t have a preview feature, yet; you’re right about that.

  17. I’ve just tried doing this, but have not been too sucessfull. So far, I’ve created the necessary showpic.php, and the .htaccess files.

    http://www.thekhans.me.uk/showpic.php

    The problem arises because I’m not entirely sure where to place the .htaccess file. I’ve placed a copy in my images folder

    http://www.thekhans.me.uk/assets/images/

    and one at root level. The root level .htaccess also contains information on error redirects. The hotlinking code appears below those statements.

    However, when I directly request an image (after clearing my cache and starting a new browser session), I can still see the image without the credit;

    http://www.thekhans.me.uk/assets/images/bullet_orange.png

    Also, an image requested via a URL will not show because .htaccess/PHP fails to send the right path information. I’ve had to change the PHP code in showpic.php from

    ” alt=”Image” />

    Which makes sense slightly because I have a slightly odd place to store images.

    Aaargh! Can anyone help with where the .htaccess needs to go, and actually try accessing an image from my site directly to see if the error message pops up?

    Thanks!

  18. —quote—
    Interesting. Thanks for seconding, Mickey, I thought I was off-topic. I wish it had worked, I’m always on the lookout for a better way, and I suspect my mod rewrite script is not as well coded as it could be.
    —quote—
    I solved my problem. I figured out that I was not useing absolute urls for my style sheets, images and favicons. This meant that when I used a virtual folder path the style sheet was not found. Example:
    Real page: /index.php
    Virtual path: /debug/
    Linked material: style.css

    So on the real page, /style.css was called, however on the virtual pathed page, the browser tryed to find /debug/style.css, which of course did not excisit. I solved my problem by linking to /style.css. I hope I clearly explained myself.

  19. .htaccess files only work on Unix servers

    Not quite. .htaccess is, AFAIK, a feature of the Apache webserver, which runs on Unix, Linux, MS Windows and probably more.

  20. Hi,

    I’m a leecher. I’m aware of this practice, and therefore I fake the http referer per request. Not really of course, but it’s easy to fake this. If I was to create a leech script, this was the first trick I’d try to get around. `curl –referer=yourdomain.com yourdomain.com/yourscript.php?image=noleech` would do the trick.

  21. Unfortunately this does nothing about people running norton internet security, computer associates ez firewall, proxies and whatever that strips the referrer, it will still show the image to them because of the “blank referrer”

    On Keenspace, the blank referrer is not allowed, which breaks peoples browsers using such broken software.

    This trick, would only work on those that are sending the referrer. It solves the problem of hotlinking only if everyone is sending the referrer.

  22. Well, to be honest, I don’t feel like reading through eight pages of posts, to find out if it has been mentioned, so ignore this post if it has.

    The regex’s are slightly flawed. They only check if the extension is at the end of the get request. I could easily write mysite.com/image.png?.html or mysite.com/image.png#.html to get around the .htaccess’ files knowledge.

    I had a regex to get around that, but I cannot recall it at this current time, but I am sure somebody can come up with one.

  23. How about users with firewall? for example, Outpost (by default) block’s HTTP_REFERER..

  24. Bless you. This fixes my situation nicely.

    (I too am on FF 0.9.2, and I get the “end result” just fine.)

  25. Hi, folks!

    I enjoyed the article and am impressed at the amount of collaborative critique and revision has happenned since the first version!

    I have revised and tested scenarios surrounding the ideas in this article (and subsequent comments) and have a version which may be of interest to someone.

    Without further ado:

    – – – A (Refined?) Version – – –

    I have removed, from the .htaccess example in the article, allowances for google. These may be added in again easily. I simply did not need them in my case. I have also incorporated an allowance for referrer filtering software that obfuscates the referrer string (the second RewriteCond line, a variation of an earlier post except without the allowance for newsreaders). Naturally, change example.com in the fourth RewriteCond line to your own domain.

    Here is the .htaccess snippet:

    ########## BEGIN .HTACCESS DIRECTIVES

    # Set per-dir options

    Options +FollowSymLinks
    RewriteEngine On
    RewriteBase /

    # Hotlink protection
    # This stops robot indexing, offsite cacheing, and embedding of our images in other webpages by
    # throwing a (403) Forbidden status (takes care of most robots) followed by a text/html MIME-type
    # as the explanation/error document (takes care of requests expecting an image MIME-type, such as
    # in the case of an embedding request from another website)
    # The explanation produced is a normal HTML document which displays:
    # 1. a copyright notice
    # 2. the originally requested image (embedded in the response page)
    # 3. a link to our homepage
    # This approach still allows linking to images, as in the case of pointing to an image in a gallery
    # Exceptions: (causes a pass-thru)
    # Referrer is invalid or empty (takes care of direct client requests and some referrer filters)
    # Requests from our own domain (so we can embed the images in our own pages)

    RewriteCond %{REQUEST_URI} .(jp(e?)g|gif|png)$ [NC]
    RewriteCond %{HTTP_REFERER} ^http(s?) [NC]
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http(s)?://(www.)?example.com [NC]

    RewriteRule .+..{3,4}$ /showpic.php [T=application/x-httpd-php,L]

    ########## END .HTACCESS DIRECTIVES

    Place this in your document root directory’s .htaccess file and your whole site is covered.

    Now, for the interesting part. You may have noticed that we did not put a query string at the end of the RewriteRule. No, it is not a bug, it is *really* a feature! No, I’m not crazy (at least nobody has ben able to prove it, yet!)

    Apache is wonderful in how much information is made available to you via environment variables — without you doing anything special! You need not rely on userspace GET variables that can be easily tampered with. Apache remembers the original location of the image requested in a special environment variable called REDIRECT_URI. The wonderful thing about this is that Apache has already stripped out any goofy XSS (Cross-Site Scripting) problems by validating the URI syntax internally. Wasn’t that a nice daemon? Good daemon.

    So now that we know our original image request data is nice and safe in Apache environment variables, we move to the revised PHP code:

    ########## BEGIN PHP SCRIPT





    Example.com : Direct Image Request


    Image file:

    /”>.


    ########## END PHP SCRIPT

    First, we need not mess with GET variables anymore. Second, as long as you put showpic.php in the document root, this will cover your whole site, since the full image path is constructed at the top of the script. Third, robots will not be thrown, because we send a correct 403 Forbidden response.

    I used a (sly?) trick with the 403 thing. The HTML following it is actually an explanatory error document, but the user will never know it unless they check the headers! So long as the document is large enough, this should be displayed in place of even MSIE’s error documents! This error document, being of the text/html MIME-type, will still break embedding of your images into other sites, regardless of whether their server ignores the 403.

    The script also updates the copyright date to the current year automatically and refers to $_SERVER[‘SERVER_NAME’] to get your site name for the link to your homepage and the link text. In the interest of accessibility, the image ALT text provides a generic explaination as to why the image has no real description.

    Obviously, you would want to adjust the CSS and copyright notice to your own needs and attributions.

    Hopefully, this provides a robust and elegant solution that someone will find useful!

    Cheers!

    BTW: My site is not up yet, but it will be sometime in September. Come visit then, won’t you? 😉

  26. Though this may be obvious to some, this site word-wrapped the code in my prior post, so be careful with the comments starting with ‘#’ and all of the lines in the .htaccess example!

    Cheers!

  27. I made a typo in the explanation of environment variables:

    “Apache remembers the original location of the image requested in a special environment variable called REDIRECT_URI.”

    “REDIRECT_URI” should be “REDIRECT_URL”.

    Thankfully, the typo was not in the all-important code itself.

    Carry on. 😉

  28. is it possible to redirect from somefile.jpg to somefile.jpg.html just using mod rewrite?

    so if someone comes in from google images they are sent to the html file without writing a rule for each file.

    Paul

  29. Where it states that the images should fail to load I can still see it, same kitty cat but without any text on both “blocked” ones.
    IE 6 is the browser and it appears the script doesn’t work very well.
    I put an HTA access file on my site server and it works for some and not for others, more often than not it doesn’t work.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career