VYPR
Moderate severityNVD Advisory· Published Nov 24, 2014· Updated May 6, 2026

CVE-2014-9060

CVE-2014-9060

Description

Moodle LTI module mishandles return URL parameters, enabling arbitrary message generation via crafted URLs.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Moodle LTI module mishandles return URL parameters, enabling arbitrary message generation via crafted URLs.

Vulnerability

The LTI (Learning Tools Interoperability) module in Moodle through version 2.4.11, 2.5.x before 2.5.9, 2.6.x before 2.6.6, and 2.7.x before 2.7.3 does not properly restrict the parameters used in a return URL [1][2]. Specifically, in mod/lti/locallib.php, the return URL parameters included lti_errormsg and lti_msg read with PARAM_RAW in return.php, which allowed arbitrary text to be injected [4]. The vulnerability is triggered when a user interacts with the LTI launch and is redirected to the return script with a modified URL containing malicious message parameters [2][4].

Exploitation

An attacker can craft a malicious URL that points to a Moodle site's LTI return endpoint (/mod/lti/return.php) with modified lti_errormsg or lti_msg parameters. The attack requires no authentication if the LTI module is accessible, but typically relies on convincing a user to click the crafted link or visit a page that triggers the redirect. The parameters were originally defined as PARAM_RAW, meaning no sanitization was applied, allowing arbitrary text to be passed [4]. The attacker does not need any special privileges; any remote user can craft the URL [2].

Impact

Successful exploitation allows the attacker to generate arbitrary messages that are displayed to the user on the LTI return page. The message is output with htmlspecialchars() in the vulnerable code [4], which prevents direct HTML injection but still permits display of arbitrary text that could include misleading or phishing content [2]. The impact is primarily informational; the attacker cannot execute scripts or modify data directly via this vulnerability, but could use crafted messages to trick users into performing actions or revealing credentials.

Mitigation

Moodle fixed this issue in versions 2.5.9, 2.6.6, 2.7.3, and 2.8 [1][2]. The fix, introduced in commit edc89df [4], changed the parameter types from PARAM_RAW to PARAM_TEXT and added sesskey validation in the return URL to prevent unauthorized use. Users should upgrade to the fixed versions immediately. No workaround is provided for unsupported versions (pre-2.5).

AI Insight generated on May 23, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
moodle/moodlePackagist
< 2.5.92.5.9
moodle/moodlePackagist
>= 2.6.0, < 2.6.62.6.6
moodle/moodlePackagist
>= 2.7.0, < 2.7.32.7.3

Affected products

20
  • Moodle/Moodle19 versions
    cpe:2.3:a:moodle:moodle:*:*:*:*:*:*:*:*+ 18 more
    • cpe:2.3:a:moodle:moodle:*:*:*:*:*:*:*:*range: <=2.4.11
    • cpe:2.3:a:moodle:moodle:2.5.0:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.1:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.2:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.3:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.4:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.5:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.6:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.7:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.5.8:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.0:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.1:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.2:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.3:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.4:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.6.5:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.7.0:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.7.1:*:*:*:*:*:*:*
    • cpe:2.3:a:moodle:moodle:2.7.2:*:*:*:*:*:*:*
  • ghsa-coords
    Range: < 2.5.9

Patches

4
339c6eca3c88

MDL-47927 LTI: Use PARAM_TEXT and p() for returned messages and errors

