Sources to check
In this article, we're going to tackle a few different tactics to safeguard images. But, let's first think about what ways people can get images.- Direct path - if a user knows the path to an image, they can simply go straight to it, and download the image.
- Drag and drop - in most browsers today, you can simply drag and drop an image right onto your desktop.
- Hotlinking - another website can link directly to your image
- Screenshot - a visitor can simply take a screenshot of your page and crop the image.
How to fix them
So, now that we've established where our problems are, how can we fix them?- Direct path - we're going to create a PHP script to "hide" the direct path.
- Drag and drop - we're going to implement something that Flickr does... placing a 1x1 transparent GIF on top of the image.
- Hotlinking - using .htaccess, we can prevent hotlinking from occurring
- Screenshot - not much you can do here. The best recommendation I have is to add a watermark to your image (not covered in this tutorial)
Blocking Direct Path Access
To block a user from using the direct path route, we will implement a combination of PHP and .htaccess. What we will do is create a PHP file that serves as the "proxy" for the image, and prevents the user from seeing the final path. So...let's get to it.image.php
|
1
2
3
4
5
6
7
8
9
10
11 |
<br>$_GET['f'] = "protectedImages/" . $_GET['f'];<br>$type = getFileType($_GET['f']);<br>if (acceptableType($type)) {<br> header("Content-type: $type");<p></p><p> echo file_get_contents($_GET['f']);<br> exit;<br>}<br>header('HTTP/1.1 403 Forbidden');<br>exit;<br></p> |
getFileType()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
<br>function getFileType($file) {<br> //Deprecated, but still works if defined...<br> if (function_exists("mime_content_type"))<br> return mime_content_type($file);<p></p><p> //New way to get file type, but not supported by all yet.<br> else if (function_exists("finfo_open")) {<br> $finfo = finfo_open(FILEINFO_MIME_TYPE);<br> $type = finfo_file($finfo, $file);<br> finfo_close($finfo);<br> return $type;<br> }</p><p> //Otherwise...just use the file extension<br> else {<br> $types = array(<br> 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',<br> 'gif' => 'image/gif', 'bmp' => 'image/bmp'<br> );<br> $ext = substr($file, strrpos($file, '.') + 1);<br> if (key_exists($ext, $types)) return $types[$ext];<br> return "unknown";<br> }<br>}<br></p> |
acceptableTypes()
Now that we have our file type, let's validate it. This will prevent users from trying to get PHP files, or any sort of file they shouldn't get. Here it is...|
1
2
3
4
5
6
7 |
<br>function acceptableType($type) {<br> $array = array("image/jpeg", "image/jpg", "image/png", "image/png");<br> if (in_array($type, $array))<br> return true;<br> return false;<br>}<br> |
Preventing Drag and Drop
To prevent drag and drop of images, we're going to use a little trick that I noticed Flickr was using. They simply stretch a 1x1 transparent gif over the image, so when a user either tries to drag and drop (or right-click and "Save image as"), the image saved is the gif, not the actual image. First, download a 1x1 transparent gif here. Now, in your HTML, all you will need to do is add in the image. So, let's do that first. Here is a pretty simple html page we'll work with.|
1
2
3
4
5
6
7
8
9
10
11
12 |
<br><html><br><head><br> <title>Page Title</title><br></head><br><body><br> <div class="image"><br> <img src="image.php?f=image.jpg" alt="Image" /><br> <div class="cover"><img src="imageCover.gif" alt="" /></div><br> </div><br></body><br></html><br> |
|
1
2
3
4
5
6
7
8
9
10
11
12
13 |
<br>.image {<br> overflow: hidden;<br> position: relative;<br> float: left;<br>}<br>.image .cover, .image .cover img {<br> position: absolute;<br> top: 0px;<br> left: 0px;<br> width: 100%;<br> height: 100%;<br>}<br> |
Disabling hotlinking
In this scenario, we are forcing PHP to handle the image handling. So, we can technically deny ALL requests for images within the protectedImages directory. How do we do that? Create a .htaccess file within the protectedImages folder, and paste this code into it.|
1
2
3
4
5
6
7
8 |
<br>#Prevent directory listing<br>Options -Indexes<p></p><p>#Prevent images from being viewed<br><Files *><br> deny from all<br></Files><br></p> |
Further protection on the image.php file
Although we have the image.php file handling all of the images, it really isn't protected. Try inserting the URL for the image src directly into your browser. You'll get an image, and you can download it. Let's do a little trick to prevent that. What we'll do is two things: force the image.php to be referred by the site and allow the user to get images only within two seconds of visiting a page.Forcing the referral
When a browser visits your index.html page, any images that are requested will have a referrer of your page. So, we can use that. We'll use .htaccess to handle that!|
1
2
3
4
5 |
<br>RewriteEngine on<br>RewriteCond %{HTTP_REFERER} ^$<br>RewriteCond %{SCRIPT_FILENAME} image\.php<br>RewriteRule (.*) image.php?onlyHappensFromHTACCESS=denied [QSA,L]<br> |
|
1
2
3
4 |
<br>if (!isset($_GET['onlyHappensFromHTACCESS'])) {<br> //Our previous code here<br>}<br> |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
<br><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><br><html><br><head><br> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /><br> <title>Image Denied</title><br> <style type="text/css" media="screen"><br> body {<br> background-color: #ccc;<br> font-family: Helvetica, Arial;<br> }<br> #wrapper {<br> margin: 30px auto;<br> background-color: #ffffff;<br> -moz-border-radius: 15px;<br> -webkit-border-radius: 15px;<br> border-radius: 15px;<br> width: 800px;<br> padding: 20px;<br> }<br> </style><br></head><p></p><p><div id="wrapper"><br> <h3>Access Denied!</h3><br> <p>You have tried to access an image, but due to security reasons, you cannot view the image.</p></p><p> <p>If you wish to use the image you requested, please contact me.</p><br></div><br></html><br></p> |
Session timer
We'll also add in a two-second "timer" to ensure the user visited one of our pages first. In each page you are going to use (index.php in our case), add a simple session variable we can check. Here's an example:|
1
2
3 |
<br>session_start();<br>$_SESSION['lastcheck'] = time();<br> |
|
1
2
3
4
5
6
7
8 |
<br>function goodTiming() {<br> $n = time();<br> session_start();<br> if ($n - $_SESSION['lastcheck'] > 2 )<br> return false;<br> return true;<br>}<br> |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
<br>if (!isset($_GET['onlyHappensFromHTACCESS'])) {<br> $_GET['f'] = "pictures/" . $_GET['f'];<br> $type = getFileType($_GET['f']);<br> if (acceptableType($type)) {<br> if (goodTiming()) {<br> header("Content-type: $type");<p></p><p> echo file_get_contents($_GET['f']);<br> exit;<br> }<br> }<br> header('HTTP/1.1 403 Forbidden');<br> exit;<br>}<br></p> |
Full code source
image.php
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 |
<br><?php<br>if (!isset($_GET['onlyHappensFromHTACCESS'])) {<br> $_GET['f'] = "protectedImages/" . $_GET['f'];<br> $type = getFileType($_GET['f']);<br> if (acceptableType($type)) {<br> if (goodTiming()) {<br> header("Content-type: $type");<p></p><p> echo file_get_contents($_GET['f']);<br> exit;<br> }<br> }<br> header('HTTP/1.1 403 Forbidden');<br> exit;<br>}</p><p>function getFileType($file) {<br> if (function_exists("mime_content_type"))<br> return mime_content_type($file);<br> else if (function_exists("finfo_open")) {<br> $finfo = finfo_open(FILEINFO_MIME_TYPE);<br> $type = finfo_file($finfo, $file);<br> finfo_close($finfo);<br> return $type;<br> }<br> else {<br> $types = array(<br> 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',<br> 'gif' => 'image/gif', 'bmp' => 'image/bmp'<br> );<br> $ext = substr($file, strrpos($file, '.') + 1);<br> if (key_exists($ext, $types)) return $types[$ext];<br> return "unknown";<br> }<br>}</p><p>function acceptableType($type) {<br> $array = array("image/jpeg", "image/jpg", "image/png", "image/png");<br> if (in_array($type, $array))<br> return true;<br> return false;<br>}</p><p>function goodTiming() {<br> $n = time();<br> session_start();<br> if ($n - $_SESSION['lastcheck'] > 2 )<br> return false;<br> return true;<br>}</p><p>?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><br><html><br><head><br> <meta http-equiv="Content-type" content="text/html; charset=utf-8" /><br> <title>Image Denied</title><br> <style type="text/css" media="screen"><br> body {<br> background-color: #ccc;<br> font-family: Helvetica, Arial;<br> }<br> #wrapper {<br> margin: 30px auto;<br> background-color: #ffffff;<br> -moz-border-radius: 15px;<br> -webkit-border-radius: 15px;<br> border-radius: 15px;<br> width: 800px;<br> padding: 20px;<br> }<br> </style><br></head></p><p><div id="wrapper"><br> <h3>Access Denied!</h3><br> <p>You have tried to access an image, but due to security reasons, you cannot view the image.</p></p><p> <p>If you wish to use the image you requested, please contact me.</p><br></div><br></html><br></p> |
index.php
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 |
<br><?php session_start(); $_SESSION['lastcheck'] = time(); ?><br><html><br><head><br> <title>Page Title</title><br> <style type="text/css"><br> .image {<br> overflow: hidden;<br> position: relative;<br> float: left;<br> }<br> .image .cover, .image .cover img {<br> position: absolute;<br> top: 0px;<br> left: 0px;<br> width: 100%;<br> height: 100%;<br> }<br> </style><br></head><br><body><br> <div class="image"><br> <img src="image.php?f=image.jpg" alt="Image" /><br> <div class="cover"><img src="imageCover.gif" alt="" /></div><br> </div><br></body><br></html><br> |
.htaccess in main folder
|
1
2
3
4
5 |
<br>RewriteEngine on<br>RewriteCond %{HTTP_REFERER} ^$<br>RewriteCond %{SCRIPT_FILENAME} image\.php<br>RewriteRule (.*) image.php?onlyHappensFromHTACCESS=denied [QSA,L]<br> |
.htaccess in protectedImages folder
|
1
2
3
4
5
6
7
8 |
<br>#Prevent directory listing<br>Options -Indexes<p></p><p>#Prevent images from being viewed<br><Files *><br> deny from all<br></Files><br></p> |
André Jaccon