2

SQL截断攻击

 2 years ago
source link: https://err0rzz.github.io/2018/02/01/SQL%E6%88%AA%E6%96%AD%E6%94%BB%E5%87%BB/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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模式:

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

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

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选项时:

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

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

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:

<!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:

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

<?php
header("Content-type: text/html; charset=utf-8");
$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:

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

guest.php:

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

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的时候,即如下:

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

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

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK