Jump to content

PHP Security


waynew

Recommended Posts

Hey guys. I was reading up on PHP security etc (have been for quite some time); from a number of pretty good sources and was wondering if maybe we could have a thread dedicated to sharing our knowledge on the subject? Maybe even a few debates will kick off. As you know, (especially from looking at the Help Forum), that many developers still build web applications as if these applications will be used by Ghandi himself; without thinking about the fact that some day, someone somewhere may try to mess things up. I think it would be a nice thread to have; and hopefully, we'll all learn something new that we didn't know before?

 

I'll start even though I am by no means; an expert on the subject; although I do read from good sources and have taken in quiet a bit:

 

1: My pet peeve is when people use addslashes( ) as a substitute for mysql_real_escape_string( ). Addslashes( ) can be fooled with the simple addition of a few more characters. A brilliant source to read up about this is http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string.

 

2: Always use a salt when hashing passwords.

 

Simple example:

<?php
$hashed_password = sha1("jhd32!23oie".$unhashed_password."jpobm3jh7");
?>

 

I've heard some people on here say that you shouldn't do something like:

$hashed_password = sha1(sha1($unhashed_password));

 

As it doesn't make your passwords any less "crackable". Somebody have more knowledge about this?

 

Note: I worked for a company who ran a mini-social network and didn't even hash passwords. When looking through the db I came across clear text. I nearly died.

 

3: I like to use htmlentities( ) for stopping XSS attacks user messages etc as I know people like to exchange code etc. I've come across instances where strip_tags( ) didn't seem to do as good a job. Escpecially in instances such as custom search engines. Why is it that search features seem to be more vulnerable? On a number of cases, I was able to get text onto the page that would work like a charm if I were to use some social engineering with it. Yet again, some of you guys will have more knowledge on this.

 

4: Try to use numeric values for $_GET / $_POST values as much as possible. For example; you have a user_id that's numeric. You can check beforehand to see if the $_GET values coming in is numeric.

 

if(is_numeric($_GET['user_id']) != 1){
header('Location: logout.php'); //Log them out... or whatever
}

 

This goes for HTML lists too. I was on a friends website that he had built. He had a HTML list that allowed you to pick a set of pre-defined moods. Happy, Sad etc. I just opened up Opera, changed the HTML and created a new mood, all for me. He was wondering for days how I had done this. Of course, if all moods had a numerical value to represent it, he could have checked it like so:

 

if($mood > 0 && $mood < 10) //data is okay

 

I think too many people still place too much faith in HTML restraints. Some people don't even filter the data coming in off restraints because they think "Hey not to worry, they can only pick something from the list I gave them." But what if I were to change one of my friends moods to some SQL? Or some JavaScript?

 

5: A lot of webhosts don't disable error reporting. This is usually why I always have one master file. In the master file, I could have something like:

 

<?php
session_start();
error_reporting(0);
require("classes.php");
?>

 

Of course, when I'm developing, I have the error_reporting(0) commented out. So when I'm ready to upload my site; I can just take the comments off.

 

6: I'm also looking into placing my dbconnect file outside of the root so that it cant be accessed via the web. Does anyone have more information on this? Usually, I put my dbconnect details in a function with an obscure name so that you have to do more than just include it to get a connection. So I'll have something like this:

<?php
require("dbconnect");
$connection = what_the_hell_are_you_doing();
?>

 

But I reckon placing outside of the site root is far safer.

 

7: I like to place redirects inside my folders. For example, in my images folder I'll have an index.php file with:

 

<?php
header('Location: ../index.php');
?>

 

Others should also look into the act of password protecting folders. Also, I usually give my folders obscure names. So I'm not naming my admin section as "admin" ect. On my personal websites, if a visitor does guess my directory password, AND guesses the folder name... they'll also have to guess the login file. If they don't, they should expect to be redirected to a pretty nasty video.

 

Could you guys add? Or feel free to pick at what I've said and recommend better.

 

 

Link to comment
Share on other sites

8: I usually name my database as if it were a random password. My tables, I like to place certain obscure keywords infront of them. So if I had a users table, I could call it something like:

 

hjedjohsd_users

 

