TalkPHP
 
 
Account Login
Latest Articles
» The basic usage of PHPTAL, a XML/XHTML template library for PHP
» Vulnerable methods and the areas they are commonly trusted in.
» Simple way to protect a form from bot
» The Basics On: How Session Stealing Works
» How to keep your forms from double posting data
Advertisement
Associates
Associates
techtuts Darkmindz
CSS Tutorials Tutorialsphere.com - Free Online Tutorials
Boston PHP SurfnLearn
Reply
 
LinkBack (3) Thread Tools Search this Thread Display Modes
Old 09-19-2007, 11:32 PM   3 links from elsewhere to this Post. Click to view. #1 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Red face How to Login to Any Account on an Insecure Site

I sat down with a friend today and spent a good 5 hours attempting to breach the security of an unnamed website. The website in question is a rather popular website with an Alexa ranking of just over 3,000. I've always been of the opinion that esoteric knowledge is only esoteric because the individuals wish to make it that way. Governmental procedures, for instance, are very esoteric. Unless you're actually there, the procedures are well over many individuals' head. If you can decipher the language used most people can understand it.

This is where I'd like to sit down with everyone at TalkPHP and explain in simple terms how I did it, the reasons why and what you can do to prevent this from happening to you.

Note: Although we successfully hacked the target site, no core information was gathered and no harmful information was injected. The administrators of the site were notified and advised on how to patch the vulnerability.

This breach of security involved the common security method, SQL injection. Now, I've used the unnamed site on numerous occasions for various reasons that I'm not going to mention. All perfectly innocuous. However, from using the website off and on I noticed many security issues that were arising from normal use. Today was the day I decided to put theory into practice.

I already had an account and so I attempted to login to my account using the following:

Username: Wildhoney
Password: ' OR 1=1


What that essentially says is take the user name, Wildhoney, and then attempt to issue my own SQL. If you think that the normal query would be like so:

Code:
SELECT
	myUsername,
	myPassword
FROM
	myTable
WHERE
	myUsername = 'Wildhoney'
AND
	myPassword = 'myPassword'
Then terminating the SQL just after the = ' would end the normal SQL and allow me to enter raw SQL commands. Thanks to our SQL injection the query would look like so:

Code:
SELECT
	myUsername,
	myPassword
FROM
	myTable
WHERE
	myUsername = 'Wildhoney'
AND
	myPassword = '' OR 1=1
As you can clearly see from there, the SQL has been significantly modified to to make the end part of the SQL say the following in pseudo terms: AND the field myPassword equals NULL OR 1 equals 1. As 1 will always equal 1 we can successfully login.

However, on this website there is more code at the end of the SQL making our MySQL statement now make absolutely no sense. The solution for this is MySQL comments! A comment will comment out any code we do not want. In this case, the code after our OR 1=1. First up was the -- comment block. However, -- only comments single lines and after that didn't work we deduced the site must have been using multiple SQL lines. Step in /*. Once that had been issued MySQL ignored everything after our OR 1=1 and the login was successful.

Note: Although we logged into our own user name, absolutely any user name on the site could have been accessed.

I presume that many individuals are asking "why?". This wasn't a case of boosting our ego or bragging rights. Rather, education. Although we did a lot more after the login attempt, nothing harmless in the least, the login attempt is perhaps 1 of the most vulnerable part to any website and I felt was worth mentioning to everyone on TalkPHP to stop them making the same mistakes in their code.

For the login attempt, the code was not complex nor was it tricky to construct. We successfully logged into our account without specifying the correct password after about the 5th attempt. A little research was required before logging in but after that, the world is yours (Or, ours).

The way to protect yourself against something like that is just so simple. You should escape all single quotes, as well as check the data using a type specifier. See our article on sprintf. You may also wish to check for the encoding type - such as UTF-7 and UTF-8. Character encoding can cause so many issues with MySQL queries. These will certainly be covered in some depth on TalkPHP in the near future.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-19-2007, 11:50 PM   #2 (permalink)
The Frequenter
Prolific Welcomer Upcoming Programmer 
 
Join Date: Sep 2007
Posts: 356
Thanks: 24
Haris is on a distinguished road
Default

sprintf saves our live!

Last edited by Haris : 09-20-2007 at 12:43 AM.
Haris is offline  
Reply With Quote
Old 09-20-2007, 01:58 AM   #3 (permalink)
daz
The Contributor
Upcoming Programmer 
 
Join Date: Sep 2007
Posts: 31
Thanks: 0
daz is on a distinguished road
Default

Doesn't mysql_real_escape_string() protect against SQL injections?
daz is offline  
Reply With Quote
Old 09-20-2007, 02:16 AM   #4 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Default

Indeed it can! Fantastic function is mysql_real_escape_string() along with sprintf.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-20-2007, 07:01 AM   #5 (permalink)
The Gregarious
Upcoming Programmer Inquisitive 
 
Join Date: Sep 2007
Posts: 748
Thanks: 85
Tanax is on a distinguished road
Default

