尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

SQL注入攻防实战:从原理到防御,开发者必学

发布时间:2026/9/25 10:16:27

资讯中心
01
ARTICLE

SQL注入攻防实战:从原理到防御,开发者必学

SQL注入攻防实战:从原理到防御,开发者必学
1. 为什么我建议每个开发者都认真学一遍 SQL 注入攻防做后端开发和数据库运维这些年我见过太多“跑得起来就行”的项目。很多团队对数据库安全的理解停留在“装个防火墙”“数据库有密码”这一层。可实际上SQL 注入作为最经典、最古老的 Web 攻击手法之一直到今天依然活跃在各类漏洞报告里。前阵子我帮朋友排查一个内部管理系统登录接口居然能用一个万能密码直接绕过去——问题不是出在数据库密码强度上而是出在开发者拼接 SQL 字符串的习惯上。这篇内容我打算把 SQL 注入这件事从头到尾拆开讲它到底是什么、为什么能成功、攻击者通常会怎么探测、我们在写代码和配数据库时又该怎么堵。整个过程会结合我在真实项目里做过的安全加固和靶场练习经验给出可以直接上手的思路和命令。不管你是刚接触后端开发的初学者还是已经带了几年项目的负责人只要你的系统里存在数据库交互这篇文章都值得花二十分钟读完。我要强调一句所有攻击演示都必须在你自己搭建的靶场环境或授权测试系统中进行。拿未授权的系统练手既不道德也违法。安全工作者的核心能力不是“能打进去”而是“知道怎么被打进去然后把它修好”。2. SQL 注入的本质把输入变成代码2.1 为什么拼接字符串会出事先说个生活化的类比。你把一串钥匙交给门卫说“帮我开一下 302 房间”。如果门卫是个死脑筋他只会用钥匙开 302。但如果门卫会“理解”钥匙上的刻字那你在钥匙上刻一句“顺便把 301 也开了”他可能就真的照做了——因为你的输入不光是数据还被当成了指令。SQL 注入就是这个原理。正常的业务逻辑里用户输入应该只是“数据”比如用户名、密码、订单号。但在拼接 SQL 的实现方式里输入被直接塞进了 SQL 语句的文本中数据库在执行时无法区分哪部分是程序员写的指令哪部分是用户输入的“数据”。当输入里含有 SQL 语法关键字时数据库就会把它当成指令去执行。-- 危险写法直接把用户输入拼进 SQL SELECT * FROM users WHERE username admin AND password 123456如果用户在密码框输入 OR 11拼接后的语句变成SELECT * FROM users WHERE username admin AND password OR 11这条语句的条件变成了“用户名是 admin 且密码为空或者 11”而 11 永远成立所以只要表里有 admin 这个用户查询就能返回结果。这就是网上常说的万能密码原理一点都不玄乎就是利用了你拼接 SQL 的这个动作。2.2 注入点的常见分类从业余到专业你得先建立对注入类型的全局认识。我习惯按两个维度分类一个是注入点位置也就是参数出现在 SQL 语句的哪个部分另一个是获取数据的方式直接看结果还是靠盲猜。按位置分常见的有三种第一种是数字型注入。参数直接拼在 SQL 的数字位置比如WHERE id 1攻击者输入1 AND 11就能改变查询逻辑。这类注入点的特征是后端通常不做类型转换直接把参数当字符串拼接。第二种是字符型注入。参数被单引号包裹比如WHERE username admin。攻击者需要先闭合前面的引号再构造自己的逻辑原理就是我们上面演示的万能密码。第三种是搜索型注入。部分系统会把用户输入直接放进 LIKE 子句比如WHERE title LIKE %关键词%。注入时要用%配合闭合引号比如输入% OR 11 --这类注入点经常被人忽视因为它藏在模糊搜索功能里看起来人畜无害。按获取数据方式分则有联合查询注入、报错注入、布尔盲注、时间盲注和堆叠注入。这个分类直接对应攻击的“难度等级”也决定你后续怎么防御和检测。2.3 攻击者是怎么判断“这里能注入”的在靶场里练习时我建议把攻击者的思路拆成四步探测、确认、利用、提权或脱库。每一步都有固定的套路搞清楚这套套路你写代码时才能站在攻击者角度反推哪里可能出问题。探测阶段攻击者会在 URL 参数、表单字段、请求头User-Agent、Referer 等里加入单引号、双引号、括号、注释符--、#等特殊字符观察页面响应是否出现数据库报错、页面内容变化或响应时间波动。一个训练有素的攻击者不看报错信息光看页面返回长度和状态码就能猜测后端是否存在拼接。确认阶段是在探测出异常后通过构造恒真和恒假条件来验证注入点。比如在参数后加 AND 11和 AND 12如果两次返回结果不同说明条件语句生效了这个点十有八九可以注入。老手还会用sleep(5)或benchmark(10000000, sha1(test))这类函数验证时间盲注。利用阶段就是实际获取数据了。能直接看到查询结果的用UNION SELECT拼列数、找显示位看不到结果的要么用报错函数如extractvalue、updatexml把数据带回报错信息里要么通过逐字符比较的方式做布尔盲注。整个流程就像开锁先试探锁芯能不能转再判断是几排弹子最后用对应的工具开。3. 核心攻防技术拆解从绕过到权限控制3.1 联合查询注入的完整链路联合查询UNION是我个人认为最适合新手入门理解 SQL 注入原理的技法因为它直观——你直接把查询结果“并”到原查询后面然后原样显示出来。第一件事是确定原查询返回多少列。这个可以在参数后加ORDER BY n不断调整 n 的值直到报错为止。比如ORDER BY 1正常ORDER BY 2正常到ORDER BY 5报错说明原查询只有 4 列。原理很简单ORDER BY 是按列序号排序列序号超出了实际列数数据库就会报错。确定列数后第二件事是找“显示位”。用UNION SELECT 1,2,3,4把数字填进所有列观察页面上哪个位置出现了 1、2、3、4。出现的位置就是你的数据回显区后续查库里的数据就往那个位置塞。第三步是利用 information_schema 元数据库查表名和字段名。这一步是很多新手的知识盲区——以为注入只能碰业务表其实数据库自带的 information_schema 库里存放着所有表的元信息。在 MySQL 里可以这样查-- 查所有数据库名 UNION SELECT schema_name,2,3,4 FROM information_schema.schemata -- 查当前库的所有表名 UNION SELECT table_name,2,3,4 FROM information_schema.tables WHERE table_schemadatabase() -- 查某张表的所有字段名 UNION SELECT column_name,2,3,4 FROM information_schema.columns WHERE table_nameusers拿到表名和字段名之后最后一步就是直接读数据。整个过程在 DVWA 这类靶场里跑一遍你会对 SQL 注入的“手感”有非常直观的认识。3.2 盲注看不到结果怎么拿数据真实的黑盒测试里你往往看不到任何查询结果页面只告诉你“查询成功”或“查询失败”。这时候就要上盲注。盲注的本质是把 SQL 查询变成一个“是非判断题”逐个字符猜出答案。布尔盲注的核心是构造真/假条件观察页面差异。比如猜数据库名的第一个字符是不是a可以构造 AND SUBSTRING(database(),1,1)a --如果页面显示正常条件为真说明猜对了显示异常条件为假说明猜错了。一个字符从a猜到z再加上数字和特殊符号最多几十次就能确定一位。配合SUBSTRING和LIMIT的偏移就能逐步把整个库的数据“抠”出来。手动做这件事非常痛苦所以大家都用 Burp Suite 跑 Intruder 或者写 Python 脚本自动遍历。时间盲注则是利用数据库的延时函数做判断。常见语法 AND IF(SUBSTRING(database(),1,1)a, SLEEP(5), 0) --如果页面在 5 秒后才返回说明第一位就是a。这种方式不依赖页面内容差异只依赖响应时间所以它对 WAF 的绕过能力更强但也更容易被日志检测到——大量延时请求本身就是明显的攻击特征。3.3 绕过与防御的攻防对抗很多初学者以为防住了单引号就万事大吉实际上绕过手段五花八门。最常见的是编码绕过把关键字符做 URL 编码变成%27、双重编码%2527或者用 Unicode 编码变体。后端如果只做了一次解码就会出现“解码后拼接进 SQL”的空子。大小写混写也经常用来绕过简单的关键字过滤SeLeCt、UnIoN SeLeCt。还有内联注释绕过MySQL 支持/*!50000SELECT*/这种特殊注释写法里面可以嵌套关键字很多正则过滤器识别不了。但上面这些在参数化查询面前都失效了。原因很简单参数化查询PreparedStatement把 SQL 语句结构和数据分成了两个通道传输。结构先编译好数据作为参数传入数据库根本不会把参数里的内容当成 SQL 关键字去解析。这就是为什么大家都说参数化是治本的方法。WAFWeb 应用防火墙则是治标的手段。它能拦截常见的攻击 payload但攻击者可以通过分块传输、垃圾字符填充、换行拆分等方法绕过。我在项目里见过不少团队上了 WAF 就觉得高枕无忧结果攻击者换了个姿势就绕进去了。WAF 只能作为第一道防线不能替代代码层的修复。3.4 数据库权限控制的实战经验还有一类常被忽视的问题是数据库账号权限过大。很多项目为了方便用 root 账号连接业务库或者给应用账号开了FILE、SUPER、GRANT权限。SQL 注入一旦落在这类账号上攻击者就有机会干更多事读服务器文件LOAD_FILE、写 webshellINTO OUTFILE、新建管理员账号。我的建议是严格遵循最小权限原则。业务应用连数据库只给增删改查的权限按需分配能用 SELECT 就不用 INSERT能不用 DELETE 就不用 DELETE。如果确实有批量导入需求单独建一个导入专用账号用完关掉。MySQL 里可以这样创建权限受限的账号CREATE USER app_select10.0.0.% IDENTIFIED BY StrongPass_2024; GRANT SELECT ON mydb.* TO app_select10.0.0.%;这样就算注入点被利用了攻击者最多只能读数据写不了、删不了、也拖不走系统文件。我在实际加固中见过太多案例数据库账号权限收窄一步风险面就能砍掉一大半。4. 靶场实战从 SQLilabs 到 Pikachu 的完整链路4.1 怎么选靶场和搭环境学习 SQL 注入光看书不实操等于白学。我推荐三个合适的靶场SQLilabs、DVWA 和 Pikachu。SQLilabs 专注于 SQL 注入从基础到进阶一共有几十关每一关对应一种注入类型或绕过手法DVWA 是一个综合漏洞靶场里面带 SQL 注入模块且有三种安全等级切换适合理解“防御方式不同攻击难度就不同”这个概念Pikachu 是国产靶场自带中文界面的漏洞案例对新手更友好。环境搭建我建议直接用 Docker。拉镜像跑容器几分钟就能把靶场起起来用完直接销毁干净利落docker run -d --name dvwa -p 8080:80 vulnerables/web-dvwa docker run -d --name sqli-labs -p 8081:80 acgpiano/sqli-labs docker run -d --name pikachu -p 8082:80 area39/pikachu注意靶场容器默认可能有弱口令比如 DVWA 的默认账号是 admin/password搭建完成后第一时间把数据库密码改成强密码毕竟靶场是在公网上暴露着的别自己把自己变成肉鸡。4.2 一次完整的注入测试过程记录我拿 SQLilabs 的 Less-1 举例带你走一遍完整的测试流程。这一关是经典的字符型注入。启动容器后打开http://your-server:8081/Less-1/?id1页面显示正常用户信息。第一步探测在 id 参数后加单引号http://your-server:8081/Less-1/?id1页面报出 SQL 语法错误仔细看报错信息会露出1 LIMIT 0,1这类语句片段这就确认了注入点存在且是字符型。第二步确认列数http://your-server:8081/Less-1/?id1 ORDER BY 3 -- http://your-server:8081/Less-1/?id1 ORDER BY 4 --第一句正常第二句报错说明原查询返回 3 列。第三步确定回显位置http://your-server:8081/Less-1/?id1 UNION SELECT 1,2,3 --页面里出现了 2 和 3 的位置说明这两个位置可以回显数据。第四步查表名http://your-server:8081/Less-1/?id1 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schemadatabase() --把目标库的所有表名一次性列出来后面查字段名、查数据就是用同样的套路替换查询语句。整个过程打下来你会发现所谓“攻破”并不是多高深的技巧就是按部就班地探测、构造、验证。真正的难点在于你能否在代码审计阶段就意识到“这个参数可能被这样利用”而这个敏感度只能靠大量练习培养。4.3 我常用的测试工具和手法命令行的 curl 是快速验证的首选。很多新手习惯直接开浏览器改 URL 参数但 curl 能精确控制请求头、Cookie 和编码方式尤其是遇到特殊字符被浏览器自动转码的场景curl 用--data-urlencode可以手动控制编码。测 POST 型注入时我常用-d参数提交表单数据curl -X POST http://target/login.php -d usernameadminpassword1Burp Suite 是抓包改包的主力工具。抓下请求发到 Repeater 里手工改参数看响应再发到 Intruder 跑字典遍历。sqlmap 则是自动化测试的利器它支持布尔盲注、时间盲注、联合查询、报错注入等多种方式自动切换一条命令就能扫出一个注入点并拖数据sqlmap -u http://target/Less-1/?id1 --batch --dbs但我必须提醒你工具是放大器不是思考器。sqlmap 确实很强但如果连手工注入的原理都不懂拿到 sqlmap 的输出也看不懂什么意思更别说判断哪些数据是真的、哪些是误报。我的建议是先手工把前面章节里的联合查询、盲注全部练熟再用 sqlmap 验证自己的判断最后再用它去处理复杂的绕过场景。4.4 加固之后再做一遍同样的测试防御不是改完代码就结束了。我在项目里一贯的做法是“先打一遍修复再打一遍”。第一轮测试记录所有能注入的点修复完成后用同样的 payload 重新测试确认修复有效。代码层的修复核心是参数化查询。拿 PHP 的 PDO 举例$stmt $pdo-prepare(SELECT * FROM users WHERE username ? AND password ?); $stmt-execute([$_POST[username], $_POST[password]]);换成参数化之后即使用户输入 OR 11它也只是被当成一个字符串值去比较而不是被解析成 SQL 条件。这里有一个新手容易踩的坑以为用 PDO 就自动安全了其实如果代码里还在用$pdo-query(SELECT ... WHERE id$_GET[id])PDO 也救不了你。参数化必须配合占位符才算数。数据库层面的加固包括去掉不必要的权限特别是 FILE、SUPER、关闭数据库的远程访问只监听 127.0.0.1 或内网地址、修改默认端口、开启审计日志。再往上加一层防护用 WAF 默认规则拦截常见注入特征。三层都做到位系统被利用的概率就会大幅下降。5. 常见问题与排查技巧实录5.1 靶场测试中的高频问题和处理方法我只挑实操中最高频的几个问题来讲每一个我都踩过。第一个是“加了单引号页面没反应”。这种情况多半是被 WAF 拦了或者参数根本就没进 SQL。先看请求响应头里有没有 WAF 特征字段再看后端日志里有没有拦截记录。如果确实没进 SQL检查下代码里是不是只在某个分支才拼接这个参数。第二个是“UNION 注入明明列数对但页面不显示数据”。常见原因是原查询结果集不为空UNION 后面的数据被挤到页面下方肉眼没看到。解法是在原查询后面加一个必然为假的条件比如id-1让原结果集为空UNION 的数据就自然顶到显示位上了。第三个是“盲注脚本跑得太慢”。布尔盲注一次只能猜一个字符如果脚本逻辑写得不好或者网络延迟高跑完一个库名可能要几个小时。我的建议是优先用二分法而不是逐字符顺序遍历先判断字符的 ASCII 值是大于还是小于中间值这样每个位最多跑 7 次请求就能确定速度提升非常明显。第四个是“报错注入时函数被过滤”。extractvalue、updatexml这类函数如果被 WAF 过滤了可以试试floor(rand(0)*2)配合GROUP BY构造主键冲突报错或者用GTID_SUBSET、NAME_CONST这类冷门函数。不过说老实话如果场景里能上时间盲注我不建议死磕报错注入——能用 SLEEP 就不必硬刚过滤器。5.2 判断系统是否被打过的排查清单安全建设到一定阶段大家都会关心自己的系统有没有被别人打过。虽然 SQL 注入发生在应用层但数据库日志里通常会留下痕迹。我整理的排查清单如下检查数据库慢查询日志有没有大量的SLEEP、BENCHMARK、IF函数调用检查应用访问日志里有没有单引号、UNION SELECT、information_schema等特征字符串检查数据库账号是否有异常的登录来源 IP检查业务表中是否出现不该有的管理员账号检查服务器上有没有新增的、非业务线的文件比如 webshell核对数据库账号权限是否被修改过有没有新增账号如果你用的是 MySQL慢查询日志里出现大量带SLEEP(5)的语句几乎可以断定被时间盲注搞过了。这类请求的特征非常明显语句简单、耗时固定、频率高。发现之后第一件事不是删日志而是先隔离服务器、保留现场再通过应用日志回溯攻击者的完整请求链路。这里再插一个我踩过的坑把数据库日志和应用日志放在同一台机器上且没做日志轮转。结果攻击者在短时间内注入了一两万条请求日志文件直接撑爆磁盘等你发现问题的时候磁盘满了数据库写不进去整个应用挂掉。后来我把日志单独挂到一块大磁盘上配上logrotate每天切割、保留 30 天才彻底解决这个问题。5.3 一个容易漏掉的注入点搜索框与排序字段不少人以为把登录接口的参数化做好就安全了实际上搜索框和排序字段是最容易漏掉的注入点。搜索功能会把输入放进LIKE子句排序字段则会拼进ORDER BY后。ORDER BY后面是不能用预编译占位符绑定列名的所以很多开发者会直接拼接字符串结果就埋了雷。对于这类场景我的方案是“白名单映射”。后端维护一个允许排序的字段列表前端传sortname时后端映射为ORDER BY name前端传的字段名不在白名单里直接拒绝。搜索框的LIKE内容即使做不了参数化至少也要转义掉%和_这两个通配符避免注入的同时也能避免用户误用通配符拖慢查询。如果你在代码审计时看到ORDER BY、GROUP BY、LIMIT这几个子句后面拼接了变量那就要打起十二分精神。这几个位置是预编译没法直接覆盖的“死角”必须靠白名单、强校验或函数化处理来兜底。最后再分享一点我在实际项目里的习惯每次上线前把接口文档和数据库账号权限表过一遍拿 sqlmap 对测试环境跑一轮自动化扫描再用 Burp 对登录、搜索、排序、导出这几个高危功能做手工测试。这套流程看着简单但坚持做下来比买再贵的 WAF 都管用。安全不是一次性的项目而是日常开发里不断重复的那个动作。如果你正准备搭建自己的靶场环境我强烈建议先把 Docker 环境装好把 SQLilabs 和 Pikachu 拉起来然后按照我说的流程把联合查询、布尔盲注、时间盲注每个至少练五遍。练到不用看笔记就能熟练构造 payload 的时候再去看真实项目里的代码你会发现那些所谓的 SQL 注入漏洞大部分都藏在最不起眼的查询语句里。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。