
Protection image
In this article I want to explain the hard way that I went through “protecting” images on the web. Before we begin this fascinating journey, I want to designate two approaches to protecting images:
- restriction / prohibition of posting direct links to original images
- you are paranoid and try to limit the distribution of copies of images
UPDATEUniversal protection of course does not exist. An article on how not to directly insert data from GET into SQL queries. Only in the context of image protection.
▌ Limit copies: my kids bike
At the beginning of my journey was traditionally a bicycle. Many years ago I was developing one great project. There were so many wonderful photos of animals and nature. It was these photos (or rather their full-size version) that had to be protected with all their might. The client did not want just to prohibit direct links to image files, but to deprive the user of the opportunity to download these same images. At the same time impose watermarks did not want.
We have already read that programmers
lie all the time . So I had to do what the client wanted. The decision was even quite nice. When you request a page with a photo, we generate a certain
$secretKey
and save to the session under this key the path to the full-size copy of the image:
')
public function actionView() {
In the view, we indicate the path to the photo in the following form:
<img src="/photo/source/{secretKey}" />
Now in
actionSource
we get the path to the full-size copy of the photo from the session, send it with the correct headers and clear the path to the full-size file:
public function actionSource() { $secretKey= $_GET['key']; $session = &$_SESSION['protected-photos']; $file = $session[$secretKey]['file']; if (is_file($file)) { header('Content-type: image/jpeg'); echo file_get_contents($file); } $session[$secretKey]['file'] = ''; }
As a result, if a user tries to download / open in a new tab / share a picture, a small copy of it will be returned to him.
Important: The weak point of this approach is pretty obvious: if you request a page with a photo not from the browser, but say via
wget . In this case, the
img
tag will not make the request
/photo/source/{secretKey}
. Thus, it will contain a full-size copy of the photo.
▌ Restrict direct links: .htaccess
Later I learned that the easiest and most common way to protect images is to configure
.htaccess accordingly. You can not only prohibit direct links to images, but also specify a stub that will be displayed on third-party resources instead of the original images from your site. Here is an example of this configuration:
RewriteEngine On RewriteCond %{HTTP_REFERER} !^http://(.+\.)?mysite\.com/ [NC] RewriteCond %{HTTP_REFERER} !^$ RewriteRule .*\.(jpe?g|gif|png)$ http://i.imgur.com/qX4w7.gif [L]
The first line contains a directive that includes the operation of the transformation mechanism. Everything is simple here. The second line, we block any sites except our own mysite.com. The code
[NC] means “no options,” in other words, case-insensitive URL matching. The third line, we allow empty referrals. And, finally, the last line of files all the files with the extension JPEG, JPG, GIF or PNG and replaces them with the image
qX4w7.gif from the server
imgur.com .
If necessary, you can do otherwise: prohibit direct links to images for specific domains.
RewriteEngine On RewriteCond %{HTTP_REFERER} ^http://(.+\.)?myspace\.com/ [NC,OR] RewriteCond %{HTTP_REFERER} ^http://(.+\.)?blogspot\.com/ [NC,OR] RewriteCond %{HTTP_REFERER} ^http://(.+\.)?livejournal\.com/ [NC] RewriteRule .*\.(jpe?g|gif|png)$ http://i.imgur.com/qX4w7.gif [L]
Each RewriteCond, except the last, must contain the code
[NC, OR] .
OR means "or next", i.e. match current domain or next.
Also, instead of a stub image, you can return an HTTP error with code 403:
RewriteRule .*\.(jpe?g|gif|png)$ - [F]
Important: do not try to return an HTML page instead of images. You can return either another image or an HTTP error.
▌ Restrict direct links: nginx
For nginx, everything is the same:
location ~* \.(jpe?g|gif|png)$ { set $bad_ref "N"; if ($http_referer !~ ^(http://(.+\.)?myspace\.com|http://(.+\.)?blogspot\.com|http://(.+\.)?livejournal\.com)) { set $bad_ref "Y"; } if ($bad_ref = "Y") { return 444; } }
Update: VBart suggested in his
comments that it is much better to use
ngx_http_referer_module
for these purposes.
▌ Restrict direct links: Amazon CloudFront Signed URLs
Amazon CloudFront is one of the best content delivery options for users. In addition to his direct duties as an ordinary CDN, he also gives the opportunity to generate signed links. Such links provide an opportunity to restrict access to the file by time range, as well as by IP. Thus, for example, you can specify that the image will be available within 10 minutes. Or 7 days from tomorrow.
Average, the link to the file is as follows:
one d111111abcdef8.cloudfront.net/image.jpg?
2 color=red&size=medium
3 &Policy=eyANCiAgICEXAMPLEW1lbnQiOiBbeyANCiAgICAgICJSZXNvdXJjZSI6Imh0dHA 6Ly9kemJlc3FtN3VuMW0wLmNsb3VkZnJvbnQubmV0L2RlbW8ucGhwIiwgDQogICAgICAiQ 29uZGl0aW9uIjp7IA0KICAgICAgICAgIklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiI yMDcuMTcxLjE4MC4xMDEvMzIifSwNCiAgICAgICAgICJEYXRlR3JlYXRlclRoYW4iOnsiQ VdTOkVwb2NoVGltZSI6MTI5Njg2MDE3Nn0sDQogICAgICAgICAiRGF0ZUxlc3NUaGFuIjp 7IkFXUzpFcG9jaFRpbWUiOjEyOTY4NjAyMjZ9DQogICAgICB9IA0KICAgfV0gDQp9DQo
7IkFXUzpFcG9jaFRpbWUiOjEyOTY4NjAyMjZ9DQogICAgICB9IA0KICAgfV0gDQp9DQo
4 &Signature=nitfHRCrtziwO2HwPfWw~yYDhUF5EwRunQA-j19DzZrvDh6hQ73lDx~ -ar3UocvvRQVw6EkC~GdpGQyyOSKQim-TxAnW7d8F5Kkai9HVx0FIu-5jcQb0UEmat EXAMPLE3ReXySpLSMj0yCd3ZAB4UcBCAqEijkytL6f3fVYNGQI6
~ -ar3UocvvRQVw6EkC ~ GdpGQyyOSKQim-TxAnW7d8F5Kkai9HVx0FIu-5jcQb0UEmat EXAMPLE3ReXySpLSMj0yCd3ZAB4UcBCAqEijkytL6f3fVYNGQI6
5 &Key-Pair-Id=APKA9ONS7QCOWEXAMPLE
And now the points:
- The basic link to your image. This is the link that you used to access the image and earlier, before the signed links.
- Arbitrary query parameters, which are usually used to log access to images. CloudFront allows you to transfer, cache and log these parameters. Important: the name of the parameters should not coincide with the reserved CloudFront itself: Expires , Key-Pair-Id , Policy , Signature . It is best to add the prefix x- to your parameters. This will be especially useful if your images are stored on Amazon S3 .
- Access rules for images in JSON-format and without spaces ( details ).
- A hashed and signed version of the access rules from the previous clause ( details ).
- Signature key ( details ).
Important: CloudFront
does not support CNAMEs with HTTPS . Those. you cannot replace
d111111abcdef8.cloudfront.net
d111111abcdef8.cloudfront.net
on
images.example.com
images.example.com
. There are two possible solutions to the problem:
- Re-use domain
https://*.cloudfront.com
for images. - Leave the domain
images.example.com
, but use it via the HTTP protocol.
The choice of one of two options is essentially a matter of taste. In principle, they do not differ among themselves.
▌ Epilogue
I hope the approaches described above will help you navigate faster in the difficult task of protecting images on the web. And some useful related links:
- Hotlinking: Generator .htaccess
- Hotlinking: .htaccess configuration
- Hotlinking: Nginx configuration example
- Hotlinking: Validation
- Amazon CloudFront Signed URLs