资讯动态

SQL注入进阶:Cookie注入三关实战与Base64编码绕过解析

发布时间:2026/10/15 6:48:36 来源:尧图企业网站定制
很长一段时间我都觉得SQL注入只要会拼参数就够了。直到把本地那套SQL注入练习靶场从头开始刷刷到第20关时才发现之前用顺手的那套经验基本失灵——URL上没有参数POST体里也没有可改的字段但你只要动一下登录成功后浏览器里那个Cookie值页面照样能跟着变。这套靶场的第20、21、22关讲的全是同一件事Cookie注入。而且它设计得特别像一条完整的进阶路线第20关是纯字符型注入第21关加了Base64编码第22关在Base64的基础上又把单引号改成了双引号。三关连在一起刷完你基本就建立了“注入点不一定在参数里”的认知。如果你正准备攻这三关或者刷到一半卡住了这篇文章应该能直接帮你跨过去。1. 第20到22关的“换战场”意义注入点离开URL之后1.1 为什么前19关的经验突然不灵了在前面那些关卡的练习里我们的习惯动作是盯住URL问号后面的参数或者盯着POST数据包里的字段然后在这些位置尝试各种闭合和联合查询。这套思路没有问题因为前面的关卡天然把注入点放在了URL参数和POST参数上。但第20关打开之后很多人第一反应是懵页面就是一个登录框登录进去之后只有一个账号信息展示页面URL里干干净净没有任何可以动手脚的参数。这时候如果你还执着于在URL或者POST数据里找注入点大概率一无所获。实际上这套靶场在这里做了一个很典型的设计转变注入点不在URL里也不在POST体里而藏在登录成功后服务端下发的Cookie里。服务端会把你登录时使用的用户名直接写进Cookie等你在站内跳转时再从Cookie里取出这个值拼接进SQL查询。整个过程对普通用户完全不可见但对做注入测试的人来说这就是一个非常典型的“隐藏注入点”。我见过不少人在这一关卡了很久不是因为技术不够而是思维方式没有切换过来。他们一直在找“哪里有参数”却忽略了HTTP请求的任何一个部分——URL、POST体、Cookie、甚至Referer头——都可能成为后端拼接SQL的数据来源。第20关的“换战场”本质上是逼你从“找参数”变成“找后端从哪里取数据”。1.2 三关的递进关系一个家族三种难度第20、21、22关在剧情上是连续的技术上也是同一个家族的变体。第20关是最基础的Cookie注入Cookie值是明文闭合方式是单引号第21关把Cookie值做了一次Base64编码你需要先把payload编码后再提交第22关则是在Base64编码的基础上把闭合字符换成了双引号。先看一眼三关的对比后面我们一关一关拆关卡注入位置Cookie原始值形态闭合字符核心难点第20关Cookie中的uname字段明文如admin单引号发现注入点从URL转移到Cookie第21关Cookie中的uname字段Base64编码如YWRtaW4单引号payload必须先做Base64编码第22关Cookie中的uname字段Base64编码双引号闭合字符变成双引号判断方式不同这个设计有很强的教学意图第20关让你理解“Cookie可以做注入点”第21关让你理解“编码不等于过滤”第22关让你理解“闭合方式必须准确识别”。三关每一步都只增加一个变量方便你去对比。1.3 适合谁来刷这三关如果你刚接触SQL注入前面几关做过但还没形成系统思路这三关正好可以帮你补上“HTTP全局数据源”这块拼图。如果你已经会手工注入但只会在URL参数上操作这三关也能帮你把思路从“参数扫描”提升到“全请求分析”。2. Cookie参数是怎么变成SQL一部分的登录闭环的逆向拆解2.1 服务端把用户名写进Cookie再在下一次请求中取出来拼SQL要理解Cookie注入先要搞清楚后端是怎么处理Cookie的。以这套靶场第20关为例后台处理逻辑和下面这段PHP代码非常接近// 登录成功后把用户名直接写进Cookie $username $_POST[uname]; setcookie(uname, $username, time() 3600);登录成功后服务端返回的响应头里会带着一个Set-Cookie。你再用浏览器或者抓包工具看这次请求就能看到类似这样的CookieCookie: unameadmin后面的页面在处理请求时直接把Cookie里的uname取出来拼进了SQL$uname $_COOKIE[uname]; $sql SELECT username, password FROM users WHERE username$uname LIMIT 0,1; $result mysqli_query($conn, $sql);这段代码就是全部问题的根源Cookie值在没有任何过滤的情况下被直接拼接进了SQL语句的字符串里而且外面还套了一层单引号。你传进去的Cookie值就成了SQL语句的一部分。2.2 判断Cookie注入的关键动作改Cookie值看页面反应手工测试Cookie注入核心动作只有一个修改Cookie值重放请求观察页面回显变化。具体操作顺序是这样的先用正常账号登录靶场比如用admin / admin。打开抓包工具或者浏览器开发者工具找到当前请求里的Cookie头。把Cookie里uname的值改掉重新发给服务端。对比修改前后的页面内容变化。这和地方上查URL参数注入的思路完全一样只是改动的位置从URL搬到了Cookie头。第一次做的时候建议老老实实用抓包工具走一遍流程亲眼看到请求头和响应内容的变化比你脑补十遍都管用。2.3 环境准备和个人工具习惯做这三关练习本地环境的准备其实很简单一套能在本地跑起来的SQL注入靶场一个能编辑和重放请求的抓包工具再加一个看得顺眼的浏览器就够了。我个人习惯是把抓包工具和浏览器配合用浏览器负责登录抓包工具负责改Cookie和重放。有一点值得提醒不要在一开始就上自动化注入工具。第20关到第22关的练习价值恰恰在于手工理解“数据从哪里进、怎么进SQL、为什么能注入”。自动化工具往往默认扫URL参数对Cookie注入的覆盖逻辑很不一样反而容易把你带偏。3. 第20关实战字符型Cookie注入的完整链路3.1 登录后确认Cookie值找到真正的可操作点先正常登录进入账号信息页面。这时用抓包工具看一眼请求你会看到Cookie头里有一个uname字段值就是刚才登录用的用户名比如admin。这一步花不了多少时间但一定别跳过。很多新手一上来就脑补“注入点应该在这里”结果连Cookie里字段名都没搞清就瞎改。实际操作中我习惯先把正常请求完整看一眼URL是什么、Cookie有哪些字段、正文参数是什么脑子里先形成一个基准线后面改动才有对照。3.2 单引号试探看到反常就停一下确认基准请求之后把Cookie里的uname从admin改成admin也就是在末尾加一个单引号然后重放请求。服务端这时会执行这样一条SQLSELECT username, password FROM users WHERE usernameadmin LIMIT 0,1SQL语句里出现了连续两个单引号字符串的闭合关系被打破整个语句语法错误。反映到页面上就是原有的账号信息回显区域空了或者页面表现异常。这个“页面异常”就是注入存在的信号。它还同时透露了另一个信息闭合方式是单引号因为多了一个单引号就破坏了语法结构。3.3 order by判断列数不凭感觉猜确认注入点之后下一步是判断查询返回的列数。这个环节我喜欢用order by而不是猜因为猜出来的数字往往不靠谱。把Cookie值改成admin order by 3--重放请求如果页面正常回显就说明当前查询的列数至少是3。然后继续把数字往上加admin order by 4--如果这次页面没有正常回显说明实际列数小于4。结合前一步的结果就能确定当前查询一共有3列。注意这里payload末尾的--作用非常重要。它把注入点后面的SQL内容整段注释掉包括原SQL语句里最后面的LIMIT 0,1。如果没有注释符号整个SQL会因为多出来的单引号和LIMIT而被MySQL判为语法错误前面的所有尝试都会失败。3.4 union select定位回显位再爆数据确认列数是3之后用union select来定位页面上的回显位置。为了让前面那段查询不干扰结果我会习惯性地在查询条件上加一个永假条件比如admin and 10 union select 1,2,3--这样第一个select查不出任何数据页面上显示的数据就完全来自union后面的第二个select。你会在页面的某个位置看到2另一个位置看到3这两个位置就是我们的“显示器”。接下来就是常规的信息收集流程我直接列一下完整payload爆当前数据库名admin and 10 union select 1,database(),3--爆当前库下所有表名admin and 10 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()--爆某张表的所有字段名以users表为例admin and 10 union select 1,group_concat(column_name),3 from information_schema.columns where table_name0x7573657273--这里顺便说明一下为什么表名要写成0x7573657273因为整个payload最终拼进SQL后字符串引号的处理容易出幺蛾子。直接写table_nameusers会多出一对单引号在某些闭合场景下会把SQL搞坏。用十六进制表示字符串既绕开了引号问题又不影响查询结果。取users表里的用户名和密码admin and 10 union select 1,group_concat(username,0x3a,password),3 from users--这里的0x3a是冒号的十六进制表示作用只是把用户名字段和密码字段在结果里拼成一个带分隔符的字符串。至此整套靶场的第20关就算完整走通了。你从“看到URL里没有参数”开始到最后把users表里的账号密码全部捞出来整个过程没有用到URL参数也没有用到POST参数完全靠修改Cookie完成。3.5 第20关最应该带走的东西第20关真正的增量不在于那几个SQL函数而在于建立一种认知注入点不一定以参数形式出现在URL或者请求体里任何能被后端取出并拼接进SQL的输入源都是潜在注入点。Cookie是其中最常见的载体因为很多人觉得Cookie是浏览器自动维护的不会有人去改它。把这一关的整套流程跑熟再去刷第21关和第22关你只需要关注两个新变量编码形式和闭合字符。4. 第21关实战Base64编码挡不住注入但挡得住“肉眼”4.1 第一次看到Cookie值是“乱码”时先别慌进入第21关重复第20关的登录流程登录成功后你会发现Cookie里的uname字段长得不一样了。它不再是admin而是一串看起来像乱码的东西比如YWRtaW4。这个字符串就是admin的Base64编码。Base64是什么简单说它是一种把二进制数据用64个可打印字符表示的编码方式。因为HTTP协议和很多系统设计上偏爱可打印字符Base64在实际系统里被大量使用。服务端拿到这个Base64字符串之后会先做一次解码再把解码结果拿去拼SQL$uname base64_decode($_COOKIE[uname]); $sql SELECT username, password FROM users WHERE username$uname LIMIT 0,1;看到这里你应该已经明白了Base64只是改变了数据传输时的外观服务端解码之后拿到的还是原始字符串。如果原始字符串里带着注入payload解码后依然会拼进SQL注入照样成立。4.2 正确姿势明文payload先编码再放进Cookie这一关最常踩的坑就是很多人直接拿第20关的明文payload往Cookie里一填比如admin union select 1,2,3--结果页面没有任何反应。原因很简单服务端拿到这个字符串后会先执行base64_decode。而admin union select 1,2,3--根本不是合法的Base64字符串解码出来的数据是乱码或者干脆失败拼进SQL之后自然也不会按你预期执行。正确顺序是先构造明文payload再对它做Base64编码然后把编码结果填进Cookie。比如明文payloadadmin and 10 union select 1,database(),3--对它做Base64编码得到类似这样的字符串YWRtaW4nIGFuZCAxPTAgdW5pb24gc2VsZWN0IDEsZGF0YWJhc2UoKSwzLS0r然后把Cookie里的uname值替换成这段编码串重放请求页面上就会出现当前数据库名。4.3 编码前后的完整对照与工具操作用命令行做Base64编码其实非常方便。在Linux或Mac的终端里执行echo -n admin and 10 union select 1,database(),3-- | base64Windows环境也可以用CertUtil或者在线工具只要确认编码结果一致就行。我平时会建一个小笔记把明文payload和编码结果成对记录下来。因为这关的payload经过编码之后根本没法肉眼阅读一旦多试几次很容易记混“这个编码串到底是哪条payload”。成对记录能省下大量排错时间。4.4 编码不等于安全的底层逻辑为什么Base64编码看起来很“安全”却挡不住注入关键在顺序服务端是先解码再拼接SQL。解码发生在拼接之前所以payload在到达SQL之前已经恢复成明文了。如果服务端的处理顺序反过来先拼接SQL再对整体做Base64编码那注入当然不成立但这个场景几乎不存在。真实系统里真正的安全机制应该是参数化查询、过滤函数、预编译语句而不是给数据包上一层“让人觉得看不懂”的编码。这一关学完你应该建立起一个明确的判断标准任何编码方案如果发生在输入校验之前都不会提供真正的安全性。它影响的只是你操作时的复杂度而不是漏洞是否存在。5. 第22关双引号加Base64三关里的最后一记组合拳5.1 先做编码再判断闭合顺序不能反第22关的界面和第21关非常相似登录后的Cookie同样是Base64编码的。很多人刷到这关时因为前两关已经形成了路径依赖直接用单引号payload一通操作发现怎么都不对。这里的差别在于后台拼接SQL时用了双引号。简化后的处理逻辑等价于$uname base64_decode($_COOKIE[uname]); $sql SELECT username, password FROM users WHERE username\$uname\ LIMIT 0,1;所以你在判断闭合方式时不能直接用明文单引号去试。正确做法是先把候选闭合字符编码成Base64再放进Cookie里重放观察页面反应。5.2 区分双引号和单引号的实测逻辑把Cookie里的uname改成admin即先构造明文admin再Base64编码提交服务端执行的SQL是SELECT username, password FROM users WHERE usernameadmin LIMIT 0,1这段SQL是语法合法的因为admin被双引号包起来单引号只是字符串内部的一个普通字符。所以页面不会报错只是查询结果为空表现为没有账号信息回显。再把Cookie改成admin同样先明文后编码提交服务端执行的SQL变成SELECT username, password FROM users WHERE usernameadmin LIMIT 0,1这里出现了两个连续的双引号第一个双引号结束了字符串第二个双引号又开启一个新字符串却没有对应的闭合引号整个语句语法错误页面立刻异常。对比这两个结果你就能确认这一关的闭合字符串是双引号不是单引号。我第二次刷这一关时曾经因为偷懒只试了单引号就直接套用第21关的payload结果浪费了很多时间。后来老老实实把两种引号都编码后重放一遍一句话就定位了问题。手工注入里“多试一遍”真的比“多想一遍”管用。5.3 双引号闭合的完整payload流程确认双引号闭合之后构造payload的思路和第20、21关完全一致只需要把闭合符从单引号换成双引号。取当前数据库名的完整操作过程如下先写明文payloadadmin and 10 union select 1,database(),3--对整条字符串做Base64编码echo -n admin\ and 10 union select 1,database(),3-- | base64命令行里别忘了对双引号做转义不然Shell会把它当成语法的一部分。编码结果填进Cookie的uname字段重放请求页面回显数据库名。后面爆表、爆字段、取数据的操作完全复用第20关的SQL写法只是闭合字符维持双引号。比如取users表的数据admin and 10 union select 1,group_concat(username,0x3a,password),3 from users--编码后提交一样能拿到账号密码。5.4 三关横向对比同一套后端逻辑三种难度层把三关放在一起看会很清晰第20关教你在Cookie里做最基础的字符型注入第21关告诉你数据经过编码后你的payload也要跟着编码第22关提醒你闭合字符可能和上一关不一样必须通过实测确认。这三关一起做完你对“HTTP数据源”和“SQL拼接”这两件事的理解会比只刷一个单关深入得多。我现在每次带新人刷这套靶场都会建议他们三关连着刷不要跳关也不要用工具一把梭。6. 从靶场走向真实世界Cookie注入的现实利用思路和防御对照6.1 真实系统里Cookie注入通常出现在哪理论上任何把Cookie值直接拼进SQL的系统都可能出现Cookie注入。现实中更常见的是这几种情况一是“记住我”功能把用户名或者用户ID直接写进Cookie下次访问时免登录直接取出来二是购物车或者临时偏好设置把商品ID或者选择项放在Cookie里后端根据这个值去查商品信息三是有些老系统的session机制设计得不规范把账号名直接存在Cookie而不是存一串随机的会话标识。这些场景的共同点就是把本应该在后端管理的数据直接交给了客户端浏览器。Cookie可以被用户任意修改任何被改动的值只要参与了SQL拼接就都有注入风险。6.2 防御层面对照编码不是边界参数化才是我自己做防御侧研究时最深的体会是拦注入不能靠“让输入变得难懂”必须从SQL执行机制入手。第一道防线是参数化查询或者预编译语句。把SQL结构固定住用户输入一律作为参数传递这样无论进来的是引号还是注释符都只是数据而不是SQL逻辑的一部分。第二道防线是输入校验。对确实需要拼进SQL的值做白名单校验比如预期是数字就必须是纯数字预期是特定枚举值就不允许出现其他字符。第三道防线才是各种编码和过滤。这类手段不是为了防注入而是为了增加攻击成本和减小攻击面。如果你把Base64当成安全机制很快就会被最简单的解码操作击穿。Cookie本身的安全设计也应该遵守同一个原则Cookie里只存会话标识一个随机字符串不要把用户名、角色、权限这些业务数据直接暴露给客户端。这样即使客户端修改了Cookie后端也查不到可拼接的业务字段注入自然无从谈起。6.3 一个必须说清楚的边界这三关的全部操作都应该在本地靶场或者你自己拥有明确授权的测试环境中进行。未经授权对任何线上站点做注入测试都是违法行为这个边界没有任何灰色地带。靶场的设计初衷就是让安全学习有地方可以放手练练完把思路带到防御工作中而不是把攻击手法应用到不该用的地方。7. 实操中我踩过的坑和值得留存的小技巧7.1 最容易被新手忽略的三件事第一件是登录态丢失。Cookie注入测试必须建立在已登录状态上因为服务端只有在登录后才把用户名写进Cookie也只有在登录态的后续请求中才会从Cookie取值拼SQL。如果你把Cookie改了之后页面跳回了登录页先检查登录态是不是被你自己改没了。第二件是注释符里的加号问题。--里的加号在某些工具或者某种处理环境下可能会被转换成空格导致注释符失效。遇到payload看起来没问题、但页面一直异常的情况可以试试把--换成-- 两个连字符加一个空格或者改成#在Cookie头里#不会像URL里那样被截断。第三件是Base64编码后的长度问题。手工复制编码串时经常漏掉末尾的等号填充。比如YWRtaW4少一个解码结果就变了。填进Cookie之后可能不报错但也不回显容易误导你对闭合方式的判断。7.2 手工复现为什么比自动化工具更适合这三关自动化工具在扫到URL参数时会很顺手但对Cookie注入的覆盖往往不够精准。原因是工具默认的测试目标集合里Cookie是一个很特殊的输入点——它同时承担着会话验证的功能如果工具改了Cookie却丢了登录态后面所有测试都会被登录页挡住。手工改包每次都知道自己改了什么、改完页面发生了什么变化排错链路非常清晰。这三关本质上难度不算高但信息密度大手工走一遍顶得上工具自动扫十遍。7.3 一个可以一直用的复盘习惯我现在刷靶场有个固定习惯每个关卡都建立一个备忘录记录三样东西——原始请求长什么样、我改了哪里、页面有什么反应。特别是第21关和第22关明文payload和编码后payload必须成对记录。这个习惯看起来朴素但真的是我多次刷关后总结出来的最省时间的做法。编码后的字符串没法肉眼阅读过两天再回头翻记录如果没有明文对照你根本想不起来当时测到哪一步。有了记录不仅复盘效率高还能随时拿出来对比不同闭合方式、不同编码条件下的行为差异。这三关刷完之后你对Cookie注入的理解应该已经不输给那些只会对着URL扫参数的人一大截。

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

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

免费获取报价 →
↑