news 2026/10/19 8:52:33

SQL注入变形记:数字型、字符型与搜索型注入原理与防御

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL注入变形记:数字型、字符型与搜索型注入原理与防御

SQL注入这个话题,我在不同的安全社区里聊过很多次,每次都能遇到新人问同一个问题:明明学了各种注入技巧,但真拿到一个目标时,还是不知道该怎么下手。多数教程喜欢列一堆payload让人背,却很少讲清楚一个关键点——SQL注入的本质是“拼接”,而拼接的方式取决于后端代码怎么写。你看到的数字型、字符型、搜索型,说到底就是开发者把用户输入塞进SQL语句的不同姿势。

这篇文章我会把三种最常见的注入类型掰开揉碎讲一遍,包括它们的判断依据、构造思路、利用细节和修复方案,还会穿插一些我在模拟环境里踩过的坑。如果你正在系统学习Web安全,或者在做代码审计时想搞清楚“这里为什么能注入、那里为什么不行”,这篇文章应该能帮你把零散的知识串成一条线。文章里的所有例子都基于某本地靶场环境(某模拟项目X),使用的数据表和SQL语句我会尽量贴近真实业务场景,方便你照着复现。

1. 先把底层的逻辑讲透:注入为什么会“变形”

1.1 一切注入的根源:动态拼接SQL

SQL注入发生的前提,是后端代码把用户输入直接拼进了SQL语句。用个生活化的类比:你让室友帮你带杯奶茶,如果把“加珍珠”这三个字原封不动转告给店员,店员会照做;但如果室友在便签上写的是“加珍珠,另外再帮我带包烟”,而你整个把便签递给店员,那店员就会一脸懵——他既看到了奶茶需求,也看到了买烟指令。SQL注入就是那个“整个递便签”的过程:你以为用户只会输入普通内容,但攻击者输入的内容里夹带了数据库能执行的“额外指令”。

我们来看一个典型的动态拼接场景。假设后端代码是这样写的:

<?php $id = $_GET['id']; $sql = "SELECT * FROM products WHERE id = " . $id; $result = mysqli_query($conn, $sql); ?>

这段代码接收URL参数id,直接拼进SQL。这里的严重性在于:$id完全可控。用户输入1,SQL是WHERE id = 1;用户输入1 OR 1=1,SQL就变成WHERE id = 1 OR 1=1——此时条件恒真,整张表的数据全被查出来。而这种注入效果好不好、怎么构造,完全取决于那个id出现在SQL语句里的什么位置、以什么形态存在。所以才会出现数字型、字符型、搜索型这些不同“变形”。

1.2 三种注入类型的核心差异:闭合与拼接

很多人初学时会背“数字型不用引号、字符型要单引号、搜索型用LIKE”,但光背结论不够,你得理解背后的逻辑。

数字型注入的场景是:SQL语句打算把参数当数字处理,代码里直接拼接,不加任何引号包裹。此时攻击者输入的内容不需要“突破引号包围”,直接参与到SQL语法里即可。

字符型注入的场景是:开发者用引号把参数包起来了,比如WHERE username = '$name'。此时用户输入被当成字符串的一部分。攻击者要先输入一个单引号,把SQL语句里预设的引号“闭合”掉,这样才能逃出字符串字面量的限制,让后面的内容变成真正的SQL语法。

搜索型注入则更隐蔽。它往往出现在搜索框中,SQL语句形如WHERE title LIKE '%$kw%'。这里的参数不仅被引号包裹,还被百分号包裹。攻击者输入的内容需要同时考虑引号闭合和百分号闭合,而且因为LIKE的特殊性,还可能利用通配符做模糊匹配的绕过。

这三种类型本质上是同一个漏洞在不同拼接方式下的表现,这就是“变形记”的含义。下面我按类型逐一拆解。

2. 数字型注入:最基础的“裸奔”场景

2.1 判断数字型注入的两个探测动作

拿到一个参数,该怎么确认它是不是数字型注入?记住一个核心思路:让真值和假值分别出现,观察页面反应是否不同。

原文里提到了对URL参数直接尝试and 1=1和and 1=2,这确实是数字型注入最常见的验证手法。假如一个商品详情页的URL是:

http://example.com/product.php?id=1

第一次构造:

http://example.com/product.php?id=1 and 1=1

因为1=1恒真,原来的条件不会受影响,页面正常显示商品1的信息。

第二次构造:

http://example.com/product.php?id=1 and 1=2

因为1=2恒假,整个WHERE条件变成WHERE id = 1 AND FALSE,查询结果为空,页面可能显示“商品不存在”或直接空白。

两次结果不同,基本可以确定存在数字型注入。这里有个细节:页面差异不明显时怎么办?我习惯在后面跟一个-- -把后续内容注释掉,或者用and配合耗时函数(如sleep(5))来判断,但最稳妥的方式还是先做“真值/假值对比”,因为延迟判断容易受网络抖动影响。

2.2 从判断到利用:逐个猜解字段与数据

确认注入点后,常规操作是猜字段数。这一步的目的是为后面的UNION SELECT做准备,因为UNION要求前后两个查询的列数一致。

猜字段数可以用ORDER BY:

http://example.com/product.php?id=1 ORDER BY 1 http://example.com/product.php?id=1 ORDER BY 2 ...

当ORDER BY n报错或页面异常,说明当前查询的字段数小于n。比如ORDER BY 5正常、ORDER BY 6报错,就说明原查询有5个字段。

确认字段数后,用UNION SELECT回显数据:

http://example.com/product.php?id=-1 UNION SELECT 1,2,3,4,5-- -

注意这里我把id改成了-1,目的是让前面的查询结果为“空”,这样UNION后面的数据才会出现在页面上。如果保持id=1,前面的查询本来就有数据,UNION会把结果合并在一起,可能只看到第一行,我们就看不到注入的数据了。这个细节特别容易被新手忽略。

然后看页面回显了哪个数字,比如页面显示3和5,说明这两个位置可以替换成我们想要的查询函数或字段。于是构造:

http://example.com/product.php?id=-1 UNION SELECT 1,2,database(),4,version()-- -

就能把当前数据库名和数据库版本都显示出来。

2.3 实战中数字型注入的防护心得

数字型注入在真实代码中相对少见了,因为稍微有点安全意识的开发者都会给参数做类型判断或参数化查询。但我在一些遗留系统、工控系统、内部管理后台的代码里还是经常见到它的影子。

最有效的修复方式是把输入强制转为整数:

<?php $id = (int)$_GET['id']; $sql = "SELECT * FROM products WHERE id = " . $id; ?>

或者直接用参数化查询:

<?php $stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?"); $stmt->execute([$_GET['id']]); ?>

注意:单纯用intval()或(int)强转会丢失精度吗?在PHP里,如果传入的是超大整数,强转后可能变成浮点数或溢出,但那不影响安全。你要关注的是:强转之后,任何非数字字符都会被截断,所以1 OR 1=1这样的输入会变成1,注入自然失效。

3. 字符型注入:突破引号包围的攻防较量

3.1 为什么多了一个引号,难度就上了一个台阶

字符型注入的典型代码长这样:

<?php $name = $_GET['name']; $sql = "SELECT * FROM users WHERE username = '$name'"; ?>

此时SQL语句预先把单引号写死了,用户输入的任何内容都被视作字符串字面量。如果用户输入admin,SQL是WHERE username = 'admin',没问题。但如果用户输入admin' OR '1'='1,拼接后的SQL变成:

SELECT * FROM users WHERE username = 'admin' OR '1'='1'

注意看,用户输入里的第一个单引号把前面那个预设的左引号“闭合”了,随后的OR '1'='1'变成了真正的SQL逻辑,而末尾的右引号则由用户输入里的最后一个'1'来配对。最终条件恒真,查询返回全部用户。

这里的核心逻辑是“闭合与配平”。你把每个单引号看作一对括号:开发者预留了一个左括号和一个右括号,攻击者要做的是输入一个额外的左括号(即单引号),然后用自己的内容替代原本右括号的位置,让整个语句保持语法完整。

3.2 注释符号:让后半段“消失”的魔法

在真实注入中,我们经常不想让语句末尾那部分继续参与解析。比如原始SQL可能是:

SELECT * FROM articles WHERE author = '$author' AND status = 1

如果注入点在$author,用户输入:

xxx' OR 1=1-- -

拼接后:

SELECT * FROM articles WHERE author = 'xxx' OR 1=1-- -' AND status = 1

-- -后面加一个空格(MySQL要求在--后至少跟一个空白字符),会把' AND status = 1整体注释掉,这样就不用管原来的业务限制了。

-- -不是唯一选择,#在MySQL里也可以注释。不同数据库的注释符有差异:MySQL支持--和#,SQL Server支持--,Oracle只支持--。这点在实战中容易被忽视,用错注释符会直接导致语句语法报错,页面报数据库异常信息(不过这反而暴露了数据库类型,也不算全无收获)。

3.3 字符型注入的常见绕过思路

对字符型注入,我补充几种值得了解的绕过思路。

单引号被过滤时,可以看看开发者是怎么过滤的。如果只是简单把单引号替换成空字符串,那么可以尝试宽字节注入——利用数据库字符集不一致,让替代转义符的\被“吃掉”,从而使单引号生效。经典的构造是在%df或%bf',原理是老版本MySQL中GBK多字节字符的尾部字节与反斜杠形成合法字符,导致转义失效。新版本MySQL默认utf8mb4基本不受影响,但我在某些老旧教育系统里确实见过这种漏洞。

引号完全被转义时,如果存在数字型参数入口,可以考虑用0x...十六进制形式代替字符串。比如WHERE name = 'admin'可以写成WHERE name = 0x61646D696E,这样不需要引号也能表示字符串。同理,很多WAF对UNION SELECT的关键字敏感时,可以用内联注释/*!50000UNION*/或者大小写混合来绕过,但这属于WAF绕过范畴,这里不展开。

3.4 修复字符型注入:关键是参数化,不是加引号

很多人第一反应是“在输入里再加一个引号把它转义”。这种思路有历史渊源:addslashes()函数就是为了转义单引号、双引号、反斜杠而存在的。但问题在于,转义方案依赖数据库字符集和上下文,一旦遇到宽字节或编码不一致的情况就可能被击穿。

我在实际项目中见过一个典型案例:某开发团队自己封装了一个过滤函数,把'替换成\',同时把所有用户输入用该函数过滤。表面上看字符串被转义了,但如果后台数据库使用GBK编码,攻击者输入%bf%27时,%bf和\会组成一个合法的GBK字符,后面的单引号就“独立”出来了,过滤直接失效。

所以最稳妥的修复方式是放弃手工拼接,改用参数化查询:

<?php $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$_GET['name']]); ?>

参数化查询的核心原理是:SQL语句的结构和参数数据分通道传输,数据库先解析SQL结构,再把参数单纯当作数据值绑定进去。用户输入永远无法改变SQL语句的结构,注入自然无效。这个机制才是真正的“免疫”,而不是“防御”。

4. 搜索型注入:藏在LIKE里的特殊“变形”

4.1 搜索框用LIKE,注入思路要跟着变

搜索型注入通常出现在站内搜索功能里。典型代码:

<?php $kw = $_GET['keyword']; $sql = "SELECT * FROM news WHERE title LIKE '%$kw%' ORDER BY id DESC"; ?>

这种查询的特点有两个:参数被单引号包围的同时,还被百分号包围。百分号是LIKE子句里的通配符,能匹配任意长度字符串。所以注入时除了要闭合引号,还要考虑怎么处理百分号。

构造一个基础测试:

http://example.com/search.php?keyword=1' and '1'='1

拼出来的SQL是:

SELECT * FROM news WHERE title LIKE '%1' and '1'='1%' ORDER BY id DESC

解析一下拼接结果:'%1'是LIKE的匹配字符串(搜索包含“1”的标题),紧接着的and '1'='1是恒真条件,而末尾的'1%'实际上是原SQL里右引号后面的%加上新的'配对出来的字符串——如果你仔细配平引号的话,会发现整个语句语法竟然是合法的,而且条件恒真。这里的关键在于,攻击者输入的内容中,单引号的配对点被巧妙地转移了。

