diff options
Diffstat (limited to 'phpBB/includes/session.php')
| -rw-r--r-- | phpBB/includes/session.php | 171 |
1 files changed, 146 insertions, 25 deletions
diff --git a/phpBB/includes/session.php b/phpBB/includes/session.php index ad6a049a65..9121a965fd 100644 --- a/phpBB/includes/session.php +++ b/phpBB/includes/session.php @@ -18,6 +18,7 @@ class session var $page = array(); var $data = array(); var $browser = ''; + var $forwarded_for = ''; var $host = ''; var $session_id = ''; var $ip = ''; @@ -91,7 +92,7 @@ class session // The script path from the webroot to the phpBB root (for example: /phpBB2/) $script_dirs = explode('/', $script_path); array_splice($script_dirs, -sizeof($page_dirs)); - $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : ''); + $root_script_path = implode('/', $script_dirs) . (sizeof($root_dirs) ? '/' . implode('/', $root_dirs) : ''); // We are on the base level (phpBB root == webroot), lets adjust the variables a bit... if (!$root_script_path) @@ -145,9 +146,40 @@ class session $this->cookie_data = array('u' => 0, 'k' => ''); $this->update_session_page = $update_session_page; $this->browser = (!empty($_SERVER['HTTP_USER_AGENT'])) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; + $this->forwarded_for = (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) ? (string) $_SERVER['HTTP_X_FORWARDED_FOR'] : ''; $this->host = (!empty($_SERVER['HTTP_HOST'])) ? (string) $_SERVER['HTTP_HOST'] : 'localhost'; $this->page = $this->extract_current_page($phpbb_root_path); + // if the forwarded for header shall be checked we have to validate its contents + if ($config['forwarded_for_check']) + { + $this->forwarded_for = preg_replace('#, +#', ', ', $this->forwarded_for); + + // Whoa these look impressive! + // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses + // can be found in the develop directory + $ipv4 = '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#'; + $ipv6 = '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){5}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:))$#i'; + + // split the list of IPs + $ips = explode(', ', $this->forwarded_for); + foreach ($ips as $ip) + { + // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly + if (!empty($ip) && !preg_match($ipv4, $ip) && !preg_match($ipv6, $ip)) + { + if (!defined('DEBUG_EXTRA')) + { + trigger_error('Hacking attempt!'); + } + else + { + trigger_error('Invalid HTTP_X_FORWARDED_FOR header detected: ' . htmlspecialchars($this->forwarded_for)); + } + } + } + } + // Add forum to the page for tracking online users - also adding a "x" to the end to properly identify the number $this->page['page'] .= (isset($_REQUEST['f'])) ? ((strpos($this->page['page'], '?') !== false) ? '&' : '?') . '_f_=' . (int) $_REQUEST['f'] . 'x' : ''; @@ -216,7 +248,10 @@ class session $s_browser = ($config['browser_check']) ? strtolower(substr($this->data['session_browser'], 0, 149)) : ''; $u_browser = ($config['browser_check']) ? strtolower(substr($this->browser, 0, 149)) : ''; - if ($u_ip === $s_ip && $s_browser === $u_browser) + $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) : ''; + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) { $session_expired = false; @@ -278,7 +313,7 @@ class session // Added logging temporarly to help debug bugs... if (defined('DEBUG_EXTRA')) { - add_log('critical', 'LOG_IP_BROWSER_CHECK', $u_ip, $s_ip, $u_browser, $s_browser); + add_log('critical', 'LOG_IP_BROWSER_FORWARDED_CHECK', $u_ip, $s_ip, $u_browser, $s_browser, $u_forwarded_for, $s_forwarded_for); } } } @@ -398,7 +433,7 @@ class session $db->sql_freeresult($result); } - // If no data was returned one or more of the following occured: + // If no data was returned one or more of the following occurred: // Key didn't match one in the DB // User does not exist // User is inactive @@ -447,7 +482,16 @@ class session // Is user banned? Are they excluded? Won't return on ban, exists within method if ($this->data['user_type'] != USER_FOUNDER) { - $this->check_ban($this->data['user_id'], $this->ip); + if (!$config['forwarded_for_check']) + { + $this->check_ban($this->data['user_id'], $this->ip); + } + else + { + $ips = explode(', ', $this->forwarded_for); + $ips[] = $this->ip; + $this->check_ban($this->data['user_id'], $ips); + } } $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; @@ -456,14 +500,17 @@ class session // If our friend is a bot, we re-assign a previously assigned session if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id']) { - // Only assign the current session if the ip and browser match... + // Only assign the current session if the ip, browser and forwarded_for match... $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); $s_browser = ($config['browser_check']) ? strtolower(substr($this->data['session_browser'], 0, 149)) : ''; $u_browser = ($config['browser_check']) ? strtolower(substr($this->browser, 0, 149)) : ''; - if ($u_ip === $s_ip && $s_browser === $u_browser) + $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) : ''; + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) { $this->session_id = $this->data['session_id']; @@ -512,6 +559,7 @@ class session 'session_last_visit' => (int) $this->data['session_last_visit'], 'session_time' => (int) $this->time_now, 'session_browser' => (string) $this->browser, + 'session_forwarded_for' => (string) $this->forwarded_for, 'session_ip' => (string) $this->ip, 'session_autologin' => ($session_autologin) ? 1 : 0, 'session_admin' => ($set_admin) ? 1 : 0, @@ -580,6 +628,14 @@ class session } else { + $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; + + // Update the last visit time + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + $SID = '?sid='; $_SID = ''; } @@ -595,7 +651,7 @@ class session * and update the users information from the relevant session data. It will then * grab guest user information. */ - function session_kill() + function session_kill($new_session = true) { global $SID, $_SID, $db, $config, $phpbb_root_path, $phpEx; @@ -656,7 +712,10 @@ class session $this->session_id = $_SID = ''; // To make sure a valid session is created we create one for the anonymous user - $this->session_create(ANONYMOUS); + if ($new_session) + { + $this->session_create(ANONYMOUS); + } return true; } @@ -757,11 +816,18 @@ class session * are passed to the method pre-existing session data is used. If $return is false * this routine does not return on finding a banned user, it outputs a relevant * message and stops execution. + * + * @param string|array $user_ips Can contain a string with one IP or an array of multiple IPs */ - function check_ban($user_id = false, $user_ip = false, $user_email = false, $return = false) + function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) { global $config, $db; + if (defined('IN_CHECK_BAN')) + { + return; + } + $banned = false; $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end @@ -774,14 +840,14 @@ class session $sql .= " AND ban_email = ''"; } - if ($user_ip === false) + if ($user_ips === false) { - $sql .= " AND (ban_ip = '' OR (ban_ip <> '' AND ban_exclude = 1))"; + $sql .= " AND (ban_ip = '' OR ban_exclude = 1)"; } if ($user_id === false) { - $sql .= ' AND (ban_userid = 0 OR (ban_userid <> 0 AND ban_exclude = 1))'; + $sql .= ' AND (ban_userid = 0 OR ban_exclude = 1)'; } else { @@ -792,7 +858,7 @@ class session $sql .= " OR ban_email <> ''"; } - if ($user_ip !== false) + if ($user_ips !== false) { $sql .= " OR ban_ip <> ''"; } @@ -805,8 +871,28 @@ class session $ban_triggered_by = 'user'; while ($row = $db->sql_fetchrow($result)) { + $ip_banned = false; + if (!empty($row['ban_ip'])) + { + if (!is_array($user_ips)) + { + $ip_banned = preg_match('#^' . str_replace('*', '.*?', $row['ban_ip']) . '$#i', $user_ips); + } + else + { + foreach ($user_ips as $user_ip) + { + if (preg_match('#^' . str_replace('*', '.*?', $row['ban_ip']) . '$#i', $user_ip)) + { + $ip_banned = true; + break; + } + } + } + } + if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || - (!empty($row['ban_ip']) && preg_match('#^' . str_replace('*', '.*?', $row['ban_ip']) . '$#i', $user_ip)) || + $ip_banned || (!empty($row['ban_email']) && preg_match('#^' . str_replace('*', '.*?', $row['ban_email']) . '$#i', $user_email))) { if (!empty($row['ban_exclude'])) @@ -823,7 +909,7 @@ class session { $ban_triggered_by = 'user'; } - else if (!empty($row['ban_ip']) && preg_match('#^' . str_replace('*', '.*?', $row['ban_ip']) . '$#i', $user_ip)) + else if (!empty($row['ban_ip']) && preg_match('#^' . str_replace('*', '.*?', $row['ban_ip']) . '$#i', $user_ips)) { $ban_triggered_by = 'ip'; } @@ -849,6 +935,23 @@ class session $this->session_kill(); } + // We show a login box here to allow founders accessing the board if banned by IP + if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS) + { + global $phpEx; + + // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again. + define('IN_CHECK_BAN', 1); + + $this->setup('ucp'); + $this->data['is_registered'] = $this->data['is_bot'] = false; + + login_box("index.$phpEx"); + + // The false here is needed, else the user is able to circumvent the ban. + $this->session_kill(false); + } + // Determine which message to output $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; @@ -870,9 +973,10 @@ class session * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups) * * @author satmd (from the php manual) + * @param string $mode register/post - spamcop for example is ommitted for posting * @return false if ip is not blacklisted, else an array([checked server], [lookup]) */ - function check_dnsbl($ip = false) + function check_dnsbl($mode, $ip = false) { if ($ip === false) { @@ -880,23 +984,40 @@ class session } $dnsbl_check = array( - 'bl.spamcop.net' => 'http://spamcop.net/bl.shtml?', 'list.dsbl.org' => 'http://dsbl.org/listing?', 'sbl-xbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=', ); + if ($mode == 'register') + { + $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?'; + } + if ($ip) { $quads = explode('.', $ip); $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0]; + // Need to be listed on all servers... + $listed = true; + $info = array(); + foreach ($dnsbl_check as $dnsbl => $lookup) { if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true) { - return array($dnsbl, $lookup . $ip); + $info = array($dnsbl, $lookup . $ip); + } + else + { + $listed = false; } } + + if ($listed) + { + return $info; + } } return false; @@ -1185,7 +1306,7 @@ class user extends session } $stylesheet = str_replace($match, $content, $stylesheet); } - unset ($content); + unset($content); } $stylesheet = str_replace('./', 'styles/' . $this->theme['theme_path'] . '/theme/', $stylesheet); @@ -1210,10 +1331,10 @@ class user extends session // Disable board if the install/ directory is still present // For the brave development army we do not care about this, else we need to comment out this everytime we develop locally - if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install')) + if (!defined('DEBUG_EXTRA') && !defined('ADMIN_START') && !defined('IN_INSTALL') && !defined('IN_LOGIN') && file_exists($phpbb_root_path . 'install')) { // Adjust the message slightly according to the permissions - if ($auth->acl_gets('a_', 'm_')) + if ($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) { $message = 'REMOVE_INSTALL'; } @@ -1226,7 +1347,7 @@ class user extends session } // Is board disabled and user not an admin or moderator? - if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_')) + if ($config['board_disable'] && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) { $message = (!empty($config['board_disable_msg'])) ? $config['board_disable_msg'] : 'BOARD_DISABLE'; trigger_error($message); @@ -1235,7 +1356,7 @@ class user extends session // Is load exceeded? if ($config['limit_load'] && $this->load !== false) { - if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_')) + if ($this->load > floatval($config['limit_load']) && !defined('IN_LOGIN') && !$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) { trigger_error('BOARD_UNAVAILABLE'); } @@ -1325,7 +1446,7 @@ class user extends session // $lang == $this->lang // $help == $this->help - // - add appropiate variables here, name them as they are used within the language file... + // - add appropriate variables here, name them as they are used within the language file... if (!$use_db) { if ((include($this->lang_path . (($use_help) ? 'help_' : '') . "$lang_file.$phpEx")) === false) |
