SQL截断攻击

2008年,Stefan Esser提出一种名为”SQL Column Truncation”攻击方式,就是以上说的SQL截断攻击。

在MYSQL的配置中,有一个sql_mode选项。当MYSQL的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES的时候,MYSQL对于用户插入的超长值只会提示warning,而不是error(如果是error则插入不成功),这可能会导致一些“截断”问题。

测试过程如下(MYSQL 5)。

首先开启strict模式:

1
mysql> set @@sql_mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION";

在该模式下,因为输入的字符串超出了字符限制,因此数据库返回一个error信息,同时数据插入不成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
mysql> create table strict_test(
-> id int not null auto_increment,
-> username varchar(10),
-> password varchar(10),
-> primary key(id));
Query OK, 0 rows affected (0.09 sec)
mysql> select * from strict_test;
Empty set (0.01 sec)
mysql> show columns from strict_test;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(10) | YES | | NULL | |
| password | varchar(10) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
mysql> insert into strict_test(username,password) values('admin','pass');
Query OK, 1 row affected (0.01 sec)
mysql> select * from strict_test;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | admin | pass |
+----+----------+----------+
1 row in set (0.00 sec)
mysql> insert into strict_test(username,password) values('admin x','new_pass');
ERROR 1406 (22001): Data too long for column 'username' at row 1
mysql> select * from strict_test;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | admin | pass |
+----+----------+----------+
1 row in set (0.00 sec)

当关闭strict选项时:

1
2
mysql> set @@sql_mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION";
Query OK, 0 rows affected (0.00 sec)

再次插入刚才那条数据,数据库只返回一个warning,但数据插入成功:

1
2
3
4
5
6
7
8
9
10
11
mysql> insert into strict_test(username,password) values('admin x','new_pass');
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> select * from strict_test;
+----+------------+----------+
| id | username | password |
+----+------------+----------+
| 1 | admin | pass |
| 2 | admin | new_pass |
+----+------------+----------+
2 rows in set (0.00 sec)

此时就可以绕过身份认证登陆admin的账号,造成越权访问。

在这个问题公布后不久,WordPress就出现了一个真实案例:

注册一个用户名为”admin(55个空格)x”的用户,就可以修改原管理员的密码了。但是因为攻击者在此只能修改管理员的密码,而新密码仍然会发送到管理员的邮箱,所以并没有造成严重的后果(该wp版本为2.6.1)


闲着没事干,自己搭了个小题目来强化记忆一下。

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>小哥哥小姐姐快来玩</title>
</head>
<body>
<a href="login.php">登陆</a>
<a href="register.php">注册</a>
</body>
</html>

login.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php
session_start();
header("Content-type: text/html; charset=utf-8");
$conn = mysql_connect("localhost","root","toor");
mysql_select_db("zz_test") or die("数据库失败!");
$_SESSION['isadmin']=0;
$_SESSION['isguest']=0;
if(isset($_POST['username']) && isset($_POST['password'])){
$uname=addslashes($_POST['username']);
$pw=addslashes($_POST['password']);
$sql="select * from strict_test where username='$uname'";
$query=mysql_query($sql);
// echo mysql_num_rows($query);
while($row=mysql_fetch_array($query)){
if($row['password']===$pw ){
if($uname==="admin"){
$_SESSION['isadmin']=1;
header('location: ./flag.php');
exit;
}
else {
$_SESSION['isguest']=1;
header('location:./guest.php');
exit;
}
}
}
echo "<script type='text/javascript'>alert('用户名或者密码错误!');history.back();</script>";
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="login.php" method="post">
用户名: <input type="text" name="username">
密码: <input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>

register.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
header("Content-type: text/html; charset=utf-8");
try{
$conn = new PDO( "mysql:host=localhost;dbname=zz_test","root", "toor");
}catch(PDDException $e){
echo"数据库连接错误";
}
if(isset($_POST['username']) && isset($_POST['password'])){
$uname=$_POST['username'];
$pw=$_POST['password'];
if($uname==="admin"){
echo "<script type='text/javascript'>alert('用户已存在');location.href='index.html';</script>";
exit;
}
$sql="insert into strict_test(username,password) values(?,?)";
$stmt=$conn->prepare($sql);
$data=array(0=>$uname,1=>$pw );
$stmt->execute($data);
echo "<script type='text/javascript'>alert('注册成功');location.href='index.html';</script>";
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="register.php" method="post">
用户名: <input type="text" name="username">
密码: <input type="password" name="password">
<input type="submit" value="注册">
</form>
</body>
</html>

flag.php:

1
2
3
4
5
6
7
8
<?php
session_start();
if($_SESSION['isadmin']===1){
echo "here is your flag:ZJGSU{zz_is_handsome!}";
}
else
echo "you are not admin!";
?>

guest.php:

1
2
3
4
5
6
7
8
<?php
session_start();
if($_SESSION['isguest']===1){
echo "this is no flag!Only admin can get the flag!";
}
else echo "who are you?";
?>

test.sql:

1
2
3
4
5
create database zz_test;
use zz_test;
create table strict_test(id int not null auto_increment,username varchar(10),password varchar(10),primary key(id));
insert into strict_test(username,password) values('admin','asdf');
set @@sql_mode='NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

可以看到如果想要拿到flag,必须通过login.php里的身份验证,但是我添加了addslashes()函数来转义单引号,而且数据库和页面编码方式都为utf-8,也不能用宽字节注入来做。同时在注册的时候,我也加上了addslashes()函数,导致二次注入也很艰难。
但是可以使用以上的攻击方法来修改掉admin的密码。


PS:
但是在本地用phpstudy测试的时候发现一个很有意思的东西,当我开启STRICT_ALL_TABLES的时候,即如下:

1
set @@sql_mode='STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

此时
到这里都是正常的,预料之中的东西。

但是呢,接下来神奇的事情发生了。
这是原表:

然后我通过一个php中的mysql_connect来插入同样的数据

1
2
3
4
5
6
7
if(isset($_POST['username']) && isset($_POST['password'])){
$uname=addslashes($_POST['username']);
$pw=addslashes($_POST['password']);
echo strlen($uname).'<br/>';
$sql="insert into strict_test(username,password) values('$uname','$pw')";
echo $sql;
$result=mysql_query($sql) or die( mysql_error() );


并没有报错,同时插入也成功了,而且实现了截断功能:

一脸懵逼,跪求希望有知道的大佬教教我。。(垃圾mysql_connect,我还是用PDO吧)(PDO也不行)

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
,