php authentication script - php

I need the following authentication script finished. I am weak at php/pdo so I do not know how to ask for the number of rows equalling one and then setting the session id's from the results of the query. I need to not only set the $_SESSION['userid'] but also the ['company'] and the ['security_id'] as well from the results.
here is what I have:
$userid = $_POST['userid'];
$password = $_POST['pass'];
if ( $userid != "" || $password != "" )
{
$sql = "SELECT * FROM contractors WHERE userid = '" . $userid . "' AND password = '" . $password . "'";
$result = $dbh->query( $sql );
} else
{
echo "login failed. Your fingers are too big";
}
Optional Information:
Browser: Firefox

DO NOT EVER USE THAT CODE!
You have a very serious SQL injection open there. Every user input that you take, whether from cookies or CGI, or wherever, must be sanitized before it's used in an SQL statement. I could easily break into that system by attempting a login with an username like:
user'; UPDATE contractors SET password = '1337'
... after which I could then login as anyone. Sorry if I sound aggressive, but what that code does is like forgetting to lock the front door into your company which probably doesn't even contain an alarm system.
Note that it doesn't matter whether the input is actually coming from the user or not (perhaps it's in a pre-filled, hidden from). From the security point of view, anything that comes from anywhere outside has to be considered to contain malicious input by the user.
As far as I know, you need to use the quote function of PDO to properly sanitize the string. (In mysql, this would be done with mysql_real_escape_string().) I'm not an expert on PDO, mind you, somebody please correct if I'm wrong here.
Also you probably shouldn't store any passwords directly in the database, but rather use a hash function to create a masked password, then also create a hash from the user provided password, and match the hashes. You can use the PHP hash function to do this.
As for other issues, I don't know if the approach you have on SQL SELECT is the best approach. I would just select the corresponding user's password and try matching that in the program. I don't think there's any fault in the method you're using either, but it just doesn't seem as logical, and thus there's a greater chance of me missing some bug - which in case of passwords and logins would create a window for exploits.
To do it your way, you need to notice that the result you are getting from the PDO query is a PDOStatement, that doesn't seem to have a reliable function to diretly count the amount of result rows. What you need to use is fetchAll which returns an array of the rows, and count that. However, as I said this all feels to me like it's open for failures, so I'd feel safer checking the password in the code. There's just too much distance from the actual password matching compasion for my taste, in such a security-critical place.
So, to the get the resulting password for the userid, you can use PDOStatement's fetch() which returns the contents of the column from the result. Use for example PDO::FETCH_ASSOC to get them in an associative array based on the column names.
Here's how to fix it:
$userid_dirty = $_POST['userid'];
$password_dirty = $_POST['pass'];
$success = false; // This is to make it more clear what the result is at the end
if ($userid != "" || $password != "") {
$userid = $dbh->quote($userid_dirty);
$passwordhash = hash('sha256',$password_dirty);
$sql = "SELECT userid, passwordhash, company, security_id FROM contractors WHERE userid = ".$userid;
$result = $dbh->query( $sql );
if ($result) { // Check if result not empty, that userid exists
$result_array = $result->fetch(PDO::FETCH_ASSOC);
if ($result_array['PASSWORDHASH'] == $passwordhash) {
// login success
$success = true;
// do all the login stuff here...
// such as saving $result_array['USERID'], $result_array['COMPANY'], $result_array['SECURITY_ID'] etc.
} // else fail, wrong password
} // else fail, no such user
} else {
// fail, userid or password missing
echo ' please enter user id and password.';
}
if (!$success) {
echo ' login failed.';
}
Of course, the code can be cleaned up a bit, but that should explain what needs to be done. Note that since the password is both hashed, and never used in the SQL, it doesn't actually need cleaning. But I left it there just in case, since in the original code it was used in the query.
Note that all the code concerning storing passwords need to be changed to store the hash instead of the password. Also, it would be a very good idea to use a salt added to the password before hashing.
Also, I provided the code simply for educational purposes - I just thought that code was the clearest way to explain how to do this. So do not mistake this site as a service to request code. :)

The php manual is an excellent resource for learning PHP. It looks like you know a little SQL, and you have heard of PDO, which is a good start. If you search google for "PDO", or look in the PHP manual for the term, you'll find the PDO section of the manual. It looks like you've found the ->query function, so now you need to see what that returns. Going to the that function's manual page, we see that it returns a PDOStatement object. The word PDOStatement is helpfully linked to the relevant page in the manual, which lists the methods available on that object. There is a rowCount() method that will likely do what you want.

