文章目录

  • SQL注入:floor报错注入的形成原理分析
  • 一、涉及的函数(或语法)
  • 1.`rand()`
  • 2.`floor()`
  • 3.`concat()`
  • 4.`count()`
  • 5.`group by`
  • 二、报错原理
  • 1.原理分析
  • 2.实例验证与分析
  • 3.一个小挑战


SQL注入:floor报错注入的形成原理分析

我们知道在前端不回显查询结果时,可以尝试使用报错注入来将目标数据作为错误提示的一部分显示出来。

常见的报错注入有:利用updatexml()extractvalue()"floor报错",其中"floor报错"报错的原因相对比较复杂,本文将用图文和实机操作过程来分析它的原理。

一、涉及的函数(或语法)

“ floor报错注入 ”的原理相对复杂的一个原因是在构造这种注入时使用到的sql函数(或sql语法)很多,如rand()floor()concat()count(*)order by

(如果您对它们都比较了解的话,可以直接点此阅读本文的第二部分)

1.rand()

rand()函数用于随机生成一个介于0~1之间的随机浮点数

POSTGRESQL报错注入 polygon sql注入floor报错_web安全

POSTGRESQL报错注入 polygon sql注入floor报错_网络安全_02

可以看到,同样的语句,每一次执行会返回不同的值。(这里添加后面from users是为了能够多展示几个由rand()生成的连续随机数)

  • 可以向rand()中传入一个参数,来得到一个固定的随机数序列。

POSTGRESQL报错注入 polygon sql注入floor报错_web安全_03

但当向rand()中传入参数0,无论执行多少次,都会返回相同的结果,如上图。

2.floor()

floor意为“地板”,和它的字面意思一样,它可以实现对输入参数的向下取整(去除小数部分)

POSTGRESQL报错注入 polygon sql注入floor报错_web安全_04

  • 如果我们将rand()floor()结合
    那么将永远得到0
  • POSTGRESQL报错注入 polygon sql注入floor报错_web安全_05

  • 如果我们将rand()乘以2,那么将永远得到0或1
    rand()得到一个零点几的数,(rand() * 2)得到一个零点几或一点几的数,经由floor()后自然得到一个0或1)
  • POSTGRESQL报错注入 polygon sql注入floor报错_web安全_06

3.concat()

一个用于拼接参数的函数

POSTGRESQL报错注入 polygon sql注入floor报错_sql_07

这里使用concat()来将users表中的first_namelast_name使用-拼接了起来。

4.count()

一种聚合函数,用于统计“表”中个项出现的次数,count(*)会统计NULL

5.group by

根据by后面的“某种规则”来对数据进行筛选分组

  • group by常常配合聚合函数来使用,比如count()
  • POSTGRESQL报错注入 polygon sql注入floor报错_安全_08


这里的SQL语句从users表中查询出first_name列对应所有值,然后以group by 后面的first_name为"分组依据",并使用count(*)来对该组中的各个项来计数,最终展现为如上图的查询结果。



二、报错原理

1.原理分析

使用group bycount(*)来得到表的过程,不是" 一蹴而就 "的

而是先创建一个虚拟表,将查询到的各项一个个地作统计

POSTGRESQL报错注入 polygon sql注入floor报错_网络安全_09

如下图,每接收到一个项,数据库都会判定虚拟表中是否有何该项值相等的组键(group_key),若没有则会重新"调用"一次该项并以调用结果创建一个新的组键,若已有则给该组键对应行的count(*)列加一。

POSTGRESQL报错注入 polygon sql注入floor报错_sql_10

到目前为止,好像还是没什么问题啊?

是的,如果写入虚拟表中的键名是“死的,不变化的普通字符串”,那么没有任何问题,如上述的users表中的first_name

但如果这个项是“活的、变化的”呢?

前面提到给rand()中输入参数会返回一个固定的连续随机数序列,那么floor(rand(0) * 2)将固定返回" 0、1、1、0,1…"

POSTGRESQL报错注入 polygon sql注入floor报错_网络安全_11

那么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代表了 | 字符)

POSTGRESQL报错注入 polygon sql注入floor报错_sql_12

如果在补全虚拟表的时候,每一次调用我们的构造项(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")。

2.实例验证与分析

我们可以构造"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

POSTGRESQL报错注入 polygon sql注入floor报错_web安全_13

可以看到数据库的版本信息被连带进了报错信息。
具体的报错流程如下:

POSTGRESQL报错注入 polygon sql注入floor报错_数据库_14

3.一个小挑战

以下有一个选项也可以达到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的对应组键)