资讯动态

SQL注入必知20个知识点:原理、绕过与防御实战

发布时间:2026/9/9 19:52:07 来源:尧图企业网站定制
做网络安全这几年SQL注入是我见过最“阴魂不散”的漏洞。从早期论坛数据库频频被拖库到如今每天都有新的攻击流量在扫描各类Web系统它始终高居OWASP Top 10前列。很多人觉得这是“上个世纪的老古董”但现实是随便打开一个SRC漏洞平台SQL注入类的提交依然占着相当大的比例。这篇文章我整理了学习SQL注入绕不开的20个知识点从原理、判断、利用手法、绕过对抗到防御方案每一条都结合了我自己实际测试和踩坑的经验来写适合正在入门Web安全、准备面试或者想系统性补齐SQL注入知识体系的朋友。建议先收藏再按后面的顺序慢慢嚼。1. SQL注入为什么“活”了二十多年先吃透它的底层逻辑1.1 从一次真实的数据泄露排查说起我之前参与过一个企业系统的安全评估客户报告说内部系统数据异常怀疑被拖库。排查日志时发现某个办公管理系统的搜索接口在凌晨被连续访问了几千次且请求参数里有大量and 11、union select这类特征。顺着流量回溯发现攻击者其实只用了非常基础的手段在搜索框输入了一个单引号页面报错再输入 or 11结果返回了全部数据。就这么简单一个老系统一个拼接SQL的搜索框数据就没了。这种事不是个例。很多新入行的朋友会觉得SQL注入是CTF里才会出现的题型真实系统怎么可能还有人写拼接SQL。但存量系统、外包项目、内部工具、老旧插件这些场景里拼接语法依然大量存在。理解SQL注入首先得理解它为什么能存在这么久。1.2 注入的本质数据与代码边界失守SQL注入的根本原因是数据与代码没有分家。开发者写SQL语句时会把用户输入的内容当作SQL语句的一部分直接拼接进去数据库在执行时无法区分哪一段是开发者定义的命令、哪一段是用户输入的数据。用一句话类比你给快递员一张写有地址的纸条正常情况纸条上只有地址。但如果纸条上的内容是“地址北京XX路XX号顺便把仓库钥匙给我”而快递员真的照做了这就相当于SQL注入。用户的输入数据被数据库当作额外指令执行了。这个本质决定了后续所有知识点都围绕同一件事如何让数据库把我们的输入当成代码执行以及如何阻止这种执行。判断注入点、构造payload、绕过过滤全都是在跟这个“边界”较劲。1.3 开始动手前先建立这三个安全认知第一注入不是“输入框”的锅而是代码写法的锅。很多人以为只要过滤输入就能防御但真正该改的是SQL语句的组装方式。这一点在后面防御章节会细讲。第二攻击视角和防御视角要同时建立不能只会“打”不会“防”。做安全测试的人如果看不懂参数化查询的原理就永远停留在“照着payload打”的层面遇到变形场景就懵。第三所有实验必须在授权环境或靶场中进行。未经授权对真实系统做注入测试不光违反职业操守也会带来法律风险。文中的所有示例都是在本地靶场或授权测试场景下的操作。2. 20个必知知识点全景图先收藏这张表2.1 20个知识点分类与优先级总览为了避免学了半天不知道自己在哪个阶段我先把20个知识点按“原理认知、注入判断、利用手法、绕过对抗、防御落地”五个维度列出来并标注了优先级。这份清单的核心价值是帮你建立一张全局地图学的时候心里有数。序号知识点类别优先级1注入的本质是数据与代码边界失守原理认知高2注入发生的三要素用户输入、动态拼接、数据库执行原理认知高3注入点不只出现在登录框原理认知高4判断注入点单引号、布尔逻辑与注释符注入判断高5数字型注入与字符型注入的区分注入判断高6联合注入的字段数探测与回显位定位利用手法高7报错注入常用函数updatexml与extractvalue利用手法中8布尔盲注基于页面真假差异逐字符猜解利用手法高9时间盲注sleep延时与条件构造利用手法中10堆叠注入与联合注入的底层区别利用手法中11万能密码 or 11--的原理与适用边界绕过对抗高12宽字节注入字符集不一致导致的转义逃逸绕过对抗中13二次注入写入时过滤、读取时拼接的隐蔽路径绕过对抗中14关键字过滤绕过注释符、大小写与内联注释绕过对抗中15等价函数替换substring/mid/substr的同义变换绕过对抗中16WAF绕过的整体思路先判断拦了什么绕过对抗中17order by / group by场景下的特殊绕过绕过对抗低18sqlmap的level、risk与--technique参数自动化工具高19参数化查询为什么能根治注入防御落地高20最小权限原则与SQL注入日志特征识别防御落地高2.2 怎么用这张表搭建学习路线很多初学者容易犯的一个错误是拿到sqlmap就开始对靶场“一键脱库”结果工具跑通了原理什么都不懂题目稍微变一下就不会了。我建议按这个顺序走先把前三个原理知识点吃透然后花时间亲手判断注入点第4、5点再把联合注入和布尔盲注练熟第6、8点这两个是后续所有手法的地基。之后根据实际需要补充报错、时间、堆叠注入接着再研究绕过对抗最后用自动化工具提高效率同时把防御部分作为收尾。这样从原理到防御形成闭环无论面试还是实战都不会只停留在“会用工具”的层次。3. 注入点定位是第一步别只盯着登录框3.1 容易被忽略的输入位置很多新手以为SQL注入就是登录框里输入 or 11--实际上只要是能被拼接到SQL语句里的外部输入都可能是注入点。这里列几个我实际遇到过的高频位置URL参数news.php?id1、product.php?category_id5这种最常见。POST表单数据登录、搜索、注册信息里的字段。Cookie中的值有些系统会把用户ID、偏好设置存在Cookie里服务端取出来直接拼SQL。HTTP请求头User-Agent、X-Forwarded-For、Referer都可能被记录到数据库查询或日志写入操作中。我印象比较深的一次授权测试目标系统的X-Forwarded-For头会被获取并拼接到一条查询用户登录记录的SQL里当时很多人只盯着页面参数测完全没注意到这个头。这种位置隐蔽性高扫描器也不一定能覆盖到却很能体现基本功。3.2 单引号、布尔判断和注释符的“侦查组合拳”判断一个地方是不是注入点核心思路就一句话让SQL语句的结构发生可被观察的变化。最基础的操作序列是这样http://example.com/news.php?id1 // 正常返回 http://example.com/news.php?id1 // 多一个单引号可能报错或返回异常 http://example.com/news.php?id1 and 11 // 返回正常 http://example.com/news.php?id1 and 12 // 返回异常第四步是关键。and 12会让整个WHERE条件永远为假如果页面返回内容和正常时不一样说明我们输入的这段逻辑真的参与到了SQL执行中即这里存在注入。注释符的作用是“吃掉”后面的内容。比如原始SQL是SELECT * FROM users WHERE id1 AND statusvalid如果我们输入1 --SQL会变成SELECT * FROM users WHERE id1 -- AND statusvalid--注意后面跟一个空格会把AND statusvalid整段注释掉这样原来被限制的查询条件就被改写了。3.3 数字型注入与字符型注入同样是注入判断逻辑完全不同很多教程会把注入分成“数字型”和“字符型”但这个分类容易让新手迷糊。我自己的理解是这取决于后端SQL怎么写。如果SQL是WHERE id$id没有单引号包裹用户输入1 and 11拼接后是WHERE id1 and 11这是数字型注入布尔判断可以之间生效。如果SQL是WHERE id$id有单引号包裹我们就必须先闭合前面的单引号再构造逻辑。输入1 and 11拼接后是WHERE id1 and 11这是字符型注入。判断方法很简单先输入1看会不会报错再尝试1 and 11看是否恢复正常。如果单引号直接导致报错多半是字符型如果单引号也不影响可能是语句结构出了问题那就得重新观察报错信息了。记住注入的第一课不是学payload而是学读懂数据库的“反馈”认真看报错页面返回了哪些信息这比盲目的payload糊脸有用得多。4. 主流通用注入手法逐一拆解从联合注入到时间盲注4.1 联合注入先数清楚字段再找对回显位联合注入是最直观、效率最高的一种利用方式前置条件是页面查询结果能直接显示在响应里有回显。原理就是利用UNION关键字把额外查询的结果和原查询结果合并输出。第一步是数字段数最常用的方法是order byhttp://example.com/news.php?id1 order by 1 // 正常 http://example.com/news.php?id1 order by 2 // 正常 http://example.com/news.php?id1 order by 5 // 报错说明字段数小于5通过逐次尝试确定表的字段数量。比如某个查询只有4个字段order by 5就会报错。第二步是定位回显位把前面id改成不存在的值让原查询的结果集为空这样UNION选出的数据就能显示在页面上http://example.com/news.php?id-1 union select 1,2,3,4页面上显示了2和3说明这两个位置可以直接输出内容。接下来就可以把查询语句替换到对应位置http://example.com/news.php?id-1 union select 1,database(),user(),4这样就能直接拿到数据库名和当前用户。之后就是查表名、查字段名、拖数据的过程。这里有个经验字段数如果很多order by一个个试会很浪费时间可以配合二分法或者直接写脚本跑。我自己在实战里更常用order by配合Burp Suite的Intruder模块做递增测试几秒钟就能得出准确字段数。4.2 报错注入updatexml、extractvalue和floor该怎么选当页面没有回显但会展示数据库报错信息时就可以用报错注入。原理是让SQL语句在计算时报错同时把我们需要的数据带进报错信息里。最常用的两个函数是updatexml和extractvalue。示例http://example.com/news.php?id1 and updatexml(1,concat(0x7e,(select database()),0x7e),1) http://example.com/news.php?id1 and extractvalue(1,concat(0x7e,(select database()),0x7e))concat(0x7e, ...)是拼接了~符号这样做的目的是让报错信息中出现一个特殊字符便于快速定位数据在报错内容里的位置。两个函数的限制类似报错信息有长度上限通常是32个字符左右所以查询较长的数据比如表名拼接需要分段截取。floor报错注入是另一种思路它利用的是group by和count(*)冲突时产生的主键重复错误。这类payload写起来比较复杂而且MySQL 5.1.5以上版本对报错内容的限制更严格实际使用频率不如前面两个函数。我在测试中遇到过一次比较尴尬的情况页面把数据库报错信息给脱敏了只显示“SQL错误”但不回显具体内容。这时报错注入就失效了只能走盲注。4.3 布尔盲注页面只有真假两种答案时的猜解策略布尔盲注适合页面没有回显、也没有报错但页面内容会因SQL结果真假而变化的情况。比如查询结果为真时显示“存在”为假时显示“不存在”。思路是把要猜解的内容逐字符拆开通过substr函数取出某一个字符再通过ascii函数转成数字和猜想的数值做比较。示意http://example.com/news.php?id1 and ascii(substr((select database()),1,1))100如果页面正常说明数据库名的第一个字符的ASCII码大于100继续二分最终锁定精确值。然后是第2个字符、第3个字符直到完整的库名、表名、字段名。这种方法效率很低但胜在稳定。实际工作中我很少手动一个字符一个字符猜通常会写一个简单的Python脚本或者直接让sqlmap跑布尔盲注。但手动练习一次非常有必要它能让你深刻理解盲注的底层逻辑这样工具跑不出结果时你才知道问题出在哪。4.4 时间盲注sleep延时背后的网络噪音问题当页面真假差异完全无法从内容上分辨或者数据库报错被完全隐藏时时间盲注就成了保底方案。原理是利用sleep()函数让数据库延时响应再根据响应时间判断条件真假。常规payload长这样http://example.com/news.php?id1 and if(ascii(substr((select database()),1,1))100,sleep(3),0)如果ascii(...)100为真数据库就睡3秒页面响应时间也会明显变慢。这样就把“内容差异”转换成了“时间差异”。时间盲注有几个坑要特别注意。第一网络本身有波动判断延时阈值不能只靠肉眼最好先测一下正常的响应时间基线再设一个合理阈值比如超过3秒算真。第二sleep()的秒数别设太大防止单次请求等待过久效率太低。第三如果目标数据库不支持sleep()比如某些数据库用pg_sleep()payload就需要换函数。我在测试PostgreSQL时第一次就踩了这个坑后来才记住不同数据库的时间函数差异很大。4.5 堆叠注入一条SQL不够用时的另类思路UNION只能执行一条查询语句而堆叠注入可以在同一个数据库连接里连续执行多条语句。原理是利用分号分隔http://example.com/news.php?id1; create table test(id int)如果数据库支持多语句执行且应用没有做限制create table这类语句也能被执行。堆叠注入的威力比UNION大得多因为它不再受限于“查询”这一种形式可以插入、删除、修改数据甚至创建账号。但它的限制也很明显。很多数据库连接默认不允许多语句执行比如MySQL的驱动会关闭allowMultiQueries同时堆叠注入不一定有回显所以实用性比UNION低。我在CTF里见到比较多真实系统里反而少见。学习时至少要知道它的存在和边界不要在遇到UNION被拦死时完全没思路。5. 绕过与变种对抗真实攻防中更高频的“非标准注入”5.1 万能密码你以为理解了实际还差一层 or 11--是SQL注入的“Hello World”几乎每个入门者都见过。它用在登录场景里的逻辑是如果后端查询是SELECT * FROM users WHERE username$user AND password$pass我们输入admin or 11 --作为用户名就能让整个条件成立绕过密码校验。但很多人只记住了payload没理解闭合逻辑。真实开发中如果使用了参数化查询这种payload完全无效如果老系统用的是拼接且把用户输入直接嵌入SQL那还需要考虑语句中的引号闭合情况。不同的SQL写法需要的payload结构不一样admin or 11 -- admin or 11 -- or 11#最后那个#也是MySQL的注释符在URL里要编码成%23。我建议动手实验时每种闭合方式都要亲自试一遍搞清楚为什么同一场景需要不同的写法。知其然也知其所以然后面遇到过滤绕道时才不会懵。5.2 宽字节注入单引号被转义后的突破口这个知识点是老生常谈但很多人会忽略它背后的核心——字符集不一致。当应用使用addslashes这类函数对单引号加反斜杠转义时输入会变成\这样单引号就失去了闭合作用。但在GBK等宽字节编码下如果数据库和页面编码都是GBK攻击者输入%bf%27%bf和反斜杠\会被解析成一个宽字节字符导致后面的单引号“逃逸”出来。输入%bf%27 经过转义%bf%5c%27 GBK解析一个宽字节字符 一个单引号所以宽字节注入能绕过的是基于反斜杠转义的过滤本质问题是转义逻辑没有考虑数据库字符集。在现在以UTF-8为主流的Web环境里宽字节注入的应用场景少了很多但测试老系统时依然值得关注。遇到GBK编码的页面第一时间就该想到这个方向。5.3 二次注入写入时过滤、读取时拼接的隐蔽路径二次注入是我认为最“阴险”的一种注入因为它绕过了很多防御者的直觉。很多开发者的过滤思路是用户输入在写入数据库前做转义只要入库时没有单引号、括号之类的字符就认为安全了。但二次注入的利用逻辑是攻击者把payload作为普通数据写入数据库入库时转义函数把单引号转成了无害字符所以写入过程不会出问题。然而后续某个功能从数据库读取这条数据在另一个SQL语句里直接拼接使用而拼接时没有再做转义注入就爆发了。典型的应用场景包括用户注册时用自己的昵称昵称被存储在数据库后台某管理页面读取昵称后拼接进SQL查询生成报表或日志。我在一次授权测试里就遇到过类似情况一个用户资料编辑功能本身很安全但管理端的用户导出功能把用户名拼进了查询语句结果通过编辑用户名实现了一次完整的注入。二次注入的可怕之处在于单看任一个功能点都是正常的必须从数据流转的完整链路去审查才会发现问题。5.4 关键字过滤绕过从注释符到等价函数的“同义替换”很多系统会对select、union、or这类关键字做简单过滤。绕过思路有很多我按使用频率从高到低排列注释符拆分sel/**/ect把关键字中间插入注释拼接后仍然能被解析为select。大小写混合SeLeCt适用于大小写敏感过滤不严的场景。内联注释/*!select*/MySQL会执行注释内的内容。等价函数替换substring换成mid或substrconcat_ws换concat。编码绕过URL编码、双重URL编码、十六进制表示字符串内容。我自己测试时的经验是先构造一个最小可用的payload逐段替换关键字观察哪一段被拦截。比如先输入union看是否被过滤再输入uniunionon中间包一层如果系统只做了简单的替换删除这种写法就可能绕过。了解过滤机制的实现方式比死记硬背绕过字典更重要。5.5 WAF绕过先判断拦了什么再谈怎么绕遇到WAF时很多人的第一反应是找一个“WAF绕过payload”直接打。但正确的思路是完全反过来的先判断WAF拦截了什么再决定绕过策略。具体分三步。第一步输入正常的请求确认没有误拦。第二步输入明显恶意的payload比如1 and 11--观察拦截特征是返回403、还是返回自定义页面、还是直接空白。第三步把payload拆解成小块逐段测试单引号拦不拦union拦不拦select拦不拦空格拦不拦注释符拦不拦有一次我测试一个站点union select被拦截但union本身不拦select也不拦说明拦截规则是针对组合的关键词。这时候用union/*!50000select*/或者换行符绕过就有机会。还有一种情况是WAF只检测GET参数不检测POST或Cookie那就可以尝试把参数迁移到其他位置。不过要强调一点WAF绕过的知识体系非常庞大而且每个WAF的实现都可能不同。入门阶段不需要死磕所有绕过姿势但一定要掌握“隔离变量”的测试思路这比任何绕过字典都通用。6. 把知识变成能力工具、靶场和练习路线6.1 sqlmap的高级参数level、risk和--technique的正确用法sqlmap是SQL注入自动化检测的标杆工具但很多人只停留在sqlmap -u URL --dbs这一层。实际场景里默认参数扫描不到的情况很常见。常用参数组合可以这么记# 基础用法检测数据库 sqlmap -u http://example.com/news.php?id1 --dbs # 提高检测深度level 3能测到HTTP头、Cookie等位置 sqlmap -u http://example.com/news.php?id1 --level 3 --risk 2 # 指定注入技术布尔盲注情况下避免跑时间盲注 sqlmap -u http://example.com/news.php?id1 --techniqueB # 指定数据库类型减少误报 sqlmap -u http://example.com/news.php?id1 --dbmsmysql--level控制检测的深度等级从1到5。level3时会测试HTTP头和User-Agent里的注入level5还包括Cookie。--risk控制风险等级risk2会尝试or 11这类可能会造成数据变更的payload。--technique用于指定注入技术类型B是布尔盲注、T是时间盲注、U是联合查询、E是报错注入。我经常遇到初学者说“sqlmap跑不出来目标一定没有注入”其实多数时候只是参数配得不对。另外一个实操建议是sqlmap跑批量检测时一定要加--batch参数否则中途会一直卡在交互确认上。6.2 CTF与SRC场景下SQL注入的差异CTF赛题里的SQL注入更像一个“无菌实验室”环境固定、flag位置明确、数据库结构已知或可猜。比如Web题里常见的SQL注入目标就是拿到flag可能藏在secret表里、可能在注释里、可能要通过堆叠注入才能读到。SRC安全响应中心场景则完全不同。真实业务系统有各种限制WAF、云防护、业务逻辑复杂性、数据隔离、审计日志等。在SRC平台测注入首先要确认测试授权范围其次要考虑影响面。很多漏洞平台都要求测试过程中不能拖库、不能破坏数据这意味着sqlmap --dump这类操作在很多场景下是违规的。所以我给想往实战方向走的朋友的建议是CTF练的是“原理和速度”SRC练的是“克制和判断”。两者都需要但别用CTF的思路直接套真实系统。在SRC上发现一个注入点先证明可以读取当前库名或用户信息能证明危害就够了不需要把整库拖下来。6.3 靶场推荐与刻意练习节奏我能稳定复现SQL注入相关手法的练习资源有以下几类sqli-labs最经典的SQL注入靶场从基础到进阶一共几十关每一关对应一种注入类型。它的价值在于关卡设计贴近“手工判断”的训练目标。DVWA自带Web漏洞的综合靶场SQL注入模块分为low、medium、high三个难度适合观察同一漏洞在不同防护强度下的表现。pikachu中文靶场覆盖了宽字节注入、二次注入等特殊场景对理解“非标准注入”很有帮助。CTF平台各类CTF赛题中的Web方向题目更灵活适合检验综合能力。练习节奏上我建议按“重复”和“复盘”两个词来安排。第一遍跟着教程慢慢打记录每个payload对应的SQL变化第二遍关掉教程自己打卡住了再去对照第三遍用sqlmap自动化跑一遍对比手动测试和工具测试的结果差异。每一遍都能看到不同的细节。7. 攻防一体把这20个知识点反过来用7.1 参数化查询为什么能“根治”注入前面讲了那么多利用手法核心都是“用户输入被拼接到SQL语句中并被当作代码执行”。那么防御的根本思路就是让用户输入永远只是数据不进SQL语句结构。参数化查询预编译正是这样做的。以Java的PreparedStatement为例String sql SELECT * FROM users WHERE username ? AND password ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery();这里的?是占位符SQL语句的结构在数据库编译阶段就已经固定了用户输入的username和password只是被当作参数传递无论输入什么内容都无法改变语句结构。也就是说即使输入 or 11--数据库也只会把它当成一个普通的字符串值处理。这也是为什么我反复强调只做输入过滤是治标参数化查询才能真正解决注入问题。对于存量系统的改造逐条替换拼接SQL确实工作量很大但这是从源头消除问题的唯一可靠路径。7.2 最小权限和输入校验数据库层与应用层的双保险参数化查询是防线的主力但光靠它还不够。数据库账号的权限如果过大即使注入发生攻击者也拿不到太多东西。最小权限原则的落地姿势包括应用连接数据库的账号只授予它必要的增删改查权限如果业务只需要查询那就只给SELECT权限禁止应用账号使用FILE、GRANT这类高危权限日常操作不使用root级别的账号连接数据库。应用层的输入校验同样不能省。虽然输入校验无法完全替代参数化查询但它能拦截大量明显恶意的请求降低被扫描器盯上的概率。校验要基于“白名单”思维规定每个字段应该是什么格式比如ID只能是数字用户名只能包含字母数字和下划线而不是简单地去黑名单拦截一些关键字。7.3 从日志中一眼认出SQL注入攻击防御做得再好也需要监控和感知能力。SQL注入攻击在日志里有很强的特征完全可以靠规则发现常见特征包括请求参数中包含 or 11、union select、sleep(等典型payload片段。同一个IP在短时间内反复请求同一个URL且参数值在频繁变化。请求参数中夹杂着大量编码后的字符比如%27单引号的URL编码、%23#注释符。我自己在用日志平台做检测时会配置类似下面的正则规则来匹配关键词(union[\s]select)|(?or?\s*11)|(sleep\()|(updatexml\()|(extractvalue\()|(0x[0-9a-f]{8,})命中这些特征就自动告警再结合IP、时间、URL做聚合分析基本能定位到可疑扫描或利用行为。当然攻击者也在不断变种绕过这些规则所以日志监控需要长期迭代但不能因为规则不完美就不做。SQL注入这个专题真正拉开差距的不是背了多少条payload而是能不能在一条请求过来时快速判断出它背后的SQL长什么样在写代码时能不能一眼看出某个拼接会出事。把上面这20个知识点吃透然后去靶场里把手弄脏你才能对“为什么一个老漏洞能活二十多年”这件事有真正的体感。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价