如果换成1' and '1'='2,页面就没有搜索结果了,说明注入判断生效。与数字型不同的是,搜索型注入用两种结果判断时,你要观察的是“有无结果”而不是“是否报错或正常显示”。

4.2 搜索型注入的盲注思路

搜索功能往往不直接回显数据库内容,只显示搜索结果列表。此时直接UNION SELECT可能看不到具体值,适合用盲注思路处理。原文标题里提到了“(25)”,估计是一个系列教程的第25篇,前面应该已经讲过盲注的基础,这里我就针对搜索场景提几个关键点。

布尔盲注的判断方式:在搜索关键词后面拼接一个子查询,让它返回真或假,观察搜索结果是否存在。

http://example.com/search.php?keyword=1' AND (SELECT SUBSTRING(database(),1,1))='t'-- -

如果当前数据库名第一个字符是t,搜索结果正常;否则结果为空。用二分法或者逐个字符枚举,就能把数据库名、表名、字段名、数据一步步拖出来。

时间盲注也是常用的备选方案,特别是页面没有明显差异时:

http://example.com/search.php?keyword=1' AND SLEEP(3)-- -

如果页面明显卡了3秒,说明条件成立。这里有个实操细节:SLEEP()能否执行还取决于当前数据账号的权限,但绝大多数情况下,注入点既然能执行查询,执行SLEEP()也没问题。

注意:盲注虽是“慢工出细活”,但效率并不像想象中那么低。我在模拟环境里用脚本批量枚举时,一个8位的库名大约一两分钟就能跑出来。手工测盲注时建议先确定字符集和大小写规则,否则枚举的效率会差很多。实际业务中很多人会用现成的自动化工具跑盲注,但我始终建议你至少手写一次脚本,这样才能真正理解盲注的请求-响应-判断循环。

4.3 搜索型注入的“通配符陷阱”

搜索型注入还有一个其他类型没有的特性:通配符本身的利用。既然LIKE里支持%和_,攻击者在输入里直接塞一个%,就能让搜索范围扩大。比如输入%,SQL变成LIKE '%%%',此时查询可能返回所有数据——这不算注入,但会造成信息泄露面扩大。

更有意思的是,如果开发者过滤了单引号但没过滤%,输入%' OR '1'='1很可能被过滤单引号的操作挡掉,但输入%admin%却能旁路匹配到包含“admin”的所有内容。这类通配符滥用对搜索功能来说是一个容易被忽略的信息泄露路径。我在一次代码审计中发现,某系统的搜索框没有注入漏洞(因为用了参数化),但业务方完全没限制通配符,攻击者输入一个%就拖走了整个商品表,这就是典型的“没有注入漏洞但有逻辑漏洞”。

修复搜索型注入时,除了参数化查询,还要考虑对%、_这两个通配符做转义处理。在参数化查询中使用LIKE时,可以这样写:

<?php $kw = '%' . addcslashes($_GET['keyword'], '%_') . '%'; $stmt = $pdo->prepare("SELECT * FROM news WHERE title LIKE ?"); $stmt->execute([$kw]); ?>

这里addcslashes会把%转义成\%,把_转义成\_,让它们不再具备通配符语义。这个细节很多开发不知道,他们把参数化一加就完事,结果通配符注入仍然存在。

5. 实操演练:三种注入在一个模拟环境里完整走一遍

5.1 本地靶场环境怎么搭

前面讲了很多原理,现在我们把三种注入放到同一个模拟项目X里跑一遍。我用的环境是:PHP 7.4 + MySQL 5.7,跑在本地容器里,数据库里建了一张商品表products和一张用户表users。

搭建时我用了一个简化版的PHP站点,模拟了三个页面:

  • product.php?id=1:数字型注入点
  • login.php?name=admin:字符型注入点(模拟登录处传参)
  • search.php?keyword=手机:搜索型注入点

你如果想知道具体的建表语句和页面代码,可以参考我前面给出的代码框架,自己组装一套就行。关键是要在可控环境里反复试,不要拿真实站点练手,这是红线。

5.2 数字型注入的完整操作记录

我先访问product.php?id=1,页面正常显示“商品:手机,价格:1999”。

然后尝试id=1 and 1=1,页面仍然正常;改为id=1 and 1=2,页面空白——这里没有报错,而是没有查询结果,说明条件为假时无数据返回。我判定这是数字型注入。