PHP Code:
$pass mysql_escape_string($_POST['pass']);
$sql printf("SELECT `pass` FROM `users` WHERE `pass` = %s"$pass);
$query mysql_query($sql); 
That would be pretty foolproof?

Anything else that I can add to make it safer?
Tanax is offline  
Reply With Quote
Old 09-20-2007, 10:46 AM   #6 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Default

That's quite safe as it is! Just don't forget to strip_tags() and %s needs to be in single quotes: '%s', else it'll simply fail.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-20-2007, 10:49 AM   #7 (permalink)
Moderateur
RegEx Guru PHP Guru Top Contributor Advanced Programmer 
 
Salathe's Avatar
 
Join Date: Apr 2007
Posts: 753
Thanks: 2
Salathe is on a distinguished road
Default

Tanax, in your example the password is sent in a POST request (from a form) and fed (via mysql_escape_string) directly into the query. There are a couple of problems with that SQL query. Firstly, there are no quotes around where the password will go (... WHERE `pass` = '%s') -- unless all of your passwords are numbers, that will cause problems. Secondly, you compare the pass column in the database directly to the string sent through the POST request. You should never, ever, store passwords exactly as they are entered into forms (in "plain text"). Wildhoney has written about salting password hashes, as well as one soon to be released on dynamic salting. Finally, your query would be useless in any real terms because it would return a result if any password in the table matched what was POSTed. You must always match it against a parameter unique to each individual (user id number or similar). Oh, and you should use sprintf to "print to a string" rather than the printf that you have there.

All that said, as far as "protecting" the actual value passed from POST that is a good start.
__________________
Salathe is offline  
Reply With Quote
Old 09-20-2007, 10:53 AM   #8 (permalink)
Moderateur
RegEx Guru PHP Guru Top Contributor Advanced Programmer 
 
Salathe's Avatar
 
Join Date: Apr 2007
Posts: 753
Thanks: 2
Salathe is on a distinguished road
Default

Quote:
Originally Posted by Wildhoney View Post
That's quite safe as it is! Just don't forget to strip_tags() ...
In this instance (password verification) why would you want to strip tags? I'm pretty sure that if someone entered the following into the password field: <strong>my<br>pass<br>word</strong>, even if "mypassword" was correct you would still want to fail because the supplied password is incorrect:
"<strong>my<br>pass<br>word</strong>" != "mypassword"

Or am I missing something?
__________________
Salathe is offline  
Reply With Quote
Old 09-20-2007, 10:56 AM   #9 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Default

No, you're not missing anything. It's just good practice to only allow alphanumeric passwords and so stripping the tags would be a useful exercise.

Here's the article on working with dynamic salts.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-20-2007, 12:49 PM   #10 (permalink)
The Reckoner
Advanced Programmer Top Contributor 
 
Karl's Avatar
 
Join Date: Sep 2007
Posts: 438
Thanks: 22
Karl is on a distinguished road
Default

Unless im completely missing something here, why has no one mentioned the fact that selecting a users password by password is prone to problems. For example, if two users have the same password the user who registered first will always be returned. If the query is geing used to validate a login the query is very insecure indeed. You'll need to also include the user's id, username, email or other primary key and use that to determine if the password belongs to the user.
Karl is offline  
Reply With Quote
Old 09-20-2007, 01:05 PM   #11 (permalink)
Moderateur
RegEx Guru PHP Guru Top Contributor Advanced Programmer 
 
Salathe's Avatar
 
Join Date: Apr 2007
Posts: 753
Thanks: 2
Salathe is on a distinguished road
Default

I mentioned it Karl, but it's good to have the message said twice because it's important. :)
__________________
Salathe is offline  
Reply With Quote
Old 09-20-2007, 01:23 PM   #12 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Default

I just constructed the following which I think would be pretty safe. It's a different way of doing the login and would throw would be hackers off the trail. If you're expecting a 32 bit string and nothing more then the substr is a good idea. The advantages of putting the if statement in the select area is that commenting the rest out would make the query fail, it also does it differently to how most people would do it so the hacker may be barking up the wrong tree. However, the disadvantage I see of putting it there is that you can modify the query more if your SQL is still insecure.

Don't get me wrong, I'm not at all adverse to the normal way of doing it as that's no doubt the best way due to the fact that you can't UNION a DELETE onto the end.

Code:
SELECT
	@myPass:= SUBSTR(MD5('myPassword'), -32),
	IF(password = @myPass, TRUE, FALSE) AS status
FROM
	members
WHERE
	username = 'myUsername'
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 09-20-2007, 01:31 PM   #13 (permalink)
The Reckoner
Advanced Programmer Top Contributor 
 
Karl's Avatar
 
Join Date: Sep 2007
Posts: 438
Thanks: 22
Karl is on a distinguished road
Default

