views:

722

answers:

12

Hello all!

I'm just about to release a website I've designed into the wild, but before I do, I would love some help determining whether I have made any silly security mistakes. Since I'm using shared hosting rather than dedicated, this is a very important concern. After much research and tutorial-reading I've done the following:

  • All PHP scripts (including database credentials) are owned by me, with 700 (-rwx------) 400 (-r--------) permissions.
  • Session files are stored in a directory owned by me, with 700 (drwx------) permissions (so people sharing the same host as me cannot hijack sessions).
  • When the user logs in they are assigned a new session ID, to prevent session fixation.
  • PHP include files are stored in a directory outside of the public webspace, with 700 (drwx------) 100 (d--x------) permissions.
  • The admin section of the site is secured with SSL and reCAPTCHA.
  • Every piece of data that is used in a mysql query is escaped with mysql_real_escape_string to prevent SQL injection.
  • Mysql queries are handled using prepared statements via the mysqli PHP plugin. This should prevent SQL injection.
  • Plaintext input is put through htmlspecialchars() in an attempt to stop XSS, and other types of input are sanitised by typecasting them.
  • The admin password is stored as an MD5 SHA-256 hash salted SHA-512 hash.
  • Sessions expire after 1 hour of inactivity.

This is my first real world web design project and I'm terrified that I may have forgotten something or left some security hole open. If you can spot any omissions or you have any further suggestions please let me know!

Thanks in advance.

+1  A: 

It's also a good idea to sanitize all request inputs made to your site. I personally just clean up _REQUEST with every request and use that.

You would also need to strip most HTML markup from being posted to your server in case you wish to create some form of CMS. That tackles XSS, make sure to keep an eye for that.

But anyways, speaking from my experience, my first couple of projects didn't really have to be 100% secure. Not sure about you but the fact those projects never gained momentum nor had a large user base brings some peace of mind knowing that few "bad people" will come across it meaning harm.

msakr
Sorry, I forgot to mention that plain text input is put through htmlspecialchars. And you're right - security on this particular site isn't going to be too big of a concern. But it's a site I designed for my mother and I would feel just awful if it was compromised by someone ;)
robinjam
Cleaning up _REQUEST with every request is quite stupid idea. Actually there is nothing to "clean up". There are different rules for the different destinations. And mostly formatting, not cleaning.
Col. Shrapnel
@Col. Shrapnel usually cleaning up involved removal of things like undesired HTML tags. If one chooses not to, the alternative is to manually "clean up" each (POST/GET) variable before actually using it -- which, I really hope you realize, is absurd. Perhaps you haven't had your share of using large scale frameworks or applications? Take a look at how CI or IPB deals with input.
msakr
Imagine it's admin area. Where site admin is allowed to enter HTML coming from the rich text editor. Gonna clean it up too?
Col. Shrapnel
@Col. Shrapnel Naturally, and I was assuming that needs not be mentioned, there will be exceptions. An admin area would be used by an admin (how ironic, huh?) and accordingly securing it will require a different approach. Namely, less security around the inputs. Although, you could avoid HTML if you want. What could be done is automatically convert HTML tags to BBCodes, if that's necessary.
msakr
Well you have said of HTML cleaning in the 2nd paragraph of your answer. What "sanitization" did you mean in the 1st then?
Col. Shrapnel
@Col. Shrapnel I'm not sure we're reading the same answer? I mentioned clean up and sanitize (which mean the same thing) in the first paragraph. The second paragraph pointed at emphasizing some tags need be dealt with even if HTML was allowed through.
msakr
you have said "You would **also** need to strip most HTML". "Also" means "in addition". And second paragraph emphasizing nothing. "Some tags" cannot be considered as a definition. As a matter of fact, you have said nothing in your answer, but empty words only.
Col. Shrapnel
I'm glad you are going offline :)
Col. Shrapnel
+1  A: 

Best practices for building websites, including security, have been covered before.

What things should a programmer implementing the technical details of a web site address before making the site public? If Jeff Atwood can forget about HttpOnly cookies, sitemaps, and cross-site request forgeries all in the same site, what important thing could I be forgetting as well?

http://stackoverflow.com/questions/72394/what-should-a-developer-know-before-building-a-public-web-site

Simon Brown
Pretty definitive indeed.
Wrikken
+13  A: 

Absolutely not.

Admin password stored as plain text? Even if it's in a database, even if it's owned by root and have 700 permissions it's not safe. Hash the password at the very least. I really don't understand why you would have SSL securing the admin area if the password is stored plain text.

All PHP scripts (including database credentials) are owned by me, with 700 (-rwx------) permissions.

This is fine.

All public content (scripts, css, etc) is owned by me, with 744 (-rwxr--r--) permissions.

Great...?

Session files are stored in a folder owned by me, with 700 (-rwx------) permissions (so people sharing the same host as me cannot hijack sessions).

Would be a long shot but I'd buy it.

When the user logs in they are assigned a new session ID, to prevent session fixation.

Good. Do you also have a session expiration set to something reasonable? 15 minutes? Ip checks to ensure they don't try to hijack the session?

