news 2026/10/20 21:51:45

pikachu靶场实战:数字型SQL注入原理与完整利用链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pikachu靶场实战:数字型SQL注入原理与完整利用链路

做安全测试的人应该都有过这样的经历:拿到一个参数,下意识先输入一个单引号去试探,结果页面直接甩出一个数据库语法错误。你要是以为这是“有戏”的信号,那就错了——这恰恰说明你连最基础的注入类型都没分清。我在pikachu靶场里练习数字型注入的时候,就是被这个细节坑过一次,才真正把“先判断类型、再构造payload”的原则刻进脑子里。

pikachu靶场是一套开源的Web漏洞练习平台,里面把SQL注入、XSS、CSRF这些入门漏洞按模块拆开,非常适合新手在授权环境里做测试练习。数字型注入是它的SQL注入模块里第一个关卡,看起来最简单,但你要是把它吃透了,后面字符型、搜索型、盲注的整套思路都能顺下来。这篇文章我会从原理、判断方法、完整利用链路,再到常见坑点和防御修复,把这一关的练习过程完整梳理一遍。适合刚开始学Web安全的人,也适合带团队做基础安全培训的人用来校准思路。

1. 数字型注入的原理与判断思路

1.1 SQL注入的本质

要说清楚数字型注入,先得回到SQL注入的本质。数据库查询语句本身就是“字符串拼接+代码执行”的组合,很多老系统在写业务逻辑时,习惯直接把用户输入当成SQL片段拼进去。比如一个查询用户信息的接口,开发者写了类似这样的代码:

$id = $_GET['id']; $query = "SELECT username, email FROM users WHERE id = $id";

问题就出在:$id是一个完全由用户控制的变量,如果用户输入的不是一个普通数字,而是1 and 1=1这样的内容,最终执行的查询就变成了:

SELECT username, email FROM users WHERE id = 1 and 1=1

数据库会把这整句话当成一条合法的SQL去执行。用户输入从“数据”变成了“代码的一部分”,这就是注入的本质。

打个比方,就好比你在一张邀请函上留了一栏“来宾姓名”,结果来的人不只是写了自己的名字,还在同一栏里写了“请把通讯录里前一百个人的名单一起拉来”。邀请函的发放逻辑没变,但名单扩展了,事情就失控了。防御SQL注入的核心,其实就是在“数据”和“代码”之间划清界线。

1.2 数字型和字符型的边界差异

很多新手分不清数字型注入和字符型注入的区别,其实关键就一个字:引号。观察这两类查询语句的写法:

// 字符型注入 $query = "SELECT username, email FROM users WHERE username = '$username'"; // 数字型注入 $query = "SELECT username, email FROM users WHERE id = $id";

字符型的参数外面有单引号包裹,你要注入,就得先想办法把前面的单引号“闭合”掉,否则你的payload会被当成一个普通字符串,注入逻辑不会生效。数字型注入完全不一样,id = $id这里没有引号,参数直接裸放在表达式里,你只要破坏原有的数字条件,再拼接新的逻辑进去就行。

这个看似微小的差异,决定了后续所有判断方法。你在字符型注入里习惯性的1'试探,用在数字型注入上,只会造成一个直接的SQL语法错误,然后页面白屏或者报错。如果你看到这个报错就慌神,很容易做出“不存在注入”的错误判断。所以这第一步,先看参数类型。

1.3 一套可靠的判断流程

我在靶场练习中总结了一套不会出错的判断流程,一共三组请求:

第一组:输入正常参数,比如1,观察页面返回。正常情况下,靶场会显示一条用户记录,包含ID、用户名、邮箱这些字段。

第二组:输入1 and 1=1,观察页面返回。这里最关键的是看响应是否和第一次完全一致。SQL语句变成WHERE id = 1 and 1=1,整个条件是“真与真”,结果自然还是那条记录。

第三组:输入1 and 1=2,观察页面返回。SQL语句变成WHERE id = 1 and 1=2,1=2是假,整个条件为假,页面应该没有任何记录,或者返回一个空结果。