I reckon that this could limit people trying to guess db names and table names.

 

What's your opinion on this?

 

9: The user that you connect to the db with should only have the relevant permissions needed. For example, allowing one of your users to use a MySQL connection that allows the dropping of databases is unnecessary. UPDATE, INSERT, DELETE.... Anyone else have more of an opinion on this?

Link to comment
Share on other sites

Protection through obscurity isn't really the best approach, but if you are also using passwords to protect anything then it wont do any harm (though i'd personally find it very irritating to work with) - just don't rely on people not being able to find files/folders.

 

6: I'm also looking into placing my dbconnect file outside of the root so that it cant be accessed via the web. Does anyone have more information on this? Usually, I put my dbconnect details in a function with an obscure name so that you have to do more than just include it to get a connection. So I'll have something like this:

 

Indeed, you should. It is possible that a web server problem will mean that, instead of being parsed, the PHP is displayed in the browser as plain text. Not good if your files contain passwords or other sensitive information. By placing things like a connect outside of the document root, they can only be accessed by your files.

 

I've heard some people on here say that you shouldn't do something like:

 

$hashed_password = sha1(sha1($unhashed_password));

 

As it doesn't make your passwords any less "crackable". Somebody have more knowledge about this?

 

Though i've not read anything about this in relation to sha1(), it has been suggested that applying the md5() algorithm twice increases the chances of collisions - e.g. the chances of two inputs having the same output. I've not seen any concrete proof of this either way, but it seems rather pointless in any case. A salt is perfectly adequate.

 

Link to comment
Share on other sites

Oh I completely agree about the obscurity thing. Take proper protection; and THEN maybe make it harder. I just figured that doing anything that makes it harder for somebody else to exploit is a good solution. Most hax0rsLOL will give up after a few tries. They just want the quick fix.

 

Indeed, you should. It is possible that a web server problem will mean that, instead of being parsed, the PHP is displayed in the browser as plain text. Not good if your files contain passwords or other sensitive information. By placing things like a connect outside of the document root, they can only be accessed by your files.

 

This happened to Facebook didn't it? Seemed to be the case that they were using Smarty and there was also no sign of OOP. Maybe a load of custom functions. Either way; I think that's a programmers worse nightmare. Is there any information relating to actually what causes this?

Link to comment
Share on other sites

I've heard some people on here say that you shouldn't do something like:

 

$hashed_password = sha1(sha1($unhashed_password));

 

As it doesn't make your passwords any less "crackable". Somebody have more knowledge about this?

 

Though i've not read anything about this in relation to sha1(), it has been suggested that applying the md5() algorithm twice increases the chances of collisions - e.g. the chances of two inputs having the same output. I've not seen any concrete proof of this either way, but it seems rather pointless in any case. A salt is perfectly adequate.

 

That's true, it will increase the chances of a hash collision because the sample size will be minimized twice.

 

There is another problem with solely relying on double hashing - you can still crack it! If the algorithm is known then it's just a matter of computation power and time before it's brute-forced. It's also still as vulnerable to dictionary attacks as just a single run through a hashing algorithm.

 

