资讯动态

SQL注入手工实战:从类型判断到盲注的完整流程与踩坑记录

发布时间:2026/10/16 10:49:42 来源:尧图企业网站定制
1. 从一次手工注入踩坑说起SQL注入这个课题我在不同阶段反复回炉过很多次。最早接触的时候总觉得它无非就是单引号加or 11工具一跑就完事。直到后来在一个模拟靶场里被一个看似简单的数字型注入卡了整整一个下午才意识到自己对注入的理解有多浅。那次经历让我重新捡起手工注入的完整流程从判断注入类型、确定字段数、找显示位到逐步提取库名、表名、字段名最后拿到目标数据。整个过程走下来比用工具自动跑一遍收获大得多。这篇记录围绕的是SQL注入专项练习的完整思路。我会把每个环节的判断依据、操作意图、参数计算过程都摊开讲清楚而不是只给一个最终payload。适合已经了解SQL基本语法、想系统梳理手工注入流程的读者也适合做过一些题目但总是卡在中间步骤、不知道下一步该干什么的朋友。全文会涉及注入类型判断、联合查询注入、报错注入、布尔盲注、时间盲注这几大类每一类都会给出可复现的操作步骤和我自己踩过的坑。需要提前说明的是以下所有操作均在本地搭建的模拟练习环境中完成目的是理解漏洞原理、提升防御意识。任何未经授权的测试都是不合规的这一点必须先摆正。2. 注入类型判断别急着上payload2.1 为什么第一步永远是判断类型很多人拿到一个带参数的URL第一反应就是直接丢and 11和and 12看页面变化。这个思路没错但前提是你得先搞清楚参数是数字型还是字符型否则payload的构造方式完全不同。数字型注入不需要闭合引号字符型注入则需要先闭合前面的引号再注释掉后面的内容。如果类型判断错了后面的所有步骤都会跑偏。判断类型的核心逻辑其实很简单观察参数在SQL语句中的位置。假设后端拼接的语句是select * from users where id $input那$input就是数字型如果是select * from users where name $input那就是字符型。我们看不到源码只能通过构造不同的输入来推断。2.2 数字型与字符型的实操区分先试?id1 and 11和?id1 and 12。如果前者页面正常、后者页面异常或内容变化说明and被当作SQL语法执行了大概率是数字型。如果两者页面都一样可能是字符型因为1 and 11整体被当成了字符串的一部分。接着试?id1 and 11和?id1 and 12。如果前者正常、后者异常说明单引号成功闭合了原语句这是字符型注入的典型特征。这里有个细节有些环境会对单引号做转义返回500错误或者直接过滤这时候就要考虑宽字节注入或者其他绕过方式。我个人的习惯是先用?id1单独测一下看是否报错。如果报错信息里出现了SQL语法错误的关键词那基本可以确认存在字符型注入点而且报错信息本身可能就是一个信息泄露渠道。2.3 注释符的选择与踩坑字符型注入闭合引号后必须把后面的内容注释掉否则原语句的剩余部分会导致语法错误。常用的注释符有三种--注意后面有个空格、#、/**/。不同数据库对注释符的支持不一样MySQL里--和#都可以但--后面必须跟空格这一点很容易被忽略。我踩过的坑是在URL里传--的时候空格可能被浏览器或中间件处理掉导致注释失效。解决办法是用--或者%23#的URL编码。另外如果参数后面还有其他拼接内容#可能比--更稳妥因为它不需要空格。提示判断注入类型时每次只改变一个变量其他参数保持不变。同时注意观察页面返回的状态码、内容长度、响应时间这三个维度往往比页面文字更可靠。3. 联合查询注入字段数是绕不过去的坎3.1 用order by确定字段数确认注入类型之后下一步是确定当前查询返回的字段数。最常用的方法是order by?id1 order by 1--、order by 2、order by 3……直到页面报错或异常最后一个正常的数字就是字段数。这里有个经验不要从1开始一个一个试可以先试order by 10如果报错再往小试用二分法能省不少时间。比如先试10报错再试5正常再试7报错再试6正常那字段数就是6。当然题目环境通常字段数不多直接顺序试也无所谓。order by报错的原理是SQL引擎在执行排序时会检查排序的列是否存在。如果指定的列序号超过了实际返回的列数就会抛出错误。所以最后一个不报错的序号就是字段总数。3.2 用union select找显示位知道字段数后构造?id1 union select 1,2,3--假设字段数为3。页面会显示查询结果其中某些数字会出现在页面上这些位置就是“显示位”可以用来回显我们想要的数据。如果页面没有显示任何数字可能是原查询有结果union查询的结果被覆盖了。这时候把前面的id1改成一个不存在的值比如id-1或id0让原查询返回空union的结果就能显示出来。显示位不一定全部可用有些位置可能被模板限制或者做了处理。我一般会先把所有显示位都试一遍看哪些位置能完整回显字符串哪些只能显示数字。能显示字符串的位置优先用来提取库名和表名。3.3 提取库名、表名、字段名的完整链路拿到显示位后按以下顺序逐步提取信息查当前数据库名union select 1,database(),3--查所有数据库union select 1,group_concat(schema_name),3 from information_schema.schemata--查指定库的表union select 1,group_concat(table_name),3 from information_schema.tables where table_schema库名--查指定表的字段union select 1,group_concat(column_name),3 from information_schema.columns where table_name表名--查数据union select 1,group_concat(字段名),3 from 库名.表名--group_concat默认有长度限制通常是1024字节。如果结果被截断可以用substring分段读取或者用limit逐条读取。我遇到过表名特别多的情况group_concat只显示了一部分后来改用limit 0,1、limit 1,1逐条查才拿全。注意information_schema是MySQL自带的元数据数据库不同数据库的元数据表名不一样。如果目标不是MySQL需要换成对应的系统表比如SQL Server的sysobjects、Oracle的all_tables。4. 报错注入没有显示位时的突破口4.1 报错注入的适用场景联合查询注入的前提是页面有显示位。如果页面不显示查询结果只返回“正常”或“异常”两种状态联合查询就无能为力了。这时候可以尝试报错注入构造一个会让数据库主动抛出错误的语句把想要的数据夹带在错误信息里返回。报错注入的核心是利用数据库的某些函数在参数中嵌入子查询当函数执行出错时错误信息会包含子查询的结果。MySQL中常用的报错函数有extractvalue、updatexml、floor等。4.2 extractvalue与updatexml的实操extractvalue的语法是extractvalue(xml_document, xpath_string)。当xpath_string格式不正确时会报错并显示xpath_string的内容。构造payload?id1 and extractvalue(1,concat(0x7e,(select database()),0x7e))--0x7e是~的十六进制用来作为分隔符方便在报错信息中定位数据。报错信息大概长这样XPATH syntax error: ~库名~。updatexml的原理类似?id1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)--这两个函数在MySQL 5.1以上版本可用但在新版本中可能被限制。如果报错函数被过滤可以尝试floor报错、exp报错等其他方式。4.3 报错注入的长度限制与分段读取报错注入有一个明显的限制错误信息通常只能显示32个字符左右。如果database()的结果很短一次就能拿到但如果要查表名、字段名往往需要分段读取。分段读取用substring或mid函数?id1 and extractvalue(1,concat(0x7e,substring((select group_concat(table_name) from information_schema.tables where table_schemadatabase()),1,30),0x7e))--然后调整substring的起始位置从1、31、61……依次读取直到拼接出完整结果。这个过程比较繁琐但熟练之后也就是改几个数字的事。我个人的经验是先用length()函数确认目标字符串的总长度再计算需要分几段读取。比如表名总长度是85每段读30个字符那就需要读3段1-30、31-60、61-85。这样不会漏读也不会多读。5. 盲注没有回显也没有报错怎么办5.1 布尔盲注的判断逻辑如果页面既不显示查询结果也不返回报错信息只根据条件真假返回不同状态那就是布尔盲注。典型场景是?id1 and 11页面正常?id1 and 12页面内容变少或提示“不存在”。布尔盲注的核心思路是把“提取数据”转化为“判断真假”。比如要判断数据库名的第一个字符是不是a可以构造?id1 and substring(database(),1,1)a--如果页面正常说明判断为真如果页面异常说明判断为假。通过遍历所有可能的字符就能逐位猜出完整的数据。5.2 用二分法加速字符猜测逐个字符遍历效率太低。假设字符集是a-z0-9每个字符最多猜36次一个10位的库名就要猜360次。用二分法可以把次数降到log2(36)≈6次。二分法的逻辑是先判断字符的ASCII码是否大于某个中间值。比如判断substring(database(),1,1)的ASCII码是否大于109m的ASCII码?id1 and ascii(substring(database(),1,1))109--如果为真说明字符在m之后如果为假说明在m之前。每次把范围缩小一半6次左右就能确定一个字符。我一般会写一个简单的脚本来自动化这个过程但手工操作时也有技巧先确定字符的大致范围比如小写字母、数字、下划线再在这个范围内二分。不要一上来就猜特殊符号那样范围太大。5.3 时间盲注最后的兜底手段如果页面连真假状态都不区分无论输入什么都返回一样的内容那就只能靠时间盲注了。时间盲注利用的是数据库的延时函数比如MySQL的sleep()?id1 and if(substring(database(),1,1)a,sleep(3),0)--如果判断为真页面会延迟3秒返回如果为假立即返回。通过测量响应时间就能判断条件真假。时间盲注的缺点是效率极低每次判断都要等几秒。实际测试中我会把sleep的时间设短一点比如1秒同时用二分法减少判断次数。另外要注意网络波动的影响最好对同一个条件测两次确认时间差异稳定。提示时间盲注对网络环境要求较高如果目标服务器响应本身就不稳定很容易误判。建议先用sleep(0)和sleep(5)对比一下基线响应时间确认延时函数确实生效。6. 常见问题与排查技巧实录6.1 注入点判断的常见误区现象可能原因排查思路and 11和and 12页面一样参数是字符型未闭合引号尝试加单引号闭合加单引号后页面报错存在注入但注释符不对换用--或%23order by一直正常字段数比预期多加大数字继续试或用二分法union查询无回显原查询有结果覆盖了union把id改为负数或不存在的值报错函数无输出函数被过滤或版本不支持换其他报错函数或改用盲注6.2 过滤绕过的几个实用技巧有些环境会对关键字做过滤比如把select、union、and替换成空字符串。常见的绕过方式大小写混写SeLeCt、UnIoN双写关键字selselectect过滤一次后剩下select用注释分隔sel/**/ect用等价符号替代and换成or换成||空格换成/**/或%09用hex编码把字符串转成十六进制避免引号被过滤我遇到过最麻烦的一种过滤是把空格全部删掉导致union select变成unionselect。解决办法是用括号或注释代替空格union(select)或union/**/select。6.3 数据提取时的编码问题提取中文数据时经常会遇到乱码。原因是数据库的字符集和页面显示的字符集不一致。解决办法是用hex()函数把数据转成十六进制拿到后再转回字符串?id1 and extractvalue(1,concat(0x7e,hex((select 字段名 from 表名 limit 0,1)),0x7e))--拿到十六进制字符串后用在线工具或Python的bytes.fromhex()转回可读文本。这个方法虽然多了一步但能避免编码问题导致的数据丢失。6.4 盲注脚本的编写要点手工盲注效率太低实际测试中一般会写脚本自动化。脚本的核心逻辑先猜长度length(database())n再逐位猜字符ascii(substring(database(),i,1))mid用二分法缩小范围把结果拼接起来写脚本时要注意每次请求之间加一点延时避免触发目标服务器的频率限制对异常响应做重试把已经猜出的部分缓存下来避免重复请求。我用Python写过一版核心就是requests库发请求根据响应内容或响应时间判断真假。脚本本身不复杂关键是二分法的边界条件要处理好否则容易死循环。7. 从注入练习中真正该带走的东西做完这一轮SQL注入的专项练习我最大的感受是工具能帮你快速拿到结果但只有手工走一遍完整流程才能真正理解每个环节为什么这么做。判断注入类型是为了确定payload的构造方式确定字段数是为了让union查询能正常执行找显示位是为了建立数据回显通道报错注入和盲注则是在没有回显时的替代方案。每一步都有明确的意图不是机械地套公式。另一个体会是防御SQL注入其实比攻击简单得多。参数化查询预编译语句能从根源上杜绝注入因为SQL语句的结构在编译时就确定了用户输入永远只是数据不会被当作语法解析。我在实际开发中坚持一条原则任何拼接SQL字符串的地方都要改成参数化查询。如果实在做不到至少要对输入做严格的类型校验和白名单过滤。最后分享一个小技巧练习注入时养成记录每一步payload和对应响应的习惯。我一般会用一个表格记录“步骤、payload、响应特征、结论”这样卡住的时候回头翻记录往往能发现之前忽略的细节。这个习惯在排查其他类型漏洞时同样适用。

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

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

免费获取报价 →
↑