资讯动态

SQL注入第一关实战:union联合注入从注入点判断到取数据的完整链路

发布时间:2026/10/17 4:54:17 来源:尧图企业网站定制
1. 为什么第一关反而是最值得反复琢磨的很多人搭好本地靶场环境之后第一关往往三分钟就过了然后急着往后刷。我自己早期也是这样觉得第一关太简单没什么可讲的。后来带新人复盘的时候才发现恰恰是第一关把 union 联合注入的完整链路暴露得最干净从判断注入点、猜列数、定位回显位、探数据库名、探表名、探字段名到最终取数据每一步都能在一关里跑通。把这关吃透后面那些关卡无非是换了过滤规则和注入位置而已。这篇内容面向的是刚接触 SQL 注入原理、正在用本地靶场练手的朋友也适合已经会跑工具但说不清手工流程的人。我会把第一关从零到取到数据的过程完整拆一遍重点讲清楚每一步为什么这么做、参数怎么定、遇到回显不对怎么排查。所有操作都在你自己搭建的本地练习环境里进行目的是理解注入的成因和防御思路不要对任何非授权目标做测试。先说清楚这一关的底层场景页面接收一个 id 参数直接拼进 SQL 查询然后把查询结果渲染到页面上。因为没有任何过滤和参数化处理我们输入的字符会原样进入数据库执行。union 联合注入的核心思路就是利用 union 可以把另一条 select 的结果拼接到原查询结果后面这个特性把原本查不到的数据“借”到页面上显示出来。理解这一点很关键。union 注入不是凭空造数据而是“搭便车”——原查询返回什么结构我们就得让附加查询返回同样的结构否则数据库直接报错。所以整条链路里猜列数、对回显位这两步是地基地基没打牢后面全是白费。2. 注入点判断与 union 注入的适用前提2.1 先确认这个参数到底能不能注入拿到一个带参数的页面第一步不是急着上 union而是先确认它是不是注入点。我习惯用最朴素的办法给参数分别传一个正常值、一个带单引号的值、一个数学表达式看页面反应。假设正常访问是?id1页面显示一个用户信息。接着试?id1如果页面报 SQL 语法错误或者内容明显异常基本可以判定单引号被带进了 SQL 语句。再试?id2-1如果页面显示的是 id1 的结果说明参数没被当成纯字符串处理而是参与了运算这进一步坐实了注入。这里有个细节很多人忽略报错不一定每次都出现。有些环境把错误关掉了页面只是空白或者显示“无结果”。这时候不能直接下结论说不能注入要换布尔判断的方式比如?id1 and 11和?id1 and 12对比返回内容是否一致。前者正常、后者异常说明 and 条件生效了注入点成立。提示判断注入点时单引号、双引号、括号都要试。不同写法对应的闭合方式不同第一关通常是数字型或字符型单引号闭合但养成都试一遍的习惯后面遇到复杂闭合才不会慌。2.2 union 注入成立需要满足哪些条件union 注入不是万能钥匙它有几个硬性前提。第一原查询和附加查询的列数必须一致这是数据库的语法要求不一致直接报错。第二页面上必须有回显位也就是原查询的某些列会显示在页面上我们才能把数据塞到那些位置。第三当前数据库用户要有对应库表的查询权限权限不够就算语句对了也取不到数据。第一关之所以适合入门就是这三个条件全都满足列数可以通过 order by 试出来回显位肉眼可见权限也是足够的。所以它成了理解 union 注入的最佳样本。我个人的经验是先判断注入类型再决定用 union 还是报错注入、布尔盲注。如果页面有回显、有报错union 是效率最高的如果页面什么都不显示那 union 就没法把数据带出来得换盲注思路。这个判断顺序别搞反否则会在错误的方向上浪费大量时间。3. 猜列数order by 与 union select 两种手法对比3.1 用 order by 二分定位列数确定注入点之后第一件正事是猜原查询有几列。最常用的手法是order by。它的原理是order by N表示按第 N 列排序如果 N 超过了实际列数数据库就会报“Unknown column”之类的错误。所以我们从 1 开始递增直到报错报错前那个数字就是列数。实际操作时访问?id1 order by 1、?id1 order by 2、?id1 order by 3……假设到 4 的时候页面报错那说明原查询是 3 列。为了快一点可以用二分法先试 10报错就说明列数小于 10再试 5以此类推。列数少的时候直接递增更直观列数多的时候二分更省事。这里有个坑有些环境对 order by 后面的数字做了处理或者页面本身不显示排序错误。这时候 order by 可能失灵得换 union select 来试。3.2 用 union select 直接试列数另一种办法是直接构造?id1 union select 1,2,3如果列数不对数据库会报“The used SELECT statements have a different number of columns”。我们不断调整数字个数直到页面正常返回那个个数就是列数。两种手法我一般配合用先用 order by 快速定位再用 union select 验证一遍。因为 order by 有时会因为排序字段类型问题产生误判而 union select 的列数校验是数据库层面的硬校验更可靠。手法优点缺点适用场景order by不改变回显内容判断干净部分环境屏蔽排序错误列数较少、报错可见union select数据库硬校验结果可靠列数不对时页面直接报错需要精确验证列数注意猜列数时不要一上来就写一大堆数字。列数猜多了报错猜少了也报错只有刚好相等才正常。所以要么递增要么二分别乱猜。4. 定位回显位让数据出现在你能看到的地方4.1 为什么原查询查不到数据反而更好用列数确定后下一步是找哪几列会显示在页面上。这里有个反直觉的技巧把原查询的 id 改成一个数据库里不存在的值比如?id-1或者?id99999。这样原查询返回空页面上原本显示用户信息的位置就空出来了union 附加查询的结果就会顶上去显示。如果原查询有结果union 的结果会拼在它后面页面上可能只显示第一条我们看不到自己塞的数据。所以让原查询“查空”是定位回显位的关键操作。这一步很多人第一次做会懵明明语句对了却看不到数据多半就是原查询还有结果占着位置。4.2 用数字标记回显位构造?id-1 union select 1,2,3页面正常返回后你会看到页面上某些位置显示了 1、2、3 中的某几个数字。这些数字出现的位置就是回显位。比如页面显示了 2 和 3说明第 2、3 列会渲染到页面上第 1 列不回显。回显位可能只有一个也可能有多个。多个回显位时我们通常选最靠前、显示最完整的那个来放数据因为有些位置可能被截断或者格式受限。第一关一般有两到三个回显位足够我们操作。提示如果页面显示的是 1、2、3 但顺序和位置对不上别慌那是页面模板决定的。你只需要记住哪个数字出现在哪个可见区域后面把查询语句替换到那个数字的位置即可。4.3 回显位选哪个更稳我一般优先选页面正中间、显示完整、周围没有多余字符干扰的那个位置。有些回显位旁边带着固定文案比如“欢迎 xxx”那 xxx 的位置就是理想的注入点。选好之后把2替换成database()如果页面显示出数据库名说明这个位置可用链路就打通了。这一步验证成功的那一刻其实 union 注入最核心的部分就完成了。剩下的都是在这个位置上换不同的查询函数把想要的数据一点点取出来。5. 从库名到数据information_schema 的实战用法5.1 先拿当前数据库名回显位确定后第一件事是查当前数据库名用database()。构造?id-1 union select 1,database(),3页面回显位显示的就是当前库名。这个库名很重要因为后面查表名、字段名都要以它为条件缩小范围。拿到库名后我习惯先记下来后面所有查询都围绕它展开。有些环境有多个库如果不限定库名查出来的表会一大堆干扰判断。5.2 查表名information_schema.tables数据库名有了接着查这个库里有哪些表。MySQL 系列数据库有一个系统库叫information_schema里面tables表记录了所有库表信息。构造查询?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()group_concat把多行结果拼成一行方便在单个回显位里看全。table_schemadatabase()限定只查当前库。页面回显出来的就是当前库所有表名用逗号分隔。如果回显位长度有限表名太多被截断可以加limit分批取或者用where table_schemadatabase() limit 0,1一条条看。第一关一般表不多一次就能看全。5.3 查字段名information_schema.columns表名拿到后挑一个看起来像存用户信息的表比如 users 之类。接着查它的字段?id-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_nameusers页面回显的就是 users 表的所有字段名。通常能看到 id、username、password 这类字段。到这一步数据结构就完全暴露了。查询目标关键函数/表限定条件当前库名database()无表名information_schema.tablestable_schemadatabase()字段名information_schema.columnstable_schema table_name具体数据目标表直接查指定字段5.4 取具体数据字段名有了最后一步就是取数据?id-1 union select 1,group_concat(username,0x3a,password),3 from users0x3a是冒号的十六进制用来把用户名和密码拼在一起显示比用逗号更清晰。页面回显出来的就是所有用户名和密码的组合。到这一步整个 union 注入链路就走完了。注意实际练习环境里取到的数据都是靶场自己造的假数据不要拿这套流程去碰任何真实系统。理解原理是为了知道怎么防不是为了去搞破坏。6. 实操中容易翻车的几个点6.1 列数对了但页面还是报错这种情况我遇到过好几次。列数明明和 order by 试出来的一致union select 却报错。排查下来通常是两个原因一是原查询用了select *实际列数比页面显示的字段多二是某些列的数据类型不兼容比如原查询某列是数字你 union 里塞了字符串数据库在类型转换时报错。解决办法是把 union 里的数字换成 null。null 可以匹配任何类型兼容性最好。构造?id-1 union select null,null,null如果正常返回说明列数对问题出在类型上再逐个把 null 换成具体查询即可。6.2 回显位显示不全或被截断有时候数据取出来了但页面只显示一半。这通常是页面模板对输出长度做了限制或者回显位本身宽度有限。应对办法是用substr分段取比如substr(database(),1,5)先取前五个字符再取后面的。也可以用group_concat配合limit分批。我一般先用length(database())看总长度再决定分几段取。虽然麻烦一点但能保证数据完整。6.3 单引号闭合方式判断错误第一关常见的是数字型注入不需要闭合引号。但如果你按字符型去闭合多加了一个单引号语句就错了。判断方法很简单传?id1正常传?id1报错说明原语句里 id 没有被引号包裹是数字型不需要闭合。如果传?id1正常、?id1报错那可能是字符型需要闭合。这个判断直接影响后面所有语句的构造搞错了后面全错。所以我在注入点判断阶段会反复确认闭合方式不急着往下走。6.4 常见问题速查表现象可能原因排查方向union select 报列数不一致列数猜错重新用 order by 或 null 法确认页面空白无回显原查询有结果占位把 id 改成不存在的值数据只显示一半输出长度限制用 substr 分段取语句报语法错误闭合方式不对确认数字型还是字符型查不到表名库名限定错误用 database() 确认当前库7. 从攻击视角反推防御思路把第一关完整走一遍之后其实最有价值的收获不是“会注入了”而是明白了漏洞是怎么来的。页面把用户输入的 id 直接拼进 SQL 语句没有做任何处理这就是根源。防御的核心就一句话让用户输入永远只是数据不能变成 SQL 代码的一部分。具体到工程实践参数化查询预编译是最有效的手段。它把 SQL 语句的结构提前固定好用户输入的值只能填进占位符无法改变语句结构。即使用户输入了单引号、union 这些字符也只会被当成普通字符串处理不会被执行。除了参数化查询还有几层辅助措施对输入做类型校验id 这种参数强制转成整数数据库账号遵循最小权限原则Web 应用连库的账号不给 information_schema 之外的敏感库权限关闭详细报错避免把数据库结构泄露给攻击者。这几层叠加起来union 注入基本就没有生存空间了。我自己在复盘时有个习惯每学一种注入手法就反过来想它在什么防御措施下会失效。union 注入怕参数化、怕类型校验、怕权限收紧。想清楚这些才算真正理解了这种漏洞而不是只会照着步骤敲语句。8. 我练第一关时踩过的几个坑第一次做第一关我在猜列数那步卡了很久。当时用 order by 一直试到 10 都没报错以为列数很多结果是我访问的 URL 里参数拼接写错了order by 根本没生效。后来发现是浏览器自动把空格编码了得用或者%20代替空格。这个细节很小但当时折腾了半小时。还有一次是回显位定位我死活看不到 union 的结果。排查半天才发现原查询的 id1 有数据union 结果被挤到后面去了。改成 id-1 之后立刻就出来了。这个“让原查询查空”的技巧现在成了我教别人的第一句话。最后一个坑是 group_concat 的长度限制。默认它有长度上限表名或字段名太多时会被截断看起来像是数据不全。解决办法是调大group_concat_max_len或者干脆用 limit 一条条取。我现在的习惯是只要用 group_concat就先确认一下会不会超长避免取到一半的数据还以为拿全了。这些坑说起来都不复杂但没踩过就是想不到。手工注入练的就是这种对细节的敏感度工具能帮你跳过这些但跳过之后你也就失去了排查问题的能力。所以我的建议是第一关至少手工完整走三遍每一遍都试着不看笔记直到能闭着眼睛说出下一步该干什么。

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

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

免费获取报价 →
↑