我们受到了攻击;黑客从下面显示的代码中
您能否指出此代码中的问题以及可能的解决方法?
<?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'";
//...
?>
这里的问题出在$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'
会导致0
并且0='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";
每次都会产生"true",如您所见,它可以应用于所有哈希函数,如 MD5 和 sha256 等。
要检查的好资源:
如何防止 PHP 中的 SQL 注入?
哈希可以阻止SQL注入吗?
补充零酷的优秀答案。
这里的问题是mysql(i)_real_escape_string阻止SQL注入的错误观念。不幸的是,太多人被引导相信此功能的目的是保护他们免受注射。当然,这几乎不是真的。
如果此代码的作者正确理解此函数的用途(即转义字符串文本中的特殊字符),他们将编写此代码
$user = mysql_real_escape_string($user);
$pass = hash("sha1", $pass, true);
$pass = mysql_real_escape_string($pass);
而且根本不会有任何注射。
在这里我们得出一个重要的结论:鉴于转义的目的不是防止SQL注入,为此我们应该使用另一种机制,即预准备语句。特别是考虑到mysql 扩展在 PHP 中不再存在,而所有其他扩展都支持预准备语句(但如果你想减少过渡的痛苦,你绝对应该使用 PDO,无论听起来多么矛盾)。
(补充有关使用 PDO、正确使用密码等的其他答案/评论;在此处记录此内容,以防其他人偶然发现此问题。
没有人指出:
mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");
作为一个弱点。
这意味着: - 数据库服务器与 Web 服务器位于同一主机上,因此具有与世界的网络接口。 - 这有最基本的用户(根)可用, - 并且没有密码。
希望这是一个示例/测试,但如果没有,请确保至少服务器端口 (3306) 被防火墙阻止/无法从外部访问。
否则,一个简单的mysql -h [webserver address] -u root
将连接起来,游戏就结束了。
您可以重写验证逻辑,以快速修复@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_* 函数,并使用更强的哈希算法。
您可以通过添加一行来修复现有代码,而不会破坏任何现有密码:
$pass = $_POST["密码"]; 实际密码 $pass = mysql_real_escape_string($pass); 实际密码的转义版本 $pass = hash("sha1",$pass, true); 转义密码的二进制哈希 此时,$pass是存储在数据库中的确切字符串。 $pass = mysql_real_escape_string($pass); 添加此行*** $query ="选择用户,从用户传递,其中用户='$user'和传递='$pass'";
请注意,存储在数据库中的密码是实际密码的转义版本的二进制哈希。由于它是一个二进制字符串,因此您需要对其进行转义。 请务必在首先存储密码的代码中添加额外的转义,否则密码设置也将存在 SQL 注入漏洞。