确认注入点后,用ORDER BY猜字段数。按顺序试到ORDER BY 6时页面报错,说明原查询有5个字段。接着用UNION SELECT探回显位置:

product.php?id=-1 UNION SELECT 1,2,3,4,5-- -

页面显示了2和5,也就是说回显位是第2和第5列。于是:

product.php?id=-1 UNION SELECT 1,database(),3,4,user()-- -

页面直接显示数据库名是shopdb,数据库用户是root@localhost。这说明当前连接数据库使用的是高权限账号。利用这个权限,我可以继续读文件或者直接into outfile写webshell,但在本地靶场我是点到为止。

5.3 字符型注入的完整操作记录

接着测试login.php?name=1' and '1'='1,页面返回“登录成功”的假象——因为条件恒真,查询返回了第一行用户记录。改为1' and '1'='2,页面提示“用户不存在”。判断为字符型注入。

字符型注入在这里的回显不太直观,因为登录成功与否只是布尔状态。我改用盲注思路,验证数据库名长度:

login.php?name=1' AND (SELECT LENGTH(database()))=6-- -

如果页面显示“用户不存在”,说明长度不等于6;试到=7时页面显示“登录成功”(注意这里的登录成功是注入构造出来的,不是真正的登录成功),说明数据库名长度是7。逐个猜字符后得到shopdb。

这个例子也说明:很多情况下,字符型注入点在业务页面上没有直接数据回显,盲注是必经之路。平时练习时不要老想着一次UNION SELECT搞定一切,要把布尔盲注的手法练熟。

5.4 搜索型注入的完整操作记录

最后测试search.php?keyword=1' and '1'='1,页面正常显示搜索结果列表;改为1' and '1'='2,页面显示“没有找到相关内容”。判定为搜索型注入。

我尝试直接UNION SELECT:

search.php?keyword=1' UNION SELECT 1,2,3,4,5-- -'

等等,这里要注意一个问题:搜索型注入的SQL语句结构是LIKE '%$kw%',如果我在输入末尾写-- -,被注释掉的部分包括原SQL的'%和后面的半截语句。但如果输入末尾写'且没有注释符,就需要保证单引号配平。我的经验是,直接拼1' UNION SELECT 1,2,3,4,5-- -并不总能成功,因为UNION的列数必须与原查询一致,而这取决于原SELECT里到底查了几个字段。先跑ORDER BY来判断列数比较靠谱。

实测结果:搜索表有5个字段,回显位在第3列和第4列。于是我构造:

search.php?keyword=1' UNION SELECT 1,2,database(),4,5-- -

页面在搜索结果列表里多了一条带数据库名的记录,注入成功。

这里还有个小坑:因为LIKE里带了%,当UNION SELECT的数据与前面的查询结果合并时,数据库名可能会被当作标题展示,但有时会被截断或转义。页面编码如果不一致,中文内容还会乱码。遇到乱码不用慌,检查一下页面charset和数据库连接字符集设置,通常是character_set_client的问题。

6. 常见问题与排查技巧实录

6.1 为什么页面没有报错,但注入还是不成功?

这种情况最常见的两个原因:一是开发者开启了display_errors=Off,SQL报错被吞了;二是查询结果为空时页面模板渲染逻辑不同,导致你以为注入失败。判断方法是:先看正常的请求返回什么,再分别构造and 1=1和and 1=2,如果两种请求返回不同,就说明“真/假”可控,即使不报错,也能走盲注。

另一个容易被忽略的问题是:参数在传进SQL前被做了二次编码。某些框架会自动对参数做addslashes或mysqli_real_escape_string处理。这时候你看到的注入点是“假注入点”,单引号被转义了,直接构造'会变成\'。要学会看后端代码的过滤逻辑,不能只盯着前端参数名。

6.2 三种注入类型判断混淆怎么办?

初学者最容易把字符型和搜索型搞混,因为它们都需要闭合引号。我的判断口诀是:

  • 参数值直接跟在等号后面,没有引号包裹 → 优先考虑数字型
  • 参数值被引号包裹,但搜索框里没有模糊匹配需求 → 字符型
  • 参数值被引号和百分号双重包裹,通常出现在搜索场景 → 搜索型

