Security advisory GHSA-72w2-j52c-7682: Moodle SQL Injection vulnerability
Package: moodle/moodle (composer), vulnerable version: v4.1.1, fixed in: 4.1.2
CWE: CWE-89
Files changed by the fix:
availability/condition/profile/classes/condition.php
availability/condition/profile/classes/frontend.php
availability/condition/profile/lang/en/availability_profile.php

Fix diff:
diff --git a/availability/condition/profile/classes/condition.php b/availability/condition/profile/classes/condition.php
index 70b54d71f3fd3..1b14217ef243f 100644
--- a/availability/condition/profile/classes/condition.php
+++ b/availability/condition/profile/classes/condition.php
@@ -197,7 +197,12 @@ public function get_description($full, $not, \core_availability\info $info) {
                         $this->customfield);
             }
         } else {
-            $translatedfieldname = \core_user\fields::get_display_name($this->standardfield);
+            $standardfields = self::get_standard_profile_fields();
+            if (array_key_exists($this->standardfield, $standardfields)) {
+                $translatedfieldname = $standardfields[$this->standardfield];
+            } else {
+                $translatedfieldname = get_string('missing', 'availability_profile', $this->standardfield);
+            }
         }
         $a = new \stdClass();
         // Not safe to call format_string here; use the special function to call it later.
@@ -321,6 +326,27 @@ protected static function is_field_condition_met($operator, $uservalue, $value)
         return $fieldconditionmet;
     }
 
+    /**
+     * Return list of standard user profile fields used by the condition
+     *
+     * @return string[]
+     */
+    public static function get_standard_profile_fields(): array {
+        return [
+            'firstname' => \core_user\fields::get_display_name('firstname'),
+            'lastname' => \core_user\fields::get_display_name('lastname'),
+            'email' => \core_user\fields::get_display_name('email'),
+            'city' => \core_user\fields::get_display_name('city'),
+            'country' => \core_user\fields::get_display_name('country'),
+            'idnumber' => \core_user\fields::get_display_name('idnumber'),
+            'institution' => \core_user\fields::get_display_name('institution'),
+            'department' => \core_user\fields::get_display_name('department'),
+            'phone1' => \core_user\fields::get_display_name('phone1'),
+            'phone2' => \core_user\fields::get_display_name('phone2'),
+            'address' => \core_user\fields::get_display_name('address'),
+        ];
+    }
+
     /**
      * Gets data about custom profile fields. Cached statically in current
      * request.
@@ -472,6 +498,11 @@ public function filter_user_list(array $users, $not, \core_availability\info $in
             $valuefield = 'data';
             $default = $customfield->defaultdata;
         } else {
+            $standardfields = self::get_standard_profile_fields();
+            if (!array_key_exists($this->standardfield, $standardfields)) {
+                // If the field isn't found, nobody matches.
+                return [];
+            }
             $values = $DB->get_records_select('user', 'id ' . $sql, $params,
                     '', 'id, '. $this->standardfield);
             $valuefield = $this->standardfield;
@@ -595,6 +626,11 @@ public function get_user_list_sql($not, \core_availability\info $info, $onlyacti
                 $where = "(ud.data IS NOT NULL AND $condition)";
             }
         } else {
+            $standardfields = self::get_standard_profile_fields();
+            if (!array_key_exists($this->standardfield, $standardfields)) {
+                // If the field isn't found, nobody matches.
+                return ['SELECT id FROM {user} WHERE 0 = 1', []];
+            }
             $tablesql = "JOIN {user} u ON u.id = userids.id";
             list ($where, $mainparams) = $this->get_condition_sql(
                     'u.' . $this->standardfield);
diff --git a/availability/condition/profile/classes/frontend.php b/availability/condition/profile/classes/frontend.php
index d6a311d9c14d5..288ad9e69ee59 100644
--- a/availability/condition/profile/classes/frontend.php
+++ b/availability/condition/profile/classes/frontend.php
@@ -42,20 +42,9 @@ protected function get_javascript_strings() {
 
     protected function get_javascript_init_params($course, \cm_info $cm = null,
             \section_info $section = null) {
+
         // Standard user fields.
-        $standardfields = array(
-            'firstname' => \core_user\fields::get_display_name('firstname'),
-            'lastname' => \core_user\fields::get_display_name('lastname'),
-            'email' => \core_user\fields::get_display_name('email'),
-            'city' => \core_user\fields::get_display_name('city'),
-            'country' => \core_user\fields::get_display_name('country'),
-            'idnumber' => \core_user\fields::get_display_name('idnumber'),
-            'institution' => \core_user\fields::get_display_name('institution'),
-            'department' => \core_user\fields::get_display_name('department'),
-            'phone1' => \core_user\fields::get_display_name('phone1'),
-            'phone2' => \core_user\fields::get_display_name('phone2'),
-            'address' => \core_user\fields::get_display_name('address')
-        );
+        $standardfields = condition::get_standard_profile_fields();
         \core_collator::asort($standardfields);
 
         // Custom fields.
diff --git a/availability/condition/profile/lang/en/availability_profile.php b/availability/condition/profile/lang/en/availability_profile.php
index c78dace0c6a0c..f1535d6361967 100644
--- a/availability/condition/profile/lang/en/availability_profile.php
+++ b/availability/condition/profile/lang/en/availability_profile.php
@@ -39,7 +39,7 @@
 $string['requires_notisequalto'] = 'Your <strong>{$a->field}</strong> is not <strong>{$a->value}</strong>';
 $string['requires_notstartswith'] = 'Your <strong>{$a->field}</strong> does not start with <strong>{$a->value}</strong>';
 $string['requires_startswith'] = 'Your <strong>{$a->field}</strong> starts with <strong>{$a->value}</strong>';
-$string['missing'] = '(Missing custom field: {$a})';
+$string['missing'] = '(Missing field: {$a})';
 $string['title'] = 'User profile';
 $string['op_contains'] = 'contains';
 $string['op_doesnotcontain'] = 'doesn\'t contain';