How can I tighten this login procedure up?

I recently had an incident where a hacker was able to access my database and retrieve a bunch of email addresses.

I responded to the alert I received from my ISP by installing an CAPTCHA dynamic as well as an SSL certificate.

Still, the idea of this happening again on my watch is loathsome. I've got my code below. I'm not even sure how this is happening other than the login entry points.

Thoughts?

Here's the form:

<table align="center"><form action="contestants_validate.php" method="Post">
					<tr>
					<td class="standard">
					email address
					</td>
					<td>
					<input type="text" size="50" name="email">
					</td>
					</tr>
					<tr>
					<td class="standard"> 
					Password
					</td>
					<td>
					<input type="password" size="50" name="password">
					</td>
					</tr>
					<tr>
					<td colspan="2" style="text-align:center;">
						<table cellspacing="0" cellpadding="0" border="0" width=100%>
						<tr>
						<td rowspan="2" class="captcha" style="vertical-align:middle;">
						Enter the letters and numbers as they<BR>appear to the right in the field below.
						</td>
						<td rowspan="2">&nbsp;<BR>
						</td>
						<td>
						<img id="captcha" src="securimage/securimage_show.php" alt="CAPTCHA Image" width="100"/>
						</td>
						</tr>
						<tr>
						<td valign="top" colspan="2" class="center">
						<input type="text" name="code" size="14" maxlength="6" />  
						</td>
						</tr>
						</table>
					</td>
					</tr>
					<tr>
					<td colspan="2" class="top_header"><BR>
					<input type="image" src="images/submit.jpg" border="0" name="submit"><input type="hidden" name="do" value="login">
					</td>
					</tr>
					</table>&nbsp;<BR>

Open in new window


...and here's the validation syntax:

 
<?php
session_start();
if(isset($_POST['email'])&& ($_POST['email']=="" OR $_POST['email']==" " OR empty($_POST['email'])))
{
header("Location:contestants_wronglogin.php");
exit();
}

if(isset($_POST['password'])&& ($_POST['password']=="" OR $_POST['password']==" " OR empty($_POST['password'])))
{
header("Location:contestants_wronglogin.php");
exit();
}

include("securimage/securimage.php");
$img = new Securimage();
$valid = $img->check($_POST['code']);
if(!$valid == true) {
header("Location:contestants_wronglogin_captcha.php");
exit();
}


$email=trim($_POST['email']);
$password = trim($_POST['password']);

$query = "select * from registration where email = '$email' AND contestant_password = '$password'";
$result = mysqli_query($cxn, $query);
if(!$result) {
$error = mysqli_errno($cxn).': '.mysqli_error($cxn);
die($error);
} 		
$rowCount = mysqli_num_rows($result);
	if(!$rowCount>0){
	header("Location:contestants_wronglogin.php");
	}
	else
	{
	$_SESSION['sv_email'] = $email;
	$contestant_email = $email;
	$result_row = mysqli_fetch_assoc($result);
	extract($result_row);
	$contestant_first_name = $first_name;
	
	/*$winner="select * from winners where email = '$contestant_email'";
	$winnerresult=mysqli_query($cxn, $winner)
	or die ("Couldn't execute query.");
	$row_winner_count = mysqli_num_rows($winnerresult);
		if($row_winner_count>0){
		$winnerrow=mysqli_fetch_assoc($winnerresult);
		extract($winnerrow);
			if($year=="2008"){
			header("Location:contestant_previous_homepage.php");
			}
		}*/
	//header("Location:contestants_homepage.php");
	$link = "contestants_homepage.php";
	}
	

Open in new window

brucegustPHP DeveloperAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Ray PaseurCommented:
Not sure I can analyze all of your code, but I can show you the design pattern for PHP client authentication.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_2391-PHP-login-logout-and-easy-access-control.html

And if you think the script is subject to automated attacks, a CAPTCHA image or similar would be helpful.
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_9849-Making-CAPTCHA-Friendlier-with-PHP-Image-Manipulation.html

You might want to think about encoding the passwords instead of storing them in clear text.  A salted md5() is very hard to break.  If you want more info on that, please post back and I'll try to describe the process you would use.