具体操作时,先用and 1=1/and 1=2测布尔差异,如果差异成立,再尝试闭合。字符型闭合用'就够,搜索型还需要考虑%的位置。

这里有一个辅助判断的技巧:搜索型注入里,如果你输入一个不存在的词,比如zzz不存在xyz',看页面的搜索结果提示。如果提示“没有找到相关内容”,说明后端逻辑是“有结果才显示列表、无结果显示空状态”。这种页面反馈本身就对应了布尔盲注里的False状态。

6.3 参数化查询后就一定安全了吗?

不一定。我在实际项目中遇到过几种“参数化后仍然被注入”的情况:

  • 部分拼接片段:SQL主体用了参数化,但排序字段、表名、列名仍用字符串拼接。排序字段注入(ORDER BY注入)就是一个经典变种,因为列名不能被参数化。此时只能用白名单校验。
  • 存储过程内部拼接:存储过程接收参数后,内部用动态SQL重新拼接,绕过参数化保护。
  • 框架查询构造器的误用:某些ORM框架允许传入“裸条件”,如果你把用户输入直接传给whereRaw或havingRaw,实际上还是拼接。

所以代码审计时,不能只看有没有用prepare,要追整个SQL语句的所有组成部分。安全不是加个函数就完事,而是要看数据流和控制流。

6.4 注入点确认了,但拿不到数据怎么办?

这种情况多出现在权限不足的场景。比如当前数据库账号只对某几张表有查询权限,information_schema可能被禁用,UNION可能因为列类型不匹配报错。我的排查顺序是:

  1. 确认数据库账号权限,用current_user()看实际用户
  2. 确认注入点所在查询的列数,用ORDER BY
  3. 确认回显位置,用无害的UNION SELECT 1,2,3...
  4. 如果UNION不能回显,改走盲注或报错注入(比如extractvalue、updatexml)
  5. 最后考虑是否可以通过into outfile写文件,但这需要FILE权限且知道网站绝对路径

有时候就算有UNION,注入的表和原表的列数一致,但列的数据类型不匹配,比如原查询第1列是整数,我UNION SELECT的第1列是字符串,数据库会报数据类型错误。这时要调整UNION里各列的类型,或者用null占位。

7. 排查速查表与防御修复对照

我把三种注入类型的关键信息整理成一张对照表,方便你快速查阅。

对比维度数字型注入字符型注入搜索型注入
SQL特征WHERE id = $idWHERE name = '$name'WHERE title LIKE '%$kw%'
判断关键参数直接参与数字运算需闭合单引号需同时处理引号和百分号
典型探测and 1=1/and 1=2' and '1'='11' and '1'='1
主要利用方式UNION、报错、盲注UNION、布尔盲注、时间盲注UNION、布尔盲注、通配符滥用
常见修复强制类型转换、参数化参数化查询参数化 + 通配符转义

防御层面,我再强调几个实战中容易漏掉的修复点:

  • 输入校验不能只做黑名单。过滤select、union这些词很容易被大小写、注释符、十六进制编码绕过。更靠谱的是“类型白名单 + 参数化”。
  • 数据库账号权限要收缩。经常看到站库配置用root连库,这种高权限账号一旦注入成功,攻击者能做的事情就太多了。正确的做法是给Web应用单独建账号,只授权SELECT/INSERT/UPDATE/DELETE,并限制可操作的库表范围。
  • SQL语句的结构和参数分离。凡是属于SQL语法结构的部分(如表名、列名、排序方向),必须用白名单校验;属于数据值的部分,用参数化绑定。
  • 日志和监控要跟上。注入攻击的请求往往有明显特征,比如URL里带'、UNION SELECT、sleep(等关键词。WAF和数据库审计日志结合使用,能提前发现问题。

8. 写这篇文章的实操体会

把三种注入类型放在一起对比着讲,是我在实际学习和测试过程中逐渐形成的思路。早期我学SQL注入时,总想找一套“万能payload”,后来发现不同场景下的构造方法差异极大,真正有用的不是记住某个payload,而是理解“用户输入如何在SQL语句中参与拼接”这个底层机制。

