7

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'";
        //...

    ?>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
joey
  • 87
  • 3
  • 6
    ***You shouldn't use [SHA1 password hashes](https://konklone.com/post/why-google-is-hurrying-the-web-to-kill-sha-1)*** or ***[MD5 password hashes](http://security.stackexchange.com/questions/19906/is-md5-considered-insecure)*** and you really should use PHP's [built-in functions](http://jayblanchard.net/proper_password_hashing_with_PHP.html) to handle password security. Make sure you [don't escape passwords](http://stackoverflow.com/q/36628418/1011527) or use any other cleansing mechanism on them before hashing. Doing so *changes* the password and causes unnecessary additional coding. – Jay Blanchard Apr 04 '19 at 19:46
  • 10
    ***Please [stop using `mysql_*` functions](http://stackoverflow.com/questions/12859942/why-shouldnt-i-use-mysql-functions-in-php).*** [These extensions](http://php.net/manual/en/migration70.removed-exts-sapis.php) have been removed in PHP 7. Learn about [prepared](http://en.wikipedia.org/wiki/Prepared_statement) statements for [PDO](http://php.net/manual/en/pdo.prepared-statements.php) and [MySQLi](http://php.net/manual/en/mysqli.quickstart.prepared-statements.php) and consider using PDO, [it's really pretty easy](http://jayblanchard.net/demystifying_php_pdo.html). *It isn't funny anymore.* – Jay Blanchard Apr 04 '19 at 19:47
  • 5
    [Little Bobby](http://bobby-tables.com/) says ***[your script is at risk for SQL Injection Attacks.](http://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php)***. Even [escaping the string](http://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string) is not safe! – Jay Blanchard Apr 04 '19 at 19:47
  • 5
    The actual problem is ***all*** of this code. Bad password hashing, outdated MySQL functions, escaping strings, SQL injection openings. How long has this been in production? – Jay Blanchard Apr 04 '19 at 19:51
  • 4
    How did you determine that this was an SQL injection and not brute force? Also please show the query execution portion of your code, that determines successful or invalid login. – Will B. Apr 04 '19 at 20:04

5 Answers5

16

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?

yivi
  • 42,438
  • 18
  • 116
  • 138
zerocool
  • 3,256
  • 2
  • 24
  • 40
  • 2
    Nice explanation. – mynd Apr 04 '19 at 19:41
  • 1
    You should be using PHP's built in password functions and avoiding SHA1 all together – Jay Blanchard Apr 04 '19 at 19:48
  • @JayBlanchard ya that's true but he want an explanation also I reference for him the best article in SO about "prevent SQL injection in PHP?". – zerocool Apr 04 '19 at 19:51
  • Yes, but what you explained is not the only problem in the code. You cannot be sure which modality the hacker used to base their attack. – Jay Blanchard Apr 04 '19 at 19:55
  • @JayBlanchard the question is regarding SQL injection, password hashing methodology/preference is beyond the scope. Albeit I agree that the OP should use the [`password_hash`](https://www.php.net/manual/en/function.password-hash.php) feature from PHP, as it is considered best practice and other reasons. – Will B. Apr 04 '19 at 19:59
  • @JayBlanchard i give him the easy way, there is other trick you can do also remember that nothing is secure xD even pdo and mysqli are hackable just ... – zerocool Apr 04 '19 at 19:59
  • @fyrye ya that's true because there is an attack named timing hashing which could tell you many information about the password so it's better use php new method that they don't reveal this info to the attackers which in this case is time taken to ... – zerocool Apr 04 '19 at 20:03
  • 1
    @JayBlanchard, it's true that one should use `password_hash()` but telling someone to change their current hashing method is not simple. How would you suggest they convert hashed passwords for their current users to the new method? – Bill Karwin Apr 05 '19 at 01:19
  • @BillKarwin there are well established methods for changing the hashing/verification methods and they all take some work. Depending on what the developer is protecting will determine the priority for that work. The description of a conversion method, even a basic one, is much longer than allowed in comments. – Jay Blanchard Apr 05 '19 at 11:14
  • 3
    It's pretty easy to summarize: You can't do a direct conversion, because hashing is one-way. You have to ask your users to re-enter their passwords after modifying your app to store the password using the newer hash format. The app needs to authenticate users by either password until all users have updated their passwords. – Bill Karwin Apr 05 '19 at 17:50
5

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).

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • if you want to see other tricks like the one I talked about above just google "exploit -db " and start looking at "SQL injection" POC and you will find many tricks like this if you are interested – zerocool Jul 12 '19 at 20:53
0

(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.

Robbie
  • 17,605
  • 4
  • 35
  • 72
-1

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.

MIvanIsten
  • 481
  • 3
  • 7
-2

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.

Tom Robinson
  • 1,850
  • 1
  • 15
  • 14