背景知识
最近,我遇到了一段有趣的代码,它尝试尽一切可能来保护数据库的访问安全,例如每当新用户进行注册时,将运行以下代码:
<?php
// Checking whether a user with the same username exists
$username = mysql_real_escape_string($_GET['username']);
$password = mysql_real_escape_string($_GET['password']);
$query = "SELECT *
FROM users
WHERE username='$username'";
$res = mysql_query($query, $database);
if($res) {
if(mysql_num_rows($res) > 0) {
// User exists, exit gracefully
.
.
}
else {
// If not, only then insert a new entry
$query = "INSERT INTO users(username, password)
VALUES ('$username','$password')";
.
.
}
}
为了验证登录信息,将用到下列代码:
<?php
$username = mysql_real_escape_string($_GET['username']);
$password = mysql_real_escape_string($_GET['password']);
$query = "SELECT username FROM users
WHERE username='$username'
AND password='$password' ";
$res = mysql_query($query, $database);
if($res) {
if(mysql_num_rows($res) > 0){
$row = mysql_fetch_assoc($res);
return $row['username'];
}
}
return Null;
攻击手法
首先,在处理SQL中的字符串时,字符串末尾的空格字符都会被删除。换句话说,“vampire”与“vampire ”几乎是等效的,这在大多数情况下是正确的,例如WHERE子句中的字符串或INSERT语句中的字符串。例如,以下语句的查询结果,与使用用户名“vampire”进行查询时的结果是一样的。
SELECT * FROM users WHERE username='vampire ';
等同于 SELECT * FROM users WHERE username='vampire';
究其原因是因为在字符串比较的过程中,内部在比较之前先进行了补充,使其长度一致再进行比较。
下面再本地复现一遍。
image.pngimage.png
SELECT username FROM users WHERE username='vampire' AND password='random_pass';
当执行这条语句后,数据库将返回我们自己注册的账户信息,但是注意此处的return $username,虽然此时查询出来的是我们自己的用户信息,但是返回的用户名则是目标的用户名。如果此后的业务逻辑直接以该用户名为准,则我们就达到了水平越权的目的。
现在讲一下sql约束攻击的条件:
1.mysql处于ANSI模式。如果是TRADITIONAL模式或者STRICT_TRANS_TABLES模式会报错data too long for column。
2.服务端没有对用户名长度进行限制。如果服务端限制了用户名长度就自然就不客能导致数据库截断,也就没有利用条件。
3.登陆验证的SQL语句必须是用户名和密码一起验证。如果是验证流程是先根据用户名查找出对应的密码然后再比对密码,当使用vampire为用户名来查询密码的话,数据库此时就会返回两条记录,而一般取第一条即目标用户的记录,那么传输的密码肯定和目标用户密码匹配不上的。
4.验证成功后返回的必须是用户传递进来的用户名,而不是从数据库取出的用户名。因为当我们以用户vampire和密码random_pass登陆时,其实数据库返回的是我们自己的用户信息,而我们的用户名其实是vampire+若干个空格,如果此后的业务逻辑以该用户名为准,那么就不能达到越权的目的了。
回到题目之中。
我们注册一个如下账号
image.png
然后直接 username:admin password:As123123登陆
网友评论