Hotlink Protection with PHP

What is Hotlinking?

Hotlinking is when another website links directly to one or more of your images or multimedia files and includes it on their web page. Not only is this theft of your intellectual property, but further more, you are paying for the bandwidth used by that site. Which can result in a problem with your budget.
The most common way to prevent others from hotlinking your content is Apache’s mod_rewrite. While this a solution that free available to use, there are a couple drawbacks. One being, that Apache has to be configured to use mod_rewrite (–enable-rewrite). Another one being, that for a lot of people writing regular expressions is not the most easiest thing to do.

Of course there are commercial solutions to the problem. Probably the most common one is cPanel. An administration interface for webserver, which let’s you create all the necessary items for your hotlink protection with a matter of clicks, in a matter of seconds.

Problems with common Hotlink Protection

While it may certainly sound promising to take the steps necessary to stop other sites from leaching your bandwidth, there are issues that can come about as a result. There is one major setback to all the server to prevent hotlinking that I have come across, and that is they all rely on using the HTTP_REFERER environment variable to work.

The main problem these days is that people are becoming more and more cautious about the way that web sites use their information. If you do decide to implement anti-leaching techniques that rely on the referer on your site then you should be aware that you could be blocking otherwise legitimate requests. A visitor who chooses to block or cipher their browsers HTTP_REFERER may have come from a page within your domain, but yet they will pass on any recognised values to the server and therefor will be stopped from viewing your images or downloading your files.

My approach to hotlinking

Whenever a surfer enters one of my websites, I always assign a session to him. The session holds a couple infos, for example: agent, IP, language, date, … etc., and gets passed along via cookie or via GET (as parameter to each one of my pages).

Since I deal with a lot of image content, I started databasing my collection. Which basically means that for administration and clustering purposes, I am saving all my images to a SQL database which is multi-homed and spread accross several servers. One could argue if that is a smart thing to do, but we can argue that on another day and in another article.

I wrote a little script which is used throughout my site:

<img src="http://htmlcenter.com/display.php?id=34" border="0" alt="" />

With an ever changing ID of course. That’s the part referencing my images in the database.
The following is the code from the script which I use to retrieve the image from the database:

<?php
$connection=@mysql_connect(...);

@mysql_select_db(...);

$query="SELECT mime, file FROM images 

WHERE id=".$_GET["id"];

$rawdb=@mysql_query ($query,$connection);

if($rawdb AND @mysql_num_rows($rawd-->0){

  $array=@ mysql_fetch_array($result);

  if (!empty($array["fileContents"])){

    // Output the MIME header

    header("Content-Type: ".$array["mime"]}");

    // Output the image

    echo $array["file"];

  }else{

    // something else...

  }

  @mysql_free_result($rawdb);

}else{

  // something else...

}

@mysql_close($connection);

?>

Since I already have a session for each user that comes to my website, I just added the following:

<img src="http://htmlcenter.com/display.php?id=34&sid=383829" border="0" alt="" />

And implement a small session checkup in the script itself:

<!--
session_start();

if($_SESSION["is_known"]){

  // do database calls

}else{

  header("Location:http://mydomain.tld/dontsteal.html");

}

-->

The main advantage to my method is, that the session is entirely server side. A user can not rid himself off it, or fake information. Since I have a timeout and save all the necessary info (IP!) to validate against, it looks pretty perfect to me and fit my needs.

One of the setbacks here are resources and performance. But since I am not forcing you, you may test and evaluate. Hope that helps!

4 Responses

  • Hi till, good article, I need to do something like this because I need to solve this “hotlinks” problem in one site. I haven’t too much experience in this kind of things related to web development.

    But I do have experience in securing web sites and applications…

    You must not never ever do this!!!:
    $query=”SELECT mime, file FROM images WHERE id=”.$_GET[“id”];

    Did you read about SQL injections???

    Cheers,
    zzenitt at gmail dot com

  • Matt

    Be careful if you are a blogger issuing a media rich RSS feed. Online RSS readers may display an image hosted on your servers on their site in a legitimate fashion. You would not want to offer a full RSS feed to your readers, encourage them to subscribe, then accuse their reader-of-choice of theft just for displaying the feed (including images) for them.

    It’s good to be aware of shady practices on the net but it behooves one’s image to consider all the implications of their choices on how to manage “3rd party” requests. Some are not only legitimate, but encouraged by the content creator themselves! :)

  • Both users raise interesting points about this article.

    The first comment raises a point that probably wasn’t extremely well-known when this article was first published (I think this article is actually five or six years old at this point).

    The current code should look more like:

    $query = (is_numeric($_GET['id'])) ? "SELECT mime, file FROM images WHERE id=".mysql_real_escape_string($_GET['id']) : NULL;

    That will first check to make sure that the ID is a number. If it’s not, then our SQL query is going to be set to NULL. If it is, then we build our SQL query. However, we also escape the string specifically for our current connection to the MySQL database. Having verified that our ID is numeric, that’s probably unnecessary, but it certainly doesn’t hurt us too badly to add that extra check in there.

    Of course, with the possibility that our SQL query is going to be set to NULL, we also need to verify that our SQL query is not empty before we actually try to run it, but that’s a little more in-depth than I want to get in a comment.

  • anonymous

    This can easily be circumvented byt doing a curl call to your website to set the session, then simply hotlink the image.