我们知道在前端不回显查询结果时,可以尝试使用报错注入来将目标数据作为错误提示的一部分显示出来。
常见的报错注入有:利用
updatexml()
、extractvalue()
和"floor报错"
,其中"floor报错"
报错的原因相对比较复杂,本文将用图文和实机操作过程来分析它的原理。
“ floor报错注入 ”的原理相对复杂的一个原因是在构造这种注入时使用到的sql函数(或sql语法)很多,如
rand()
、floor()
、concat()
、count(*)
和order by
(如果您对它们都比较了解的话,可以直接点此阅读本文的第二部分)
rand()
rand()
函数用于随机生成一个介于0
~1
之间的随机浮点数
可以看到,同样的语句,每一次执行会返回不同的值。(这里添加后面from users
是为了能够多展示几个由rand()
生成的连续随机数)
rand()
中传入一个参数,来得到一个固定的随机数序列。但当向rand()
中传入参数0
,无论执行多少次,都会返回相同的结果,如上图。
floor()
如果我们将rand()
和floor()
结合
如果我们将rand()
乘以2,那么将永远得到0或1
(rand()
得到一个零点几的数,(rand() * 2)
得到一个零点几或一点几的数,经由floor()
后自然得到一个0或1)
concat()
一个用于拼接参数的函数
这里使用concat()
来将users表中的first_name
和last_name
使用-
拼接了起来。
count()
一种聚合函数,用于统计“表”中个项出现的次数,
count(*)
会统计NULL
group by
根据
by
后面的“某种规则”来对数据进行筛选分组
这里的SQL语句从users
表中查询出first_name
列对应所有值,然后以group by
后面的first_name
为"分组依据",并使用count(*)
来对该组中的各个项来计数,最终展现为如上图的查询结果。
使用
group by
和count(*)
来得到表的过程,不是" 一蹴而就 "的。
如下图,每接收到一个项,数据库都会判定虚拟表中是否有何该项值相等的组键(group_key),若没有则会重新"调用"一次该项并以调用结果创建一个新的组键,若已有则给该组键对应行的count(*)
列加一。
到目前为止,好像还是没什么问题啊?
是的,如果写入虚拟表中的键名是“死的,不变化的普通字符串”,那么没有任何问题,如上述的
users
表中的first_name
但如果这个项是“活的、变化的”呢?
前面提到给rand()
中输入参数会返回一个固定的连续随机数序列,那么floor(rand(0) * 2)
将固定返回" 0、1、1、0,1…"
那么concat(floor(rand(0)*2),0x7c,(select version()))
就将返回"0|5.7.26"、“1|5.7.26”、“1|5.7.26”、“1|5.7.26”,“1|5.7.26”… (0x7c代表了 | 字符)
如果在补全虚拟表的时候,每一次调用我们的构造项(
concat(floor(rand(0)*2),0x7c,(select version()))
),它都会“向下“变化。我们假设虚拟表中有且只有"1|5.7.26",这时刚好读取到的项是"0|5.7.26",因为虚拟表中没有组键"0|5.7.26",那么会重新调用该项并以调用结果创建新组键(但因为被重新调用,该项“向下”变化,变为了"1|5.7.26",所以虚拟表是为为"1|5.7.26"创建了新组键,而此时就会报错——试图创建重复(已存在)的组键"1|5.7.26")。
我们可以构造"payload"来验证我们所想:
select count(*), concat(floor(rand(0)*2),0x7c,(select version()))as alia from users group by alia;
(这里的as alia
是为我们构造的长长的表达式concat(floor(rand(0)*2),0x7c,(select version()))
起了别名alia
)
可以看到数据库的版本信息被连带进了报错信息。
具体的报错流程如下:
以下有一个选项也可以达到floor(rand(0) * 2)
一样的效果
您可以通过将它们的返回前几位固定数像上面一样带入“虚拟表”来判断是哪一个?
以此检测一下自己是否真的理解了floor报错注入相关的原理。
floor(rand(1) * 2) | floor(rand(2) * 2) | floor(rand(3) * 2) | floor(rand(4) * 2) | floor(rand(5) * 2) |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 1 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
# 答案是...
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# .
# floor(rand(4) * 2),可以验证得知其余的构造均不可行(都会正常地创建拼接了0和1的对应组键)