如果第二组和第三组的页面状态有明确差异,那基本可以断定:这个参数被干净地拼进了SQL语句的条件里,而且没有任何类型约束。配合上参数本身没有引号包裹这个事实,答案就是数字型注入。

注意:这里说的空格,在实际URL请求里经常需要处理成%20或+,尤其是当你用自动化工具或直接在地址栏操作时,很容易因为空格没编码而导致payload不完整。这点在后面常见问题里还会单独说。

2. pikachu靶场的环境搭建与关卡定位

2.1 本地靶场环境准备

pikachu靶场本身是一套PHP写的Web应用,依赖常见的PHP+MySQL环境。本地搭建时不需要费太大劲,我用的方案是装一个集成环境,把PHP、MySQL、Apache一次搞定,然后下载靶场源码放到Web根目录下。

启动服务之后,访问靶场的安装路径,系统会自动跳转到初始化页面,创建所需的数据库和表结构。这个过程会生成一个默认的数据库连接配置,初始化完成后,进入靶场首页即可开始练习。几个容易踩的坑提前说:

  • PHP版本不要太新也不要太老,遇到过在新版本PHP下靶场部分页面出现函数兼容告警的情况,老版本反向兼容性问题更多,选一个社区里用得最多的稳定版本最省心。
  • 数据库账号建议直接用本地的root账号初始安装,练完再改都行,别在环境准备上消耗太多时间。
  • 如果页面打开后是空白或者直接下载文件,多半是PHP解析没生效,检查Web服务有没有正确加载PHP模块。

2.2 打开数字型注入关卡

靶场首页做了模块分组,SQL注入这一类下面一般会列出多个子关卡。找到“数字型注入”的入口,点击进入后,页面呈现的是一个简单的查询表单,提示输入用户ID进行查询。

我通常会直接手动输入一个1,点查询,页面会显示一条记录。页面结构很简单:一个输入框,一个查询按钮,结果区域显示查到的记录。这种页面越简单,越适合做手工注入练习,没有多余的干扰因素,你观察响应变化只需要关注结果区域显示什么、不显示什么。

2.3 从后端逻辑理解注入成因

教学靶场和真实系统最大的区别在于,它会把漏洞成因用代码展示出来,方便你对照理解。这一关的后端逻辑大致是这样的:

$id = $_GET['id']; $query = "select id, username, email from member where id = $id"; $result = mysqli_query($conn, $query);

看到这段代码,你就能明白为什么它和业务系统里那些“祖传代码”如此相似。三个致命点全占齐了:第一,直接拿$_GET参数拼SQL字符串,没有任何预编译处理;第二,没有对参数做数字类型的校验,连一个is_numeric都没写;第三,没有给参数加引号,导致它天然适合数字型注入。

理解成因远比直接抄payload重要。你在真实测试中不可能总是看到后端代码,但你可以通过输入输出表现反推出它的写法。比如这一关,只要判断出“参数不带引号直接进入数字比较”,基本就能确定后端的写法,后面的利用思路也顺理成章。

3. 数字型注入完整实操记录

3.1 基础探测:用三组请求锁定注入点

假设靶场地址中的查询参数名为id,完整请求路径大致形如http://your-server/pikachu/vul/sqli/sqli_id.php?id=1。这里的your-server是你本地环境的访问地址,不同环境会有差异,实际练习时以自己的部署路径为准。

第一步,先发一个标准请求:?id=1。页面返回一条记录,记录里有ID、用户名、邮箱。我习惯顺手把这条正常返回的页面状态记下来,后面所有判断都基于“它和正常页面是否不同”这个基准。

第二步,发送?id=1 and 1=1。注意在浏览器地址栏直接输入时,空格可能会被处理掉,稳妥起见用?id=1%20and%201=1。页面返回结果和第一次完全一致,说明and 1=1被成功执行,并且整个条件是真的。

第三步,发送?id=1 and 1=2。页面返回空白,或者结果区域什么记录都没有。到这里,数字型注入已经实锤。一个带有布尔逻辑的表达式竟然能影响查询结果,说明参数确实直接参与了SQL执行。

3.2 手工联合查询注入全链路

