seckit.module in Security Kit 7
Same filename and directory in other branches
Allows administrators to improve security of the website.
File
seckit.moduleView source
<?php
/**
* @file
* Allows administrators to improve security of the website.
*/
/**
* Define the flags/values for certain options.
*/
define('SECKIT_X_XSS_DISABLE', 0);
// disable X-XSS-Protection HTTP header
define('SECKIT_X_XSS_0', 1);
// set X-XSS-Protection HTTP header to 0
define('SECKIT_X_XSS_1_BLOCK', 2);
// set X-XSS-Protection HTTP header to 1; mode=block
define('SECKIT_X_XSS_1', 3);
// set X-XSS-Protection HTTP header to 1
define('SECKIT_X_FRAME_DISABLE', 0);
// disable X-Frame-Options HTTP header
define('SECKIT_X_FRAME_SAMEORIGIN', 1);
// set X-Frame-Options HTTP header to SAMEORIGIN
define('SECKIT_X_FRAME_DENY', 2);
// set X-Frame-Options HTTP header to DENY
define('SECKIT_X_FRAME_ALLOW_FROM', 3);
// set X-Frame-Options HTTP header to ALLOW-FROM
define('SECKIT_CSP_REPORT_URL', 'report-csp-violation');
/**
* Default limits for CSP violation reports.
*/
define('SECKIT_CSP_REPORT_MAX_SIZE', 4096);
// Max accepted byte count
define('SECKIT_CSP_REPORT_FLOOD_LIMIT_USER', 100);
// Max reports per IP address...
define('SECKIT_CSP_REPORT_FLOOD_WINDOW_USER', 900);
// ...per time window (in seconds)
define('SECKIT_CSP_REPORT_FLOOD_LIMIT_GLOBAL', 1000);
// Max reports globally...
define('SECKIT_CSP_REPORT_FLOOD_WINDOW_GLOBAL', 3600);
// ...per time window (in seconds)
/**
* Implements hook_permission().
*/
function seckit_permission() {
return array(
'administer seckit' => array(
'title' => t('Administer SecKit'),
'description' => t('Configure security features of your Drupal installation.'),
),
);
}
/**
* Implements hook_menu().
*/
function seckit_menu() {
// Settings page
$items['admin/config/system/seckit'] = array(
'title' => 'Security Kit',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'seckit_admin_form',
),
'description' => 'Configure various options to improve security of your website.',
'access arguments' => array(
'administer seckit',
),
'file' => 'includes/seckit.form.inc',
);
// Menu callback for CSP reporting.
// We nominally accept all CSP violation reports (no access callback)
// but the page callback avoids processing invalid or unwanted requests.
$items[SECKIT_CSP_REPORT_URL] = array(
'page callback' => '_seckit_csp_report',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
// Original path for the above; deprecated in 7.x-1.10. It is important
// that this remains valid for now, as the CSP headers in cached pages
// will report the path as it was at the time they were cached.
//
// TODO: Remove this in some future release. There is no hurry to do this;
// it is better that we maintain this for a few releases (because not
// everyone will upgrade at every release), than risk that any CSP
// violation reports hit invalid URLs. Note that pages may be cached for
// long periods of time. It is probably reasonable to remove this after
// minimums of one year and two intervening releases.
$items['admin/config/system/seckit/csp-report'] = $items[SECKIT_CSP_REPORT_URL];
return $items;
}
/**
* Implements hook_init().
*/
function seckit_init() {
// Do nothing for command-line requests.
if (drupal_is_cli()) {
return;
}
// get default/set options
$options = _seckit_get_options();
if ($options['seckit_advanced']['disable_seckit']) {
return;
}
// execute necessary functions
if ($options['seckit_csrf']['origin']) {
_seckit_origin();
}
if ($options['seckit_xss']['csp']['checkbox']) {
_seckit_csp();
}
if ($options['seckit_xss']['x_xss']['select']) {
_seckit_x_xss($options['seckit_xss']['x_xss']['select']);
}
// Always call this (regardless of the setting) since if it's disabled it may
// be necessary to actively disable the Drupal core clickjacking defense.
_seckit_x_frame($options['seckit_clickjacking']['x_frame']);
if ($options['seckit_clickjacking']['js_css_noscript']) {
_seckit_js_css_noscript();
}
if ($options['seckit_ssl']['hsts']) {
_seckit_hsts();
}
if ($options['seckit_ct']['expect_ct']) {
_seckit_expect_ct();
}
if ($options['seckit_various']['from_origin']) {
_seckit_from_origin();
}
if ($options['seckit_various']['referrer_policy']) {
_seckit_referrer_policy();
}
if ($options['seckit_fp']['feature_policy']) {
_seckit_fp();
}
// load jQuery listener
if ($_GET['q'] == 'admin/config/system/seckit') {
$path = drupal_get_path('module', 'seckit');
$listener = "{$path}/js/seckit.listener.js";
drupal_add_js($listener);
}
}
/**
* Implements hook_boot().
*
* When multiple 'ALLOW-FROM' values are configured for X-Frame-Options,
* we dynamically set the header so that it is correct even when pages are
* served from the page cache.
*
* In other circumstances, Drupal does not see this implementation.
* @see seckit_module_implements_alter().
*/
function seckit_boot() {
$options = _seckit_get_options();
if ($options['seckit_clickjacking']['x_frame'] != SECKIT_X_FRAME_ALLOW_FROM) {
return;
}
// If this request's Origin is allowed, we specify that value.
// If the origin is not allowed, we can use any other value to prevent
// the client from framing the page.
$allowed = $options['seckit_clickjacking']['x_frame_allow_from'];
$origin = !empty($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
if (!in_array($origin, $allowed, TRUE)) {
$origin = array_pop($allowed);
}
drupal_add_http_header('X-Frame-Options', "ALLOW-FROM {$origin}");
}
/**
* Implements hook_module_implements_alter().
*
* The 'ALLOW-FROM' field of X-Frame-Options supports a single origin only.
* http://tools.ietf.org/html/rfc7034#section-2.3.2.3
*
* Consequently, when multiple values are configured we must resort to
* hook_boot() to dynamically set the header to the Origin of the current
* request, if that is one of the allowed values.
*
* Conversely, when we do not require hook_boot(), we unset our
* implementation, preventing _system_update_bootstrap_status() from
* registering it, and anything from invoking it.
*
* @see seckit_admin_form_submit().
*/
function seckit_module_implements_alter(&$implementations, $hook) {
if ($hook != 'boot') {
return;
}
$options = _seckit_get_options(TRUE);
if ($options['seckit_clickjacking']['x_frame'] != SECKIT_X_FRAME_ALLOW_FROM || count($options['seckit_clickjacking']['x_frame_allow_from']) <= 1) {
// seckit_boot() is not needed.
unset($implementations['seckit']);
// In this case, _seckit_x_frame() will generate the header
// (which will be cacheable), if it is required.
}
}
/**
* Implements hook_form_FORM_ID_alter() for 'user_login'.
*/
function seckit_form_user_login_alter(&$form, &$form_state) {
_seckit_form_alter_login_form($form, $form_state);
}
/**
* Implements hook_form_FORM_ID_alter() for 'user_login_block'.
*/
function seckit_form_user_login_block_alter(&$form, &$form_state) {
_seckit_form_alter_login_form($form, $form_state);
}
/**
* Implements hook_form_FORM_ID_alter() for 'user_register'.
*/
function seckit_form_user_register_form_alter(&$form, &$form_state) {
_seckit_form_alter_login_form($form, $form_state);
}
/**
* Form alteration helper for user login forms.
*/
function _seckit_form_alter_login_form(&$form, &$form_state) {
$options = _seckit_get_options();
if ($options['seckit_various']['disable_autocomplete']) {
$form['#attributes']['autocomplete'] = 'off';
if (isset($form['pass'])) {
$form['pass']['#attributes']['autocomplete'] = 'off';
}
}
}
/**
* Sends Content Security Policy HTTP headers.
*
* Header specifies Content Security Policy (CSP) for a website,
* which is used to allow/block content from selected sources.
*
* Based on specification available at http://www.w3.org/TR/CSP/
*/
function _seckit_csp() {
// get default/set options
$options = _seckit_get_options();
$options = $options['seckit_xss']['csp'];
$csp_vendor_prefix_x = $options['vendor-prefix']['x'];
$csp_vendor_prefix_webkit = $options['vendor-prefix']['webkit'];
$csp_report_only = $options['report-only'];
$csp_default_src = $options['default-src'];
$csp_script_src = $options['script-src'];
$csp_object_src = $options['object-src'];
$csp_img_src = $options['img-src'];
$csp_media_src = $options['media-src'];
$csp_style_src = $options['style-src'];
$csp_frame_src = $options['frame-src'];
$csp_frame_ancestors = $options['frame-ancestors'];
$csp_child_src = $options['child-src'];
$csp_font_src = $options['font-src'];
$csp_connect_src = $options['connect-src'];
$csp_report_uri = $options['report-uri'];
$csp_policy_uri = $options['policy-uri'];
$csp_upgrade_req = $options['upgrade-req'];
// prepare directives
$directives = array();
// if policy-uri is declared, no other directives are permitted.
if ($csp_policy_uri) {
$directives = "policy-uri " . base_path() . $csp_policy_uri;
}
else {
if ($csp_default_src) {
$directives[] = "default-src {$csp_default_src}";
}
if ($csp_script_src) {
$directives[] = "script-src {$csp_script_src}";
}
if ($csp_object_src) {
$directives[] = "object-src {$csp_object_src}";
}
if ($csp_style_src) {
$directives[] = "style-src {$csp_style_src}";
}
if ($csp_img_src) {
$directives[] = "img-src {$csp_img_src}";
}
if ($csp_media_src) {
$directives[] = "media-src {$csp_media_src}";
}
if ($csp_frame_src) {
$directives[] = "frame-src {$csp_frame_src}";
}
if ($csp_frame_ancestors) {
$directives[] = "frame-ancestors {$csp_frame_ancestors}";
}
if ($csp_child_src) {
$directives[] = "child-src {$csp_child_src}";
}
if ($csp_font_src) {
$directives[] = "font-src {$csp_font_src}";
}
if ($csp_connect_src) {
$directives[] = "connect-src {$csp_connect_src}";
}
if ($csp_report_uri) {
$directives[] = "report-uri " . url($csp_report_uri);
}
if ($csp_upgrade_req) {
$directives[] = 'upgrade-insecure-requests';
}
// merge directives
$directives = implode('; ', $directives);
}
// send HTTP response header if directives were prepared
if ($directives) {
if ($csp_report_only) {
// use report-only mode
drupal_add_http_header('Content-Security-Policy-Report-Only', $directives);
if ($csp_vendor_prefix_x) {
drupal_add_http_header('X-Content-Security-Policy-Report-Only', $directives);
}
if ($csp_vendor_prefix_webkit) {
drupal_add_http_header('X-WebKit-CSP-Report-Only', $directives);
}
}
else {
drupal_add_http_header('Content-Security-Policy', $directives);
if ($csp_vendor_prefix_x) {
drupal_add_http_header('X-Content-Security-Policy', $directives);
}
if ($csp_vendor_prefix_webkit) {
drupal_add_http_header('X-WebKit-CSP', $directives);
}
}
}
}
/**
* Log CSP violation reports to watchdog.
*/
function _seckit_csp_report() {
// Only allow POST data with Content-Type application/csp-report
// or application/json (the latter to support older user agents).
// n.b. The CSP spec (1.0, 1.1) mandates this Content-Type header/value.
// n.b. Content-Length is optional, so we don't check it.
if (empty($_SERVER['CONTENT_TYPE']) || empty($_SERVER['REQUEST_METHOD'])) {
return;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return;
}
$pattern = '~^application/(csp-report|json)\\h*(;|$)~';
if (!preg_match($pattern, $_SERVER['CONTENT_TYPE'])) {
return;
}
$options = _seckit_get_options();
// If SecKit is disabled, do not process the report.
if ($options['seckit_advanced']['disable_seckit']) {
return;
}
// If the CSP feature is currently disabled, do not process the report.
// This could be considered inaccurate (violation reports for a cached page
// which was generated with CSP headers might be expected to be processed);
// but a single on/off switch for both aspects of CSP support seemed sanest,
// and this approach ensures that we do not process requests sent to this URL
// before CSP headers have ever been enabled (prior to which reporting limits
// are usually ignored to facilitate initial CSP development).
if (!$options['seckit_xss']['csp']['checkbox']) {
return;
}
// Check for flooding.
// Do not write to watchdog when our limits are exceeded.
$enforce_limits = !$options['seckit_advanced']['unlimited_csp_reports'];
if ($enforce_limits && _seckit_csp_report_flooding_detected()) {
return;
}
// Read the report data.
if ($enforce_limits) {
$max_size = (int) $options['seckit_advanced']['csp_limits']['max_size'];
$reports = file_get_contents('php://input', FALSE, NULL, 0, $max_size + 1);
if (strlen($reports) > $max_size) {
return;
}
}
else {
$reports = file_get_contents('php://input');
}
$reports = json_decode($reports);
if (!is_object($reports)) {
return;
}
// Log the report data to watchdog.
foreach ($reports as $report) {
if (!isset($report->{'violated-directive'})) {
continue;
}
// Log the violation to watchdog.
$info = array(
'@directive' => $report->{'violated-directive'},
'@blocked_uri' => $report->{'blocked-uri'},
'@data' => print_r($report, TRUE),
);
watchdog('seckit', 'CSP: Directive @directive violated.<br /> Blocked URI: @blocked_uri.<br /> <pre>Data: @data</pre>', $info, WATCHDOG_WARNING);
}
}
/**
* Check for CSP violation report flooding.
*
* @return (bool)
* TRUE if flooding is detected (report logging should be inhibited).
* FALSE if it is safe to proceed with logging the report.
*/
function _seckit_csp_report_flooding_detected() {
$options = _seckit_get_options();
$flood_options = $options['seckit_advanced']['csp_limits']['flood'];
// The global limit provides some DDOS protection.
$global_limit = $flood_options['limit_global'];
$global_window = $flood_options['window_global'];
// flood_is_allowed() does not presently allow us to ignore the identifier,
// meaning we would need to log two flood events per CSP report in order to
// check both the global and per-user counts using the API function. This
// query enables us to do this while only registering one event per report.
// @see https://www.drupal.org/node/2472941
$sql = "\n SELECT COUNT(*)\n FROM {flood}\n WHERE event = 'seckit_csp_report'\n AND timestamp > :timestamp\n ";
$args = array(
':timestamp' => REQUEST_TIME - $global_window,
);
$global_count = db_query($sql, $args)
->fetchField();
if ($global_count >= $global_limit) {
return TRUE;
// Flooding is in effect
}
// Per-user limit.
$user_limit = $flood_options['limit_user'];
$user_window = $flood_options['window_user'];
if (!flood_is_allowed('seckit_csp_report', $user_limit, $user_window)) {
return TRUE;
// Flooding is in effect
}
// Flooding is not in effect. Log this event, and return the status.
flood_register_event('seckit_csp_report', $user_window);
return FALSE;
// No flooding
}
/**
* Sends X-XSS-Protection HTTP header.
*
* X-XSS-Protection controls IE8/Safari/Chrome internal XSS filter.
*/
function _seckit_x_xss($setting) {
switch ($setting) {
case SECKIT_X_XSS_0:
drupal_add_http_header('X-XSS-Protection', '0');
// set X-XSS-Protection header to 0
break;
case SECKIT_X_XSS_1:
drupal_add_http_header('X-XSS-Protection', '1');
// set X-XSS-Protection header to 1
break;
case SECKIT_X_XSS_1_BLOCK:
drupal_add_http_header('X-XSS-Protection', '1; mode=block');
// set X-XSS-Protection header to 1; mode=block
break;
case SECKIT_X_XSS_DISABLE:
default:
// do nothing
break;
}
}
/**
* Aborts HTTP request upon invalid 'Origin' HTTP request header.
*
* When included in an HTTP request, the Origin header indicates the origin(s)
* that caused the user agent to issue the request. This helps to protect
* against CSRF attacks, as we can abort requests with an unapproved origin.
*
* Applies to all HTTP request methods except GET and HEAD.
*
* Requests which do not include an 'Origin' header must always be allowed,
* as (a) not all user-agents support the header, and (b) those that do may
* include it or omit it at their discretion.
*
* Note that (a) will become progressively less of a factor over time --
* CSRF attacks depend upon convincing a user agent to send a request, and
* there is no particular motivation for users to prevent their web browsers
* from sending this header; so as people upgrade to browsers which support
* 'Origin', its effectiveness increases.
*
* Implementation of Origin is based on specification draft available at
* http://tools.ietf.org/html/draft-abarth-origin-09
*/
function _seckit_origin() {
// Allow requests without an 'Origin' header, or with a 'null' origin.
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
if (!$origin || $origin === 'null') {
return;
}
// Allow GET and HEAD requests.
$method = $_SERVER['REQUEST_METHOD'];
if (in_array($method, array(
'GET',
'HEAD',
), TRUE)) {
return;
}
// Allow requests from localhost.
if (in_array(ip_address(), array(
'localhost',
'127.0.0.1',
'::1',
), TRUE)) {
// Unless this is a test.
if (!drupal_valid_test_ua()) {
return;
}
}
// Allow requests from whitelisted Origins.
global $base_root;
$options = _seckit_get_options();
$whitelist = $options['seckit_csrf']['origin_whitelist'];
$whitelist[] = $base_root;
// default origin is always allowed
if (in_array($origin, $whitelist, TRUE)) {
return;
// n.b. RFC 6454 allows Origins to have more than one value (each
// separated by a single space). All values must be on the whitelist
// (order is not important). We intentionally do not handle this
// because the feature has been confirmed as a design mistake which
// user agents do not utilise in practice. For details, see
// http://lists.w3.org/Archives/Public/www-archive/2012Jun/0001.html
// and https://www.drupal.org/node/2406075
}
// The Origin is invalid, so we deny the request.
// Clean the POST data first, as drupal_access_denied() may render a page
// with forms which check for their submissions.
$_POST = array();
// Log the blocked attack.
$args = array(
'@ip' => ip_address(),
'@origin' => $origin,
);
watchdog('seckit', 'Possible CSRF attack was blocked. IP address: @ip, Origin: @origin.', $args, WATCHDOG_WARNING);
// Deliver the 403 (access denied) error page to the user.
drupal_access_denied();
// Abort this request.
drupal_exit();
}
/**
* Sends X-Frame-Options HTTP header.
*
* X-Frame-Options controls should browser show frames or not.
* More information can be found at initial article about it at
* http://blogs.msdn.com/ie/archive/2009/01/27/ie8-security-part-vii-clickjacking-defenses.aspx
*
* Implementation of X-Frame-Options is based on specification draft availabe at
* http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-01
*/
function _seckit_x_frame($setting) {
switch ($setting) {
case SECKIT_X_FRAME_SAMEORIGIN:
drupal_add_http_header('X-Frame-Options', 'SAMEORIGIN');
// set X-Frame-Options to SAMEORIGIN
break;
case SECKIT_X_FRAME_DENY:
drupal_add_http_header('X-Frame-Options', 'DENY');
// set X-Frame-Options to DENY
break;
case SECKIT_X_FRAME_ALLOW_FROM:
$options = _seckit_get_options();
$allowed = $options['seckit_clickjacking']['x_frame_allow_from'];
if (count($allowed) == 1) {
$value = array_pop($allowed);
drupal_add_http_header('X-Frame-Options', "ALLOW-FROM {$value}");
}
// If there were multiple values, then seckit_boot() took care of it.
break;
case SECKIT_X_FRAME_DISABLE:
// Make sure Drupal core does not set the header either. See
// drupal_deliver_html_page().
$GLOBALS['conf']['x_frame_options'] = '';
break;
}
}
/**
* Enables JavaScript + CSS + Noscript Clickjacking defense.
*
* Closes inline JavaScript and allows loading of any inline HTML elements.
* After, it starts new inline JavaScript to avoid breaking syntax.
* We need it, because Drupal API doesn't allow to init HTML elements in desired sequence.
*/
function _seckit_js_css_noscript() {
drupal_add_js(_seckit_get_js_css_noscript_code(), array(
'type' => 'inline',
));
}
/**
* Gets JavaScript and CSS code.
*
* @return string
*/
function _seckit_get_js_css_noscript_code() {
$options = _seckit_get_options();
$message = filter_xss($options['seckit_clickjacking']['noscript_message']);
$path = base_path() . drupal_get_path('module', 'seckit');
return <<<EOT
// close script tag for SecKit protection
//--><!]]>
</script>
<script type="text/javascript" src="{<span class="php-variable">$path</span>}/js/seckit.document_write.js"></script>
<link type="text/css" rel="stylesheet" id="seckit-clickjacking-no-body" media="all" href="{<span class="php-variable">$path</span>}/css/seckit.no_body.css" />
<!-- stop SecKit protection -->
<noscript>
<link type="text/css" rel="stylesheet" id="seckit-clickjacking-noscript-tag" media="all" href="{<span class="php-variable">$path</span>}/css/seckit.noscript_tag.css" />
<div id="seckit-noscript-tag">
{<span class="php-variable">$message</span>}
</div>
</noscript>
<script type="text/javascript">
<!--//--><![CDATA[//><!--
// open script tag to avoid syntax errors
EOT;
}
/**
* Sends Excpect-CT HTTP response header.
*
* Implementation is based on specification draft
* available at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT.
*/
function _seckit_expect_ct() {
$options = _seckit_get_options();
if ($options['seckit_ct']['expect_ct']) {
$header[] = sprintf("max-age=%d", $options['seckit_ct']['max_age']);
if ($options['seckit_ct']['enforce']) {
$header[] = 'enforce';
}
if ($options['seckit_ct']['report-uri']) {
$header[] = 'report-uri="' . $options['seckit_ct']['report-uri'] . '"';
}
$header = implode(', ', $header);
drupal_add_http_header('Expect-CT', $header);
}
}
/**
* Sends From-Origin HTTP response header.
*
* Implementation is based on specification draft
* available at http://www.w3.org/TR/from-origin.
*/
function _seckit_from_origin() {
$options = _seckit_get_options();
$value = $options['seckit_various']['from_origin_destination'];
drupal_add_http_header('From-Origin', $value);
}
/**
* Sends Feature-Policy HTTP response header.
*
* Implementation is based on specification draft
* available at https://developers.google.com/web/updates/2018/06/feature-policy.
*/
function _seckit_fp() {
$options = _seckit_get_options();
$value = $options['seckit_fp']['feature_policy_policy'];
drupal_add_http_header('Feature-Policy', $value);
}
/**
* Sends Referrer-Policy HTTP response header.
*
* Implementation is based on specification draft
* available at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy.
*/
function _seckit_referrer_policy() {
$options = _seckit_get_options();
$value = $options['seckit_various']['referrer_policy_policy'];
drupal_add_http_header('Referrer-Policy', $value);
}
/**
* Sends HTTP Strict-Transport-Security header (HSTS).
*
* The HSTS header prevents certain eavesdropping and MITM attacks like
* SSLStrip. It forces the user-agent to send requests in HTTPS-only mode.
* e.g.: http:// links are treated as https://
*
* Implementation of HSTS is based on the specification draft available at
* http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
*/
function _seckit_hsts() {
$options = _seckit_get_options();
$header[] = sprintf("max-age=%d", $options['seckit_ssl']['hsts_max_age']);
if ($options['seckit_ssl']['hsts_subdomains']) {
$header[] = 'includeSubDomains';
}
if ($options['seckit_ssl']['hsts_preload']) {
$header[] = 'preload';
}
$header = implode('; ', $header);
drupal_add_http_header('Strict-Transport-Security', $header);
}
/**
* Converts a multi-line (or otherwise delimited) string to an array of
* strings. Sanitises each value by trimming whitespace, and filters empty
* values from the array.
*
* @param string $text
* String of delimited values.
* @param string $delimiter
* Value delimiter. Defaults to newline for handling multi-line strings.
*/
function _seckit_explode_value($text, $delimiter = "\n") {
$values = explode($delimiter, $text);
return array_values(array_filter(array_map('trim', $values)));
}
/**
* Define the default values for our settings variables.
*
* @see _seckit_get_options().
*/
function _seckit_get_options_defaults() {
$defaults = array();
// Defaults for variable_get('seckit_xss');
$defaults['seckit_xss'] = array(
// Content Security Policy (CSP)
'csp' => array(
'checkbox' => 0,
// CSP disabled by default
'vendor-prefix' => array(
'x' => 0,
'webkit' => 0,
),
'report-only' => 0,
'default-src' => "'self'",
'script-src' => '',
'object-src' => '',
'style-src' => '',
'img-src' => '',
'media-src' => '',
'frame-src' => '',
'frame-ancestors' => '',
'child-src' => '',
'font-src' => '',
'connect-src' => '',
'report-uri' => SECKIT_CSP_REPORT_URL,
'policy-uri' => '',
'upgrade-req' => '',
),
// X-XSS-Protection header.
'x_xss' => array(
'select' => SECKIT_X_XSS_DISABLE,
),
);
// Defaults for variable_get('seckit_csrf');
// Enable Origin-based protection.
$defaults['seckit_csrf'] = array(
'origin' => 1,
'origin_whitelist' => '',
);
// Defaults for variable_get('seckit_clickjacking');
$defaults['seckit_clickjacking'] = array(
'x_frame' => SECKIT_X_FRAME_SAMEORIGIN,
'x_frame_allow_from' => '',
'js_css_noscript' => 0,
// Do not require Javascript by default!
'noscript_message' => t('Sorry, you need to enable JavaScript to visit this website.'),
);
// Defaults for variable_get('seckit_ssl');
$defaults['seckit_ssl'] = array(
'hsts' => 0,
'hsts_max_age' => 1000,
'hsts_subdomains' => 0,
'hsts_preload' => 0,
);
// Defaults for variable_get('seckit_ct');
$defaults['seckit_ct'] = array(
'expect_ct' => 0,
'max_age' => 1000,
'report-uri' => '',
'enforce' => 0,
);
// Defaults for variable_get('seckit_fp');
$defaults['seckit_fp'] = array(
'feature_policy' => 0,
'feature_policy_policy' => '',
);
// Defaults for variable_get('seckit_various');
$defaults['seckit_various'] = array(
'referrer_policy' => 0,
'referrer_policy_policy' => '',
'from_origin' => 0,
'from_origin_destination' => 'same',
'disable_autocomplete' => 0,
);
// Advanced / Development options.
// Defaults for variable_get('seckit_advanced');
$defaults['seckit_advanced'] = array(
'disable_seckit' => 0,
'unlimited_csp_reports' => 0,
'csp_limits' => array(
'max_size' => SECKIT_CSP_REPORT_MAX_SIZE,
'flood' => array(
'limit_user' => SECKIT_CSP_REPORT_FLOOD_LIMIT_USER,
'window_user' => SECKIT_CSP_REPORT_FLOOD_WINDOW_USER,
'limit_global' => SECKIT_CSP_REPORT_FLOOD_LIMIT_GLOBAL,
'window_global' => SECKIT_CSP_REPORT_FLOOD_WINDOW_GLOBAL,
),
),
);
return $defaults;
}
/**
* Return the current SecKit settings.
*
* @param boolean $reset
* If TRUE then re-generate (and re-cache) the options.
*
* @param boolean $alter
* Whether to invoke hook_seckit_options_alter().
* (Used internally to prevent altered values being used
* in the admin settings form.)
*/
function _seckit_get_options($reset = FALSE, $alter = TRUE) {
$options =& drupal_static(__FUNCTION__, array());
if ($reset) {
$options = array();
}
elseif ($options) {
return $options;
}
// Merge the defaults into their associated saved variables, as necessary.
// Each (scalar) value will be used only if its key does not exist in the
// saved value (if any) for that variable.
//
// This means that we can introduce new settings with default values,
// without affecting the saved values from earlier versions (which do
// not yet contain the new keys).
$defaults = _seckit_get_options_defaults();
foreach (array_keys($defaults) as $option) {
$options[$option] = array_replace_recursive($defaults[$option], variable_get($option, array()));
}
// Ensure there are non-empty values for the CSP default-src and report-uri
// directives.
$csp_defaults = $defaults['seckit_xss']['csp'];
if (!$options['seckit_xss']['csp']['default-src']) {
$options['seckit_xss']['csp']['default-src'] = $csp_defaults['default-src'];
}
if (!$options['seckit_xss']['csp']['report-uri']) {
$options['seckit_xss']['csp']['report-uri'] = $csp_defaults['report-uri'];
}
// Convert ['seckit_clickjacking']['x_frame_allow_from'] to an array.
$x_frame_allow_from =& $options['seckit_clickjacking']['x_frame_allow_from'];
$x_frame_allow_from = _seckit_explode_value($x_frame_allow_from);
// Convert $options['seckit_csrf']['origin_whitelist'] to an array.
$whitelist =& $options['seckit_csrf']['origin_whitelist'];
$whitelist = _seckit_explode_value($whitelist, ',');
// Process alterations and return.
if ($alter) {
drupal_alter('seckit_options', $options);
}
return $options;
}
Functions
Name | Description |
---|---|
seckit_boot | Implements hook_boot(). |
seckit_form_user_login_alter | Implements hook_form_FORM_ID_alter() for 'user_login'. |
seckit_form_user_login_block_alter | Implements hook_form_FORM_ID_alter() for 'user_login_block'. |
seckit_form_user_register_form_alter | Implements hook_form_FORM_ID_alter() for 'user_register'. |
seckit_init | Implements hook_init(). |
seckit_menu | Implements hook_menu(). |
seckit_module_implements_alter | Implements hook_module_implements_alter(). |
seckit_permission | Implements hook_permission(). |
_seckit_csp | Sends Content Security Policy HTTP headers. |
_seckit_csp_report | Log CSP violation reports to watchdog. |
_seckit_csp_report_flooding_detected | Check for CSP violation report flooding. |
_seckit_expect_ct | Sends Excpect-CT HTTP response header. |
_seckit_explode_value | Converts a multi-line (or otherwise delimited) string to an array of strings. Sanitises each value by trimming whitespace, and filters empty values from the array. |
_seckit_form_alter_login_form | Form alteration helper for user login forms. |
_seckit_fp | Sends Feature-Policy HTTP response header. |
_seckit_from_origin | Sends From-Origin HTTP response header. |
_seckit_get_js_css_noscript_code | Gets JavaScript and CSS code. |
_seckit_get_options | Return the current SecKit settings. |
_seckit_get_options_defaults | Define the default values for our settings variables. |
_seckit_hsts | Sends HTTP Strict-Transport-Security header (HSTS). |
_seckit_js_css_noscript | Enables JavaScript + CSS + Noscript Clickjacking defense. |
_seckit_origin | Aborts HTTP request upon invalid 'Origin' HTTP request header. |
_seckit_referrer_policy | Sends Referrer-Policy HTTP response header. |
_seckit_x_frame | Sends X-Frame-Options HTTP header. |
_seckit_x_xss | Sends X-XSS-Protection HTTP header. |
Constants
Name | Description |
---|---|
SECKIT_CSP_REPORT_FLOOD_LIMIT_GLOBAL | |
SECKIT_CSP_REPORT_FLOOD_LIMIT_USER | |
SECKIT_CSP_REPORT_FLOOD_WINDOW_GLOBAL | |
SECKIT_CSP_REPORT_FLOOD_WINDOW_USER | |
SECKIT_CSP_REPORT_MAX_SIZE | Default limits for CSP violation reports. |
SECKIT_CSP_REPORT_URL | |
SECKIT_X_FRAME_ALLOW_FROM | |
SECKIT_X_FRAME_DENY | |
SECKIT_X_FRAME_DISABLE | |
SECKIT_X_FRAME_SAMEORIGIN | |
SECKIT_X_XSS_0 | |
SECKIT_X_XSS_1 | |
SECKIT_X_XSS_1_BLOCK | |
SECKIT_X_XSS_DISABLE | Define the flags/values for certain options. |