Also, what is the intent of this line of code?  Can you describe it in English?
if(!$valid == true)
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
tel2Commented:
Hi Bruce,

Sorry to hear this happened to your database.  Must be like getting robbed.

> I've got my code below. I'm not even sure how this is happening other than the login entry points.
Firstly, how do you know a hacker was able to access your database and get those email addresses?
0
brucegustPHP DeveloperAuthor Commented:
Hey, tel2!

I know I was hacked because of three things: First off, my ISP notified me to let me know that they had detected a security breach. I was asked to look at the particular file they had identified as being a vulnerability and add the CAPTCHA dynamic.

The other "notification" came in the form of a post that was made by the hacker himself, boasting that he had successfully popped the hood on the database and was basking in his self-made glory.

Finally, the sponsor of this event emailed me today and sent me a copy of some text that she had come across that reads as follows:

Data Leaked: Hacker @Ag3nt47 claimed to have compromised a website associated with (my server), a singer/songwriter competition sponsored by (our corporate sponsor). Both the contest and its website are managed by a third party not associated with the sponsor's brand name. The compromise resulted in leaked data about the contestants that was hosted on the third party’s website. The incident posed no risk to our sponsor's networks.
0
Starting with Angular 5

Learn the essential features and functions of the popular JavaScript framework for building mobile, desktop and web applications.

tel2Commented:
That sounds pretty conclusive.


After a bit of hacking...
Hard to be sure, but sounds like the work of Larry Patterson.

Want me to send him a FB message asking him to cuff himself to the nearest fixed object and wait for the cops to come around and pick him up for questioning?