Related

SQL injection vulnerable code even when we are sanitizing the input mysql_real_escape_string

We have been attacked; the hackers entered the system from a page <login> that's in the code shown below, but we couldn't figure out the actual problem in this code.
Could you point out the problem in this code and also a possible fix?
<?php
//login.php page code
//...
$user = $_POST['user'];
$pass = $_POST['password'];
//...
mysql_connect("127.0.0.1", "root", "");
mysql_select_db("xxxx");
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);
$pass = hash("sha1", $pass, true);
//...
$query = "select user, pass from users where user='$user' and pass='$pass'";
//...
?>
The problem here is in $pass= hash("sha1",$pass, true);
You need to put it like this $pass= hash("sha1",$pass, false);
A good option is to move to PDO.
Let's see why this happen:
What your code is doing is returning a raw binary hash that means at a point in time the hash may contain an equal character =,
for your example the hash that going to result in SQL injection in this case is "ocpe" because hash ("ocpe",sha1) have a '=' character,
but how can I figure that out?
You only need to run a simple brute force and test if it contains a '=' inside the hash raw bit.
This is a simple code which can help you with that
<?php
$v = 'a';
while(1)
{
$hash = hash("sha1",$v, true);
if( substr_count( $hash, "'='" ) == 1 ) {
echo $v;
break;
}
$v++;
}
?>
Now you you have a string that gives a hash that has an equal inside of it '='
The query becomes:
$query = "select user, pass from users where user='$user' and pass='hash("ocpe",sha1)'";
then
$query = "select user, pass from users where user='$user' and pass='first_Part_of_hash'='Second_part_of_hash'";
In this case I assume that ocpe string has a hash of this format first_Part_of_hash'='Second_part_of_hash
Because pass='first_Part_of_hash' going to result in 0 and 0='Second_part_of_hash' is typecasted by the SQL engine, but in case of string if we type cast it to a int it's going to give as 0 ((int)'Second_part_of_hash' is result in 0)
so in the end 0=0
$query = "select user, pass from users where user='$user' and 0=0";
Which going to result in "true" every time and as you can see it can be applied to all hash functions like MD5 and sha256 etc.
Good resources to check:
How can I prevent SQL injection in PHP?
Could hashing prevent SQL injection?
To supplement the excellent answer from zerocool.
The problem here is the false notion that mysql(i)_real_escape_string prevents SQL injection. Unfortunately, too many people have been led to believe that this function's purpose is to protect them from injections. While of course it is not nearly true.
Had the author of this code the correct understanding of this function's purpose (which is escaping special characters in a string literal), they would have written this code as
$user = mysql_real_escape_string($user);
$pass = hash("sha1", $pass, true);
$pass = mysql_real_escape_string($pass);
and there wouldn't have been any injections at all.
And here we come to an important conclusion: given escaping's purpose is not to prevent SQL injections, for such a purpose we should use another mechanism, namely prepared statements. Especially given the fact that mysql extension doesn't exist in PHP anymore while all other extensions support prepared statements all right (yet if you want to reduce the pain of transition you should definitely use PDO, however paradoxical it may sound).
(Supplementary to the other answers / comments about using PDO, correct use of passwords etc; Logging this here in case someone else stumbles on this question.)
No one has pointed out:
mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");
as being a point of weakness.
This means that:
- the DB server is on the same host as the web server, and therefore has a network interface to the world.
- this have the most basic user (root) available,
- and without a password.
Hopefully this is an example/test, but if not, ensure that at least the server port (3306) is blocked by firewall / not accessible externally.
Otherwise a simple mysql -h [webserver address] -u root will connect and it's game over.
You can rewrite your validation logic as a quick fix to the issue explained by #zerocool.
// don't send password hash to mysql, user should be uniqe anyway
$query = "select user, pass from users where user='$user'";
// validate hash in php
if (hash_equals(hash('sha1', $pass, true), $user_hash_from_db)){...}
And as others wrote, stop using mysql_* functions ASAP, and use stronger hashing algo.
You can fix your existing code, without breaking any of the existing passwords, by adding one line:
$pass = $_POST['password']; // the actual password
$pass = mysql_real_escape_string($pass); // escaped version of the actual password
$pass = hash("sha1",$pass, true); // binary hash of the escaped password
// At this point, $pass is the exact string that is stored in the database.
$pass = mysql_real_escape_string($pass); // ***ADD THIS LINE***
$query = "select user, pass from users where user='$user' and pass='$pass'";
Note that the password stored in the database is the binary hash of the escaped version of the actual password. Since it is a binary string, you need to escape it.
Be sure to add the extra escaping to the code that stores the password in the first place, otherwise password setting will also have a SQL injection vulnerability.

