Link to home
Start Free TrialLog in
Avatar of lwfuk
lwfuk

asked on

PHP Security - How do protect my chart.

I'm looking for ideas.

Server = Linux.

Problem

1. I have a form that posts back to itself

2. The form has a JPGraph chart

3. After post back the src of an image on the form is set to the JPGraph chart with the post parameters echoed like this:
www.host.com/jp-chart.php?var1=12&var2=14

4. The application operates in a members area.

5. How can I protect the chart so that non-members cannot access the chart?

As the application stands anybody could right click and view the path and variables required to make the chart work therefore bypassing the login process.
Avatar of Michael701
Michael701
Flag of United States of America image

You probably set a cookie whent he user logs into the member area.

Just make sure it's set when the jp-chart.php program executes. If it's not set, send them to a login area.
Avatar of lwfuk
lwfuk

ASKER

That would be easy to hack.

Thanks Anyway.
why would that be an easy hack? how do you verify that a member logged in?
Avatar of lwfuk

ASKER

Easy - Copy it.
As Michael701 says,

I generally use session variables to verify login, as well as to check permissions, never use cookies to store user information.

In the php script that generates the graph I would do the same checking that is required to access the page that contains the link. If your login checking is secure for the page then it should be just as secure for the script that is called in the link/image/whatever.

You could even create a "login error" image that is returned if the user is not logged in.
Avatar of lwfuk

ASKER

Many thanks Hube02. The only problem with this method is that a registered user could still get direct access to the graph once they had established a session. I want to stop direct access to the graph as well as stop external visitors from getting to it.
Avatar of lwfuk

ASKER

PS Hube 02.

Michael701 recommended using text based cookies not sessions.
I do a triple check on one of my sites.

When the user logs in: I create a session / cookie with user name. also I generate a random number and store it as a cookie called checksum, I then write this value to the user's record along with the IP address.

To verify the user is valid, I get the session / cookie user name and checksum, then read the user "select username from users where username='cleaned username data' and checksum='cleaned checksum data' and last_ip='cleaned ip data';

if no records are found then it's not a valid user
Session variables can be used as log as you don't mind users being logged off after the 20 minute session timeout.
Avatar of lwfuk

ASKER

Many thanks Michael701.

This would still allow a registered user to access the chart directly.

OSCommerce has a secure file download facility for merchants who sell electronic products.

You can be a registered user and still you're not allowed to download a file until you have paid for it.

The download directory has a .htaccess file as shown.

I wonder how they override it to enable and disable downloads.

AuthType Basic
AuthName "No access"
AuthUserFile .htnopasswd
AuthGroupFile /dev/null
Require valid-user

Open in new window

Avatar of lwfuk

ASKER

Michale701.

Session variables are not suitable. A registered user could access the index page, setup a session and then go directly to the chart and use it as often as they like.

I did a test.
Ah, well that's a whole new ballgame.

This is normally done by having a download.php program that first verifies the user and then does a forced download of the data. This way the actual data file path is never reviled to the clients machine.
Avatar of lwfuk

ASKER

Sounds interesting Michael701:

To show you are right though - could you produce a short test script that would force and image download with the htaccess file above. Assume that there is a directory called charts with the htaccess file above and a single image called chat.png. Outside there is a file called download.php.

ie:

/download.php
/charts/.htaccess
/charts/chart.png

When I go to /charts/chart.png I will be rejected by the ht access file.

When I go to download.php I will either see the image or be presented with a download popup

try this
<?php
$strFilePath = "charts/chart.png";
 
header("Content-Type: image/png");
header("Content-Length: ".filesize($strFilePath));
header("Content-Disposition:attachment; filename=something_different.png");
 
echo file_get_contents($$strFilePath);
 
?>

Open in new window

You could try setting a session variable to a unique random token such as:
sha1(uniqid(rand(), TRUE))

when the form is requested and set the src of the JPGraph to include this token in its paramaters. Then when the JPGraph is requested, you check that the token in the request is present and is the same as the session variable.  If it's present and they match, unset the session variable and process the request. If they don't match, reject the request.
If the user then tries to request the JPGraph again, directly or otherwise, the token won't match the session variable and the request can be rejected.
Avatar of lwfuk

ASKER

Here is how I solved it.

Here is the directory structure.

/test/index.html
/test/charts/.htaccess
/test/charts/chart.php

The code is below.

To test:

1. Go to your test directory and you will see a black rectangle
2. Go to test/chart/chart.png and you will get a 403 error
3. Disable .htaccess and repeat 2 and you will see the black rectangle

Can anybody hack it?

# .htaccess
 
# Below I am blocking just PHP files but you can block any type of file.
# For example, the line below blocks direct access to jpg|jpeg|gif|png|php
# <FilesMatch "\.(jpg|jpeg|gif|png|php)$">
 
<FilesMatch "\.(php)$">
    SetEnvIfNoCase Referer "^http://([^/]*\.)?your_domain.net/" local_referrer=1
    Order Allow,Deny
    Allow from env=local_referrer
</FilesMatch>
 
<!-- index.html -->
 
<html>
<body>
<img src="chart/test1.php" />
</body>
</html>
 
//chart.php
 
<?php
 
//Create a black rectangle.
 
