Buca Bay - Always nice

Dua tiko noqu toa loaloa, na yacana ko… laga mai…

Secure password hashing and storage in PHP

November7

Everyone knows you should never store passwords as plain text, right?

Recently I’ve come across a lot of bad advice on how to store passwords securely. I thought I’d share a bit of my research into the subject. Like most web developers, I’m not a cryptography expert. However, this should not be the excuse we use when we make a database of thousands of passwords available to hackers. It is quite simple to save passwords in a reasonably secure manner.

Main points: secure password hashing

  1. Force your users to use secure passwords
  2. Do not use 2 way encryption, save passwords as hashes
  3. Salt each password with a unique salt
  4. Hash the password and salt together multiple times (1000 or more)
  5. Use an adaptive hashing technique

Cryptographic Hashes

Passwords should never be stored in their original form, instead a cryptographic hash must be stored. A hash function is a function that performs a one way operation on an input producing an output of fixed length called a digest.

A popular hash function is MD5. An example in PHP:

$hash = md5('test'); // 098f6bcd4621d373cade4e832627b4f6

From the hash, the original text cannot be computed, however, knowing the original text you can easily compute the hash. This is the basis of saving passwords securely.

To verify the password, we see if it hashes to the same hash as the one we have stored.

if (md5($password) == $hash) { /* password is valid */ }

Notice how we never store the actual password, only the hash. We also don’t need to be able to decrypt the hash, we only have to verify it given the password supplied by the user.

Note I’m only using MD5 for example, since it is well known. You probably want to use a more secure hash such as SHA256 or Whirlpool as MD5 and SHA1 have a few problems (don’t ask me what they are, I don’t know).

Key Based Encryption

I’ve come across developers advising the use of a key based encryption. However, this is not a secure way of storing passwords. You’re only deferring the task of having to hide the password, to having to hide the secret key.

Another argument is that you need to be able to retrieve the password if the user forgets it. If we backtrack to why we use passwords, we realize that allowing the user to gain access to their account is the concern, not retrieving their password. So instead, send the user a unique key (token) through email or some other authenticated resource, so the user can generate a new password.

Precomputation Attacks - Rainbow Tables and salts

Now, the problem with just hashing a password, is that users normally use very easy to remember passwords, which are thus very easy to guess. Due to this, there are many databases available that map all the possible passwords made up of up of alpha-numeric characters in upper and lower case, as well as a lot of special characters. There are usually in the form of rainbow tables. Most do not have matches for passwords longer then 14 characters however due to the amount of computation required to generate these tables.

The way to beat rainbow tables or any other precomputation attack is to use a salt. A salt is a randomly generated input that you add to the password before hashing. This is done so that the input becomes long enough that generating a table that includes it is computationally infeasible.

So our hash function becomes:

$salt = 'f39ae656bc79a9b2398890bb4';
$hash = md5('test'.$salt); // 1db36e73cf2e68df3640fb1052e801da 

Now even if the user supplies a very easy to guess password, such as “love”, it still cannot be looked up in a rainbow table because the table would have to hold the whole input, “lovef39ae656bc79a9b2398890bb4″. No public rainbow tables have that many characters in them.

Use a unique salt for each password

If you use the same salt for each password (a common practice in PHP applications) is a very bad idea. It allows the attacker to crack all your passwords in one go using brute force. Imagine a database with a million passwords. Each guess becomes one million times more likely to match one of those passwords. Using a unique salt for each password prevents this.

Does salt size matter?

Another piece of advice I’ve come across is that the size of the salt does not matter. Using common sense this is not correct. A salt such as “52e801da” will still generate the input “love52e801da” which will be in rainbow tables.

Early Unix user passwords had a vulnerability that was partly due to the size of the salt.

Brute Force Attacks on passwords

Now even though we have mitigated precomputation attacks such as rainbow tables, we still have one issue. We have no way to hide the salt. Since we cannot hide the salt securely, we must assume it is visible. If the salt is visible, the attacker still cannot use a rainbow table, however, they can just guess the password by simply guessing passwords and then hashing them with the visible salt, then comparing the result to the stored hash. This is called a brute force attack.

