diff --git a/admin/report/security/db/access.php b/admin/report/security/db/access.php new file mode 100644 index 00000000000..8534dd81e46 --- /dev/null +++ b/admin/report/security/db/access.php @@ -0,0 +1,36 @@ + array( + 'riskbitmask' => RISK_CONFIG, + 'captype' => 'read', + 'contextlevel' => CONTEXT_SYSTEM, + 'legacy' => array( + 'admin' => CAP_ALLOW + ), + ) +); diff --git a/admin/report/security/index.php b/admin/report/security/index.php new file mode 100644 index 00000000000..f243abbeb40 --- /dev/null +++ b/admin/report/security/index.php @@ -0,0 +1,113 @@ +dirroot.'/'.$CFG->admin.'/report/security/lib.php'); +require_once($CFG->libdir.'/adminlib.php'); + +require_login(); + +$issue = optional_param('issue', '', PARAM_FILE); // show detailed info about one issue only + +$issues = report_security_get_issue_list(); + +// test if issue valid string +if (array_search($issue, $issues, true) === false) { + $issue = ''; +} + +// Print the header. +admin_externalpage_setup('reportsecurity'); +admin_externalpage_print_header(); + +print_heading(get_string('reportsecurity', 'report_security')); + +$strok = ''.get_string('statusok', 'report_security').''; +$strinfo = ''.get_string('statusinfo', 'report_security').''; +$strwarning = ''.get_string('statuswarning', 'report_security').''; +$strserious = ''.get_string('statusserious', 'report_security').''; +$strcritical = ''.get_string('statuscritical', 'report_security').''; + +$strissue = get_string('issue', 'report_security'); +$strstatus = get_string('status', 'report_security'); +$strdesc = get_string('description', 'report_security'); +$strconfig = get_string('configuration', 'report_security'); + +$statusarr = array(REPORT_SECURITY_OK => $strok, + REPORT_SECURITY_INFO => $strinfo, + REPORT_SECURITY_WARNING => $strwarning, + REPORT_SECURITY_SERIOUS => $strserious, + REPORT_SECURITY_CRITICAL => $strcritical); + +$url = "$CFG->wwwroot/$CFG->admin/report/security/index.php"; + +if ($issue and ($result = $issue(true))) { + $table = new object(); + $table->head = array($strissue, $strstatus, $strdesc, $strconfig); + $table->size = array('30%', '10%', '50%', '10%' ); + $table->align = array('left', 'left', 'left', 'left'); + $table->width = '90%'; + $table->data = array(); + + // print detail of one issue only + $row = array(); + $row[0] = $result->name; + $row[1] = $statusarr[$result->status]; + $row[2] = $result->info; + $row[3] = is_null($result->link) ? ' ' : $result->link; + + $table->data[] = $row; + + print_table($table); + + print_box($result->details, 'generalbox boxwidthnormal boxaligncenter'); // TODO: add proper css + + print_continue($url); + +} else { + $table = new object(); + $table->head = array($strissue, $strstatus, $strdesc); + $table->size = array('30%', '10%', '60%' ); + $table->align = array('left', 'left', 'left'); + $table->width = '90%'; + $table->data = array(); + + foreach ($issues as $issue) { + $result = $issue(false); + if (!$result) { + // ignore this test + continue; + } + $row = array(); + $row[0] = "$result->name"; + $row[1] = $statusarr[$result->status]; + $row[2] = $result->info; + + $table->data[] = $row; + } + print_table($table); +} + +print_footer(); \ No newline at end of file diff --git a/admin/report/security/lib.php b/admin/report/security/lib.php new file mode 100644 index 00000000000..c8a75820af5 --- /dev/null +++ b/admin/report/security/lib.php @@ -0,0 +1,1009 @@ +libdir/adminlib.php"); + + +define('REPORT_SECURITY_OK', 'ok'); +define('REPORT_SECURITY_INFO', 'info'); +define('REPORT_SECURITY_WARNING', 'warning'); +define('REPORT_SECURITY_SERIOUS', 'serious'); +define('REPORT_SECURITY_CRITICAL', 'critical'); + + +function report_security_get_issue_list() { + return array( + 'report_security_check_globals', + 'report_security_check_unsecuredataroot', + 'report_security_check_displayerrors', + 'report_security_check_noauth', + 'report_security_check_embed', + 'report_security_check_mediafilterswf', + 'report_security_check_openprofiles', + 'report_security_check_google', + 'report_security_check_passwordpolicy', + 'report_security_check_emailchangeconfirmation', + 'report_security_check_cookiesecure', + 'report_security_check_configrw', + 'report_security_check_riskxss', + 'report_security_check_riskadmin', + 'report_security_check_defaultuserrole', + 'report_security_check_guestrole', + 'report_security_check_frontpagerole', + 'report_security_check_defaultcourserole', + 'report_security_check_courserole', + + ); +} + +///============================================= +/// Issue checks +///============================================= + + +/** + * Verifies register globals PHP setting. + * @param bool $detailed + * @return object result + */ +function report_security_check_globals($detailed=false) { + $result = new object(); + $result->issue = 'report_security_check_globals'; + $result->name = get_string('check_globals_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = null; + + if (ini_get_bool('register_globals')) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_globals_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_globals_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_globals_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies unsupported noauth setting + * @param bool $detailed + * @return object result + */ +function report_security_check_noauth($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_noauth'; + $result->name = get_string('check_noauth_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=manageauths\">".get_string('authsettings', 'admin').''; + + if (is_enabled_auth('none')) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_noauth_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_noauth_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_noauth_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies if password policy set + * @param bool $detailed + * @return object result + */ +function report_security_check_passwordpolicy($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_passwordpolicy'; + $result->name = get_string('check_passwordpolicy_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').''; + + if (empty($CFG->passwordpolicy)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_passwordpolicy_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_passwordpolicy_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_passwordpolicy_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies sloppy embedding - this should have been removed long ago!! + * @param bool $detailed + * @return object result + */ +function report_security_check_embed($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_embed'; + $result->name = get_string('check_embed_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').''; + + if (!empty($CFG->allowobjectembed)) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_embed_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_embed_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_embed_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies sloppy swf embedding - this should have been removed long ago!! + * @param bool $detailed + * @return object result + */ +function report_security_check_mediafilterswf($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_mediafilterswf'; + $result->name = get_string('check_mediafilterswf_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=filtersettingfiltermediaplugin\">".get_string('filtersettings', 'admin').''; + + if (!empty($CFG->textfilters)) { + $activefilters = explode(',', $CFG->textfilters); + } else { + $activefilters = array(); + } + + if (array_search('filter/mediaplugin', $activefilters) !== false and !empty($CFG->filter_mediaplugin_enable_swf)) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_mediafilterswf_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_mediafilterswf_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_mediafilterswf_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies fatal misconfiguration of dataroot + * @param bool $detailed + * @return object result + */ +function report_security_check_unsecuredataroot($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_unsecuredataroot'; + $result->name = get_string('check_unsecuredataroot_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = null; + + $insecuredataroot = is_dataroot_insecure(true); + + if ($insecuredataroot == INSECURE_DATAROOT_WARNING) { + $result->status = REPORT_SECURITY_SERIOUS; + $result->info = get_string('check_unsecuredataroot_warning', 'report_security', $CFG->dataroot); + + } else if ($insecuredataroot == INSECURE_DATAROOT_ERROR) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_unsecuredataroot_error', 'report_security', $CFG->dataroot); + + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_unsecuredataroot_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_unsecuredataroot_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies disaplying of errors - problem for lib files and 3rd party code + * because we can not disable debugging in these scripts (they do not include config.php) + * @param bool $detailed + * @return object result + */ +function report_security_check_displayerrors($detailed=false) { + $result = new object(); + $result->issue = 'report_security_check_displayerrors'; + $result->name = get_string('check_displayerrors_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = null; + + if (defined('WARN_DISPLAY_ERRORS_ENABLED')) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_displayerrors_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_displayerrors_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_displayerrors_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies open profiles - originaly open by default, not anymore because spammer abused it a lot + * @param bool $detailed + * @return object result + */ +function report_security_check_openprofiles($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_openprofiles'; + $result->name = get_string('check_openprofiles_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').''; + + if (empty($CFG->forcelogin) and empty($CFG->forceloginforprofiles)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_openprofiles_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_openprofiles_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_openprofiles_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies google access not combined with disabled guest access + * because attackers might gain guest access by modifying browser signature. + * @param bool $detailed + * @return object result + */ +function report_security_check_google($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_google'; + $result->name = get_string('check_google_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').''; + + if (empty($CFG->opentogoogle)) { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_google_ok', 'report_security'); + } else if (!empty($CFG->guestloginbutton)) { + $result->status = REPORT_SECURITY_INFO; + $result->info = get_string('check_google_info', 'report_security'); + } else { + $result->status = REPORT_SECURITY_SERIOUS; + $result->info = get_string('check_google_error', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_google_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies email confirmation - spammers were changing mails very often + * @param bool $detailed + * @return object result + */ +function report_security_check_emailchangeconfirmation($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_emailchangeconfirmation'; + $result->name = get_string('check_emailchangeconfirmation_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=sitepolicies\">".get_string('sitepolicies', 'admin').''; + + if (empty($CFG->emailchangeconfirmation)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_emailchangeconfirmation_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_emailchangeconfirmation_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_emailchangeconfirmation_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies if https enabled only secure cookies allowed, + * this prevents redirections and sending of cookies to unsecure port. + * @param bool $detailed + * @return object result + */ +function report_security_check_cookiesecure($detailed=false) { + global $CFG; + + if (strpos($CFG->wwwroot, 'https://') !== 0) { + return null; + } + + $result = new object(); + $result->issue = 'report_security_check_cookiesecure'; + $result->name = get_string('check_cookiesecure_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=httpsecurity\">".get_string('httpsecurity', 'admin').''; + + if (empty($CFG->cookiesecure)) { + $result->status = REPORT_SECURITY_SERIOUS; + $result->info = get_string('check_cookiesecure_error', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_cookiesecure_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_cookiesecure_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies config.php is not writable anymore after installation, + * config files were changed on several outdated server. + * @param bool $detailed + * @return object result + */ +function report_security_check_configrw($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_configrw'; + $result->name = get_string('check_configrw_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = null; + + if (is_writable($CFG->dirroot.'/config.php')) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_configrw_warning', 'report_security'); + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_configrw_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_configrw_details', 'report_security'); + } + + return $result; +} + +/** + * Lists all users with XSS risk, it would be great to combine this with risk trusts in user table, + * unfortunately nobody implemented user trust UI yet :-( + * @param bool $detailed + * @return object result + */ +function report_security_check_riskxss($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_riskxss'; + $result->name = get_string('check_riskxss_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = REPORT_SECURITY_WARNING; + $result->link = null; + + $sqlfrom = "FROM {$CFG->prefix}role_capabilities rc + JOIN {$CFG->prefix}capabilities cap ON cap.name = rc.capability + JOIN {$CFG->prefix}context c ON c.id = rc.contextid + JOIN {$CFG->prefix}context sc ON (sc.path = c.path OR sc.path LIKE ".sql_concat('c.path', "'/%'").") + JOIN {$CFG->prefix}role_assignments ra ON (ra.contextid = sc.id AND ra.roleid = rc.roleid) + JOIN {$CFG->prefix}user u ON u.id = ra.userid + WHERE ".sql_bitand('cap.riskbitmask', RISK_XSS)." <> 0 + AND rc.permission = ".CAP_ALLOW." + AND u.deleted = 0"; + + $count = count_records_sql("SELECT COUNT(DISTINCT u.id) $sqlfrom"); + + $result->info = get_string('check_riskxss_warning', 'report_security', $count); + + if ($detailed) { + $users = get_records_sql("SELECT DISTINCT u.id, u.firstname, u.lastname, u.picture, u.imagealt $sqlfrom"); + foreach ($users as $uid=>$user) { + $users[$uid] = fullname($user); + } + $users = implode(', ', $users); + $result->details = get_string('check_riskxss_details', 'report_security', $users); + } + + return $result; +} + +/** + * Verifies sanity of default user role. + * @param bool $detailed + * @return object result + */ +function report_security_check_defaultuserrole($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_defaultuserrole'; + $result->name = get_string('check_defaultuserrole_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'';; + + if (!$default_role = get_record('role', 'id', $CFG->defaultuserroleid)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_defaultuserrole_notset', 'report_security'); + $result->details = $result->info; + + return $result; + } + + // first test if do anything enabled - that would be really crazy! + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability = 'moodle/site:doanything' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $default_role->id"; + + $anythingcount = count_records_sql($sql); + + // risky caps - usually very dangerous + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + JOIN {$CFG->prefix}capabilities cap ON cap.name = rc.capability + WHERE ".sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0 + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $default_role->id"; + + $riskycount = count_records_sql($sql); + + // default role can not have view cap in all courses - this would break moodle badly + $viewcap = record_exists('role_capabilities', 'roleid', $default_role->id, 'permission', CAP_ALLOW, 'capability', 'moodle/course:view'); + + // it may have either no or 'user' legacy type - nothing else, or else it would break during upgrades badly + $legacyok = false; + $sql = "SELECT rc.capability, 1 + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability LIKE 'moodle/legacy:%' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $default_role->id"; + $legacycaps = get_records_sql($sql); + if (!$legacycaps) { + $legacyok = true; + } else if (count($legacycaps) == 1 and isset($legacycaps['moodle/legacy:user'])) { + $legacyok = true; + } + + if ($anythingcount or $riskycount or $viewcap or !$legacyok) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_defaultuserrole_error', 'report_security', format_string($default_role->name)); + + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_defaultuserrole_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_defaultuserrole_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies sanity of guest role + * @param bool $detailed + * @return object result + */ +function report_security_check_guestrole($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_guestrole'; + $result->name = get_string('check_guestrole_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'';; + + if (!$guest_role = get_record('role', 'id', $CFG->guestroleid)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_guestrole_notset', 'report_security'); + $result->details = $result->info; + + return $result; + } + + // first test if do anything enabled - that would be really crazy! + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability = 'moodle/site:doanything' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $guest_role->id"; + + $anythingcount = count_records_sql($sql); + + // risky caps - usually very dangerous + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + JOIN {$CFG->prefix}capabilities cap ON cap.name = rc.capability + WHERE ".sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0 + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $guest_role->id"; + + $riskycount = count_records_sql($sql); + + // it may have either no or 'guest' legacy type - nothing else, or else it would break during upgrades badly + $legacyok = false; + $sql = "SELECT rc.capability, 1 + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability LIKE 'moodle/legacy:%' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $guest_role->id"; + $legacycaps = get_records_sql($sql); + if (!$legacycaps) { + $legacyok = true; + } else if (count($legacycaps) == 1 and isset($legacycaps['moodle/legacy:guest'])) { + $legacyok = true; + } + + if ($anythingcount or $riskycount or !$legacyok) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_guestrole_error', 'report_security', format_string($guest_role->name)); + + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_guestrole_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_guestrole_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies sanity of frontpage role + * @param bool $detailed + * @return object result + */ +function report_security_check_frontpagerole($detailed=false) { + global $CFG; + + $result = new object(); + $result->issue = 'report_security_check_frontpagerole'; + $result->name = get_string('check_frontpagerole_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=frontpagesettings\">".get_string('frontpagesettings','admin').'';; + + if (!$frontpage_role = get_record('role', 'id', $CFG->defaultfrontpageroleid)) { + $result->status = REPORT_SECURITY_INFO; + $result->info = get_string('check_frontpagerole_notset', 'report_security'); + $result->details = get_string('check_frontpagerole_details', 'report_security'); + + return $result; + } + + // first test if do anything enabled - that would be really crazy! + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability = 'moodle/site:doanything' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $frontpage_role->id"; + + $anythingcount = count_records_sql($sql); + + // risky caps - usually very dangerous + $sql = "SELECT COUNT(DISTINCT rc.contextid) + FROM {$CFG->prefix}role_capabilities rc + JOIN {$CFG->prefix}capabilities cap ON cap.name = rc.capability + WHERE ".sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0 + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $frontpage_role->id"; + + $riskycount = count_records_sql($sql); + + // there is no legacy role type for frontpage yet - anyway we can not allow teachers or admins there! + $sql = "SELECT rc.capability, 1 + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability LIKE 'moodle/legacy:%' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $frontpage_role->id"; + $legacycaps = get_records_sql($sql); + $legacyok = (!isset($legacycaps['moodle/legacy:teacher']) + and !isset($legacycaps['moodle/legacy:editingteacher']) + and !isset($legacycaps['moodle/legacy:coursecreator']) + and !isset($legacycaps['moodle/legacy:admin'])); + + if ($anythingcount or $riskycount or !$legacyok) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_frontpagerole_error', 'report_security', format_string($frontpage_role->name)); + + } else { + $result->status = REPORT_SECURITY_OK; + $result->info = get_string('check_frontpagerole_ok', 'report_security'); + } + + if ($detailed) { + $result->details = get_string('check_frontpagerole_details', 'report_security'); + } + + return $result; +} + +/** + * Verifies sanity of site default course role. + * @param bool $detailed + * @return object result + */ +function report_security_check_defaultcourserole($detailed=false) { + global $CFG; + + $problems = array(); + + $result = new object(); + $result->issue = 'report_security_check_defaultcourserole'; + $result->name = get_string('check_defaultcourserole_name', 'report_security'); + $result->info = null; + $result->details = null; + $result->status = null; + $result->link = "wwwroot/$CFG->admin/settings.php?section=userpolicies\">".get_string('userpolicies', 'admin').'';; + + if ($detailed) { + $result->details = get_string('check_defaultcourserole_details', 'report_security'); + } + + if (!$student_role = get_record('role', 'id', $CFG->defaultcourseroleid)) { + $result->status = REPORT_SECURITY_WARNING; + $result->info = get_string('check_defaultcourserole_notset', 'report_security'); + $result->details = get_string('check_defaultcourserole_details', 'report_security'); + + return $result; + } + + // first test if do anything enabled - that would be really crazy! + $sql = "SELECT DISTINCT rc.contextid + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability = 'moodle/site:doanything' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $student_role->id"; + + if ($anything_contexts = get_records_sql($sql)) { + foreach($anything_contexts as $contextid) { + if ($contextid == SYSCONTEXTID) { + $a = "$CFG->wwwroot/$CFG->admin/roles/define.php?action=view&roleid=$CFG->defaultcourseroleid"; + } else { + $a = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$contextid&roleid=$CFG->defaultcourseroleid"; + } + $problems[] = get_string('check_defaultcourserole_anything', 'report_security', $a); + } + } + + // risky caps - usually very dangerous + $sql = "SELECT DISTINCT rc.contextid + FROM {$CFG->prefix}role_capabilities rc + JOIN {$CFG->prefix}capabilities cap ON cap.name = rc.capability + WHERE ".sql_bitand('cap.riskbitmask', (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))." <> 0 + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $student_role->id"; + + if ($riskycontexts = get_records_sql($sql)) { + foreach($riskycontexts as $contextid=>$unused) { + if ($contextid == SYSCONTEXTID) { + $a = "$CFG->wwwroot/$CFG->admin/roles/define.php?action=view&roleid=$CFG->defaultcourseroleid"; + } else { + $a = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$contextid&roleid=$CFG->defaultcourseroleid"; + } + $problems[] = get_string('check_defaultcourserole_risky', 'report_security', $a); + } + } + + // course creator or administrator does not make any sense here + $sql = "SELECT rc.capability, 1 + FROM {$CFG->prefix}role_capabilities rc + WHERE rc.capability LIKE 'moodle/legacy:%' + AND rc.permission = ".CAP_ALLOW." + AND rc.roleid = $student_role->id"; + $legacycaps = get_records_sql($sql); + if (isset($legacycaps['moodle/legacy:coursecreator']) or isset($legacycaps['moodle/legacy:admin'])) { + $problems[] = get_string('check_defaultcourserole_legacy', 'report_security'); + } + + if ($problems) { + $result->status = REPORT_SECURITY_CRITICAL; + $result->info = get_string('check_defaultcourserole_error', 'report_security', format_string($student_role->name)); + if ($detailed) { + $result->details .= "
If you enable https communication it is recommended that you also enable secure cookies. You should also add permanent redirection from http to https.
'; +$string['check_cookiesecure_error'] = 'Please enable secure cookies'; +$string['check_cookiesecure_name'] = 'Secure cookies'; +$string['check_cookiesecure_ok'] = 'Secure cookies enabled.'; + +$string['check_courserole_anything'] = 'The do anything capability must not be allowed in this context.'; +$string['check_courserole_details'] = 'Each course has one default enrolment role specified. Please make sure no risky capabilities are allowed for this role.
+The only supported legacy type for the default course role is Student.
'; +$string['check_courserole_error'] = 'Incorrectly defined default course roles detected!'; +$string['check_courserole_legacy'] = 'Unsupported legacy type detected in the role.'; +$string['check_courserole_name'] = 'Default course roles'; +$string['check_courserole_notyet'] = 'Used only default course role.'; +$string['check_courserole_ok'] = 'Default course role definitions OK.'; +$string['check_courserole_risky'] = 'Risky capabilities detected in context.'; + +$string['check_defaultcourserole_anything'] = 'The do anything capability must not be allowed in this context.'; +$string['check_defaultcourserole_details'] = 'The default student role for course enrolment specifies the default role for courses. Please make sure no risky capabilities are allowed in this role.
+The only supported legacy type for default role is Student.
'; +$string['check_defaultcourserole_error'] = 'Incorrectly defined default course role \"$a\" detected!'; +$string['check_defaultcourserole_legacy'] = 'Unsupported legacy type detected.'; +$string['check_defaultcourserole_name'] = 'Site default course role'; +$string['check_defaultcourserole_notset'] = 'Default role is not set.'; +$string['check_defaultcourserole_ok'] = 'Site default role definition OK.'; +$string['check_defaultcourserole_risky'] = 'Risky capabilities detected in context.'; + +$string['check_defaultuserrole_details'] = 'All logged in users are given capabilities of the default user role. Please make sure no risky capabilities are allowed in this role.
+The only supported legacy type for the default user role is Authenticated user. The course view capability must not be enabled.
'; +$string['check_defaultuserrole_error'] = 'Incorrectly defined default user role \"$a\" detected!'; +$string['check_defaultuserrole_name'] = 'Registered user role'; +$string['check_defaultuserrole_notset'] = 'Default role is not set.'; +$string['check_defaultuserrole_ok'] = 'Registered user role definition ok.'; + +$string['check_displayerrors_details'] = 'Enabling the PHP setting display_errors is not recommended on production sites because error messages can reveal sensitive information about your server.
It is recommended that an email confirmation step is required when users change their email address in their profile. If disabled, spammers may try to exploit the server for resending of spam.
'; +$string['check_emailchangeconfirmation_error'] = 'Users may enter any email address.'; +$string['check_emailchangeconfirmation_name'] = 'Email change confirmation'; +$string['check_emailchangeconfirmation_ok'] = 'Confirmation of change of email address in user profile.'; + +$string['check_embed_details'] = 'Unlimited object embedding is very dangerous - any registered user may launch an XSS attack against other server users. This setting should be disabled on production servers.
'; +$string['check_embed_error'] = 'Unlimited object embedding enabled - this is very dangerous for the majority of servers.'; +$string['check_embed_name'] = 'Allow EMBED and OBJECT'; +$string['check_embed_ok'] = 'Unlimited object embedding is not allowed.'; + +$string['check_frontpagerole_details'] = 'The default frontpage role is given to all registered users for frontpage activities. Please make sure no risky capabilities are allowed for this role.
+It is recommended that a special role is created for this purpose and a legacy type role is not used.
'; +$string['check_frontpagerole_error'] = 'Incorrectly defined frontpage role \"$a\" detected!'; +$string['check_frontpagerole_name'] = 'Frontpage role'; +$string['check_frontpagerole_notset'] = 'Frontpage role is not set.'; +$string['check_frontpagerole_ok'] = 'Frontpage role definition OK.'; + +$string['check_globals_details'] = 'Register globals is considered to be a highly insecure PHP setting.
+register_globals=off must be set in PHP configuration. This setting is controlled by editing your php.ini, Apache/IIS configuration or .htaccess file.
The Open to Google setting enables search engines to enter courses with guest access. There is no point in enabling this setting if guest login not allowed.
'; +$string['check_google_error'] = 'Search engines guest access allowed and guest access disabled.'; +$string['check_google_info'] = 'Search engines may enter as guests.'; +$string['check_google_name'] = 'Open to Google'; +$string['check_google_ok'] = 'Search engines guest access not enabled.'; + +$string['check_guestrole_details'] = 'The guest role is used for guests, not logged in users and temporary guest course access. Please make sure no risky capabilities are allowed in this role.
+The only supported legacy type for guest role is Guest.
'; +$string['check_guestrole_error'] = 'Incorrectly defined guest role \"$a\" detected!'; +$string['check_guestrole_name'] = 'Guest role'; +$string['check_guestrole_notset'] = 'Guest role is not set.'; +$string['check_guestrole_ok'] = 'Guest role definition OK.'; + +$string['check_mediafilterswf_details'] = 'Automatic swf embedding is very dangerous - any registered user may launch an XSS attack against other server users. Please disable it on production servers.
'; +$string['check_mediafilterswf_error'] = 'Flash media filter is enabled - this is very dangerous for the majority of servers.'; +$string['check_mediafilterswf_name'] = 'Enabled .swf media filter'; +$string['check_mediafilterswf_ok'] = 'Flash media filter is not enabled.'; + +$string['check_noauth_details'] = 'The No authentication plugin is not intended for production sites. Please disable it unless this is a development test site.
'; +$string['check_noauth_error'] = 'The No authentication plugin cannot be used on production sites.'; +$string['check_noauth_name'] = 'No authentication'; +$string['check_noauth_ok'] = 'No authentication plugin is disabled.'; + +$string['check_openprofiles_details'] = 'Open user profiles can be abused by spammers. It is recommended that either Force users to login for profiles or Force users to login are enabled.
It is recommended that a password policy is set, since password guessing is very often the easiest way to gain unauthorised access. +Do not make the requirements too strict though, as this can result in users not being able to remember their passwords and either forgetting them or writing them down.
'; +$string['check_passwordpolicy_error'] = 'Password policy not set.'; +$string['check_passwordpolicy_name'] = 'Password policy'; +$string['check_passwordpolicy_ok'] = 'Password policy enabled.'; + +$string['check_riskadmin_detailsok'] = 'Please verify the following list of administrators:
$a
Please verify the following list of administrators:
$a->admins
It is recommended to assign administrator role in system context only. Following users have unsupported admin role assignments:
$a->unsupported
RISK_XSS denotes all dangerous capabilities that only trusted users may use.
+Please verify following list of users and make sure that you trust them completely on this server:
$a
The dataroot directory must not be accessible via web. The best way to make sure the directory is not accessible is to use a directory outside the public web directory.
+If you move the directory, you need to update the \$CFG->dataroot setting in config.php accordingly.
$a is in the wrong location and is exposed to the web!';
+$string['check_unsecuredataroot_name'] = 'Insecure dataroot';
+$string['check_unsecuredataroot_ok'] = 'Dataroot directory must not be accessible via the web.';
+$string['check_unsecuredataroot_warning'] = 'Your dataroot directory $a is in the wrong location and might be exposed to the web.';
+?>
\ No newline at end of file
diff --git a/theme/standard/styles_color.css b/theme/standard/styles_color.css
index 870e3c819e4..2d5188930a9 100644
--- a/theme/standard/styles_color.css
+++ b/theme/standard/styles_color.css
@@ -322,6 +322,17 @@ table.flexible .r1 {
background-color: green;
}
+#admin-report-security-index .statuswarning {
+ background-color: #f0e000;
+}
+
+#admin-report-security-index .statusserious {
+ background-color: #f07000;
+}
+
+#admin-report-security-index .statuscritical {
+ background-color: #f00000;
+}
.plugincompattable td.ok {
color: #008000;
diff --git a/version.php b/version.php
index 36ebf81e149..d37a97ab237 100644
--- a/version.php
+++ b/version.php
@@ -6,7 +6,7 @@
// This is compared against the values stored in the database to determine
// whether upgrades should be performed (see lib/db/*.php)
- $version = 2007101533.01; // YYYYMMDD = date of the 1.9 branch (don't change)
+ $version = 2007101533.02; // YYYYMMDD = date of the 1.9 branch (don't change)
// X = release number 1.9.[0,1,2,3...]
// Y.YY = micro-increments between releases