确定注入点之后,下一步要拿到数据。数字型注入有页面回显,最适合用联合查询(UNION SELECT)来获取额外数据。

第一步是确定查询的列数。用order by一个个试:

?id=1 order by 1 ?id=1 order by 2 ?id=1 order by 3

前两个请求都正常返回记录,第三个请求如果页面报错“Unknown column '3'”,说明原查询只有2列。这个报错信息非常重要,它不是坏事,而是告诉你列数已经探测出来了。

第二步是确定回显位置。用一条联合查询语句:

?id=1 and 1=2 union select 1,2

这里and 1=2的作用是让原查询条件为假,前半个查询不返回任何记录,页面上自然就只会显示后面union select 1,2的结果。如果页面第二行显示1和2,恭喜,两个字段位置全都能回显。

第三步就是逐步拿数据了。先查当前数据库名:

?id=1 and 1=2 union select 1,database()

页面上会直接显示数据库名。接着查库里的所有表:

?id=1 and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()

这里用了information_schema系统库,它是MySQL自带的元数据仓库。group_concat函数会把多行表名拼接成一行,方便一次看完。这个payload执行后,页面会列出当前数据库下的所有表名,比如member、users这样的内容。再进一步查某个表的字段名:

?id=1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='member'

注意这里出现了单引号,它是为了匹配table_name这个字符串条件,属于SQL语法的一部分。如果靶场对引号有特殊过滤,这个手法就需要变形,但这一关没有,可以正常使用。查到字段名之后,最后一步就是把数据拿出来:

?id=1 and 1=2 union select 1,group_concat(username,0x3a,password) from member

这里0x3a是冒号的十六进制表示,作用是把用户名和密码用冒号拼接起来显示。这样一条完整的手工注入链路就走通了,从探测列数到脱库,逻辑是层层递进的,每一步都是下一步的前提。

3.3 用Burp Suite标准化测试流程

浏览器地址栏虽然能直接测试,但真实场景里我强烈建议把请求放到Burp Suite的Repeater里操作。原因很简单,你在浏览器里输入一长串payload,浏览器会自动做URL编码,空格、单引号、特殊符号都会被转成各种百分号编码,看起来一团乱麻,不利于你分析原始请求和响应。

实际操作时,我会先在浏览器里配置好代理,让流量经过Burp,然后正常提交一次id=1的查询,在Burp的历史记录里找到这个请求,右键发送到Repeater。后面的所有payload都直接在Repeater里改参数值,点发送就能看到响应。

这样做的另一个好处是方便对比。你能同时开两个Repeater标签页,一个放id=1 and 1=1,一个放id=1 and 1=2,来回切换发送,页面差异一目了然。等整个利用链路走完,你还能把最终的请求包保存下来,直接转成Python脚本做自动化批量验证,这个习惯到了真实项目里能省大量时间。

3.4 从显式回显延伸到盲注思路

pikachu这一关是有回显的,联合查询轻轻松松就能把数据带到页面上。但真实渗透测试中,很多接口是不回显查询结果的,或者把查询结果隐藏在复杂的页面渲染里,这时候就需要盲注。

盲注分两种思路。布尔盲注:构造条件判断,让页面在“真”和“假”之间切换,然后通过二分法一位位猜数据内容。比如要判断当前数据库名的第一个字符的ASCII码是否大于100:

?id=1 and ascii(substr(database(),1,1)) > 100

如果页面正常返回,说明条件为真,字符范围可以继续缩小;如果页面返回空,说明条件为假,调整阈值再试。这个过程虽然机械,但通过脚本化处理,效率并不低。

时间盲注:如果页面不管条件真假结果都一样,就基于数据库的sleep函数制造响应时间差异。请求?id=1 and sleep(3)时,如果页面等了大约3秒才返回,说明目标把sleep(3)也当成了SQL的一部分执行了。这种方式对页面结构完全没要求,只需要你能感知响应延迟。靶场练习虽然用不上盲注,但理解它是为了让你在真实场景里不慌——不是所有注入点都有这么好的回显条件。

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

4.1 为什么判断不准确

