目录
1. extractvalue报错注入
2. updatexml报错注入
3. floor报错注入
当网站的页面上没有显示位用于展示SQL语句执行后的结果,但是sql语句执行可以输出错误信息,那么攻击者可以利用注入过程中返回的错误信息进行判断。
报错注入就是客户端可以构造SQL语句,让错误信息中可以显示数据库内容的查询语句,当数据库执行SQL语句返回报错提示中包含数据库的内容。
来看报错注入的一个简单案例:
在构造的SQL语句中,故意把database()函数写错成datadase()了,返回的错误信息提示是security数据库不存在datadase函数,换句话说,数据库给出的错误信息中直接暴露了查询的数据库信息:security。
报错注入的类型有12种,这里只介绍三种:extractvalue报错注入,updatexml报错注入,floor报错注入。MySQL5.0以上版本中添加了对XML文档进行查询和修改的两个函数:extractvalue和updatexml。
extractvalue(参数1 , 参数2):Mysql数据库中的extractvalue函数是用于对XML文档进行查询的函数。
参数1表示操作的目标XML文档,参数2则表示目标XML的查找路径。
通过extractvalue报错注入,创建数据库ctfstu数据表xml,插入两条数据为例:
#创建数据库表xml
create table xml(doc varchar(150));
#插入数据1
insert into xml values('
how to become a bad boy
howlong
Allen
');
#插入数据2
insert into xml values('
A bad boy how to get a girlfriend
Love
Mike
');
使用extractvalue函数查询数据库表xml的作者,构造的sql语句如下:
select extractvalue(doc,'/book/author/surname')from xml;
SQL语句执行结果:
通过extractvalue函数注入报错我们可以把查询参数格式符号写错,例如select extractvalue(doc , '~book/titleaaa') from xml这样的格式,mysql数据库在执行过程中会直接报错:
0x7e是“~”符号的十六进制表现形式,而concat函数的作用就是将“~”符号与select database()查询的结果进行拼接成字符串,从而使extractvalue函数报错:
从数据库返回的报错信息来看,页面显示了数据库名字。
然后我们再讲上面的SQL语句进行改造,将select database()部分更改成如下:
# 这条SQL语句表示查询当前数据库security下的所有数据库表
select group_concat(table_name) from information_schema.tables where table_schema=database()
SQL语句执行结果如下:
直接爆出了当前数据库下所有的数据库表。
updatexml(XML_document , XPath_string , new_value)
updatexml函数有三个参数,XML_document参数表示目标XML文档(例如doc), XPath_string参数表示路径,new_value替换查找的数据。updatexml函数报错注入的原理和extractvalue函数是相通的。
确定当前网站是字符串注入类型,且闭合方式为")(双引号加括号)的方式:
接下来通过updatexml函数进行报错注入,使用concat函数对查询的目标与0x7e进行拼接,从而导致updatexml报错,那么构造sql语句如下:
?id=1") and 1=updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),3) --+
数据库执行SQL语句返回的结果:
获取用户名和密码,构造sql语句:
?id=1") and 1=updatexml(1,concat(0x7e,(select substring(group_concat(username,':',password),1,30) from users)),3) --+
页面的显示位置每次只能显示32个字符,无法完全显示全部,可以通过substring函数把之后所有的内容显示出来,例如substring(str,1,30)表示从第1个字符显示30个字符。
floor报错注入跟前面两个函数有所不同,floor函数抱错注入的原理涉及到以下几个函数:
rand函数用法:
直接执行select rand()语句发现,数据库每次都会随机返回0 ~ 1之间的小数,但是执行select rand()*2时,数据库则可能会返回大于1的小数,因为*符号在数学里是一个乘号。对于select rand() from users;语句来说,数据库在执行该语句查询数据库时,rand()函数会计算出users表的行数,users表每有一行数据rand()函数就会计算一次,上图中rand()函数计算了13次,说明users表中有13行数据,换句话说,users表中有多少行数据,rand函数就会执行次。
floor函数用法:小数向下取整数
floor函数很好理解,如果rand函数返回的结果是小于1的小数的话,例如select floor(rand())函数返回的小数是0.5694725639833813,那么floor函数则会向下取整数,就变成了0。如果select floor(rand()*2)函数返回的小数是1.7217163776249778,那么floor函数向下取整,得到的结果就是1,这就是floor函数的作用。
concat_ws函数用法:将括号内数据用第一个字段连接起来。
可以看到concat_ws函数内有三个参数,第一个是“-”符号,第二个是select database(),当前数据库是security;第三个参数则是floor(rand()*2),结果为0或1。执行上面的语句后,concat_ws函数会将括号内的数据用第一个参数连接起来,最后返回的结果是security-1或者security-0。
group by子句用法:
前面已经介绍过了,group by子句是用于对结果集进行分组的,as关键字对结果集进行起别名,然后使用group by子句对结果集进行分组,从之前查询的结果来看,无非就是security-0和security-1两种结果。
对于count函数和limit函数,之前的学习中已经介绍过了,这里不再赘述。
使用count函数对结果集进行统计:
count函数统计的结果中security-0出现了8次,security-1出现了5次。
floor报错注入的原理主要有两点:报错语句和报错位置。
示例:
select count(*),concat_ws('-',(select database()),floor(rand(0)*2)) as a from users group by a;
执行SQL报错信息:
前面我们说过SQL语句报错主要有两点一个是报错语句和报错位置,这条SQL语句每次执行时都会报错,报错的原因在于SQL语句中count(*),rand(0),group by这些语句的组合引起的。
单独的去掉组合中的一个,都不会引起报错,真正的原因在于rand函数进行分组group by和统计count()时可能会多次执行,导致键值key重复:
以rand(0)为例,在多次执行过程中会出现键值key重复,当我们在构造SQL语句时使用count(*),rand(0),group by这些语句的组合时,rand(0)和rand(4)会引起数据库的报错,因此rand函数中的参数除了0或4,可以是任意其他数字。
分析SQL语句:select count(*),concat_ws('-',(select database()),floor(rand(0)*2)) as a from users group by a;
分析SQL语句执行过程:
第一次统计时,rand函数进行第一次计算,concat_ws函数得到的结果是security-0,然后count函数对concat_ws的结果进行统计发现,security-0在group_key中的键值不存在,于是concat_ws函数重新计算结果为security-1,然后把该key值存入到group_key中。
第二次以此类推,进行到第三次统计的时候,已经是第四次计算了,concat_ws函数得到的结果是security-0(正常来说,第三次统计,concat_ws函数得到的结果应该是security-1才对),发现security-0在group_key中并不存在,concat_ws函数会进行重新计算,将得到的结果security-1放入到group_key中,但问题在于:security-1的key值在group_key中已经存在,数据库在执行过程中就会报错(ERROR 1062 (23000): Duplicate entry 'security-1' for key 'group_key')。
当users表中的数据不足5条,再次执行SQL语句数据库并不会返回错误。换句话说,rand函数只要执行次数不超过5次,那么数据库在执行过程中就不会报错。
但是在真正的SQL注入环境中,数据库表中的数据有可能会不够,一般情况下会使用数据库默认的information_schema.tables表。
例如查询当前security数据库下的所有表和表的所有字段,构造SQL语句:
#查询当前security数据库下的所有表
select group_concat(table_name) from information_schema.tables where table_schema=database();
#查询users表的所有字段
select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users';
#查询users表的所有数据
select group_concat(username,'@','password') from users;
获取users表的所有字段,测试结果如下: