diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 222e5f762d..3e1ae339f9 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -103,6 +103,8 @@
  • [Fix] Correctly sort by rank in memberlist (Bug #24435)
  • [Fix] Purge cache after database restore (Bug #24245)
  • [Fix] Correctly display subforum read/unread icons from RTL in FF3, Konqueror and Safari3+. (thanks arod-1 for the fix, related to Bug #14830)
  • +
  • [Feature] Added optional referer validation of POST requests as additional CSRF protection.
  • +
  • [Fix] Added missing form token in acp (thanks NBBN).
  • 1.ii. Changes since 3.0.0

    diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 4d82926ca2..4a42f1fd8b 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -323,6 +323,7 @@ class acp_board 'ip_check' => array('lang' => 'IP_VALID', 'validate' => 'int', 'type' => 'custom', 'method' => 'select_ip_check', 'explain' => true), 'browser_check' => array('lang' => 'BROWSER_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'forwarded_for_check' => array('lang' => 'FORWARDED_FOR_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'referer_validation' => array('lang' => 'REFERER_VALID', 'validate' => 'int:0:3','type' => 'custom', 'method' => 'select_ref_check', 'explain' => true), 'check_dnsbl' => array('lang' => 'CHECK_DNSBL', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'email_check_mx' => array('lang' => 'EMAIL_CHECK_MX', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), 'pass_complex' => array('lang' => 'PASSWORD_TYPE', 'validate' => 'string', 'type' => 'select', 'method' => 'select_password_chars', 'explain' => true), @@ -676,7 +677,17 @@ class acp_board return h_radio('config[ip_check]', $radio_ary, $value, $key); } + + /** + * Select referer validation + */ + function select_ref_check($value, $key = '') + { + $radio_ary = array(REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION'); + return h_radio('config[referer_validation]', $radio_ary, $value, $key); + } + /** * Select account activation method */ diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index eb4eb77f22..7c681a4040 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -171,6 +171,11 @@ define('FIELD_BOOL', 4); define('FIELD_DROPDOWN', 5); define('FIELD_DATE', 6); +// referer validation +define('REFERER_VALIDATE_NONE', 0); +define('REFERER_VALIDATE_HOST', 1); +define('REFERER_VALIDATE_PATH', 2); + // Additional constants define('VOTE_CONVERTED', 127); diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index 8239921ba8..33fce6731b 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -158,6 +158,7 @@ class session $this->cookie_data = array('u' => 0, 'k' => ''); $this->update_session_page = $update_session_page; $this->browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? htmlspecialchars((string) $_SERVER['HTTP_USER_AGENT']) : ''; + $this->referer = (!empty($_SERVER['HTTP_REFERER'])) ? htmlspecialchars((string) $_SERVER['HTTP_REFERER']) : ''; $this->forwarded_for = (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? (string) $_SERVER['HTTP_X_FORWARDED_FOR'] : ''; $this->host = (!empty($_SERVER['HTTP_HOST'])) ? (string) strtolower($_SERVER['HTTP_HOST']) : ((!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME')); $this->page = $this->extract_current_page($phpbb_root_path); @@ -263,8 +264,17 @@ class session $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; + + // referer checks + $check_referer_path = $config['referer_validation'] == REFERER_VALIDATE_PATH; + $referer_valid = true; + if ($config['referer_validation'] && isset($_SERVER['REQUEST_METHOD']) && strtolower($_SERVER['REQUEST_METHOD']) !== 'get') + { + $referer_valid = $this->validate_referer($check_referer_path); + } + - if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid) { $session_expired = false; @@ -343,7 +353,14 @@ class session // Added logging temporarly to help debug bugs... if (defined('DEBUG_EXTRA') && $this->data['user_id'] != ANONYMOUS) { - add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); + if ($referer_valid) + { + add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, htmlspecialchars($u_forwarded_for), htmlspecialchars($s_forwarded_for)); + } + else + { + add_log('critical', 'LOG_REFERER_INVALID', $this->referer); + } } } } @@ -1279,6 +1296,35 @@ class session $this->set_login_key($user_id); } } + + + /** + * Check if the request originated from the same page. + * @param bool $check_script_path If true, the path will be checked as well + */ + function validate_referer($check_script_path = false) + { + // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason) + if (empty($this->referer) || empty($this->host) ) + { + return true; + } + $host = htmlspecialchars($this->host); + $ref = substr($this->referer, strpos($this->referer, '://') + 3); + if (!(stripos($ref , $host) === 0)) + { + return false; + } + else if ($check_script_path && !empty(rtrim($this->page['root_script_path'], '/'))) + { + $ref = substr($ref, strlen($host)); + if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0)) + { + return false; + } + } + return true; + } } diff --git a/phpBB/install/database_update.php b/phpBB/install/database_update.php index 211bbec8d1..bead1cad93 100644 --- a/phpBB/install/database_update.php +++ b/phpBB/install/database_update.php @@ -1740,6 +1740,10 @@ function change_database_data($version) // TODO: remove all form token min times break; + + case '3.0.1': + + set_config('referer_validation', '1'); } } diff --git a/phpBB/install/schemas/schema_data.sql b/phpBB/install/schemas/schema_data.sql index 43ab04d77e..2d417129ed 100644 --- a/phpBB/install/schemas/schema_data.sql +++ b/phpBB/install/schemas/schema_data.sql @@ -184,6 +184,7 @@ INSERT INTO phpbb_config (config_name, config_value) VALUES ('print_pm', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('queue_interval', '600'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('ranks_path', 'images/ranks'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('require_activation', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('referer_validation', '1'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('script_path', ''); INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_block_size', '250'); INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_gc', '7200'); diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index f328c05882..ae122c54ee 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -206,10 +206,6 @@ $lang = array_merge($lang, array( 'ENABLE_COPPA_EXPLAIN' => 'This requires users to declare whether they are 13 or over for compliance with the U.S. COPPA. If this is disabled the COPPA specific groups will no longer be displayed.', 'MAX_CHARS' => 'Max', 'MIN_CHARS' => 'Min', - 'MIN_TIME_REG' => 'Minimum time for registration', - 'MIN_TIME_REG_EXPLAIN' => 'The registration form cannot be submitted before this time has passed.', - 'MIN_TIME_TERMS' => 'Minimum time for accepting terms', - 'MIN_TIME_TERMS_EXPLAIN' => 'The terms page cannot be skipped before this time has passed.', 'NO_AUTH_PLUGIN' => 'No suitable auth plugin found.', 'PASSWORD_LENGTH' => 'Password length', 'PASSWORD_LENGTH_EXPLAIN' => 'Minimum and maximum number of characters in passwords.', @@ -375,8 +371,6 @@ $lang = array_merge($lang, array( 'FORCE_PASS_CHANGE_EXPLAIN' => 'Require user to change their password after a set number of days. Setting this value to 0 disables this behaviour.', 'FORM_TIME_MAX' => 'Maximum time to submit forms', 'FORM_TIME_MAX_EXPLAIN' => 'The time a user has to submit a form. Use -1 to disable. Note that a form might become invalid if the session expires, regardless of this setting.', - 'FORM_TIME_MIN' => 'Minimum time to submit forms', - 'FORM_TIME_MIN_EXPLAIN' => 'Submissions faster than this time are ignored by the board. Use 0 to disable.', 'FORM_SID_GUESTS' => 'Tie forms to guest sessions', 'FORM_SID_GUESTS_EXPLAIN' => 'If enabled, the form token issued to guests will be session-exclusive. This can cause problems with some ISPs.', 'FORWARDED_FOR_VALID' => 'Validated X_FORWARDED_FOR header', @@ -386,12 +380,17 @@ $lang = array_merge($lang, array( 'MAX_LOGIN_ATTEMPTS' => 'Maximum number of login attempts', 'MAX_LOGIN_ATTEMPTS_EXPLAIN' => 'After this number of failed logins the user needs to additionally confirm his login visually (visual confirmation).', 'NO_IP_VALIDATION' => 'None', + 'NO_REF_VALIDATION' => 'None', 'PASSWORD_TYPE' => 'Password complexity', 'PASSWORD_TYPE_EXPLAIN' => 'Determines how complex a password needs to be when set or altered, subsequent options include the previous ones.', 'PASS_TYPE_ALPHA' => 'Must contain letters and numbers', 'PASS_TYPE_ANY' => 'No requirements', 'PASS_TYPE_CASE' => 'Must be mixed case', 'PASS_TYPE_SYMBOL' => 'Must contain symbols', + 'REF_HOST' => 'Only validate host', + 'REF_PATH' => 'Also validate path', + 'REFERER_VALID' => 'Validate Referer', + 'REFERER_VALID_EXPLAIN' => 'If enabled, the referer of POST requests will be checked against the host/script path settings. This may cause issues with boards using several domains and or external logins.', 'TPL_ALLOW_PHP' => 'Allow php in templates', 'TPL_ALLOW_PHP_EXPLAIN' => 'If this option is enabled, PHP and INCLUDEPHP statements will be recognised and parsed in templates.', )); diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index f380b1f570..85c57fb1c3 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -608,6 +608,7 @@ $lang = array_merge($lang, array( 'LOG_REASON_REMOVED' => 'Removed report/denial reason
    » %s', 'LOG_REASON_UPDATED' => 'Updated report/denial reason
    » %s', + 'LOG_REFERER_INVALID' => 'Referer validation failed
    »Referer was “%1$s”. The request was rejected and the session killed.', 'LOG_RESET_DATE' => 'Board start date reset', 'LOG_RESET_ONLINE' => 'Most users online reset', 'LOG_RESYNC_POSTCOUNTS' => 'User post counts resynchronised',