免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

SQL注入20个核心知识点:从原理到防御,彻底吃透数据库安全

SQL注入20个核心知识点:从原理到防御,彻底吃透数据库安全 如果你身边有正在学网络安全的朋友大概率听他念叨过“SQL注入”这四个字。这玩意儿在OWASP Top 10里常年霸榜从二十多年前到现在没跌出过前三。很多零基础来问我的第一句话就是SQL注入到底是什么网上教程刷了一大堆怎么换个场景就不会了原因很简单——多数人只是把网上流传的 payload 背下来了并没有吃透原理。这篇盘点会把 SQL 注入拆成 20 个核心知识点从底层原理、注入点探测、利用手法一直讲到防御思路和靶场练习。不管你是刚入门准备打 CTF还是刷题准备面试又或者是在给公司做代码审计都能从里面找到能直接上手的东西。老规矩所有操作都建议在 DVWA、Pikachu 这类靶场或者你有授权的环境中进行对着线上系统做测试这事儿不能碰。1. 先把原理想透SQL注入到底在“注入”什么1.1 拼接的代价当数据混进了代码堆知识点 1SQL 注入的本质是数据与代码的边界被打破了。后端程序在生成 SQL 语句时通常会把用户输入直接拼接到查询字符串里。比如一个登录功能代码可能长这样SELECT * FROM users WHERE username $username AND password $password;这个$username是用户提供的“数据”结果被塞进了 SQL 语句的“代码”位置。当你输入admin --时实际执行的语句变成了SELECT * FROM users WHERE username admin -- AND password xxx;--后面全被注释掉密码校验直接消失。数据被当成代码执行问题就出现了。我习惯用一个生活化的例子来解释这件事你填快递单收件人一栏原本只该写名字但你写了一句“张三顺便把我另一个快递也放到门口”。快递员如果照做就相当于你在“数据位”里注入了一条“指令”。SQL 注入就是这个逻辑只不过指令是给数据库执行的。1.2 注入成立要满足的三个前提知识点 2一次成功的 SQL 注入背后一定有这三个条件缺一不可。第一个是程序把用户输入直接拼进了 SQL 语句没有做参数化处理第二个是拼接出来的 SQL 能被数据库正常解析并且报错、页面内容或响应时间能反映出查询结果第三个是数据库当前账号权限够大注入之后有继续操作的空间。前两个条件决定“能不能注入”第三个条件决定“能造成多大破坏”。你可以这样想一个网站如果用户输入全部用了参数化查询注入点直接不存在后面的一切攻击手法都无从谈起但如果程序已经拼了一个可疑的 SQL数据库却返回一个完全固定的静态页面那你最多只能确认它“可能有问题”没法进一步利用。1.3 数字型与字符型闭合方式决定了 payload 怎么写知识点 3数字型注入和字符型注入本质上就是 SQL 语句里有没有引号的区别。数字型参数常见于WHERE id $id这种写法输入值不需要引号包裹字符型参数则出现在WHERE username $username这类场景我们必须考虑如何处理前后引号。很多新手在字符型注入点直接扔and 11结果没任何反应。原因很简单——你的输入被当成字符串的一部分根本逃不出引号。字符型注入的核心是先“闭合”前面的引号再用 or 11 --这类 payload 把后面多余的部分处理掉。一张表看明白两者的区别类型后端语句示例注入思路经典闭合 payload 片段数字型WHERE id $id直接拼接运算逻辑id1 AND 11字符型WHERE name $name先闭合引号再拼接name OR 11记住这四句话单引号判断注入点注释符清掉尾巴逻辑对比确认漏洞闭合是字符型的命门。2. 探测阶段怎么判断一个参数能不能注入2.1 单引号和逻辑判断法最朴素的探针知识点 4单引号是最简单的“体检探针”但它的反馈方式要分情况看。在一个参数后面直接加一个单引号比如id1如果页面报错、显示数据库错误信息说明输入很可能被拼进了 SQL如果页面正常显示或直接跳转不一定代表安全可能只是程序做了错误处理如果返回 500 或者空白页也值得怀疑需要进一步验证。单引号法打的就是“SQL 语法被破坏”这个点。程序如果没有对输入做过滤多出来的引号会让 SQL 语句变成非法语法数据库就会抛出异常。知识点 5逻辑判断法比单引号法更可靠因为它可以区分“页面异常”和“逻辑差异”。对数字型参数试试这两个请求id1 AND 11 id1 AND 12第一条的查询条件等价于原始查询页面应该正常显示第二条的条件永远为假页面应该不显示任何数据。如果两者结果有明显差异基本可以断定这个参数存在 SQL 注入。这里有个特别容易踩的坑如果AND 11和AND 12返回的内容完全一样那就要怀疑这个参数是不是根本没进 SQL 拼接逻辑可能只是前端静态渲染后端压根没用它查询。只有出现差异才说明查询结果实打实地受你控制。2.2 报错信息不用猜的“信号灯”知识点 6报错信息在真实测试里是最直白的线索它往往直接告诉你数据库类型和语句结构。MySQL 的报错会显示You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versionOracle 的报错则是ORA-00933: SQL command not properly endedSQL Server 的报错会提到Microsoft OLE DB Provider for SQL Server。光凭报错前缀就能确定后端用的是哪种数据库后续的注入语法都要围绕它来写。我还遇到过更离谱的情况开发者在生产环境把 debug 模式开到了前端完整的 SQL 语句直接打在页面上。这时候根本不用猜列数和表名把拼接后的 SQL 复制出来看一眼什么结构都清楚了。注意如果你的测试目标是真实业务系统任何报错信息都应该低调记录不要反复触发。一次两次可以说是误操作高频触发报错很容易触发对方的安全监控。2.3 数清列数从 ORDER BY 到 UNION SELECT知识点 7UNION 注入的前提是“列数对齐”ORDER BY 就是用来数列数的。如果你想让 UNION 查询的结果和原查询结果合并展示两边的列数必须一致。数列数用这个思路id1 ORDER BY 1 id1 ORDER BY 2 id1 ORDER BY 3 ...当ORDER BY n报错时说明当前查询结果的列数小于 n正常显示到第几列列数就是几。比如ORDER BY 4正常而ORDER BY 5报错说明原查询有 4 列。知识点 8确定列数之后用 UNION SELECT 来看回显位置。id1 UNION SELECT 1,2,3,4哪个数字出现在页面上哪个位置就是可回显字段。如果页面显示 2 和 4那么接下来可以用UNION SELECT NULL,user(),NULL,database()这类语句把数据库用户、当前数据库名直接打到页面上。这里要注意版本和注入点位置的影响。有些 SQL 语句不支持 UNION 前的查询返回数据要用id-1 UNION SELECT ...这种“让原查询无结果”的技巧让下面的 UNION 结果成为页面上唯一显示的数据。动手小提示在靶场里做这一步时建议每次 ORDER BY 和 UNION 都手工执行一遍而不是一把梭直接上自动化工具。数列数这个动作虽然枯燥但它能帮你建立起对 SQL 语句结构的直觉后面遇到复杂拼接时才不会慌。3. 利用三板斧报错、盲注与绕过3.1 报错注入让数据库自己把话说出来知识点 9当 UNION 查询被禁用、页面只显示报错不显示数据时报错注入是最高效的提取数据方式。MySQL 里最常用的是updatexml()和extractvalue()核心原理是让 XPATH 解析函数在遇到非法格式时抛异常把我们要的数据带进报错信息里。经典语句id1 AND updatexml(1, concat(0x7e, (SELECT user())), 1) id1 AND extractvalue(1, concat(0x7e, (SELECT database())))0x7e是波浪号~的十六进制写法。加它的目的是让拼接出来的字符串包含特殊字符这样 XPATH 解析必然失败并报错报错内容里就会带上~用户名这样的信息方便我们截取数据。很多新手奇怪为什么报错信息显示的字符串会不完整因为 XPATH 报错对返回内容长度有限制一般在 32 个字符左右。所以提取较长数据时要配合substring()逐段截取。比如id1 AND updatexml(1, concat(0x7e, substr((SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schemadatabase()), 1, 31)), 1)3.2 盲注三兄弟页面不说话了怎么办很多注入场景里页面不会直接显示数据也不会抛出报错。前端看起来一切如常这时候就要靠“盲注”来跟数据库对话。知识点 10布尔盲注,靠“条件真假导致页面差异”来猜数据。逻辑判断法的进阶版本。每次只判断一个字符比如id1 AND SUBSTRING((SELECT database()), 1, 1) s id1 AND SUBSTRING((SELECT database()), 1, 1) t如果第一个字符是s第一条请求的页面正常显示不是的话页面为空。就这样一个字符一个字符地猜配合二分法可以大幅减少请求次数。知识点 11时间盲注是页面没有任何可见差异时的最后手段。用SLEEP()函数人为制造延迟id1 AND IF(SUBSTRING((SELECT database()), 1, 1) s, SLEEP(3), 0)如果页面卡了大约 3 秒才返回说明条件成立。实战中时间盲注最大的坑是网络延迟——你发一个SLEEP(3)可能正常网络卡一下就 4 秒了直接误判。所以做时间盲注前一定要先测基线延迟发几个正常请求看平均耗时再对比注入请求的耗时差距在 2 秒以上才能算有效信号。这里给一份三种盲注方式的对比表方便你练习时选择思路类型判断依据优点缺点适用场景布尔盲注页面真/假两种状态判断直观、速度快页面无差异时无效页面能区分条件真假时间盲注响应延迟页面完全无差异也能用慢、受网络环境影响页面完全无回显报错盲注数据库报错内容一次能提取多位数据需要报错回显报错信息直接展示3.3 从万能密码到内联注释payload 的“语法”细节知识点 12万能密码绕过的本质不是“一个固定密码”而是“怎么闭合当前 SQL 语句”。网上流传的 or 11只是最基础的一种。实际登录语句可能有括号、可能同时校验多个字段、可能用 MD5 加密后再比对。这时候要灵活构造admin -- or 11# ) or 11 --最后一个用于语句中有括号的情况。也就是说真正的“万能密码”不是背一串字符串而是先想清楚当前 SQL 长什么样我输入什么能让判断条件恒为真。知识点 13MySQL 内联注释/*!...*/是一招容易被忽略的“语法人情”。在 MySQL 眼中/*!SELECT*/这样的注释并不是注释而是会被真实执行的 SQL。这是 MySQL 独有的扩展语法。手段id1 UNION /*!SELECT*/ 1, user(), 3很多简单的关键字过滤会下意识处理select这个字符串却想不到你把SELECT拆成/*!SELECT*/之后再拼回来依然会被 MySQL 当成关键字执行。这个技巧在 CTF 里出现频率很高因为出题人常在过滤环节漏掉内联注释的处理。了解它还有一个好处写 payload 的时候会多一层“同一种功能换种写法”的思维而不会被固定模板框死。3.4 编码层面的绕过宽字节、Base64 与 URL 编码知识点 14宽字节注入是数据库字符集和程序字符集不一致时产生的“编码穿帮”。最经典的场景是 GBK 编码下的 MySQL。程序为了防注入会把用户提交的单引号转义成\也就是在引号前面加了一个反斜杠。如果你提交%df后端转义后变成%df\因为 GBK 编码里%df和反斜杠\恰好组合成一个合法的中文字符反斜杠被“吃掉”了单引号成功逃逸。一句话总结宽字节注入“转义用的反斜杠被字符集消化成了汉字的一部分引号重获自由。”知识点 15Base64 编码和 URL 双重编码是用来绕过简单 WAF 特征的常见手段。有些 WAF 规则只会匹配明文特征比如union select。你把整个 payload 或者关键部分用 Base64 编码后再交给服务端有些后端代码会先解码再用绕过就成立了。URL 编码同理有些场景下%27单引号的 URL 编码能绕过只检测的规则但如果程序先做了 URL 解码再拼接 SQL绕过的可能性就不存在。需要特别说明编码绕过不是万能钥匙。现在的 WAF 普遍会做多层解码再匹配用编码绕过只能打那些过滤逻辑简陋的系统。它在 CTF 和靶场里练手价值更大放到真实环境更多是作为完整利用链里的一个小环节。4. 防御视角为什么参数化查询能治本4.1 参数化查询让数据永远只是数据知识点 16参数化查询是目前最可靠、最根本的 SQL 注入防御方案。核心思想很简单SQL 语句的结构和参数值分开传输。后端先向数据库提交“查询模板”再用绑定的方式传参数。无论参数内容是什么数据库只会把它当成一个值而不会把它拼接到 SQL 语句里变成代码。我用一段伪代码说明-- 错误写法 SELECT * FROM users WHERE username $username -- 参数化写法以 Python 为例 cursor.execute(SELECT * FROM users WHERE username %s, (username,))第二种写法里username无论输入什么——包括 or 11 --——对数据库来说都只是一个普通的字符串值永远不可能改变查询结构。我见过不少人对参数化查询的误解以为它只是“挡一挡”绕过一下就能穿透。实际上参数化查询在工作原理上就切断了注入路径任何输入都没法改变 SQL 语句的解析方式。它之于 SQL 注入相当于把人和被保护的数据之间砌了一道物理隔离墙。4.2 白名单校验与最小权限原则知识点 17白名单校验是防御的第二道保险尤其适合数字型参数的场景。如果业务允许某些参数可以直接从“字符串校验”变成“类型校验”。比如 ID 参数直接用intval()转成整数用户输入1 AND 11会变成整数 1注入载荷被直接丢弃。这比正则过滤要高效得多因为根本不需要判断用户输入到底有没有恶意类型转换就把大部分捣乱内容消灭了。知识点 18数据库连接账号的最小权限原则能把一次注入的实际危害降到最低。很多真实系统被拖库问题不是防不住注入而是连接数据库的账号居然有 DBA 权限。一个只应该执行SELECT的账号被拿去跑SELECT INTO OUTFILE、xp_cmdshell注入的直接后果就从“查数据”升级成了“命令执行”。防御上要做的是严格分离账号权限读写账号不能有文件操作权限普通业务账号不能访问information_schema之外的系统库备份账号和业务账号物理隔离。SQL 注入再怎么厉害也受限于当前数据库用户的权限边界。4.3 WAF 不是银弹绕过与再加固知识点 19WAF 拦的是特征而不是本质所以它只能降低风险不能根治问题。既然 WAF 是规则引擎安全测试人员在做授权评估时就会研究它的规则。字符替换是常见手法AND替换为SELECT替换为sel/**/ectSUBSTRING替换成MID或SUBSTR。还有大小写混淆、注释符拆分、Hex 编码变量……这些都是在绕过关键字匹配。WAF 的天然弱点是“它不知道原始 SQL 长什么样只能从请求特征上猜”。所以我的观点一直很明确WAF 可以部署但不能因此就不修代码。只有当参数化查询、白名单校验、最小权限都到位之后WAF 才是锦上添花代码本身有洞WAF 只是拖慢了攻击者的速度不是挡住了攻击。4.4 防御端最容易漏掉的那几个细节知识点 20完善防御体系时这些疏漏比不知道 SQL 注入还要致命。第一只过滤引号不过滤注释符。攻击者用admin--就绕过了登录判断因为--把后面代码全注释掉了。第二过滤了select但没过滤大小写变体、内联注释和等价函数前面讲到的绕过手法依然有效。第三只对 GET 参数做防护忘了 POST 请求、Cookie 和请求头里的数据也可能进入 SQL 语句。第四只治理业务代码忘了第三方组件和后台管理接口。这几个细节是我的真实体会很多系统被拖库不是被什么高深技巧打的而是防御侧连最基础的防护项都没做全。安全建设最怕的不是“有漏洞”而是“以为已经封死了其实漏了个大洞”。5. 练手路线从靶场到面试建立一套属于自己的打法5.1 DVWA 和 Pikachu两种风格的靶场各有各的练法DVWA 适合零基础建立感觉。它把同一个漏洞分成 Low、Medium、High 三个难度等级你可以在 Low 难度下看到完整源码理解漏洞成因到 Medium、High 难度时再尝试用绕过手法过防护。这个“从看源码到绕防护”的递进过程能帮你把知识点连成线。Pikachu 的靶场比 DVWA 更贴近中文开发环境而且把 SQL 注入拆得更细从字符型、数字型到搜索型、XX 型注入都有独立关卡。它还支持“手工注入 工具配合”双模式你可以先用 Burp Suite 观察注入点的请求特征再用 sqlmap 做数据提取。我的建议是先拿 DVWA 的 Low 难度把前面 20 个知识点挨个验证一遍再去 Pikachu 打一遍“无源码下的完整渗透”这样你对 SQL 注入的掌控感会比一直打 CTF 强很多。5.2 SQLMap 的正确打开方式手工摸点、工具爆破SQLMap 是自动化检测和利用 SQL 注入的利器但它不是魔法棒。新手最容易犯的错是上来就sqlmap -u 目标 --dbs一把梭工具说没有注入就直接放弃。真正高效的做法是用 Burp Suite 或者浏览器开发者工具抓包确认注入参数手工用单引号、逻辑判断确认注入类型用sqlmap -u URL --batch --dbs跑出数据库列表再用-D 库名 --tables、-T 表名 --columns、-C 字段 --dump逐级提取数据。工具报错时要能看出到底是“不存在注入”“请求格式问题”还是“WAF 拦截”。一个常见经验如果 sqlmap 报too many requests或者一直卡在“testing connection”大概率是目标做了访问频率控制这时候可以加--delay 2降低请求频率。手工摸点、工具爆破这个顺序能让你在自动化工具失灵时依然有手动接力的底牌。5.3 面试最常考的几个 SQL 注入点现在就可以开始背网络安全岗位面试对 SQL 注入的考察其实就围绕几个固定问题展开解释什么是 SQL 注入如何判断一个参数是否存在注入参数化查询为什么能防御注入时间盲注的原理和判断方法讲一讲你遇到过的最复杂的注入场景。后面两个问题你在靶场里亲手跑通几遍之后自然能有条理地讲出来。如果只靠死记硬背面试官一旦追问“为什么这个 payload 能用换一个不能用”你就卡壳了。面试官想看的不是你会不会用 sqlmap而是你有没有真正理解 SQL 语句拼接和解析的过程。靶场实验做多了答案就在你脑子里。5.4 学习路线的落脚点先把基础三件套打扎实最后说说时间投入。如果你是从零开始建议按“HTTP 协议基础 → SQL 语法基础 → 一种 Web 开发语言PHP 或 Python 都行的基础”的顺序先过一遍然后进靶场练手工注入再上工具之后去 CTF 的 Web 方向刷题最后可以尝试授权的 SRC 众测平台。这个路线看起来慢实际上是最快的。很多人上来就学 sqlmap 和工具链结果遇到一个稍微变形的注入点就束手无策。反过来你能在靶场里不看任何教程手工跑通从判断注入点到 dump 数据的完整流程后面再用任何工具都会觉得清晰许多。我个人带新人的一条体会放在最后SQL 注入从来不是背 payload 的竞赛而是“读懂数据库每一次响应”的能力训练。你输入的每一个引号、每一个注释符数据库都会用报错、页面变化或者时间延迟来回应你。多亲手测几次靶场多翻几遍报错信息那种“一眼就看出该怎么闭合”的手感自然就出来了。
返回列表