漏洞简介
MetaCRM是一款智能平台化CRM软件,通过提升企业管理和协同办公,全面提高企业管理水平和运营效率,帮助企业实现卓越管理。美特CRM getFile 接口存在任意文件读取漏洞和fastjson反序列化漏洞。
影响版本
CRM6.5
fofa语法
body="/common/scripts/basic.js" && body="www.metacrm.com.cn"
漏洞分析
先看 web.xml 里对于 getFile 接口的定义
<!-- 流的方式展示文件 -->
<servlet>
<servlet-name>getFile</servlet-name>
<servlet-class>com.metasoft.framework.controller.getFile</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>getFile</servlet-name>
<url-pattern>/getFile</url-pattern>
</servlet-mapping>
进入 com.metasoft.framework.controller.getFile 看下其实现逻辑

以及系统的fastjson版本 fastjson-1.2.4.jar,是存在漏洞的版本

看下AES加解密的部分
public class AesEcbCipher {
private static final String SECRET_KEY = "metacrmloginpass";
private byte[] key = "metacrmloginpass".getBytes();
public AesEcbCipher(String secretKey) {
this.key = secretKey.getBytes();
}
public AesEcbCipher() {
}
public String Encrypt(String sSrc) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(this.key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(1, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("UTF-8"));
return (new BASE64Encoder()).encode(encrypted);
} catch (Exception ex) {
Debug.error("", ex);
return null;
}
}
public String Decrypt(String sSrc) {
if (sSrc != null && sSrc.length() != 0) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(this.key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(2, skeySpec);
byte[] encrypted1 = (new BASE64Decoder()).decodeBuffer(sSrc);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, "UTF-8");
} catch (Exception ex) {
Debug.error("", ex);
return sSrc;
}
} else {
return sSrc;
}
}
public String encrypt(String sSrc) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(this.key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(1, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("UTF-8"));
return Hex.encodeHexStr(encrypted);
} catch (Exception ex) {
Debug.error("", ex);
return null;
}
}
public String decrypt(String sSrc) {
if (sSrc != null && sSrc.length() != 0) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(this.key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(2, skeySpec);
byte[] encrypted1 = Hex.decodeHex(sSrc.toCharArray());
byte[] original = cipher.doFinal(encrypted1);
return new String(original, "UTF-8");
} catch (Exception ex) {
Debug.error("", ex);
return sSrc;
}
} else {
return sSrc;
}
}
}
硬编码密钥 metacrmloginpass ,加解密都是基于hex格式进行,即传入的数据为hex格式。
反序列化
再看下涉及反序列化的处理逻辑
import com.alibaba.fastjson.JSONObject;
......
String a = request.getServletPath();
String c = request.getRequestURI();
c = StringUtil.replaceStr(c, a + "/", "");
String[] ps = StringUtil.string2array(c, "/");
String url = ps[0];
AesEcbCipher aec = new AesEcbCipher();
String dec = aec.decrypt(url);
JSONObject json = JSONObject.parseObject(dec);
dec 最终来自AES解密后 url 后的结果,而 url又来自 request.getRequestURI() 替换掉 ServletPath+/ 后的值,如请求 /mrxn/xxxxxxx 得到的最终 url 值就是去掉 mrxn/ 后的 xxxxxxx ;经过上述处理后,直接调用 fastjson 的 parseObject 方法处理,导致fastjson 反序列化漏洞。
文件读取
String fileName = json.getString("fileName");
String folder = json.getString("folder");
String sCorpName = json.getString("sCorpName");
String securityCode = json.getString("securityCode");
UserState us = UserManager.getUserBySessionId(request.getSession().getId());
if (us == null && !FileSecurityCode.checkSecurityCode(securityCode)) {
request.setAttribute("error", (new ResourceService()).getDispMessage("error.common.upanddown.login"));
try {
request.getRequestDispatcher("/common/images/default/limit.png").forward(request, response);
} catch (ServletException e) {
Debug.error("", e);
}
} else {
FileSecurityCode.visits(securityCode);
response.setContentType(Path.getContentType(fileName));
String path;
if ("attach".equals(folder)) {
path = Path.getAttachAbsoluteDirectory(sCorpName);
} else if ("template".equals(folder)) {
path = Path.getTemplateAbsoluteDirectory(sCorpName);
} else if ("userlogo".equals(folder)) {
path = Path.getLogoAbsoluteDirectory(sCorpName);
} else if ("messageserv".equals(folder)) {
path = Path.getMessageServerAbsoluteDirectory(sCorpName);
} else if ("picture".equals(folder)) {
path = Path.getPictureAbsoluteDirectory(sCorpName);
} else if ("skinbackground".equals(folder)) {
path = Path.getBackgroundAbsoluteDirectory(sCorpName);
} else if ("icon".equals(folder)) {
path = Path.getIconAbsoluteDirectory(sCorpName);
} else {
path = Path.getTempAbsoluteDirectory(sCorpName);
}
path = path + fileName;
this.log.debug("path={}", path);
FileInputStream fio = new FileInputStream(new File(path));
IOUtils.copyLarge(fio, response.getOutputStream());
IOUtils.closeQuietly(fio);
AES解密后的内容经fastjson处理后,取出其中需要的 fileName 、folder、sCorpName、securityCode等,主要是path = path + fileName; 其中 path 与 fileName 都是用户可控,且对路径无任何校验或过滤,拼接后直接调用 new File(path) 进行文件操作,造成任意文件读取漏洞。
整体执行流程如下图所示

漏洞复现
反序列化
GET /getFile/AES加密后的payload HTTP/1.1
Host: metasoft.mrxn.net
Cookie: JSESSIONID=D74D969B9489FFBE41D5F5ACE5CAC014

DNSLOG平台成功收到请求
文件读取
漏洞利用需要合法的cookie或者securityCode
而系统自带 druid ,因此可以访问
/druid/websession.html拿到系统以及存在的SESSIONID后进行批量枚举尝试
GET /getFile/AES加密后的payload HTTP/1.1
Host: metasoft.mrxn.net
Cookie: JSESSIONID=2A6482AE7774FBC13DD7854E15CE90F4


成功读取到 web.xml 文件内容