PHP include files are stored outside of the public webspace, also with 700 (-rwx------) permissions.

Excellent. This is good.

The admin section of the site is secured with SSL.

SSL, might be overkill but worth while.

Every piece of data that is used in a mysql query is escaped with mysql_real_escape_string to prevent SQL injection.

All of the above is done very well. I wouldn't expect any issues but a lot of that depends on how the site was coded. I can't think off the top of my head bad programming practices the lead to severe insecurity but there could be several.

Okay. Here's why I would just say "okay." Every piece of data is one thing, I would prefer to use some sort of ORM / Active Record style syntax for querying simply because it eliminates the headache of making sure everything is escaped.

Josh K
Care to elaborate?
msakr
@mahmaud: Already done. Don't downvote thirty seconds after a post man. ;)
Josh K
Quit edit spamming. It's a bad habit here and needs to stop. ;)
Aaron Harun
If we didn't play it "fastest gun in the west" style there wouldn't need to have a initial brief post with a slew of edits to follow it up.
Josh K
Agreed with using database abstraction. SSL is a must if you must at enforce confidentiality and authentication. If you think your site is too small to be worth attacking, it's too small for SSL to be overkill. "Good. Do you also have a session expiration set to something reasonable? 15 minutes? Ip checks to ensure they don't try to hijack the session?" Note that a site can be secure without these checks, this is only a fallback defense that saves you in **certain** scenarios. "Admin password stored as plain text" Once again, hashing is only fallback defense.
Longpoke
@Longpoke: A fallback defense to what? A determined hacker? Assuming he's on shared hosting (which I believe has been confirmed) there are only so many steps you can take. The ones he has taken are *good* steps. Not bad ones.
Josh K
@Josh K, I didn't say they are bad. They are a last resort defense for when the server or client were already compromised (obviously).
Longpoke
@Longpoke: Considering he is on shared hosting consider the server already compromised.
Josh K
@Josh If you're compromised, that means your compromised, end of story. Not all shared hosting is insecure (or maybe it is, who knows), it just means you have to trust the admin to a) isolate the users properly b) not be malicious. You still have to trust the admin if you buy a remote dedicated server...
Longpoke
+3  A: 

It is a continuing battle, now you need to ensure accounts are secure. You should:

  1. Add scripts to keep passwords from being brute forced
  2. Ensure passwords are secure and stored securely (if you store them plaintext, the site fails).
  3. Ensure that all inputs are secure.
  4. Also, NEVER have anything writable that doesn't need to be (Use, 666 or 644 if you need things to be writable.)

Also, you didn't mention injection hacks through XSS.

Aaron Harun
Now #1 is something I didn't consider. Guess I have some more research to do ;)Regarding #4, every public file in my webspace is writeable by me alone.
robinjam
It's not **you** alone, it is your web server's user account alone. There is a big difference.
Aaron Harun
Thanks, I hadn't given this enough consideration before. All my scripts are now read-only. I have also added reCAPTCHA to the login page to hopefully prevent brute-force attacks.
robinjam
+1  A: 
  • Secure all GET values.
  • Write your robot.txt.
