上个星期末打NJCTF,一共没做两个题,蓝瘦。
其中有一个Web,是一个后台登陆页面,可以注册新用户,试着注册登陆后,提示不是admin。
所以要以admin身份登陆,于是想到爆破,但感觉这个题并不是。纠结了好一会,表哥告诉是
基于约束的SQL攻击这个东西。

一般用户注册后端代码如下:

<?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;

也利用mysql_real_escape_string()函数过滤了用户输入,但是我们攻击的点不在这里,而是在服务器的SQL上。

首先,SQL进行字符串处理时,大部分会将字符串后无用的空格删去(WHERE的字符串或者INSERT的字符串等),也就是说”string[空格]”与”string”是等同的。
当然也有例外,比如LIKE语句,因为LIKE函数比较时,要俩字符串长度相等,所以会将短的字符串后面填充空格。

所以假设username的varchar(10),我们注册username=’admin[十个空格]1’

SELECT * FROM users WHERE username='$username';

执行这条语句时,SQL查询的是’admin[十个空格]1’这个字符串,并不会找到匹配的结果。

INSERT INTO users(username, password) VALUES('admin          1','anypassword');

这里INSERT查询时,SQL会根据创建表时的varchar(n)来限制字符串的最大长度,所以我们插入的username就会变成admin加5个空格。
这样数据库中就会有两个admin用户了,当我们进行登陆admin时,SQL进行SELECT就会返回原始admin的记录,这样我们就可以以admin的身份登陆了。
此攻击已在MySQL和SQLite上成功测试。

如果要防护,可以使用UNIQUE约束,UNIQUE约束确保在非主键列中不输入重复的值。一般使用id作为数据库表的主键,在后端验证时可以验证id。