很多人拿到数字型注入的靶场,上来就先输入一个1',页面直接报SQL语法错误,然后以为测试失败了,或者误判成字符型注入。这个问题的根源在于:数字型注入里,原SQL语句是id=1',语法确实错了,但这个错误恰恰说明参数被直接拼了进去,只是你的闭合方式不对。

正确做法是不要急着加引号,先用and 1=1和and 1=2做布尔判断。如果这两种请求能引起页面差异,那就说明注入存在,并且类型大概率是数字型。把这个思路反过来用于字符型靶场也一样成立——一切从观察入手,而不是从固定的payload模板入手。

4.2 过滤与编码干扰的处理

靶场虽然是无过滤环境,但你在URL中直接操作时,还是会遇到一些编码层面的坑。最常见的是空格问题,浏览器的地址栏会自动把空格转换成%20,但某些环境下如果不转换,请求就会中断。

另一个容易忽略的是&符号问题。如果payload里出现了&&,在URL中会被解析成参数连接符,导致后半段被当成另一个参数。解决方法是把&&做URL编码为%26%26,或者改用and关键字。

还有一种情况:payload里的单引号在某些中间件配置下会被吃掉或转义。数字型注入本身不需要引号,但在查字段名时table_name='xxx'这种写法绕不开引号。如果遇到过滤,可以把字符串用十六进制编码代替,比如table_name=0x6d656d626572,MySQL完全支持这种写法。

4.3 常见问题速查表

把练习中遇到的现象、原因和处理方法整理成一张表,方便你以后遇到同类问题快速对照。

现象可能原因排查方法
输入1 and 1=1无任何反应参数可能是字符型改用1' and '1'='1测试闭合方式
页面报SQL语法错误原查询外层有引号检查参数类型,尝试闭合单引号
请求提交后只剩一半payloadURL中的空格或&没编码用%20代替空格,&做URL编码
联合查询报列数不匹配原查询列数和union select不一致用order by N探测真实列数
页面有回显但看不到注入结果原查询结果被拆在了多个显示位置用and 1=2清空原结果,让union内容单独显示
查询数据库名时不显示当前用户权限不足换用user()函数确认当前连接账号权限

这些坑都不是大问题,但每一个都可能导致你在一个最简单的靶场关卡上卡住半天。排查的核心原则很简单:把请求和响应放在一起看,别猜,用证据说话。

5. 从攻击视角看防御:数字型注入的修复方案

5.1 参数化查询是最底的底线

安全圈里有一句老话:“过滤不能保你平安,参数化查询才是底线。”这话放到数字型注入上尤其贴切。修复方式非常直接,后端只要把字符串拼接改成预编译绑定参数,用户输入就永远不会被当成SQL指令执行。

$stmt = $pdo->prepare("SELECT username, email FROM users WHERE id = ?"); $stmt->execute([$id]); $user = $stmt->fetch();

这段代码里的?是占位符,用户的id参数是先被传给数据库端,由数据库把这个值按“数据”类型处理,而不是把它拼进SQL文本再解析。还是拿邀请函打比方:参数化查询就是让来客在签到处登记,而不是让他们自己在邀请函上扩写名单。无论输入是1 and 1=1还是1 or sleep(3),数据库都只把它当成一个字面量参与比较,根本进不了语法解析环节。

5.2 输入校验要设计成白名单思维

如果业务场景必须用动态SQL,至少得加一层输入校验。对数字型字段来说,校验方式极其简单——判断它到底是不是一个纯数字就行。

if (!ctype_digit($id)) { die("Invalid input"); }

或者使用正则校验:

if (!preg_match('/^\d+$/', $id)) { die("Invalid input"); }

看到这里你可能会觉得太简单了,但实际很多漏洞恰恰是因为开发者漏写了这种“愚蠢”的校验。白名单校验的思维是“默认拒绝,只放行符合规则的值”,这和黑名单过滤的“默认放行,只拦截已知危险字符”有本质区别。黑名单永远没法穷尽攻击手法,攻击者换个编码、换个拼接方式就能绕过去,而白名单直接缩小了合法输入的范围,数字字段只准数字进来,思路瞬间清晰。

5.3 纵深防御的落地配置