A brute force attack in it’s simplest form, is to try every combination of passwords in sequence. However, sophisticated forms will use precomputated data such as the word combinations in a dictionary (dictionary attack) combined with statistics to find the password “love” very quickly.

Better Passwords - Key Stretching and Adaptive Hashing

Now the first way to combat this is to force your users to have a password at minimum 6 characters and consist of numbers as well as alphabetic characters in both upper and lower case. This is probably the best thing you could do as you have made the range of possible passwords large enough that a brute force will take a while.

Now you need to slow down the brute force. One way to do this is by using key strengthening. This is the process of hashing multiple times, in order to make the hashing process longer. Hash functions such as MD5, SHA1 are very fast. This is not a good thing for you, since the attacker can do numerous hashes very quickly. However, if you hash 1000 times, then the attacker has to also hash each possible password the same number of times, making their brute force attack 1000 times slower.

Now it is up to you to figure out how many times you want to hash the password. Computational power is increasing every day, so you may also want to increase the number of times you hash the password as time progresses. There are hash functions that take this into account such as MD5 crypt and BCrypt. A lot of why this is needed is explained at this Matasano security article.

PHP implementation of secure password hashing

Now I know this has been a rather long article, so here is what I have come up with that incorporates these methods of mitigating these password cracking techniques.

/**
 * Generate cryptographic Hashes for passwords
 *
 * Features:
 * 	Harderned against precomputation attacks like rainbow tables (using unique salts)
 * 	Harderned against brute force and dictionary attacks (using key stretching and optional secret key)
 *
 *  http://en.wikipedia.org/wiki/Password_cracking
 *
 *  Note: for PHP4 and lower, just remove the "public static" before function declaration
 *
 * @author gabe@fijiwebdesign.com
 * @link http://www.fijiwebdesign.com/
 * @version $Id$
 */
class Password_Hash
{

	/**
	 * Generate the Hash
	 * @return String
	 * @param $password String
	 * @param $salt String[optional]
	 * @param $iterations Int[optional]
	 * @param $secret String[optional]
	 */
	public static function generate($password, $salt = null, $iterations = 10000, $hash_function = 'sha1', $secret = '')
	{
		$salt or $salt = self::generateToken();
		$hashes = array();
		$hash = $password;
		// hash a sequence of hashes, each hash depends on the last one, so any implementation must hash each one individually
		$i = $iterations;
		while(--$i)
		{
			$hash = $hash_function($hash.$salt.$secret);
		}
		return implode(':', array($hash, $iterations, $hash_function, $salt));
	}

	/**
	 * Verify a password meets a hash
	 * @return Bool
	 * @param $password String
	 * @param $hash String
	 * @param $secret String[optional]
	 */
	public static function verify($password, $hash, $secret = '')
	{
		list($_hash, $iterations, $hash_function, $salt) = explode(':', $hash);
		return ($hash == self::generate($password, $salt, $iterations, $hash_function, $secret));
	}

	/**
	 * Generate a random hex based token
	 * @return String
	 * @param $length Int[optional]
	 */
	public static function generateToken($length = 40)
	{
		$token = array();
		for( $i = 0; $i < $length; ++$i )
		{
			$token[] =	dechex( mt_rand(0, 15) );
		}
		return implode('', $token);
	}

}

Example using SHA1 with default of 10000 iterations.

// generating the hash
$password = 'test';
$hash = Password_Hash::generate($password);

// verifying a password
$result = Password_Hash::verify($password, $hash);

// dump results
var_dump($hash, $result);

Example using whirlpool as the hash function, a 128 length salt as well as a secret.

// define our custom hash function
function whirlpool($str) {
	return hash('whirlpool', $str);
}

$password = 'test';
$salt = password_Hash::generateToken(128);
$secret = password_Hash::generateToken(128);
$iterations = 10000;

// generate hash
$hash = Password_Hash::generate($password, $salt, $iterations, 'whirlpool', $secret);

// verify
$result = Password_Hash::verify($password, $hash, $secret);

// dump results
var_dump($result);

Secure PHP Programming for Web Developers

February12