header('Content-type: image/png');
$im = imagecreatetruecolor(400, 400);
$text = 'Hello';
$black = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, 400, 400, $black);
imagepng($im);
imagedestroy($im);
 
?> 

Open in new window

Yes, very simply by sending the correct Referer header (via the browser, you could use something like the tamperdata add-on, or you could use wget --referer).

Your solution is not a very robust one, but if it works for you then all well and good.
.. and by way of illustration, a screenshot of the direct access using tamperdata to add a referer - this is why hotlinking is so trivial to circumvent.
ref.png
ASKER CERTIFIED SOLUTION
Avatar of jahboite
jahboite
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of lwfuk

ASKER

jahboite

I love your solution and you deserve all of the points which I will give you.

I noticed the issue you mentioned but I put it down to disk caching.

I'm going to do a quick test with a random number generator on the image and see what happens.

I'll report back and award you the points before shutting down the issue.

Many Thanks,

Adrian Smith
London



Avatar of lwfuk

ASKER

Hi Jahboite,

As I thought it is disk caching (which is OK in my application but your solution is the most robust).

Replace the code in chart.php and you will see that the random number (10 exp 11) in the index file and the chart file is identical. The probability against this happening is (10 exp 22).

I'll leave the issue open for a while incase you want to reply.

If not I'd like to thank you for a very intelligent response.

Kind Regards,

Adrian Smith
London
<?php
// Set the content-type
header('Content-type: image/png');
 
// Create the image
$im = imagecreatetruecolor(400, 30);
 
// Create some colors
$white = imagecolorallocate($im, 255, 255, 255);
$grey = imagecolorallocate($im, 128, 128, 128);
$black = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, 399, 29, $white);
 
// The text to draw
$text = rand(1, 100000000000);
// Replace path by your own font path
$font = '../../chart/truetype/arial.ttf';
 
// Add some shadow to the text
imagettftext($im, 20, 0, 11, 21, $grey, $font, $text);
 
// Add the text
imagettftext($im, 20, 0, 10, 20, $black, $font, $text);
 
// Using imagepng() results in clearer text compared with imagejpeg()
imagepng($im);
imagedestroy($im);
?>

Open in new window

Hey Adrian,

I just wanted to comment on the solution you gave and to expand upon why it's not a robust one and, additionally, why it's possibly a problematic one.  I'm not sure whether you fully understand what it does/doesn't do so please forgive me if I'm going over old ground ...

Your solution is a common method to prevent direct access to a resource (your chart image) and enforce it's retrieval via a linking page at your domain.
The FilesMatch directive in the .htaccess you gave is simply checking that there is an HTTP Referer header in the HTTP request (from the visiting users browser) and that the domain part of the referring URL matches your domain.
This is a method to determine whether the request was made by typing a URL into the address bar (or similar means) and directly requesting the resource or whether the the request was generated indirectly either by following a hyperlink or by requesting a linked resource (e.g. <img />).
The idea is that if the Referer header is present in the request, it must be an indirect request and if the domain part matches your domain then the linking resource must be one of your pages - and only then do you want to permit access to the resource.

The problem here is twofold.
1) There are legitimate reasons why the Referer header might not be present - it might be a privacy concern that prompted a user to disable it's use in their browser or it the header may have been stripped from the request by some kind of proxy.  In these cases, you may be denying access to legitimate users.
2) Your security is reliant on a variable which is completely under the user's control.  As I hoped to demonstrate with the screenshot above, the user can very easily inject the Referer header to make a direct request for your resource look like an indirect one and thus accessing the resource directly - which is exactly what you wanted to prevent.

This has nothing to do with caching at all.  It's never a good idea to "protect" a resource based on something the user can control (in fact it's a very bad idea).  Having said all of that, if it's not a major security issue if someone directly access the chart (given that they are authentcated users as you suggest in the original question) then your solution should work to prevent some users from doing so, but anyone who actually wants to find a way to access the chart directly will not have a very difficult job on their hands.  And bear in mind 1) above too.

The solution I presented in no way sought to prevent unauthorised access to the chart, for this you need to make the same authentiction checks in jp-chart.php as you do in any of the php files in the members area - in addtion to the token method I presented whose aim was only to prevent direct access to the resource.

Finally, I couldn't reproduce the identical random number issue, but then I didn't quite understand what you meant by
"the random number (10 exp 11) in the index file and the chart file is identical"
since only one random number is being generated - in chart.php

Avatar of lwfuk

ASKER

Hi jahboite

Sorry. I missed your response - for which I am very grateful.

I just revisited this solution because I'm having trouble using the charts in a PDF file and I decided to adopt your solution.

Many thanks for the detailed response you gave and I have learned something very valuable from you. I had never heard of Tamper Data until I met you and I have always wondered if there was a way of fiddling the http referer header. Now I know. Many thanks.

The (10 exp 11) issue just means that I was generating a random number between 1 and 10 to the power 11. The chance of getting identical numbers in both situations is 10 to the power 11 multiplyed by 10 to the power 11 which equals 10 to the power 22. ie 1 in 1 000 000 000 000 000 000 000 0.

I just wanted to show that there was little chance of 2 identical numbers being generated and that it had to be a caching issue. However, I didn't really grasp the tampa data issue.

Thanks Again and Best Wishes.

Adrian