(Just joking about the hacking.  I'm the other type of programmer.)

Must be frustrating to know you got hacked but not know how.

Have you guarded against SQL Injection attacks throughout all your code, Bruce?
0
brucegustPHP DeveloperAuthor Commented:
Yo, tel2!

I've got a buddy who heads up internet security for the state and his counsel was the same as yours as far as SQL Injection.

I've done a little research, but I want to explain this back to you and then show you what I've done to see if that will do the trick.

SQL Injection is a creative way of introducing characters into an input field that, when processed by the SQL query that's interpreting the inputted content, responds by running a rogue query and the user is suddenly able to access every row in the database.

The places where I'm vulnerable is wherever I have an input field, either to login or to do a search.

While I've got CAPTCHA dynamics in place now, what else do I need to do to prevent SQL Injection? I've seen some safeguards that will look to ensure the only characters that are allowed are numeric, but how do you implement that kind of thing where you have alphanumeric characters by default?
0
tel2Commented:
Hi again Bruce,

I barely touch PHP (I use Perl), and I'm not very experienced re SQL Injection, but does this help?:
    http://php.net/manual/en/security.database.sql-injection.php
(I love the cartoon almost half way down the page.)

Also, regarding finding out how the hacker got access, have you been able to check any access logs?  Does the server use Apache, or what?  If you can identify the hacker, you may be able to see where access was gained.  Let us know about this, or if you have any questions about these logs.
0
brucegustPHP DeveloperAuthor Commented:
Gentlemen, thanks for the responses! I was able to figure out a PDO approach to authentication and I think that is going to do the trick as far as beefing up my defenses against SQL injection.

Ray, I would like to take you up on your offer of learning more about the salted md5() dynamic you referenced. The one "positive" about dealing with these kind of situations is that you're compelled to learn more than you would otherwise.

Thanks to you both for your time and expertise.
0
Ray PaseurCommented:
SQL Injection?  See #18. You can learn much more about security if you join OWASP or hang out in this Oct 18th day camp.
http://www.daycamp4developers.com/
0
Ray PaseurCommented:
Using md5() to encode passwords...

MD5 is an abbreviation for "message digest." Please read the critique, here.  In most cases, I disagree with the notion that md5() hashing is inadequate if you understand what you're doing and use md5() correctly.  Here is my thinking on the subject.

1. Let's say you have foolishly stored the client password in clear text.  Advantage: Your scripts can send the client password when the client forgets the password.  Disadvantage: If your DB is compromised, all of the client passwords are exposed.

2. Let's say you have not stored clear-text passwords, but have instead foolishly stored the md5() digest of the password.  It looks something like this...

5f4dcc3b5aa765d61d8327deb882cf99

...which is the md5() for "password."  You can't readily tell that is matches "password" just by looking at it, but the problem with md5() digests is that they are programmatically idempotent.  No matter how many times you hash "password" you will always get the same md5() string.  If two people choose the same password, the md5() string will be the same.  Hackers know the md5() strings of the most popular passwords, so if your DB is compromised, many of the client passwords are exposed.

3. If you let your clients choose their own passwords and one of the idiots chooses "password," it is all the more likely that a hacker can decipher the other common passwords.  A brute-force approach that matches the md5() strings of the popular passwords is computationally trivial.  It would take at most a few seconds to match every dictionary word with its md5() digest.

4. In an effort to make the passwords harder to crack, a sub-industry has sprung up full of all sorts of voodoo and nonsense about cryptography.  You can avoid the nonsense and learn more about this from the OWASP project or from this day camp (I recommend the day camp; I know these guys and they are good).
http://www.daycamp4developers.com/

5. Because md5() is idempotent, security demands that something must be done to break the direct links between, eg, "password" and 5f4dcc3b5aa765d61d8327deb882cf99.  That something is a "salt" added to the password during the encoding process.  With a salt string appended to the password, the idempotent relationship requires not only the password, but also the salt.  So if a client chooses "password" the server stores the md5 string of something like "passwordXYZ" and the resulting digest is cceef54fde042f058f571084338e2c40.  Compare these and see if you can see a relationship.  I can't.

5f4dcc3b5aa765d61d8327deb882cf99
cceef54fde042f058f571084338e2c40

6. Your salt does not have to be easy to guess.  But it has to be stored somewhere that makes it programmatically available to PHP during the execution of your scripts.  You would want to guard it carefully.  If the salting string(s) and algorithm were compromised, your client's passwords are potentially exposed.  Maybe you want to put this into a PHP script that is stored above the WWW root and brought into the scope of the web root scripts via the include() function.

7. You can salt both ends of the password (Maybe call this salt and pepper).  You can use a long and arbitrary string for the salt and pepper.  As long as you add the same salt and pepper to whatever the client types into the password box, your md5() algorithm will create the idempotent message digest.  So you can look up the password with a simple SQL query.

8. The likelihood of md5() collision (two different input strings matching the same message digest) is about the same as the likelihood that you will meet someone else with your DNA sequence.  This is theoretically possible, but don't bet your time and money on it.

9. How sturdy is a salted password?  I will buy anyone a beer who can tell me the original input string that created this md5() digest:  

e0f1299ed629d3c8826e2dd2be4780cf

To facilitate your search for the original input string, I have installed this script on my server.  Experiment here:
http://www.laprbass.com/RAY_md5.php

10. If you choose good salt and pepper strings and keep them secret, your md5() digest will be adequate to protect your client passwords.  But you just can't fix stupid.  If a client chooses "password" and your login process requires only an email address and a password, there isn't much protecting the client from exposure.  It doesn't matter how you encode "password" internally.  Anyone who knows the email address can try pairing it with "password" or the other common passwords to see if it is effective.

11. For reason 10, some password selection processes require you to use a combination of letters and numbers, upper and lower, etc.  I find these annoying and tend to prefer the idea of a multi-word pass-phrase instead of a single password.  I'm sure there will be popular and common phrases, but at the time this is being written no successful attacks have been documented.  And a salted phrase is at least as good as a salted password.
0
tel2Commented:
Thanks for the points, Bruce.
Were you able to find any access logs?
Even if you were, you're still faced with the mission of working out which records are those of the hacker.  But I guess if you find one record, you may be able to find others by matching fields like IP or browser.

Nice work, Ray!
I've recently been reading about MD5, SHA, salt, etc, from pages like this, and I have wondered why no one seemed to be suggesting that programmers make their own variations on these standards.  For example, making some custom changes to the password before hashing it.  Custom changes like swapping some characters around, or changing all "a"s to "z"s, etc.  Surely this would make it very hard for people to use standard brute-force or dictionary-based tools for cracking passwords?  How could they possibly do it?  Guess your custom change?
A bit similar to the salt idea, I guess.  Putting salt at both ends of (or even part way through) a password sounds good to me, too.
But another thing that I've wondered about is, if someone's hacked your database, then what else is to be gained by working out what the passwords are?  Haven't they already got the most important thing?  Or is it just so they can maximise damage by posting the usernames+passwords on some webpage?  Or logging in themselves, whenever they like, to any account they like, to do what they like.  I guess it depends on what the website is for.
0
Ray PaseurCommented:
@tel2: When the attacker gets the data base with the md5 coded passwords, he gets something like this:

5f4dcc3b5aa765d61d8327deb882cf99

You can't use that to log in to the site because the login expects to receive the password in clear text.  Then the login encodes the clear text password via md5() and tries to match the data base.  md5() is a one-way encoding; there is no programmatic way to take that 32-byte hexadecimal string and recover the original password which is what would be required to log in to the site.

If he's a dedicated hacker he probably has a library of md5 strings, and can easily match that string to "password" and once he has made that match then he can log in to the site using a known User-ID and the password, "password" and now one client is compromised.  But to a dedicated hacker, that one compromised account tells him that there is NO SALT STRING used in password hashing, so he knows that his library of md5 strings for common passwords is very useful and he will almost certainly be able to use other common passwords, too, to compromise other accounts.

A side effect of the ArsTechnica study is that thousands of new passwords have been added to the dictionaries of the participants.  Each dictionary entry is an md5() string for something.  

If you're creative about salting the string before the md5() encoding, you might find that a good salt looks something like this:

,cdsaoiovnuuiewuihvabhk dvs895789dvfkjs89(&*&OIN8008i%&nce901q&&&&&&@?}{}{

A good salt does not prevent a hacker from breaking your passwords.  It's like the fire safe rating system, which states both a temperature and a time before the contents are incinerated.  A good salt may buy you enough time to warn your clients that the data has been breached.
0
tel2Commented:
Thanks for your comments Ray.

I think you may have misunderstood some of my questions.
I know about MD5 vs. clear text passwords.

My main question is:
If a hacker can see all the MD5 digests, then he can probably also see (or has a copy of) your entire database, right?  (I'm not saying he can see it by logging in to accounts, but see it in the same way he saw the MD5 digests - e.g. a dump of the tables.)
It is in that context that I meant to ask:
"...if someone's hacked your database, then what else is to be gained by working out what the passwords are?"  (And review my other comments below that question.)

> A good salt may buy you enough time to warn your clients that the data has been breached.
True, but this would only help if the breach is detected early enough, right?  A hacker could crack one password (maybe test it by just logging in & out), then with what he learned, crack the rest (or many of them) over the following days/weeks/months, then start his dirty work (e.g. financial transactions) only after he has enough passwords to satisfy him.

Thanks.
Tel2
0
Ray PaseurCommented:
A copy of the data base?  Probably.  What else is to be gained by knowing the passwords?  Perhaps the hacker can use your site to send money from a compromised account to Kazakhstan.  Try to get that back in dollars!   With interest!

To detect a breach early enough is a fairly complex task.  Some of my friends are security experts (including one who is a graduate of the Naval Academy) and they make a very, very good living because they know how this is done.  It's a combination of pattern matching algorithms that monitor in real time the human client behaviors versus the expected behaviors of the client population.  I'll give one simple example, then I have to sign off because the topic is less like an EE question and more like a PhD in computer science.

What if you live in San Francisco and log in to the web site with a fixed IP address.  Then one day you log in from Kazakhstan?  Impossible?  No.  Improbable?  Maybe.  What if dozens of people who live in San Francisco suddenly start logging in from Kazakhstan?
0
tel2Commented:
Thanks Ray.
Good answer.

Do you have any ideas how such software confirms an IP address (or even a user agent) of a visitor, since these can easily be spoofed, and I expect hackers would often do so?
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.