479 lines
12 KiB
PHP
479 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Bitrix Framework
|
|
* @package bitrix
|
|
* @subpackage security
|
|
* @copyright 2001-2013 Bitrix
|
|
*/
|
|
|
|
namespace Bitrix\Security;
|
|
|
|
use Bitrix\Main\Config;
|
|
use Bitrix\Main\Context;
|
|
use Bitrix\Main\Data;
|
|
use Bitrix\Main\EventManager;
|
|
use Bitrix\Main\Loader;
|
|
use Bitrix\Main\ArgumentNullException;
|
|
use Bitrix\Main\ArgumentOutOfRangeException;
|
|
use Bitrix\Main\ArgumentTypeException;
|
|
|
|
/**
|
|
* Class HostRestriction
|
|
* @since 14.0.6
|
|
* @example tests/security/hosts/basic.php
|
|
* @package Bitrix\Security
|
|
*/
|
|
class HostRestriction
|
|
{
|
|
const ACTION_REDIRECT = 'redirect';
|
|
const ACTION_STOP = 'stop';
|
|
|
|
private $optionPrefix = 'restriction_hosts_';
|
|
private $cacheInitPath = 'security';
|
|
private $cacheId = 'restriction_hosts';
|
|
private $cacheTtl = 31536000; //one year
|
|
private $action = 'stop';
|
|
private $actionOptions = array();
|
|
private $isLogNeeded = true;
|
|
private $hosts = null;
|
|
private $validActions = array(
|
|
self::ACTION_REDIRECT,
|
|
self::ACTION_STOP
|
|
);
|
|
private $validationRegExp = null;
|
|
private $isActive = null;
|
|
|
|
/**
|
|
* Handler for system event "OnPageStart", does nothing in CLI mode because it does not make sense
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function onPageStart()
|
|
{
|
|
if (\CSecuritySystemInformation::isCliMode())
|
|
return;
|
|
|
|
/** @var HostRestriction $instance */
|
|
$instance = new static;
|
|
$instance->process();
|
|
}
|
|
|
|
public function __construct()
|
|
{
|
|
$this->hosts = Config\Option::get('security', $this->optionPrefix.'hosts', '');
|
|
$this->action = Config\Option::get('security', $this->optionPrefix.'action', '');
|
|
$this->actionOptions = unserialize(Config\Option::get('security', $this->optionPrefix.'action_options', '{}'), ['allowed_classes' => false]);
|
|
$this->isLogNeeded = Config\Option::get('security', $this->optionPrefix.'logging', false);
|
|
}
|
|
|
|
/**
|
|
* The main method that checks the current host, logging and starting action
|
|
*
|
|
* @param string $host Requested host for checking.
|
|
* @return $this
|
|
*/
|
|
public function process($host = null)
|
|
{
|
|
if (is_null($host))
|
|
$host = $this->getTargetHost();
|
|
|
|
if ($this->isValidHost($host))
|
|
return $this;
|
|
|
|
if ($this->isLogNeeded)
|
|
$this->log($host);
|
|
|
|
$this->doActions();
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Checking host by host restriction policy
|
|
*
|
|
* @param string $host Host for checking.
|
|
* @return bool Return true for valid (allowed) host.
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
*/
|
|
public function isValidHost($host)
|
|
{
|
|
if (is_string($host) && $host !== '')
|
|
{
|
|
$count = 0;
|
|
preg_replace($this->getValidationRegExp(), '', $host, 1, $count);
|
|
//var_dump($count);
|
|
if ($count)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getProperties()
|
|
{
|
|
return array(
|
|
'hosts' => $this->hosts,
|
|
'current_host' => $this->getTargetHost(),
|
|
'action' => $this->action,
|
|
'action_options' => $this->actionOptions,
|
|
'logging' => $this->isLogNeeded,
|
|
'active' => is_null($this->isActive)? $this->getActive(): $this->isActive
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set various properties for host checking, now support:
|
|
* - hosts: a string with allowed hosts (wild card supported, e.g.: *.example.com) {@see setHosts}
|
|
* - action: a string with action for unallowed host {@see validActions}
|
|
* - action_options: array with some options for action {@see setAction}
|
|
* - logging: bool, set true if need logging unallowed host {@see setLogging}
|
|
* - active: bool, set true if automatic checking on every request needed
|
|
*
|
|
* @param array $properties See above.
|
|
* @throws \Bitrix\Main\ArgumentOutOfRangeException
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
* @throws \Bitrix\Main\ArgumentNullException
|
|
* @throws LogicException
|
|
* @return $this
|
|
*/
|
|
public function setProperties(array $properties)
|
|
{
|
|
if (isset($properties['hosts']))
|
|
{
|
|
$this->setHosts($properties['hosts']);
|
|
}
|
|
|
|
if (isset($properties['action']))
|
|
{
|
|
if (isset($properties['action_options']))
|
|
{
|
|
$this->setAction($properties['action'], $properties['action_options']);
|
|
}
|
|
else
|
|
{
|
|
$this->setAction($properties['action']);
|
|
}
|
|
}
|
|
|
|
if (isset($properties['logging']))
|
|
{
|
|
$this->setLogging($properties['logging']);
|
|
}
|
|
|
|
if (isset($properties['active']))
|
|
{
|
|
$this->setActive($properties['active']);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getAction()
|
|
{
|
|
return $this->action;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getActionOptions()
|
|
{
|
|
return $this->actionOptions;
|
|
}
|
|
|
|
/**
|
|
* Set action performed while checking
|
|
*
|
|
* @param string $action Some action, now supported: redirect and stop.
|
|
* @param array $options Some options for action, so far supported only host for redirect in redirect action.
|
|
* @return $this
|
|
* @throws \Bitrix\Security\LogicException
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
* @throws \Bitrix\Main\ArgumentNullException
|
|
* @throws \Bitrix\Main\ArgumentOutOfRangeException
|
|
*/
|
|
public function setAction($action, array $options = array())
|
|
{
|
|
if (!$action)
|
|
throw new ArgumentNullException('action');
|
|
|
|
if (!is_string($action))
|
|
throw new ArgumentTypeException('action', 'string');
|
|
|
|
if (!in_array($action, $this->validActions))
|
|
throw new ArgumentOutOfRangeException('action', $this->validActions);
|
|
|
|
if ($action === self::ACTION_REDIRECT)
|
|
{
|
|
if (!isset($options['host']) || !$options['host'])
|
|
throw new LogicException('options[host] not present', 'SECURITY_HOSTS_EMPTY_HOST_ACTION');
|
|
|
|
if (!preg_match('#^https?://#', $options['host']))
|
|
throw new LogicException('invalid redirecting host present in options[host]', 'SECURITY_HOSTS_INVALID_HOST_ACTION');
|
|
}
|
|
|
|
|
|
$this->action = $action;
|
|
$this->actionOptions = $options;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function getLogging()
|
|
{
|
|
return $this->isLogNeeded;
|
|
}
|
|
|
|
/**
|
|
* Activate or deactivate logging on unallowed host requested
|
|
*
|
|
* @param bool $isLogNeeded Set true if need logging unallowed host.
|
|
* @return $this
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
*/
|
|
public function setLogging($isLogNeeded = true)
|
|
{
|
|
if (!is_bool($isLogNeeded))
|
|
throw new ArgumentTypeException('isLogNeeded', 'bool');
|
|
|
|
$this->isLogNeeded = $isLogNeeded;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function getActive()
|
|
{
|
|
if (is_null($this->isActive))
|
|
$this->isActive = $this->isBound();
|
|
|
|
return $this->isActive;
|
|
}
|
|
|
|
/**
|
|
* Activate or deactivate automatic checking
|
|
*
|
|
* @param bool $isActive Set true for enable checking on every request.
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
* @return $this
|
|
*/
|
|
public function setActive($isActive = false)
|
|
{
|
|
if (!is_bool($isActive))
|
|
throw new ArgumentTypeException('isActive', 'bool');
|
|
|
|
$this->isActive = $isActive;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getHosts()
|
|
{
|
|
return $this->hosts;
|
|
}
|
|
|
|
/**
|
|
* Set allowed hosts
|
|
*
|
|
* @param string $hosts Allowed hosts (wild card supported, e.g.: *.example.com).
|
|
* @param bool $ignoreChecking Set false for disable host validating before set.
|
|
* @throws \Bitrix\Main\ArgumentTypeException
|
|
* @throws LogicException
|
|
* @return $this
|
|
*/
|
|
public function setHosts($hosts, $ignoreChecking = false)
|
|
{
|
|
if (!is_string($hosts))
|
|
throw new ArgumentTypeException('host', 'string');
|
|
|
|
if (!$ignoreChecking)
|
|
$this->checkNewHosts($hosts);
|
|
|
|
$this->hosts = $hosts;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return regular expressions (based on hosts) for checking.
|
|
* Note: regular expression is cached for performance improvement and auto cleared after saving {@see save}
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getValidationRegExp()
|
|
{
|
|
if ($this->validationRegExp === null)
|
|
{
|
|
$cache = Data\Cache::createInstance();
|
|
if($cache->initCache($this->cacheTtl, $this->cacheId, $this->cacheInitPath) )
|
|
{
|
|
$this->validationRegExp = $cache->getVars();
|
|
}
|
|
else
|
|
{
|
|
$this->validationRegExp = $this->genValidationRegExp($this->hosts);
|
|
$cache->startDataCache();
|
|
$cache->endDataCache($this->validationRegExp);
|
|
}
|
|
}
|
|
|
|
return $this->validationRegExp;
|
|
}
|
|
|
|
/**
|
|
* Save all properties, enable automatic checking and clear cache if needed
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function save()
|
|
{
|
|
Config\Option::set('security', $this->optionPrefix.'hosts', $this->hosts, '');
|
|
Config\Option::set('security', $this->optionPrefix.'action', $this->action, '');
|
|
Config\Option::set('security', $this->optionPrefix.'action_options', serialize($this->actionOptions), '');
|
|
Config\Option::set('security', $this->optionPrefix.'logging', $this->isLogNeeded, '');
|
|
if (!is_null($this->isActive))
|
|
{
|
|
|
|
if ($this->isActive)
|
|
{
|
|
EventManager::getInstance()
|
|
->registerEventHandler('main', 'OnPageStart', 'security', get_class($this), 'onPageStart');
|
|
}
|
|
else
|
|
{
|
|
EventManager::getInstance()
|
|
->unRegisterEventHandler('main', 'OnPageStart', 'security', get_class($this), 'onPageStart');
|
|
}
|
|
}
|
|
Data\Cache::createInstance()->clean($this->cacheId, $this->cacheInitPath);
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Return true if HostRestriction already handled on system event "OnPageStart"
|
|
*
|
|
* @return bool
|
|
*/
|
|
protected function isBound()
|
|
{
|
|
$handlers = EventManager::getInstance()->findEventHandlers('main', 'OnPageStart', array('security'));
|
|
|
|
foreach($handlers as $handler)
|
|
{
|
|
if ($handler['TO_CLASS'] === get_class($this))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return requested host for checking
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function getTargetHost()
|
|
{
|
|
static $host = null;
|
|
if (is_null($host))
|
|
$host = Context::getCurrent()->getServer()->getHttpHost();
|
|
|
|
return $host;
|
|
}
|
|
|
|
/**
|
|
* Logging current host by event manager
|
|
*
|
|
* @param string $host Requested host.
|
|
* @return bool
|
|
*/
|
|
protected function log($host)
|
|
{
|
|
return \CSecurityEvent::getInstance()->doLog('SECURITY', 'SECURITY_HOST_RESTRICTION', 'HTTP_HOST', $host);
|
|
}
|
|
|
|
/**
|
|
* Perform some actions when requested host is not allowed by host restriction policy
|
|
*
|
|
* @return $this
|
|
*/
|
|
protected function doActions()
|
|
{
|
|
switch($this->action)
|
|
{
|
|
case self::ACTION_STOP:
|
|
/** @noinspection PhpIncludeInspection */
|
|
include Loader::getLocal('/admin/security_403.php');
|
|
die();
|
|
break;
|
|
case self::ACTION_REDIRECT:
|
|
localRedirect($this->actionOptions['host'], true);
|
|
break;
|
|
default:
|
|
trigger_error('Unknown action', E_USER_WARNING);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Generates regular expression obtained from hosts
|
|
*
|
|
* @param string $hosts Allowed hosts (wild card supported, e.g.: *.example.com).
|
|
* @return string
|
|
*/
|
|
protected function genValidationRegExp($hosts)
|
|
{
|
|
$hosts = preg_replace('/#.*/', '', $hosts);
|
|
$hosts = trim($hosts, " \t\n\r");
|
|
$hosts = preg_quote($hosts);
|
|
$hosts = preg_replace('~\\\\\*~', '.*', $hosts);
|
|
$hosts = preg_split('~\s+~s', $hosts);
|
|
|
|
foreach ($hosts as $i => $host)
|
|
{
|
|
$hosts[$i] = "#^\s*($host)(:\d+)?\s*$#iD";
|
|
}
|
|
return $hosts;
|
|
}
|
|
|
|
/**
|
|
* Checks the host to detect logical errors (eg blocking the current host)
|
|
*
|
|
* @param string $hosts Allowed hosts (wild card supported, e.g.: *.example.com).
|
|
* @return $this
|
|
* @throws \Bitrix\Security\LogicException
|
|
*/
|
|
protected function checkNewHosts($hosts)
|
|
{
|
|
$this->validationRegExp = $this->genValidationRegExp($hosts);
|
|
|
|
$count = 0;
|
|
preg_replace($this->validationRegExp, '', $this->getTargetHost(), 1, $count);
|
|
if (!$count)
|
|
throw new LogicException('Current host blocked', 'SECURITY_HOSTS_SELF_BLOCK');
|
|
|
|
$count = 0;
|
|
preg_replace($this->validationRegExp, '', 'some-invalid-host.com', 1, $count);
|
|
if ($count)
|
|
throw new LogicException('Any host passed restrictions', 'SECURITY_HOSTS_ANY_HOST');
|
|
|
|
return $this;
|
|
}
|
|
} |