Security in PHP is the same as any server side programming language, they are all vulnerable to the same attacks.

PHP has a history of being vulnerable mainly because of its popularity.

1) More non-security aware developers use PHP then any other language, so there code has flaws almost 100% of the time.
2) There are more sites written in PHP then any other server side language, thus more chances to find security holes amongst so many.

PHP has had a rep of having security problems in the PHP core itself, but this has improved greatly. Much of which can be attributed to the Hardened-PHP project.
http://www.hardened-php.net/suhosin/

If you are using a shared host, ask if they have PHP4 with the suhosin patch or PHP5 or higher. I’m not sure if PHP5 still needs Suhosin but it seems many large sites aren’t using the two together so I believe PHP5 has a better security then PHP4 natively.

As a developer I think there are just about 4 or so main security vulnerabilities to keep in mind when coding.

1) XSS (Cross Site Scripting)

This is the most common vulnerability in any website. It is estimated that around 70% of websites have an XSS vulenerability.
http://en.wikipedia.org/wiki/Cross-site_scripting

A simple example in PHP:

<?php

echo $_GET['username'];

?>

What happens is the PHP echo’s a variable passed in from HTTP (in this case a GET parameter). If a user typed in the browser URL:

site.com/example.php?username=<script>alert('document.cookie')</script>

They would see the cookies saved for their session. An attacker can make a user click a link that will also retrieve these cookies from JavaScript, and send it to them - without the user knowing.

To prevent it:

<?php

echo htmlentities($_GET['username'], ENT_QUOTES, 'UTF-8');

?>[

This will turn any HTML into HTML entities. You also have to specify the encoding you used for the page (in this case UTF-8). The reason is so PHP codes not mangle the character encoding, which can also result in XSS.

I must say here, that you should never use your own PHP filtering functions for HTML, or any other "cleansing" of user input. Most likely you will miss something that an attacker will use.

2) XSRF - Cross Site Request Forgery

This is similar to XSS and just as common or maybe even more common. It is when a website fails to protect it's users from being used by 3rd parties without their knowledge.
http://en.wikipedia.org/wiki/Cross-site_request_forgery

And example of this in PHP is a simple comment form.

<form action="submit.php">

<textarea name="comment">
<input type="submit" value="Post Comment" />

</form>

Imagine the comment form is only available for logged in users. Now an attacker can just send an already logged in user the URL:

site.com/submit.php?comment=I hacked you&submit=Post Comment

So when the logged in user clicks on that link, they have posted the comment without knowing. This can even be done in a hidden frame, so the user never see's it.

So the attacker is using the user's already authenticated session (privileges) to do his/her bidding.

Preventing XSRF:

<form action="submit.php">

<textarea name="comment">
<input type="submit" value="Post Comment" />
<input type="key" value="some_random_value" />

</form>

Notice the new <input /> called "key". It will contain a random value remembered by PHP. This random value should be saved, and be unique for every form that is sensitive.

This way, the attacker would not be able to make the user post something on their behalf, since they don't know the value of "key".

(This only works if you don't have an XSS vulnerability of the page itself, as that can lead to the attacker knowing what the value of "key" is)

3) SQL injection

SQL injection is when an attacker manages to manipulate any SQL database queries in your website in a way you didn't intend.
http://en.wikipedia.org/wiki/SQL_injection

Example:

<?php

$query = "SELECT * FROM users where password = '".$_GET['password']."'";
$result = mysql_query($query);

?>

Because the $_GET['password'] can be anything the attacker wishes to put in the URL, they could craft a URL like:


site.com/login.php?username=joe&password=nothing' or 1

Notice the ‘ in the value for the parameter “password”

This will make your sql query:

SELECT * FROM users where password = 'nothing' or 1

This would make it return the first user instead of the user with password = “nothing” since “or 1″ is always true.

Preventing SQL injection:

< ?php

$query = "SELECT * FROM users where password = '".mysql_real_escape_string($_GET['password'])."'";
$result = mysql_query($query);

?>

the function mysql_real_escape_string() will prevent any SQL injection by escaping any character that would otherwise terminate the string.

4) Remote File inclusion