Really slow login with PHP and MySQL

I'm working on an old site ad the login function takes forever. I'm trying to get to the bottom of this only im unsure whats causing it.
The login function uses AJAX.
AJAX
$password = md5($_POST['thepassword']);
$user = $_POST['theusername'];
$loginVar = $usersClass->login($user, $password);
if(is_array($loginVar))
{
$_SESSION['loggedIn'] = $loginVar;
#session_regenerate_id(true);
print "success";
}else{
print "Whoops, something went wrong! Try again.";
}
PHP Class
public function login($username, $password)
{
$rs = mysql_query("SELECT `id`,`active` from `$this->usersTable` WHERE
`username` = '".mysql_real_escape_string($username)."' AND
`password` = '".mysql_real_escape_string($password)."'");
if($rs) {
$row = #mysql_fetch_object($rs);
return $this->userInfo($row->id);
}else{
return false;
}
}
Since you have no index, I will suggest adding a composite index over username and password:
CREATE INDEX `idx_user_pass` ON `user_table` (`username`, `password`)
Review the MySQL CREATE INDEX syntax for full details.
As I mentioned in the comment thread, there is nothing inherently slow about your code as it is.
I will note some things to be aware of (and I suspect you know this since you have been an SO member for a long time). It is recommended to remove the # error suppression operators. You do have error checking on $rs already, so there's no need for additional suppression here.
$row = #mysql_fetch_object($rs);
//----^^
And I know you are already familiar with PDO and prepared statements from other questions, so no need to go into that...
Well, seems ike everything is ok with the code itself, it could be the server that is having troubles and it slows script down.
Also, $password = md5($_POST['thepassword']); $user = $_POST['theusername']; :
Don't use md5 hashing anymore, instead go for sha2 and for the user, please use mysql_real_escape_string at least for security around it :)
Try using PDO or mysql_fetch_assoc, according to PHP.net this function is slightly slower then mysql_fetch_assoc,
Speed-wise, the function is identical to mysql_fetch_array(), and
almost as quick as mysql_fetch_row() (the difference is insignificant).
Echo is slightly faster then print;
But even if you're code has better performance I doubt it has something to do with you're code. I think the database is a bit slow, are you using INNODB with an index in the fiels you're searching? This is a lot faster..
First as said in the comments, you should check if your table has indexes. For that, you can find out if it's the case by running this query :
EXPLAIN SELECT id, active FROM [users_tables] WHERE `username` = '[username]' AND `password` = '[password]'
(replace [...] by the rights values)
Then, I think you should avoid using # in front of session_generate_id and mysql_fetch_object.
Also, check the function userInfo if it's not doing another MySQL request, if you've a busy table it's better to get all data once you check the login password than in two times.
Finally, change your table (caution with this process, you can loose data) from MyISAM to InnoDB, as last one has a lock per row and MyISAM a lock per table.

PHP authenticating registered users

When you authenticate registered users you make request e.g. one examples i found:
$user = $_POST['user'];
$pw = $_POST['password'];
$sql = "SELECT user,password FROM users
WHERE user='$user'
AND password='$pw'
LIMIT 1";
$result = mysql_query($sql);
if (mysql_num_rows($result)){
//we have a match!
}else{
//no match
}
Now what would be the benefit or any point of having LIMIT 1 at the end?
And why you need to select user and password when you can just select user_id?
Would not the
SELECT user_id FROM users
WHERE user = '{$user}'
AND password = '{$pw}'
be same exact logistics but shorter code?
EDIT: thinking about this little detail made me find one more check to prevent hackers.
There should not be more than one user with same email and password so if they somehow supply instead of password 123 e.g. string ' OR password = '*' (or similar logics) this will compromise my query, having no limit would help because next step i can count
if (count($result) > 1) {
echo "we got hacked";
else
<proceed...>
Assuming you have only a single row in the database for each username/password pair, the LIMIT clause improves performance by discontinuing the search after the first match is found, primarily when used in conjunction with an ORDER BY clause.
From the MySQL manual:
If you use LIMIT row_count with ORDER BY, MySQL ends the sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast.
Most likely, you will have a unique user column, so LIMIT 1 is not necessary - you won't have more than 1 row anyway.
In this case, it might be a decorative element - self explaining syntax to tell a programmer that reads a code, that query is expected to return no more than one row.
Aside from your question, I would strongly recommend to use some password encryption, for example MD5(). A tutorial that teaches you to store a plain passwords is not the best one...
You can read "SQL Injection: How To Prevent Security Flaws In PHP / MySQL" and see how your login might be useless without proper measures.
Everything else was answered by far wiser posters above.
In most cases your table will hold unique usernames so you will always get back 1 or 0 rows. And as such as far as your code is concerned it doesn't make a difference. Even if you had multiple rows returned your code would still work, as you are only checking for existence of rows and not how many of them have been returned (but it would be wrong, as you wouldn't know which user actually logged in).
Basically it just tells MySQL to stop searching the table after the first row that meets the conditions in WHERE is found. In some cases even this could be redundant (if you had a UNIQUE index on the "user" field for instance).
One more thing not related to your question: please do not use this code for anything but learning. It's full of security holes. Google for "SQL injection" and "storing password securely" before you put this code into production.
Firs you need to sanitize strings from injection:
class main{
public function sanitize($str,$remove_nl=true)
{
stripslashes($str);
if($remove_nl)
{
$injections = array('/(\n+)/i',
'/(\r+)/i',
'/(\t+)/i',
'/(%0A+)/i',
'/(%0D+)/i',
'/(%08+)/i',
'/(%09+)/i'
);
$str = preg_replace($injections,'',$str);
}
return $str;
}
}
Next yours code:
$main_class = new main();
$user = $main_class->sanitize(trim($_POST['user']));
$pw = $main_class->sanitize(trim($_POST['password']));
$sql = "SELECT * FROM `users` WHERE `user`='".$user."' AND `password`='".$pw."' LIMIT 0,1";
$result = mysql_query($sql) or die(mysql_error());
$count = mysql_num_rows($result);
if($count > 0){
//we have a match!
}else{
//no match
}

Most secure php user athentication function

What is the best way to create a secure user authentication function? Below is the core of a php function that takes in the username and password and checks it against the database.
I am specifically interested in the query and its return value. Is using the 'else if($query1)' the best way to validate and set the session variable? Also, what value is best to set for the session variable? An email address, username, bool variable, primary key index, etc?
$query1 = mysql_fetch_array(mysql_query("SELECT primaryKey
FROM loginInfo
WHERE email = md5('$email')
AND password = md5(CONCAT('$password',salt))
LIMIT 1"));
if (!$query1)
return false;
else if ($query1) {
$_SESSION['userNumber'] = $query1[primaryKey];
return true;
}
else
return false;
MD5 has known vulnerabilities and is no longer considered secure. You should switch to a stronger hash such as SHA-2.
Also, $query1 can only evaluate to true or false, so the final else part is useless and will never be reached. Your 3 branches are equivalent to just this:
if (!$query1)
return false;
else { // else $query1 is obviously true
$_SESSION['userNumber'] = $query1[primaryKey];
return true;
}
There is no such thing as a "best value" to store in the session, but the primary key is usually a convenient choice, since it is guaranteed to be unique and also provides an easy way to look up the remaining details. Additionally, if you find yourself frequently displaying some information such as the user's name, you could additionally store that in the session for easy access.
There are multiple issues with this code:
SQL injection vulnerability. (What happens when a user enters an email address of ') OR 1=1 OR '' = ('?) You should have a look at mysql_real_escape_string, or consider using parametrized queries.
Your never call mysql_free_result on the resource returned from mysql_query, which will leak resources on the MySQL server (until the script terminates), and may prevent future queries in the same script from executing.
MD5 is deprecated due to vulnerabilities. Consider using a hash in the SHA family instead.
It depends on where the attacker will have access. If he somehow has access to the database, the suggested changes of the hash-type are important.
If he doesn't, it's more important to restrict the number of failed logins to avoid bruteforce-attacks.
However, the vulnerability cdhowie pointed at has to be fixed at all.

How dangerous is this PHP code?

How dangerous is this php code? What can be done about it?
$name = $_POST["user"];
$pwd = $_POST["pwd"];
$query = "SELECT name,pwd FROM users WHERE name = '$name' AND pwd = '$pwd'";
Possible Problems:
SQL Injection
XSS Injection (if this code was an insert query, it would be a definite problem)
Plain Text Password
Your SQL Statement can be problematic. It is bad practice to leave yourself open for SQL injection.
SQL Injection is bad. Trust me.
If you want to display the $user on an HTML page, then you may not want to include the ability for people to "hack" your layout by typing in commands like
<H1>HI MOM</H1>
or a bunch of javascript.
Also, never store your password in plain text (good catch cagcowboy!). It gives too much power to people administering (or hacking) your database. You should never NEED to know someone's password.
Try tactics like these:
// mostly pulled from http://snippets.dzone.com/posts/show/2738
function MakeSafe($unsafestring)
{
$unsafestring= htmlentities($unsafestring, ENT_QUOTES);
if (get_magic_quotes_gpc())
{
$unsafestring= stripslashes($unsafestring);
}
$unsafestring= mysql_real_escape_string(trim($unsafestring));
$unsafestring= strip_tags($unsafestring);
$unsafestring= str_replace("\r\n", "", $unsafestring);
return $unsafestring;
}
// Call a function to make sure the variables you are
// pulling in are not able to inject sql into your
// sql statement causing massive doom and destruction.
$name = MakeSafe( $_POST["user"] );
$pwd = MakeSafe( $_POST["pwd"] );
// As suggested by cagcowboy:
// You should NEVER store passwords decrypted.
// Ever.
// sha1 creates a hash of your password
// pack helps to shrink your hash
// base64_encode turns it into base64
$pwd = base64_encode(pack("H*",sha1($pwd)))
It's this dangerous:
SQL Injection aside, it looks like your passwords might be stored in plain text, which isn't great.
That code is very safe if you never pass $query to a SQL database.
If one were to post 0';drop table users;-- for a name
your command would end up being
select name, pwd form users where name='0';
drop table users; --'and pwd = '[VALUE OF PWD]'
So first it would get your data, then kill your users table, and do nothing with the rest since it is a comment.
Certain mysql commands in php will perform multiple queries when passed sql, the best way to avoid this is parametrized queries.
I use PDO for all my DB access, and highly recommend it. I do not have any links off the top of my head but I remember the tutorials I used topped Google.
It is not only prone to SQL injections, it will also fail in cases where an injection is not even intended:
For example a user wants the name "Guillaume François Antoine, Marquis de L’Hospital". Since the username contains a quote and you are not escaping it, your query will fail, although the user never wanted to break the system!
Either use PDO or do it in this way:
$query = sprintf(
"SELECT 1 FROM users WHERE name = '%s' AND password = '%s'",
mysql_real_escape_string($_POST['name']),
mysql_real_escape_string(md5($_POST['password']))
);
Believe it or not, this is safe... if magic_quotes_gpc is turned on. Which it will never be in PHP6, so fixing it prior to then is a good idea.
$_POST['user'] = "' or 1=1; --";
Anyone gets instant access to your app
$_POST['user'] = "'; DROP TABLE user; --";
Kiss your (paid?) user list goodbye
If you later echo $name in your output, that can result in a XSS injection attack
:O don't do it never ever,
This can cause SQLInjection attack. If for example user input somehow:
' drop table users --
as input in $username; this code will concatinate to your orginal code and will drop your table. The hackers can do more and can hack your website.
This is typically very dangerous. It could be mitigated by database permissions in some cases.
You don't validate the input ($name and $pwd). A user could send in SQL in one or both of these fields. The SQL could delete or modify other data in your database.
Very very dangerous. A good idea for passwords is to convert the password into a MD5 hash and store that as the user's 'password'.
1) protects the users from having their passwords stolen
2) if a user writes a malicious string they could wipe out your entry/table/database
Also you should do some basic match regex expression on the name to make sure it only uses A-Za-z0-9 and maybe a few accented characters (no special characters, *'s, <'s, >'s in particular).
When user data is involed in a SQL query, always sanatize the data with mysql_real_escape_string.
Furthermore, you should store just a salted hash of the password instead of the password itself. You can use the following function to generate and check a salted hash with a random salt value:
function saltedHash($data, $hash=null)
{
if (is_null($hash)) {
$salt = substr(md5(uniqid(rand())), 0, 8);
} else {
$salt = substr($hash, 0, 8);
}
$h = $salt.md5($salt.$data);
if (!is_null($hash)) {
return $h === $hash;
}
return $h;
}
All together:
$query = 'SELECT pwd FROM users WHERE name = "'.mysql_real_escape_string($_POST['user']).'"';
$res = mysql_query($query);
if (mysql_num_rows($res)) {
$row = mysql_fetch_assoc($res);
if (saltedHash($_POST["pwd"], $row['pwd'])) {
// authentic
} else {
// incorrect password
}
} else {
// incorrect username
}
Its not safe, you might want to look into something like PDO.
PHP PDO

Categories