Babiker
Ah! I forgot about robots.txt. Writing it now.
robinjam
what adds a robot.txt to the security?
Col. Shrapnel
stops google indexing files/directories that may reveal info on how your site i structured
Haroldo
I denied robots access to the admin login not so much for security, but because a login page would look a bit silly among a set of search results ;)
robinjam
-1, robot.txt has absolutely nothing to do with security. And what does "Secure all GET values" mean?
Longpoke
@Longpoke: robots.txt has nothing to do with site security, but can involve some *privacy* security. As Haroldo said, google and other crawler can indexing some files that, maybe, you dont want to be public. Its more about logic business then security, but is something he has to know.
DaNieL
@DaNieL: If you don't want something to be viewed, you don't make it viewable, period. If you have `12iuerhgiuejhiusrehrijthorjh.html`, this is essentially confidential as long as nobody links to it. You wouldn't link to it because you don't want anyone see it, thus no search engine will find it. If you add it to robots.txt, everyone who looks at robots.txt will find it... they just wont have cached Google/archive.org views of it. I'm sorry I don't see how robots.txt has anything to do with privacy, it's purely to prevent search engines from wasting both parties' bandwidth and time.
Longpoke
robots.txt may have nothing to do with security, but it is useful nonetheless. I don't want people to see "Welcome to the admin control panel. Please log in" in their google results when they search for my site. It's just unprofessional.
robinjam
@robinjam: indeed.
Longpoke
@Longpoke: i bet that your `12iuerhgiuejhiusrehrijthorjh.html` will (soon or later) be indexed from google even if there is no page linking it (dont ask me how, i just saw it happen many times), and its content will be of public domain.
DaNieL
@DaNieL: Okay fine, then put it in robots.txt, and anyone with half a brain will read robots.txt and see it. Or put the directory in robots.txt, then anyone can still obtain the page URL however the search engine magically does according to you. If you want privacy don't publish the private material on the internet (ie: require authentication to see it), 'nuff said...
Longpoke
@Longpoke: i agree with you, in some way! (take it easy, we are here to learn, not to demonstrate who's right!).My experience is been that: i developed an web application that create pdf. My robots.txt deny the accesso th the folder that contain the pdf (the whole folder).The pdf names was randomly generated.Dont know how, but google indexed those files (checking the backlinks, seem that noone linked them over the web)...of course, the error is been mine becose those pdf were in a web folder, but as i said, this is a things that the OP should know in a matter of 'security'
DaNieL
+5  A: 
All PHP scripts (including database credentials) are owned by me, with 700 (-rwx------) permissions.

Most of the things you mention as being -rwx------ should not have execute permission turned on. .css and .js certainly don't, and I don't think the PHP scripts need x either, unless your host does something like the Apache XBitHack to indicate the .php files can be executed.

All of those (essentially plain text) files can be 600 instead of 700, and could probably even be 400 -r-------- so they can't easily be overwritten.

Only the directory (aka "folder") that you mention has to be -rwx------ so its contents can be accessed.

Every piece of data that is used in a mysql query is escaped with mysql_real_escape_string to prevent SQL injection.

If you can use prepared statements, that is better than escaping.

BTW, I tend not to sanitize user input when I receive it; rather, I sanitize it before writing it -- but I've always had to store exactly what the user typed, not an escaped representation of what they typed. If it's escaped on input to make it safe for HTML display, when it's written to a PDF I generate on the server then it's wrong.

Stephen P
+5  A: 

All PHP scripts (including database credentials) are owned by me, with 700 (-rwx------) permissions.

If your files are permed 700, only you can read the file... which means that if your Web server can read the file to serve it up, it's running as you or as root (ick!).

Ideally, the Web server should be running as an unprivileged user who only has read access to any files/directories in your public Web root.

joealba
I suspect he is on shared hosting..
Earlz
Yep, I am on shared hosting. The web server is running as an unprivileged user, but PHP itself is for some reason running under my user.
robinjam
+1  A: 

How are you generating your session IDs? You should make sure that they are large (32 bits at minimum) and generated completely randomly (not based on any sort of hashing or sequential iteration etc). If your session IDs are too small, or even slightly predictable, an attacker might be able to brute force active session IDs and hijack user sessions.

Also, are you passing your session IDs over HTTPS? Many people use HTTPS for their login POST, but forget to secure the session IDs. Make sure your cookies are being passed over an HTTPS connection, and it is good practice to set the HTTPS_ONLY flag on the cookie that contains the session ID so the browser knows to only pass the cookie to secure servers (and not some kid in the coffee shop).

pierce
+1  A: 

You should test each release of your site with skipfish. It can help you find many vulnerabilities.

Annie
Good suggestion, thank you. I'm compiling it now.
robinjam
A scan with the complete wordlist didn't show up anything except for incorrect/missing charsets (which I have since corrected). I suppose that's a good thing :D
robinjam
+4  A: 

Others have raised several good points. I'll comment on one: mysql_real_escape_string.

Yes, it's probably secure if you do in fact use it everywhere, but it's also easier to mess up. Rule of thumb: never paste user input into SQL queries. Use the modern PHP data access facilities (I don't remember what they're called, but the mysql_* functions are the old, pre-5.x way of doing things) and use prepared statements. With prepared queries, you create your query with placeholders (typically "?") for the input values and then "bind" the query parameters as a separate call. You don't need to worry about escaping input (in fact, you shouldn't escape it), and you're SQL-injection-free (the database engine takes care of inserting the query data properly). If you always use prepared statements, then you don't leave the opportunity to forget one string escape and blow a hole in your application. It also frees you from having to worry about where data is escaped, making sure you don't escape it twice, and a similar concerns.

Michael E
Thank you so much for suggesting that - I never even knew prepared statements existed! I'll rewrite my DB code to use the mysqli extension so I can take advantage of this.
robinjam
+2  A: 

A few more things:

Just hashing your passwords may not be enough, so make sure you use a salt value to make lookups on those hashes harder. This is a much more common attack on hashes than decoding them.

Secondly, make sure that you run some checks on your sessions to further prevent hijacking. For instance, check if the user's IP and UA haven't changed, and kill the session if they have.

Joseph Mastey
Good suggestion. However, is it actually possible to generate passwords that are less than 16 characters long but have the same hash? I'm not too sure about the mathematics behind it, but it seems unlikely to me.
robinjam
The issue is less of finding a "collision" in the space, but of pre-generating the results. If a user has a common password ("abc123"), the hash of that password *will* have been precomputed, making a lookup simple. If you have a decent salt ("xf;liq4"), then the odds of being able to do a lookup on the full string precomputed are essentially nil.
Joseph Mastey
+2  A: 

As many others suggested, use prepared statemens to be more safe with sql injection.

Please note that using mysql_real_escape for every kind of input may lead you to a certain level of security, but its not the best practice: you should treat every input depending on what you you expect the input to be.

For example, if you expect a input to be an integer number, mysql_real_escape is useless, try (int)$_GET['var_name'] instead.

DaNieL
Of course when I expect an integer I typecast to an integer. That's just common sense :D
robinjam