Remote file inclusion is when an attacker can include remote file into your PHP code. This is the most dangerous attack, as it allows the attacker to execute arbitrary code on your PHP server.
http://en.wikipedia.org/wiki/Remote_File_Inclusion

eg:

<?php

include('/pages/'.$_GET['page'].'.php');

?>

With this code the developer is hoping to have a URL such as:

site.com/pages.php?page=home

And this would include the file:

/pages/home.php

However, any attacker can now place a URL such as:


site.com/pages.php?page=../../passwords.txt

And it would reveal the contents of the file passwords.txt

Or they could use it to include a remove file from their server, if the URL wrappers are enabled for file includes (which is common).

It is best not to have any user input in the files to include. However, if you think it is beneficial to your PHP website, then make sure you have a predefined list of files that can be included.

For example:

<?php

if (in_array($_GET['page'], array('home', 'contact', 'links', 'about')) {
    include('/pages/'.$_GET['page'].'.php');
}

?>

With these in mind, you should be able to keep your PHP site secure from most attacks. Now in one paragraph:

When ever you write a PHP page, make sure all input from users (HTTP) is escaped according to the the following rules, HTML just be entity encoded with htmlentities(), XML with htmlspecialchars(), SQL must be escaped by mysql_real_escape_string(). User input should not be used for file inclusions at all, but if you do, then make sure it is from a predefined list of possible files. All forms must contain a secure, random, key that is used to authenticate it only once so that there can be no subsequent posts of the same form, or posts from other sources.

That actually covered all the points above. Can you see how simple it is? It is just keeping those in mind at all times while coding that is the hard part.

Security of Fiji’s Major Company Websites

September25

Taking a look at the largest websites on the com.fj domain (Fiji domains) I was surprised that 8 out of the 11 websites I looked at had security flaws that can be detected in about 10 seconds (literally) with just a browser.

These websites were Vodafone, Connect, Fiji White Pages, AFL, Fiji Sun, Air Fiji, Fiji TV, Fiji Times among others.

Those that don’t have apparent security flaws:
Airports Fiji Limited
Air Fiji
Fiji Times


Those that have apparent security flaws:

Telecom Fiji Limited
Vulnerability: XSS, XSRF
Severity: Low
Note: No user accounts to exploit

Vodafone
Vulnerability: XSS, XSRF
Severity: Critical
Note: User accounts are affected. An attacker can log in as another user with their privileges

Connect
Vulnerability: XSS, XSRF
Severity: Critical
Note: User accounts are affected. An attacker can log in as another user with their privileges

Fiji White Pages
Vulnerability: XSS
Severity: Low
Note: There are no user accounts so users are not affected

Fiji Yellow Pages
Vulnerability: XSS, Blind SQL Injection
Severity: Medium
Note: There are no user accounts so users are not affected. However, the whole database is vulnerable to reading.

Fiji Sun
Vulnerability: XSS
Severity: Low
Note: There are no user accounts so users are not affected. Attack requires social engineering.

Fiji TV
Vulnerability: XSS, XSRF, SQL Injection
Severity: Critical
Note: User accounts are affected. An attacker can log in as another user with their privileges. Direct SQL injection can retrieve all user details and possiblity administrative access to the website.

South Pacific Stock Exchange
Vulnerability: Blind SQL Injection
Severity: Critical
Note: Blind SQL injection can blind read the database, and possibly gain administrative privileges.

Now this is quite a disturbing. I only tested two basic exploits, XSS and SQL Injection. The XSRF vulnerabilities are implied when XSS is present and user accounts are present on the same domain.

No need to panic, estimates claim that around 70% of the websites on the internet are vulnerable to XSS. What amazes me however, is that these are large corporate websites, their web developers should know better.

Disclaimer: I am not disclosing any details on the vulnerabilities found on the mentioned websites except the fact that they exists. You’ll have to take my word on it.

Update: As requested by JJ, here’s a look at the FVB website:

FVB
Vulnerability: XSS, Blind SQL Injection
Severity: Critical
Note: XSS can be used to log in as another user, possible gaining administrative privileges. Blind SQL injection can blind read the database, and possibly gain administrative privileges.

Tag Cloud