在这个模拟项目X里反复测试时,我印象最深的是搜索型注入的单引号配平问题。第一次构造payload时,我自作聪明地在后面多加了一个百分号,结果语法一直报错。后来我把拼接后的完整SQL语句打印出来看,才发现问题出在引号和百分号的配对关系上。这个经验让我养成一个习惯:遇到注入不成功,先把拼接后的SQL语句完整还原出来,而不是盲目换payload。

另外一个建议是:练习时多用不同的输出方式。你可以在本地代码里临时加一条var_dump($sql)来查看真实拼接结果,这样能更快地理解漏洞成因。生产环境里看不到SQL语句,但本地练习时一定要把SQL打印出来看,这是建立直觉最有效的办法。

SQL注入的“变形”并不神秘。数字型、字符型、搜索型只是同一漏洞在不同代码写法下的外在表现,一旦你看懂了拼接点在哪里,就能自如地构造payload,也能更稳妥地写出修复代码。希望这篇文章能对你的学习有点帮助,接下来你可以去靶场环境里亲手复现一遍,把每个步骤跑通。等到自己独立发现一个注入点并成功拿出数据时,你会觉得之前的钻研都值得。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 8:52:29

Spark任务报错排查与性能优化:从OOM、Shuffle到数据倾斜的实战指南

这些年调试Spark任务&#xff0c;我听得最多的话就是“代码在本地跑得好好的&#xff0c;一上集群就崩”。屏幕上的英文报错一长串&#xff0c;有的是Timeout&#xff0c;有的是OOM&#xff0c;有的是压根不知道哪里来的Exception。大多数时候&#xff0c;问题不在业务逻辑&…

作者头像 李华
网站建设 2026/10/19 8:50:32

开源代码审查工具open-code-review:设计原理与实战部署

我一直在想一个问题&#xff1a;为什么很多团队明明上了CI、跑了测试、做了自动化扫描&#xff0c;线上还是会出现那些本可以被人类肉眼拦住的低级 bug&#xff1f;后来我想明白了&#xff0c;自动化工具擅长的是“已知问题的重复检查”&#xff0c;而代码审查解决的是“未知问…

作者头像 李华
网站建设 2026/10/19 8:50:20

requests大文件下载实战:流式写入、断点续传与并发分段

简介&#xff1a;Python开发者常用的requests模块完整资源包&#xff0c;面向因urllib2的API过于繁琐而希望简化HTTP请求代码的Python学习者与工程师。该工具库以“让HTTP服务人类”为设计理念&#xff0c;提供了更直观的请求发送、会话维持、重定向跟踪、超时与异常处理等封装…

作者头像 李华
网站建设 2026/10/19 8:49:51

工业反应器设计案例:从搅拌选型到换热与安全控制

1. 项目概述搞工业反应器设计这事&#xff0c;乍一听像是化工专业的老法师才碰的活儿&#xff0c;但真上手了会发现&#xff0c;它其实是“工艺、机械、控制、安全”四门功课的交叉口。标题里的“工业反应器设计案例”&#xff0c;往大了说可以是一整套年产几十万吨的连续搅拌釜…

作者头像 李华
网站建设 2026/10/19 8:49:38

Diagram Design:系统性思维的视觉契约与可执行设计方法

1. 为什么“diagram-design”不是画图&#xff0c;而是一场系统性思维重构最近在几个跨团队协作项目里反复遇到一个现象&#xff1a;需求文档写得密密麻麻&#xff0c;流程图也画了七八张&#xff0c;可开发一动手就卡在“这一步到底该谁触发”“这个判断分支有没有遗漏边界条件…

作者头像 李华
网站建设 2026/10/19 8:49:37

从自研CRM到业务增长:销售管理系统的数据模型与性能优化实践

1. 从“随便用个CRM”到动手写DeskcommCRM&#xff1a;这中间发生了什么1.1 市面上一堆CRM&#xff0c;为什么还是想自己折腾先说说背景。我们是一个二十来人的销售型团队&#xff0c;业务是典型的B2B项目制销售&#xff0c;客户从线索到成交流转周期长&#xff0c;涉及售前、销…

作者头像 李华