2012-06-25

plaintext passwords is crazy, init

If you're accessing a MySQL database using PHP, you have to hand the mysql functions your username and password, as well as the name of the database you want to work with.

mysql_connect('localhost', 
    'myname', 
    'mypass');
//etc

This is a massive security risk.



The Problem

Security risk? But why, you ask? But PHP code isn't visible to the general public, you say? Well, yes, that is indeed the way it is designed. But what happens if for whatever reason PHP stops working on your server? Go ahead, disable it on your server and try to access a PHP page. I'll wait.

Not long though, so hurry up.

Okay, so I'm sure you noticed that you don't get an error message stating the page wasn't found. In fact, the page was found. Like, way too much finding went on. Because what gets handed to someone who accesses index.php when PHP is not there to intercept? That's right - the entire PHP file, <?php including these parts ?>, in plain text.

I have my main site on a fairly reputable host, with excellent support and uptime. I've almost never had a problem, and they fix issues as soon as they arise. But one of the problems was that one day PHP failed. For a good hour or so everything was in plain view. Anyone could have seen my login information. Now, while my database is not exactly guarding nuclear secrets, I still don't want random people wandering in.

There are ways to make accessing a database more secure - obviously number one is never use the root account for live stuff. Create new accounts for each major task, with privileges limiting them to exactly what they need. If you only need to read a table to get and display some information, then obviously use an account that has read-only access.

hacker (n) 1. a person who hacks
But that's not really a solution to the problem. Even if they can only see what is in the database, there may be security risks, not to mention the fact that a malevolent snoop can eat up processing time by making thousands of pointless complex queries, affecting the database's other operations.



The Solution

Simple - store the user and password information in a separate file that the general public cannot access, but your PHP scripts can. The trick is to put the file where no one can see its contents, even if PHP fails. This is incredibly easy to do.

Files accessible on the web are usually contained in a single directory - public_html or www are common names for this. Store a file here, and a determined snooper can get it - you have to refer to it in your PHP file to see its contents, so anyone who can read your PHP file then knows the address of the password file. All they need do is enter it into a web browser and they can download it.

But worry not! There are other directories available besides the public one. Anything you put in the parent directory, for example, cannot be accessed from the web. But it can be easily accessed from a script.

The easiest way to do it is to follow these three steps:

1. Put all your login information in a configuration file.


A configuration (*.ini) file is just a plaintext file used to store information. It's incredibly basic, which makes it good for simple tasks like this. Additionally, PHP has a helpful function that allows you to parse configuration files easily. The file should look like this:

username = myname
password = mypass

2. Place the configuration file above the public_html directory.


The location of your actual site (where www.mysite.com maps to):
servername/public_html/mysite/index.php

The location of the passwords - above public_html and thus inaccessible from the web:
servername/databaseinfo.ini
Confused? Don't be. Behold, a preformatted text diagram!

The filesystem structure:
servername
  │
  └─public_html
  ⁞   │
  ⁞   ├─mysite
  ⁞   ⁞  │
  ⁞   ⁞  └─index.php
  ⁞   │
  ⁞   └─myothersite
  ⁞
  │
  └─databaseinfo.ini

3. Parse the configuration file in your PHP script.


Call it from your PHP file like this:
//the double-period (..) means "go up one directory"
$databaseinfo = parse_ini_file("../../databaseinfo.ini");

mysql_connect('localhost', 
    $databaseinfo['username'], 
    $databaseinfo['password']);
//etc

And there you have it.



Multiple logins? No problem!


If you have more than one set of login information (and you should), you can set your configuration file up with subheadings like this:

[readonly]
username = ro_user
password = readingisgoodforthebrain

[readandwrite]
username = rw_user
password = pen_is_mightierTHANsword

[god]
username = admin
password = "passwords_with-odd!Chars_need-doublequotes#!"

... and make use of parse_ini_file()'s second argument:

$databaseinfo = parse_ini_file("../../databaseinfo.ini", TRUE);

//if we only want to be able to read and not write...
mysql_connect('localhost', 
    $databaseinfo['readonly']['username'], 
    $databaseinfo['readonly']['password']);
//etc

The TRUE argument creates a multidimensional array, with the main keys defined in the [squarebrackets], as you can see.



This may seem like overkill on a smaller site or a personal one, but it's good to develop a habit for being as secure as possible. It'll matter when you really do need to be.

No comments:

Post a Comment