修复代码只是第一步,我在实践里还会同步做几件加固工作,避免单点失效。

第一,关闭数据库错误信息的直接输出。在PHP环境里就是设置display_errors = Off,在框架层面统一接管异常处理。很多注入测试的第一步就是故意触发SQL语法错误,从报错信息里反推数据库类型和语句结构,关掉错误回显等于断掉攻击者的一条情报线。

第二,给Web应用连接数据库的账号做最小权限分配。业务代码如果只需要对某几张表做增删改查,那给这个账号的权限就严格限制在这几张表上。这样即使注入发生了,攻击者想通过information_schema列库名表名,也会因为权限不足而寸步难行。

第三,在应用层之前加一道WAF规则,针对数字型注入的典型特征做拦截,比如参数值里出现and、union、select的关键字组合,直接拒绝请求。WAF不能当作主防御手段,因为总有绕过可能,但它能挡住大量自动化工具的扫描试探,降低被发现的概率。

最后说一句我在多次练习和实战排查里的体会:数字型注入是所有注入类型里最简单的一种,但它带来的警惕性教育价值一点都不简单。很多开发同事喜欢问我“为什么我写了那么多过滤函数还是被绕过”,答案往往就藏在这个基础关卡里——你不是过滤不够,而是根本没有把输入当成不可信数据来设计方案。把参数化查询这个习惯焊死在编码流程里,比你在过滤规则里缝缝补补一百次都有用。所以每次带新人练这个靶场,我都让他们在纸上先把后端可能的SQL语句写出来,再开始动手注入,这个习惯后来帮他们避掉了不少真实项目里的雷。

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

VLAN修剪原理与配置实战:告别Trunk广播风暴

最近有个朋友在搞网络改造,照着一张拓扑图把两台交换机之间的Trunk配好了,然后抓包发现一个很奇怪的现象:楼下明明只有VLAN 20的终端,可是在Trunk口上却能看到VLAN 10、VLAN 30的广播帧也在来回溜达。这个现象太典型了——Trunk口…

作者头像 李华
网站建设 2026/10/20 21:51:39

FANUC机器人TCP通信瞬时中断排查与自动重连实战

在泰国工厂做FANUC机器人TCP通信调试时,我遇到过一个非常折磨人的现象:机器人每隔一段时间就报告通信断开,可现场交换机指示灯明明正常,上位机软件也没有任何崩溃记录。“瞬时中断”这个词,就是在这种一线环境里被逼着…

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

离线强化学习与模仿学习的边界:从分布偏移到过估计的实战解析

你有没有遇到过这种情况:同样一批离线数据,行为克隆跑出来的指标稳稳当当,一上离线强化学习反而反复崩溃,训练曲线像过山车,最后部署效果甚至不如最朴素的监督学习?说实话,这种情况太常见了。这…

作者头像 李华
网站建设 2026/10/20 21:49:56

Node 内存监控实战:用 v8.getHeapStatistics 构建生产级防线

凌晨三点被内存告警吵醒,然后盯着监控面板看曲线一条直线往上爬,这种事情凡是在公司维护过 Node 线上服务的同学,大概率都经历过。第一次遇到的时候,我还在一个业务团队写后端,看到某个常驻服务的 RSS 从 1GB 涨到 3GB…

作者头像 李华
网站建设 2026/10/20 21:49:13

Transformer从输入到推理:Attention、训练与KV Cache全链路解析

Transformer 这几年被聊得太多了,但大部分讲解要么停在“Self-Attention 就是让每个词看其他词”这种一句话层面,要么直接甩公式把人劝退。我自己带过几个刚入行的同学,发现一个很普遍的现象:单独问 Attention 怎么算,…

作者头像 李华
网站建设 2026/10/20 21:46:46

Spring Boot农产品交易平台毕设实战:从表设计到并发扣库存避坑指南

1. 先搞清楚:农产品交易平台这个选题到底在考察什么1.1 为什么一个CRUD项目能难住这么多人接手一套Spring Boot农产品交易平台的源码,准备当作计算机毕业设计二次开发的人,十个里至少有七个栽在同一件事上:以为这东西就是网站后台…

作者头像 李华