从防御者视角拆解:基于floor()报错注入的攻击链溯源与WAF规则设计实战

当安全团队在凌晨三点收到数据库异常告警时,那条带着floor(rand(0)*2)的畸形SQL语句正在悄悄撕开系统防线。不同于传统渗透测试手册里泛泛而谈的原理说明,本文将带您亲历防御者工作台,用三组关键日志片段还原攻击者完整的横向移动路径,并给出可直接部署的WAF规则集与代码层修复方案。

1. 攻击链全貌还原:从单次请求到完整渗透

某电商平台的审计日志显示,攻击者通过商品详情API发起试探性攻击。以下是我们从Nginx日志中提取的关键字段:

GET /product?id=1%20AND%20(SELECT%201%20FROM%20(SELECT%20COUNT(*),CONCAT(0x23,(SELECT%20SCHEMA_NAME%20FROM%20INFORMATION_SCHEMA.SCHEMATA%20LIMIT%200,1),0x23,FLOOR(RAND(0)*2))%20AS%20x%20FROM%20INFORMATION_SCHEMA.COLUMNS%20GROUP%20BY%20x)%20AS%20y) HTTP/1.1

这个经过URL解码的payload暴露出典型攻击特征:

AND (SELECT 1 FROM (
  SELECT COUNT(*),
    CONCAT(0x23,
      (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1),
      0x23,
      FLOOR(RAND(0)*2)
    ) AS x 
  FROM INFORMATION_SCHEMA.COLUMNS 
  GROUP BY x
) AS y)

攻击阶段拆解

  1. 信息收集阶段
    攻击者首先确认注入点有效性,通过floor()报错获取当前数据库版本:

    Duplicate entry '#mysql5.7#1' for key 'group_key'
    
  2. 数据库枚举阶段
    修改LIMIT参数逐步获取所有库名:

    (SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 1,1)
    
  3. 表结构探测阶段
    锁定目标库后提取表名:

    (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES 
     WHERE TABLE_SCHEMA=database() LIMIT 0,1)
    

防御要点:此类攻击往往在2-5次请求内完成数据库测绘,WAF需在首次异常请求时阻断会话而非单次请求。

2. 深度技术解析:为什么floor()会产生致命漏洞

理解攻击原理是设计防御的基础。我们通过数据库引擎视角还原报错发生过程:

2.1 关键函数相互作用机制

函数作用攻击利用点
RAND(0)产生确定性伪随机序列 (0.1,0.9,...)种子0确保序列可预测
FLOOR(x)向下取整将随机数转换为稳定的0/1值
GROUP BY创建临时哈希表临时表主键冲突触发报错

2.2 报错触发详细时序

以包含3条记录的users表为例:

-- 攻击语句
SELECT COUNT(*), CONCAT(user(), FLOOR(RAND(0)*2)) AS x 
FROM users GROUP BY x;

执行流程分解:

  1. 首次计算FLOOR(RAND(0)*2)得到0
  2. 临时表不存在0键,触发第二次计算得到1
  3. 插入键值1到临时表
  4. 处理第二条记录时第三次计算得到1
  5. 处理第三条记录时第四次计算得到0
  6. 尝试插入0键时第五次计算得到1 → 主键冲突
# Python模拟计算过程
import random
random.seed(0)

def floor_rand():
    return int(random.random() * 2)

calculations = [
    floor_rand(),  # 第一次: 0
    floor_rand(),  # 第二次: 1 (插入)
    floor_rand(),  # 第三次: 1 (存在)
    floor_rand(),  # 第四次: 0 
    floor_rand()   # 第五次: 1 (冲突)
]

3. 立体防御方案:从WAF到代码层的协同防护

3.1 WAF规则设计实战

基于ModSecurity的核心规则:

SecRule REQUEST_URI|REQUEST_BODY 
    "@rx (?i)(?:floor\s*\(\s*rand\s*\([^)]*\)\s*\*\s*2\s*\)|group\s+by\s+[\w\(\)]+having)" 
    "id:10001,phase:2,deny,msg:'SQLi: floor() error-based injection'"

规则优化要点

  • 匹配floor(rand(*2)的多种空格变形
  • 捕获group by x having变种
  • 忽略大小写防止绕过

3.2 代码层修复方案

Java MyBatis修复示例:
// 原始漏洞代码
@Select("SELECT * FROM products WHERE id = #{id}")
Product findById(String id);

// 修复方案:强制参数化
@Select("SELECT * FROM products WHERE id = #{id,jdbcType=INTEGER}")
Product findById(@Param("id") Integer id);
PHP PDO最佳实践:
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = :id");
$stmt->execute([':id' => $_GET['id']]); // 自动类型转换

3.3 数据库审计策略

配置MySQL审计日志监控异常模式:

-- 监控information_schema异常访问
CREATE AUDIT POLICY sql_injection_policy 
  ACTIONS SELECT ON SCHEMA::information_schema;

关键监控指标:

  • 单会话短时间内多次查询information_schema
  • 异常函数调用链(concat+floor+rand
  • 来自同一IP的渐进式LIMIT查询

4. 防御体系压力测试:绕过手法与应对策略

攻击者常用绕过技术及防御方案:

绕过手法检测特征防御规则增强
注释符分割floor/**/(rand/**/(0)*2)规范化处理后检测
十六进制编码0x666C6F6F72代替floor解码后规则匹配
函数别名select count(*),a as x from t检测临时表创建行为
非常量种子rand(user()*0)检测非数字种子

实战压力测试用例:

GET /product?id=1%20AND%201=(SELECT%20MIN(@a)%20FROM%20(SELECT%20@a:=@a%2b1%20FROM%20mysql.user%20JOIN%20(SELECT%20@a:=0)%20b%20WHERE%20@a%3C10)%20c) 

对应防御规则升级:

SecRule REQUEST_URI "@rx @[a-z_]+:=" 
    "id:10002,phase:2,deny,msg:'SQLi: Variable assignment detected'"

在安全组策略部署后的三个月内,该电商平台成功拦截了217次同类攻击尝试,其中184次在首次探测阶段即被阻断。最有效的防御策略是组合使用语义分析(检测函数调用链)和行为分析(识别渐进式信息收集模式),而非依赖单一特征匹配。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