Is there really any advantage to that over the tradional method? Surrounding passwords in quotations and ensuring you escape the data should be sufficient to stop the majority of attackers. I dont see any benefit to using the alternative method over the standard method.
Karl is offline  
Reply With Quote
Old 11-13-2007, 10:52 AM   #14 (permalink)
The Contributor
 
EyeDentify's Avatar
 
Join Date: Nov 2007
Location: Sweden
Posts: 92
Thanks: 11
EyeDentify is on a distinguished road
Smile My standard login mechanics - is it secure ?

While reading this interesting thread i got a little worried that my standard login code had short commings. so i wish for you to take a look and see if i can make some improvements to it.

I apologize in advanced for my english since i´m from sweden and it might be a little rusty. :)

PHP Code:
$user strip_tags(trim($_POST['adm_login_user']));
$password strip_tags(trim($_POST['adm_login_psw']));

// login --------------------------------

$sql "SELECT * from com_usr WHERE is_username='" $user "' AND is_password='" md5($password) . "' AND active=1";
    
$result mysql_query($sql);
        if (!
$result) {
            echo(
"Could not perform MySQL query: " mysql_error() . "");
            exit();
        }

        
$user_exist mysql_num_rows($result);
            
            while ( 
$row mysql_fetch_array($result) ) {
            
$id $row["ID"];
            
$username $row["is_username"];
            
$active $row["is_active"];
            
$admin_lvl $row["is_admin_level"];
            
$auth $row["is_author"];
            }


    if (
$user_exist 0) {
        
// Do something like setting session variables
        
} else {
        
// Send user back with error message
        

EyeDentify is offline  
Reply With Quote
Old 11-13-2007, 12:35 PM   #15 (permalink)
La Vida es Sueño
Advanced Programmer Top Contributor 
 
Wildhoney's Avatar
 
Join Date: Sep 2007
Location: Oldham
Posts: 1,645
Thanks: 73
Wildhoney is on a distinguished road
Default

Well, assuming that GPC magic quotes is disabled - which they will be in the next version of PHP as it is a real annoyance, your login script is quite easy to crack, I'm afraid. With them removing magic quotes all together in PHP6, people really need to get up-to-scratch with the way they do things.

I can by crack your login script by entering a user name like so: 'bleh' /* All this does is ends the user name segment and then comments out the rest of the code.

You may wish to have yourself a read through this article and also use a function, such as the one I used below in a few of my projects:

php Code:
function mysql_parse_value($szValue, $bStripTags = true, $szAllowableTags = null)
{
    if (is_array($szValue))
    {
        return
    }
   
    if (get_magic_quotes_gpc())
    {
        $szValue = stripslashes($szValue);
    }
         
    if ($bStripTags)
    {
        $szValue = strip_tags($szValue, $szAllowableTags);
    }
       
    if (!is_numeric($szValue))
    {
     $szValue = "'" . mysql_real_escape_string($szValue) . "'";
    }

    return $szValue;
}

And thus when used in conjunction with the sprintf function. Your MySQL will now look something like this:

php Code:
$sql = sprintf("    SELECT * from com_usr WHERE is_username = %s AND is_password md5(%s) AND active = 1",
                    mysql_parse_value($user),
                    mysql_parse_value($password));

This would make my earlier attempt futile, and in all honesty will just make me end up sat there looking silly. You'd have got one over on me :) ! Moreover, as you're new to TalkPHP, we prefix all our variables by their data-type - this admittedly may seem somewhat confusing to begin with, but please have a read through Bluesaga's article and you'll soon understand. It makes code look a thousand times better.

Last but not least, you're in safe hands with your PHP code now :). Glad to have you here.
__________________
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.
Send a message via AIM to Wildhoney Send a message via MSN to Wildhoney Send a message via Yahoo to Wildhoney
Wildhoney is offline  
Reply With Quote
Old 11-13-2007, 01:36 PM   #16 (permalink)
The Contributor
 
EyeDentify's Avatar
 
Join Date: Nov 2007
Location: Sweden
Posts: 92
Thanks: 11
EyeDentify is on a distinguished road
Default

Thank you Wildhoney for the quick reply.

I´m going to go over your post and sugestions more in detail when i get of work.

And i´m going to read the Bluesaga article so that i use the proper variable naming convention.

/EyeDentify :D
__________________
Of course the whole point of a doomsday machine, would have been lost if you keep it a secret.
EyeDentify is offline  
Reply With Quote
Old 12-14-2008, 09:30 PM   #17 (permalink)
The Visitor
 
Mohammad's Avatar
 
Join Date: Dec 2008
Location: Tehran, Iran
Posts: 2
Thanks: 2
Mohammad is on a distinguished road
Default

I created my personal! function:
PHP Code:
function CleanInput($input) {
return 
trim(str_replace(array("/","'",'"',";",":","+","--","*"), ""$input));

and what I do in login pages is :
- First i check that the user exists or not
- Then i retrive user information
- Passwords which are entered in form & are in DB (both in md5) compared with php
- Session/cookie sets with entered information