我们受到了攻击;黑客从下面显示的代码中的页面<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'";
//...
?>
这里的问题是在$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";
每次都会产生“真实”,正如您所看到的,它可以应用于所有散列函数,如MD5和sha256等。
好的资源来检查:
为了补充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,不管它听起来多么矛盾)。
(关于使用PDO的其他答案/评论的补充,正确使用密码等;在此处记录此以防其他人偶然发现此问题。)
没有人指出:
mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");
作为一个弱点。
这意味着: - 数据库服务器与Web服务器位于同一主机上,因此具有通向世界的网络接口。 - 这有最基本的用户(root),而且没有密码。
希望这是一个示例/测试,但如果没有,请确保至少服务器端口(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['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注入漏洞。