The great thing about salts is that first of all it adds random data to the string so that renders dictionary attacks completely useless. Moreover, if you add something like a 30-char long salt then brute-forcing it will take considerably longer time (long enough time that it wont even happen within a billion years with the computational power that's available today).

 

It's imperative that you keep the salt absolutely secret, otherwise it's completely useless. You might also consider to add the salt into different parts of the original string before hashing it so that even if people get the salt then they won't know where it belongs in the string.

 

Another issue you (waynewex) address is the visibility of files that lie within document root. The fix is simple: move it to a directory above document root. If people are not supposed to request the file directly (e.g. an image, stylesheet, javascript file or index.php) then it doesn't belong within document root.

 

I've written an article about PHP security here on PHP Freaks: http://www.phpfreaks.com/tutorial/php-security

It does obviously not address everything under the sun, but I feel it's a good primer for security related issues.

Link to comment
Share on other sites

4: Try to use numeric values for $_GET / $_POST values as much as possible. For example; you have a user_id that's numeric. You can check beforehand to see if the $_GET values coming in is numeric.

 

Code:

 

if(is_numeric($_GET['user_id']) != 1){

header('Location: logout.php'); //Log them out... or whatever

}

 

 

This goes for HTML lists too. I was on a friends website that he had built. He had a HTML list that allowed you to pick a set of pre-defined moods. Happy, Sad etc. I just opened up Opera, changed the HTML and created a new mood, all for me. He was wondering for days how I had done this. Of course, if all moods had a numerical value to represent it, he could have checked it like so:

 

Code:

 

if($mood > 0 && $mood < 10) //data is okay

 

 

I think too many people still place too much faith in HTML restraints. Some people don't even filter the data coming in off restraints because they think "Hey not to worry, they can only pick something from the list I gave them." But what if I were to change one of my friends moods to some SQL? Or some JavaScript?

 

The overall idea of what your doing here is that you're creating a whitelist.  It doesn't necessarily have to be numeric. 

 

 

Link to comment
Share on other sites

4: Try to use numeric values for $_GET / $_POST values as much as possible. For example; you have a user_id that's numeric. You can check beforehand to see if the $_GET values coming in is numeric.

 

Code:

 

if(is_numeric($_GET['user_id']) != 1){

header('Location: logout.php'); //Log them out... or whatever

}

 

 

This goes for HTML lists too. I was on a friends website that he had built. He had a HTML list that allowed you to pick a set of pre-defined moods. Happy, Sad etc. I just opened up Opera, changed the HTML and created a new mood, all for me. He was wondering for days how I had done this. Of course, if all moods had a numerical value to represent it, he could have checked it like so:

 

Code:

 

if($mood > 0 && $mood < 10) //data is okay

 

 

I think too many people still place too much faith in HTML restraints. Some people don't even filter the data coming in off restraints because they think "Hey not to worry, they can only pick something from the list I gave them." But what if I were to change one of my friends moods to some SQL? Or some JavaScript?

 

The overall idea of what your doing here is that you're creating a whitelist.  It doesn't necessarily have to be numeric. 

 

 

 

When I am not using an actual whitelist (i.e. a specific list of allowed variable values), I like a lot of control over user provided data, so I use regular expressions.  For example, for a image gallery, one could do the following...

 

        //Sets album directory and page
        $regex  = '/[^-_A-z0-9]++/'; //Only letters, numbers, underscores and/or hyphens allowed
        $regex2 = '/[^0-9]++/';      //Only numbers allowed
        $this->albumDir = isset($_GET['album']) ? preg_replace($regex, '', $_GET['album']) : null;
        $this->page  = isset($_GET['page']) ? preg_replace($regex2, '', $_GET['page']) : 1;

 

...then, after I filter the data, I can validate it (i.e. in this example, check, via some conditional statement, whether the provided album or page (now filtered) exists).

Link to comment
Share on other sites

That is in fact white listing, you allow a very particular thing while everything else gets rejected. There is nothing that prevents a whitelist to use wildcards. The opposite is a black list where you disallow something very particular and allow everything else. A whitelist will in almost every case be better.

Link to comment
Share on other sites

Has anyone ever seen #1 work in a live situation?  I mean theoretically it could, but between character encodings, and whitelisting, I think most of the time it would still be safe to use addslashes().  What about with MSSQL where the escape char is '?  Wouldn't that mean that a simple str_replace('\'', '\'\'', $str) would be vulnerable too?  Would you use regexp there, or look for the char code or what would you do?

Link to comment
Share on other sites

Has anyone ever seen #1 work in a live situation?  I mean theoretically it could, but between character encodings, and whitelisting, I think most of the time it would still be safe to use addslashes().  What about with MSSQL where the escape char is '?  Wouldn't that mean that a simple str_replace('\'', '\'\'', $str) would be vulnerable too?  Would you use regexp there, or look for the char code or what would you do?

 

I tried out what Chris in number #1 was talking about. I was working on an old site that only used addslashes; and indeed, SQL errors started popping up all over the place. Seriously, it is recommended that something better addslashes() be used. Still, imagine how many systems out there use addslashes and may be vulnerable?

Link to comment
Share on other sites

That's another thing. Brute forcing could easily be stopped if you just allowed three failed attempts per hour or two hours. Simple enough to do.

 

I'm a fan of sha1(). Mostly because that's just what I started out on. Habits etc.

 

And how would you track attempts? IP address? Brute force behind a proxy.  Not to mention, the more you try to prevent brute force attacks in that manner, the harder you're going to make it on legit users.  Which is always one of the biggest issues with making things secure - still keeping them easy and usable.

Link to comment
Share on other sites

Last I heard sha1() had been comprised so I moved up to sha256 - hash('sha256', $var);  Although I remember reading something recently suggesting sha512 now.  I guess I'll have to read up on it all and get caught up with times.

 

Also, you can track both the IP the attacker is using and the username they're attempting to login to.  If a username has 3 failed attempts lock that account for 30 mins and offer early re-activation via email or something. 

Link to comment
Share on other sites

That's another thing. Brute forcing could easily be stopped if you just allowed three failed attempts per hour or two hours. Simple enough to do.

 

I'm a fan of sha1(). Mostly because that's just what I started out on. Habits etc.

 

And how would you track attempts? IP address? Brute force behind a proxy.  Not to mention, the more you try to prevent brute force attacks in that manner, the harder you're going to make it on legit users.  Which is always one of the biggest issues with making things secure - still keeping them easy and usable.

 

Three failed attempts to log in to an account could lock the account. So it wouldn't really matter what IP address is etc. If they were just a clumsy user; you could offer a "send to email" function that allows the user who owns the account to reset their password AND the number of failed attempts for that account in that hour. With a little bit of extra work, you could make it secure and usable.

Link to comment
Share on other sites

Last I heard sha1() had been comprised so I moved up to sha256 - hash('sha256', $var);  Although I remember reading something recently suggesting sha512 now.  I guess I'll have to read up on it all and get caught up with times.

 

Also, you can track both the IP the attacker is using and the username they're attempting to login to.  If a username has 3 failed attempts lock that account for 30 mins and offer early re-activation via email or something. 

 

Should have read your post first!

Link to comment
Share on other sites

Back to the SQL injection discussion... I always use prepared statements, and could not agree more with this first comment on that blog post...

 

My main advice to people is to always use prepared statements and then bind your parameters. Even if you are not planning to reuse the prepared statement, and won't get any performance benefit from doing so, it will prevent your apps from being attached using SQL injections because parameters are bound after the statement is compiled.

 

People just shouldn't be using anything else!

Link to comment
Share on other sites

A three strikes out method works fine. We do it on the PHP Freaks servers for SSH logins using denyhosts. If you have three failed login attempts within an amount of time (can't remember how much) then you get your IP in /etc/hosts.deny and that file is huge.

 

Agreed on the prepared statements. I haven't been using anything else for a long time.

Link to comment
Share on other sites

A three strikes out method works fine. We do it on the PHP Freaks servers for SSH logins using denyhosts. If you have three failed login attempts within an amount of time (can't remember how much) then you get your IP in /etc/hosts.deny and that file is huge.

 

Agreed on the prepared statements. I haven't been using anything else for a long time.

 

I don't understand the purpose of blacklisting IP's. On my router it has a feature where it can use a different MAC address to use with my ISP's DHCP server. Different mac, different IP. Also on my previous ISP, we'd get a new IP everytime we connected.

Link to comment
Share on other sites

Protection via obscurity just makes life more difficult for the programmer.  If you've been a good developer and followed all of the best security practices, it shouldn't matter that your table names are predictable.

 

Most of my recent projects have a single entry point (index.php) that kick-starts my framework.  index.php is the only script file available via URL so none of my credentials will ever come out.  Also, we encode source files where I work.

 

Also, having lines that you comment in / out between servers is error-prone.  You can detect which host you're on using better methods.

<?php

if( detection_method_says_dev_server ) {
  define( 'APP_DEV', true );
}else{
  define( 'APP_DEV', false );
}


// elsewhere in code
if( !APP_DEV ) {
  error_reporting( 0 );
  ini_set( 'display_errors', false );
}
?>

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.