我们在哪里遭到攻击, 那些黑客从下面显示代码的页面进入系统,但是我们无法找出此代码中的实际问题。 您能否指出这段代码中的问题以及可能的解决方法
<?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'";
//...
?>
答案 0 :(得分:4)
这里的问题出在$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
,则我们将其强制转换为{{1 }}的值为0(int
的结果为(int)'Second_part_of_hash'
)
所以最后0
0=0
每次都会产生“ true”,并且您可以看到它可以应用于所有哈希函数,例如MD5和sha256等。
要检查的好资源:
答案 1 :(得分:1)
以补充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注入,为此,我们应该使用另一种机制,即 prepared statement 。特别是考虑到 mysql扩展在PHP中已不存在,而所有其他扩展都支持预备语句的事实(但是,如果您想减轻过渡的痛苦,则应该使用PDO,尽管这很矛盾)可能会听起来)。
答案 2 :(得分:0)
(关于使用PDO,正确使用密码等其他答案/评论的补充;在其他人偶然发现此问题的情况下,请在此处登录。)
没有人指出:
mysql_connect("127.0.0.1","root","");
mysql_select_db("xxxx");
是弱点。
这意味着: -DB服务器与Web服务器位于同一主机上,因此具有到世界的网络接口。 -该用户拥有最基本的用户(root), -并且没有密码。
希望这是一个示例/测试,但如果没有,请确保至少服务器端口(3306)被防火墙阻止/无法从外部访问。
否则,将连接一个简单的mysql -h [webserver address] -u root
,游戏结束。
答案 3 :(得分:-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_ *函数,并使用更强大的哈希算法。
答案 4 :(得分:-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注入漏洞。