https://github.com/moodle/moodleDamyon WieseNov 3, 2014via ghsa
2 files changed · +9 5
  • mod/lti/locallib.php+4 1 modified
    @@ -154,7 +154,10 @@ function lti_view($instance) {
         $requestparams = lti_build_request($instance, $typeconfig, $course);
     
         $launchcontainer = lti_get_launch_container($instance, $typeconfig);
    -    $returnurlparams = array('course' => $course->id, 'launch_container' => $launchcontainer, 'instanceid' => $instance->id);
    +    $returnurlparams = array('course' => $course->id,
    +                             'launch_container' => $launchcontainer,
    +                             'instanceid' => $instance->id,
    +                             'sesskey' => sesskey());
     
         if ( $orgid ) {
             $requestparams["tool_consumer_instance_guid"] = $orgid;
    
  • mod/lti/return.php+5 4 modified
    @@ -31,8 +31,8 @@
     $courseid = required_param('course', PARAM_INT);
     $instanceid = optional_param('instanceid', 0, PARAM_INT);
     
    -$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
    -$msg = optional_param('lti_msg', '', PARAM_RAW);
    +$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
    +$msg = optional_param('lti_msg', '', PARAM_TEXT);
     $unsigned = optional_param('unsigned', '0', PARAM_INT);
     
     $launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
    @@ -48,6 +48,7 @@
     
     
     require_login($course);
    +require_sesskey();
     
     if (!empty($errormsg) || !empty($msg)) {
         $url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
    @@ -73,7 +74,7 @@
     if (!empty($errormsg)) {
         echo get_string('lti_launch_error', 'lti');
     
    -    echo htmlspecialchars($errormsg);
    +    p($errormsg);
     
         if ($unsigned == 1) {
     
    @@ -100,7 +101,7 @@
         echo $OUTPUT->footer();
     } else if (!empty($msg)) {
     
    -    echo htmlspecialchars($msg);
    +    p($msg);
     
         echo $OUTPUT->footer();
     
    
edc89dfecb3f

MDL-47927 LTI: Use PARAM_TEXT and p() for returned messages and errors

https://github.com/moodle/moodleDamyon WieseNov 3, 2014via ghsa
2 files changed · +9 5
  • mod/lti/locallib.php+4 1 modified
    @@ -179,7 +179,10 @@ function lti_view($instance) {
             $instance->instructorcustomparameters, $islti2));
     
         $launchcontainer = lti_get_launch_container($instance, $typeconfig);
    -    $returnurlparams = array('course' => $course->id, 'launch_container' => $launchcontainer, 'instanceid' => $instance->id);
    +    $returnurlparams = array('course' => $course->id,
    +                             'launch_container' => $launchcontainer,
    +                             'instanceid' => $instance->id,
    +                             'sesskey' => sesskey());
     
         // Add the return URL. We send the launch container along to help us avoid frames-within-frames when the user returns.
         $url = new \moodle_url('/mod/lti/return.php', $returnurlparams);
    
  • mod/lti/return.php+5 4 modified
    @@ -30,8 +30,8 @@
     $courseid = required_param('course', PARAM_INT);
     $instanceid = optional_param('instanceid', 0, PARAM_INT);
     
    -$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
    -$msg = optional_param('lti_msg', '', PARAM_RAW);
    +$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
    +$msg = optional_param('lti_msg', '', PARAM_TEXT);
     $unsigned = optional_param('unsigned', '0', PARAM_INT);
     
     $launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
    @@ -47,6 +47,7 @@
     
     
     require_login($course);
    +require_sesskey();
     
     if (!empty($errormsg) || !empty($msg)) {
         $url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
    @@ -72,7 +73,7 @@
     if (!empty($errormsg)) {
         echo get_string('lti_launch_error', 'lti');
     
    -    echo htmlspecialchars($errormsg);
    +    p($errormsg);
     
         if ($unsigned == 1) {
     
    @@ -99,7 +100,7 @@
         echo $OUTPUT->footer();
     } else if (!empty($msg)) {
     
    -    echo htmlspecialchars($msg);
    +    p($msg);
     
         echo $OUTPUT->footer();
     
    
15bde5352bd4

MDL-47927 LTI: Use PARAM_TEXT and p() for returned messages and errors

https://github.com/moodle/moodleDamyon WieseNov 3, 2014via ghsa
2 files changed · +7 3
  • mod/lti/locallib.php+4 1 modified
    @@ -154,7 +154,10 @@ function lti_view($instance) {
         $requestparams = lti_build_request($instance, $typeconfig, $course);
     
         $launchcontainer = lti_get_launch_container($instance, $typeconfig);
    -    $returnurlparams = array('course' => $course->id, 'launch_container' => $launchcontainer, 'instanceid' => $instance->id);
    +    $returnurlparams = array('course' => $course->id,
    +                             'launch_container' => $launchcontainer,
    +                             'instanceid' => $instance->id,
    +                             'sesskey' => sesskey());
     
         if ( $orgid ) {
             $requestparams["tool_consumer_instance_guid"] = $orgid;
    
  • mod/lti/return.php+3 2 modified
    @@ -31,14 +31,15 @@
     $courseid = required_param('course', PARAM_INT);
     $instanceid = required_param('instanceid', PARAM_INT);
     
    -$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
    +$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
     $unsigned = optional_param('unsigned', '0', PARAM_INT);
     
     $launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
     
     $course = $DB->get_record('course', array('id' => $courseid));
     
     require_login($course);
    +require_sesskey();
     
     if (!empty($errormsg)) {
         $url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
    @@ -59,7 +60,7 @@
     
         echo get_string('lti_launch_error', 'lti');
     
    -    echo htmlspecialchars($errormsg);
    +    p($errormsg);
     
         if ($unsigned == 1) {
     
    
44e712e9b72a

MDL-47927 LTI: Use PARAM_TEXT and p() for returned messages and errors

https://github.com/moodle/moodleDamyon WieseNov 3, 2014via ghsa
2 files changed · +9 5
  • mod/lti/locallib.php+4 1 modified
    @@ -153,7 +153,10 @@ function lti_view($instance) {
         $requestparams = lti_build_request($instance, $typeconfig, $course);
     
         $launchcontainer = lti_get_launch_container($instance, $typeconfig);
    -    $returnurlparams = array('course' => $course->id, 'launch_container' => $launchcontainer, 'instanceid' => $instance->id);
    +    $returnurlparams = array('course' => $course->id,
    +                             'launch_container' => $launchcontainer,
    +                             'instanceid' => $instance->id,
    +                             'sesskey' => sesskey());
     
         if ( $orgid ) {
             $requestparams["tool_consumer_instance_guid"] = $orgid;
    
  • mod/lti/return.php+5 4 modified
    @@ -30,8 +30,8 @@
     $courseid = required_param('course', PARAM_INT);
     $instanceid = optional_param('instanceid', 0, PARAM_INT);
     
    -$errormsg = optional_param('lti_errormsg', '', PARAM_RAW);
    -$msg = optional_param('lti_msg', '', PARAM_RAW);
    +$errormsg = optional_param('lti_errormsg', '', PARAM_TEXT);
    +$msg = optional_param('lti_msg', '', PARAM_TEXT);
     $unsigned = optional_param('unsigned', '0', PARAM_INT);
     
     $launchcontainer = optional_param('launch_container', LTI_LAUNCH_CONTAINER_WINDOW, PARAM_INT);
    @@ -47,6 +47,7 @@
     
     
     require_login($course);
    +require_sesskey();
     
     if (!empty($errormsg) || !empty($msg)) {
         $url = new moodle_url('/mod/lti/return.php', array('course' => $courseid));
    @@ -72,7 +73,7 @@
     if (!empty($errormsg)) {
         echo get_string('lti_launch_error', 'lti');
     
    -    echo htmlspecialchars($errormsg);
    +    p($errormsg);
     
         if ($unsigned == 1) {
     
    @@ -99,7 +100,7 @@
         echo $OUTPUT->footer();
     } else if (!empty($msg)) {
     
    -    echo htmlspecialchars($msg);
    +    p($msg);
     
         echo $OUTPUT->footer();
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.