即使我们正在清理输入mysql_real_escape_string,SQL注入易受攻击的代码

问题描述 投票:3回答:2

我们受到了攻击;黑客从下面显示的代码中的页面<login>进入系统,但我们无法弄清楚此代码中的实际问题。

你能指出这段代码中的问题,还有一个可能的解决方法吗?

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

    ?>
php sql security sql-injection owasp
2个回答
17
投票

这里的问题是在$pass= hash("sha1",$pass, true);

你需要把它像这个$pass= hash("sha1",$pass, false);

一个很好的选择是转向PDO。


让我们看看为什么会这样:

您的代码正在做的是返回原始二进制哈希,这意味着在一个时间点哈希可能包含相等的字符=,对于您的示例,在这种情况下将导致SQL注入的哈希是"ocpe",因为哈希(“ocpe”) ,sha1)有一个'='字符,但我怎么能弄明白呢?

你只需要运行一个简单的暴力并测试它是否在哈希原始位中包含'='

这是一个简单的代码,可以帮助您

<?php
$v = 'a';
while(1)
{
        $hash = hash("sha1",$v, true);
        if( substr_count( $hash, "'='" ) == 1 ) {
            echo $v;
            break;
        }
        $v++;
}

?>

现在你有了一个字符串,它给出了一个内部相等的哈希值'='

查询变为:

$query = "select user, pass from users where user='$user' and pass='hash("ocpe",sha1)'";

然后

$query = "select user, pass from users where user='$user' and pass='first_Part_of_hash'='Second_part_of_hash'";

在这种情况下,我假设ocpe字符串有这种格式的散列first_Part_of_hash'='Second_part_of_hash

因为pass='first_Part_of_hash'将导致00='Second_part_of_hash'由SQL引擎进行类型化,但是如果我们输入string int它将会给出为0((int)'Second_part_of_hash'0的结果) 所以最后0=0

$query = "select user, pass from users where user='$user' and 0=0";

每次都会产生“真实”,正如您所看到的,它可以应用于所有散列函数,如MD5和sha256等。


好的资源来检查:

How can I prevent SQL injection in PHP?

Could hashing prevent SQL injection?


5
投票

为了补充zerocool的优秀答案。

这里的问题是false notion that mysql(i)_real_escape_string prevents SQL injection。不幸的是,有太多人被认为这个功能的目的是保护他们免受注射。当然,这并不是真的。

如果这段代码的作者正确理解了这个函数的目的(它是在字符串文字中转义特殊字符),他们会把这段代码写成

$user = mysql_real_escape_string($user);
$pass = hash("sha1", $pass, true);
$pass = mysql_real_escape_string($pass);

并且根本不会有任何注射。

在这里我们得出一个重要结论:给出逃避的目的不是为了防止SQL注入,为了这个目的,我们应该使用另一种机制,即预备语句。特别是考虑到PHP中不再存在mysql扩展,而所有其他扩展都支持准备好的语句(但是如果你想减少过渡的痛苦,你肯定应该使用PDO,不管它听起来多么矛盾)。


0
投票

(关于使用PDO的其他答案/评论的补充,正确使用密码等;在此处记录此以防其他人偶然发现此问题。)

没有人指出:

mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");

作为一个弱点。

这意味着: - 数据库服务器与Web服务器位于同一主机上,因此具有通向世界的网络接口。 - 这有最基本的用户(root),而且没有密码。

希望这是一个示例/测试,但如果没有,请确保至少服务器端口(3306)被防火墙阻止/无法从外部访问。

否则一个简单的mysql -h [webserver address] -u root将连接,它的游戏结束。


-1
投票

您可以重写验证逻辑,以快速解决@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)){...}

正如其他人写的那样,尽快停止使用mysql_ *函数,并使用更强大的哈希算法。


-1
投票

您可以通过添加一行来修复现有代码,而不会破坏任何现有密码:

 $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'";

请注意,存储在数据库中的密码是实际密码的转义版本的二进制哈希值。由于它是二进制字符串,您需要将其转义。确保首先将额外的转义添加到存储密码的代码中,否则密码设置也会有SQL注入漏洞。

© www.soinside.com 2019 - 2024. All rights reserved.