mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-09 03:32:00 +09:00
Merge pull request #366 from kijin/pr/security-refactor
보안관련 클래스 전반적 정리 및 기능 개선 프로젝트
This commit is contained in:
commit
4f015f7bbc
78 changed files with 3860 additions and 3336 deletions
|
|
@ -394,11 +394,11 @@ class Context
|
|||
}
|
||||
if (strpos($current_url, 'xn--') !== false)
|
||||
{
|
||||
$current_url = self::decodeIdna($current_url);
|
||||
$current_url = Rhymix\Framework\URL::decodeIdna($current_url);
|
||||
}
|
||||
if (strpos($request_uri, 'xn--') !== false)
|
||||
{
|
||||
$request_uri = self::decodeIdna($request_uri);
|
||||
$request_uri = Rhymix\Framework\URL::decodeIdna($request_uri);
|
||||
}
|
||||
self::set('current_url', $current_url);
|
||||
self::set('request_uri', $request_uri);
|
||||
|
|
@ -574,8 +574,8 @@ class Context
|
|||
$db_info->sitelock_title = $config['lock']['title'];
|
||||
$db_info->sitelock_message = $config['lock']['message'];
|
||||
$db_info->sitelock_whitelist = count($config['lock']['allow']) ? $config['lock']['allow'] : array('127.0.0.1');
|
||||
$db_info->embed_white_iframe = $config['embedfilter']['iframe'];
|
||||
$db_info->embed_white_object = $config['embedfilter']['object'];
|
||||
$db_info->embed_white_iframe = $config['mediafilter']['iframe'] ?: $config['embedfilter']['iframe'];
|
||||
$db_info->embed_white_object = $config['mediafilter']['object'] ?: $config['embedfilter']['object'];
|
||||
$db_info->use_mobile_view = $config['use_mobile_view'] ? 'Y' : 'N';
|
||||
$db_info->use_prepared_statements = $config['use_prepared_statements'] ? 'Y' : 'N';
|
||||
$db_info->use_rewrite = $config['use_rewrite'] ? 'Y' : 'N';
|
||||
|
|
@ -712,61 +712,66 @@ class Context
|
|||
$current_site = self::getRequestUri();
|
||||
|
||||
// Step 1: if the current site is not the default site, send SSO validation request to the default site
|
||||
if($default_url !== $current_site && !self::get('SSOID') && $_COOKIE['sso'] !== md5($current_site))
|
||||
if($default_url !== $current_site && !self::get('sso_response') && $_COOKIE['sso'] !== md5($current_site))
|
||||
{
|
||||
// Set sso cookie to prevent multiple simultaneous SSO validation requests
|
||||
setcookie('sso', md5($current_site), 0, '/');
|
||||
|
||||
// Redirect to the default site
|
||||
$redirect_url = sprintf('%s?return_url=%s', $default_url, urlencode(base64_encode($current_site)));
|
||||
$sso_request = Rhymix\Framework\Security::encrypt(Rhymix\Framework\URL::getCurrentURL());
|
||||
$redirect_url = $default_url . '?sso_request=' . urlencode($sso_request);
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: receive and process SSO validation request at the default site
|
||||
if($default_url === $current_site && self::get('return_url'))
|
||||
if($default_url === $current_site && self::get('sso_request'))
|
||||
{
|
||||
// Get the URL of the origin site
|
||||
$url = base64_decode(self::get('return_url'));
|
||||
$url_info = parse_url($url);
|
||||
$sso_request = Rhymix\Framework\Security::decrypt(self::get('sso_request'));
|
||||
if (!$sso_request || !preg_match('!^https?://!', $sso_request))
|
||||
{
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the origin site is a valid site in this XE installation (to prevent open redirect vuln)
|
||||
if(!getModel('module')->getSiteInfoByDomain(rtrim($url, '/'))->site_srl)
|
||||
{
|
||||
htmlHeader();
|
||||
echo self::getLang("msg_invalid_request");
|
||||
htmlFooter();
|
||||
return FALSE;
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Request', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Redirect back to the origin site
|
||||
$url_info['query'] .= ($url_info['query'] ? '&' : '') . 'SSOID=' . session_id();
|
||||
$redirect_url = sprintf('%s://%s%s%s%s', $url_info['scheme'], $url_info['host'], $url_info['port'] ? (':' . $url_info['port']) : '', $url_info['path'], ($url_info['query'] ? ('?' . $url_info['query']) : ''));
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
$sso_response = Rhymix\Framework\Security::encrypt(session_id());
|
||||
header('Location: ' . Rhymix\Framework\URL::modifyURL($sso_request, array('sso_response' => $sso_response)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: back at the origin site, set session ID to be the same as the default site
|
||||
if($default_url !== $current_site && self::get('SSOID'))
|
||||
if($default_url !== $current_site && self::get('sso_response'))
|
||||
{
|
||||
// Check that the session ID was given by the default site (to prevent session fixation CSRF)
|
||||
// Check SSO response
|
||||
$sso_response = Rhymix\Framework\Security::decrypt(self::get('sso_response'));
|
||||
if ($sso_response === false)
|
||||
{
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the response was given by the default site (to prevent session fixation CSRF)
|
||||
if(isset($_SERVER['HTTP_REFERER']) && strpos($_SERVER['HTTP_REFERER'], $default_url) !== 0)
|
||||
{
|
||||
htmlHeader();
|
||||
echo self::getLang("msg_invalid_request");
|
||||
htmlFooter();
|
||||
return FALSE;
|
||||
self::displayErrorPage('SSO Error', 'Invalid SSO Response', 400);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set session ID
|
||||
setcookie(session_name(), self::get('SSOID'));
|
||||
setcookie(session_name(), $sso_response);
|
||||
|
||||
// Finally, redirect to the originally requested URL
|
||||
$url_info = parse_url(self::getRequestUrl());
|
||||
$url_info['query'] = preg_replace('/(^|\b)SSOID=([^&?]+)/', '', $url_info['query']);
|
||||
$redirect_url = sprintf('%s://%s%s%s%s', $url_info['scheme'], $url_info['host'], $url_info['port'] ? (':' . $url_info['port']) : '', $url_info['path'], ($url_info['query'] ? ('?' . $url_info['query']) : ''));
|
||||
header('Location:' . $redirect_url);
|
||||
return FALSE;
|
||||
header('Location: ' . Rhymix\Framework\URL::getCurrentURL(array('sso_response' => null)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If none of the conditions above apply, proceed normally
|
||||
|
|
@ -1073,15 +1078,7 @@ class Context
|
|||
*/
|
||||
public static function encodeIdna($domain)
|
||||
{
|
||||
if(function_exists('idn_to_ascii'))
|
||||
{
|
||||
return idn_to_ascii($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$encoder = new TrueBV\Punycode();
|
||||
return $encoder->encode($domain);
|
||||
}
|
||||
return Rhymix\Framework\URL::encodeIdna($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1092,15 +1089,7 @@ class Context
|
|||
*/
|
||||
public static function decodeIdna($domain)
|
||||
{
|
||||
if(function_exists('idn_to_utf8'))
|
||||
{
|
||||
return idn_to_utf8($domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
$decoder = new TrueBV\Punycode();
|
||||
return $decoder->decode($domain);
|
||||
}
|
||||
return Rhymix\Framework\URL::decodeIdna($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1283,11 +1272,15 @@ class Context
|
|||
}
|
||||
|
||||
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
|
||||
if(Security::detectingXEE($xml))
|
||||
if(!Rhymix\Framework\Security::checkXEE($xml))
|
||||
{
|
||||
header("HTTP/1.0 400 Bad Request");
|
||||
exit;
|
||||
}
|
||||
if(function_exists('libxml_disable_entity_loader'))
|
||||
{
|
||||
libxml_disable_entity_loader(true);
|
||||
}
|
||||
|
||||
$oXml = new XmlParser();
|
||||
$xml_obj = $oXml->parse($xml);
|
||||
|
|
@ -1490,13 +1483,9 @@ class Context
|
|||
}
|
||||
|
||||
// Allow if the current user is in the list of allowed IPs.
|
||||
$allowed_list = config('lock.allow');
|
||||
foreach ($allowed_list as $allowed_ip)
|
||||
if (Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, config('lock.allow')))
|
||||
{
|
||||
if (Rhymix\Framework\IpFilter::inRange(RX_CLIENT_IP, $allowed_ip))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Set headers and constants for backward compatibility.
|
||||
|
|
|
|||
|
|
@ -3,313 +3,78 @@
|
|||
|
||||
class EmbedFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* allow script access list
|
||||
* Deprecated properties
|
||||
* @var array
|
||||
*/
|
||||
var $allowscriptaccessList = array();
|
||||
|
||||
/**
|
||||
* allow script access key
|
||||
* @var int
|
||||
*/
|
||||
var $allowscriptaccessKey = 0;
|
||||
var $whiteUrlList = array();
|
||||
var $whiteIframeUrlList = array();
|
||||
var $mimeTypeList = array();
|
||||
var $extList = array();
|
||||
var $parser = NULL;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @return void
|
||||
*/
|
||||
function __construct()
|
||||
{
|
||||
$this->_makeWhiteDomainList();
|
||||
}
|
||||
public $whiteUrlList = array();
|
||||
public $whiteIframeUrlList = array();
|
||||
public $mimeTypeList = array();
|
||||
public $extList = array();
|
||||
|
||||
/**
|
||||
* Return EmbedFilter object
|
||||
* This method for singleton
|
||||
*
|
||||
* @return EmbedFilter
|
||||
*/
|
||||
function getInstance()
|
||||
{
|
||||
if(!isset($GLOBALS['__EMBEDFILTER_INSTANCE__']))
|
||||
{
|
||||
$GLOBALS['__EMBEDFILTER_INSTANCE__'] = new EmbedFilter();
|
||||
}
|
||||
return $GLOBALS['__EMBEDFILTER_INSTANCE__'];
|
||||
return new self();
|
||||
}
|
||||
|
||||
|
||||
public function getWhiteUrlList()
|
||||
{
|
||||
return $this->whiteUrlList;
|
||||
return Rhymix\Framework\Filters\MediaFilter::getObjectWhitelist();
|
||||
}
|
||||
|
||||
|
||||
public function getWhiteIframeUrlList()
|
||||
{
|
||||
return $this->whiteIframeUrlList;
|
||||
return Rhymix\Framework\Filters\MediaFilter::getIframeWhitelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the content.
|
||||
* @return void
|
||||
*/
|
||||
function check(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<(object|param|embed)[^>]*/is', array($this, '_checkAllowScriptAccess'), $content);
|
||||
$content = preg_replace_callback('/<object[^>]*>/is', array($this, '_addAllowScriptAccess'), $content);
|
||||
|
||||
$this->checkObjectTag($content);
|
||||
$this->checkEmbedTag($content);
|
||||
$this->checkParamTag($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check iframe tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkIframeTag(&$content)
|
||||
{
|
||||
// check in Purifier class
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check object tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkObjectTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*object\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('object') as $element)
|
||||
{
|
||||
if ($element->data && !$this->isWhiteDomain($element->data))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
if ($element->type && !$this->isWhiteMimetype($element->type))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check embed tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkEmbedTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*embed\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('embed') as $element)
|
||||
{
|
||||
if ($element->src && !$this->isWhiteDomain($element->src))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
if ($element->type && !$this->isWhiteMimetype($element->type))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check param tag in the content.
|
||||
* @return void
|
||||
*/
|
||||
function checkParamTag(&$content)
|
||||
{
|
||||
$content = preg_replace_callback('/<\s*param\s*[^>]+(?:\/?>?)/is', function($m) {
|
||||
$html = Sunra\PhpSimple\HtmlDomParser::str_get_html($m[0]);
|
||||
foreach ($html->find('param') as $element)
|
||||
{
|
||||
foreach (array('movie', 'src', 'href', 'url', 'source') as $attr)
|
||||
{
|
||||
if ($element->$attr && !$this->isWhiteDomain($element->$attr))
|
||||
{
|
||||
return escape($m[0], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white domain in object data attribute or embed src attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteDomain($urlAttribute)
|
||||
{
|
||||
if(is_array($this->whiteUrlList))
|
||||
{
|
||||
foreach($this->whiteUrlList AS $key => $value)
|
||||
{
|
||||
if(preg_match('@^https?://' . preg_quote($value, '@') . '@i', $urlAttribute))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
return Rhymix\Framework\Filters\MediaFilter::matchObjectWhitelist($urlAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white domain in iframe src attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteIframeDomain($urlAttribute)
|
||||
{
|
||||
if(is_array($this->whiteIframeUrlList))
|
||||
{
|
||||
foreach($this->whiteIframeUrlList AS $key => $value)
|
||||
{
|
||||
if(preg_match('@^https?://' . preg_quote($value, '@') . '@i', $urlAttribute))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
return Rhymix\Framework\Filters\MediaFilter::matchIframeWhitelist($urlAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check white mime type in object type attribute or embed type attribute.
|
||||
* @return string
|
||||
*/
|
||||
|
||||
function isWhiteMimetype($mimeType)
|
||||
{
|
||||
if(isset($this->mimeTypeList[$mimeType]))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function isWhiteExt($ext)
|
||||
{
|
||||
if(isset($this->extList[$ext]))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
function _checkAllowScriptAccess($m)
|
||||
|
||||
function check(&$content)
|
||||
{
|
||||
if($m[1] == 'object')
|
||||
{
|
||||
$this->allowscriptaccessList[] = 1;
|
||||
}
|
||||
|
||||
if($m[1] == 'param')
|
||||
{
|
||||
if(stripos($m[0], 'allowscriptaccess'))
|
||||
{
|
||||
$m[0] = '<param name="allowscriptaccess" value="never"';
|
||||
if(substr($m[0], -1) == '/')
|
||||
{
|
||||
$m[0] .= '/';
|
||||
}
|
||||
$this->allowscriptaccessList[count($this->allowscriptaccessList) - 1]--;
|
||||
}
|
||||
}
|
||||
else if($m[1] == 'embed')
|
||||
{
|
||||
if(stripos($m[0], 'allowscriptaccess'))
|
||||
{
|
||||
$m[0] = preg_replace('/always|samedomain/i', 'never', $m[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$m[0] = preg_replace('/\<embed/i', '<embed allowscriptaccess="never"', $m[0]);
|
||||
}
|
||||
}
|
||||
return $m[0];
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function _addAllowScriptAccess($m)
|
||||
|
||||
function checkIframeTag(&$content)
|
||||
{
|
||||
if($this->allowscriptaccessList[$this->allowscriptaccessKey] == 1)
|
||||
{
|
||||
$m[0] = $m[0] . '<param name="allowscriptaccess" value="never"></param>';
|
||||
}
|
||||
$this->allowscriptaccessKey++;
|
||||
return $m[0];
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
/**
|
||||
* Make white domain list cache file from xml config file.
|
||||
* @param $whitelist array
|
||||
* @return void
|
||||
*/
|
||||
function _makeWhiteDomainList($whitelist = NULL)
|
||||
|
||||
function checkObjectTag(&$content)
|
||||
{
|
||||
$whiteUrlDefaultList = (include RX_BASEDIR . 'common/defaults/whitelist.php');
|
||||
$this->extList = $whiteUrlDefaultList['extensions'];
|
||||
$this->mimeTypeList = $whiteUrlDefaultList['mime'];
|
||||
$this->whiteUrlList = array();
|
||||
$this->whiteIframeUrlList = array();
|
||||
|
||||
if($whitelist !== NULL)
|
||||
{
|
||||
if(!is_array($whitelist) || !isset($whitelist['object']) || !isset($whitelist['iframe']))
|
||||
{
|
||||
$whitelist = array(
|
||||
'object' => isset($whitelist->object) ? $whitelist->object : array(),
|
||||
'iframe' => isset($whitelist->iframe) ? $whitelist->iframe : array(),
|
||||
);
|
||||
}
|
||||
foreach ($whitelist['object'] as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
foreach ($whitelist['iframe'] as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($whiteUrlDefaultList['object'] as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = $prefix;
|
||||
}
|
||||
foreach ($whiteUrlDefaultList['iframe'] as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = $prefix;
|
||||
}
|
||||
if ($embedfilter_object = config('embedfilter.object'))
|
||||
{
|
||||
foreach ($embedfilter_object as $prefix)
|
||||
{
|
||||
$this->whiteUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
if ($embedfilter_iframe = config('embedfilter.iframe'))
|
||||
{
|
||||
foreach ($embedfilter_iframe as $prefix)
|
||||
{
|
||||
$this->whiteIframeUrlList[] = preg_match('@^https?://(.*)$@i', $prefix, $matches) ? $matches[1] : $prefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->whiteUrlList = array_unique($this->whiteUrlList);
|
||||
$this->whiteIframeUrlList = array_unique($this->whiteIframeUrlList);
|
||||
natcasesort($this->whiteUrlList);
|
||||
natcasesort($this->whiteIframeUrlList);
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function checkEmbedTag(&$content)
|
||||
{
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
|
||||
function checkParamTag(&$content)
|
||||
{
|
||||
// This functionality has been moved to the HTMLFilter class.
|
||||
}
|
||||
}
|
||||
/* End of file : EmbedFilter.class.php */
|
||||
|
|
|
|||
|
|
@ -6,26 +6,12 @@ class IpFilter
|
|||
public function filter($ip_list, $ip = NULL)
|
||||
{
|
||||
if(!$ip) $ip = $_SERVER['REMOTE_ADDR'];
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
if(Rhymix\Framework\IpFilter::inRange($ip, $filter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Rhymix\Framework\Filters\IpFilter::inRanges($ip, $ip_list);
|
||||
}
|
||||
|
||||
public function validate($ip_list = array())
|
||||
{
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
if(!Rhymix\Framework\IpFilter::validateRange($filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return Rhymix\Framework\Filters\IpFilter::validateRanges($ip_list);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,458 +1,79 @@
|
|||
<?php
|
||||
/* Copyright (C) NAVER <http://www.navercorp.com> */
|
||||
|
||||
/**
|
||||
* This class can be used to hash passwords using various algorithms and check their validity.
|
||||
* It is fully compatible with previous defaults, while also supporting bcrypt and pbkdf2.
|
||||
*
|
||||
* @file Password.class.php
|
||||
* @author Kijin Sung (kijin@kijinsung.com)
|
||||
* @package /classes/security
|
||||
* @version 1.1
|
||||
*/
|
||||
class Password
|
||||
{
|
||||
/**
|
||||
* @brief Custom algorithms are stored here
|
||||
* @var array
|
||||
*/
|
||||
protected static $_custom = array();
|
||||
|
||||
/**
|
||||
* @brief Register a custom algorithm for password checking
|
||||
* @param string $name The name of the algorithm
|
||||
* @param string $regexp The regular expression to detect the algorithm
|
||||
* @param callable $callback The function to call to regenerate the hash
|
||||
* @return void
|
||||
*/
|
||||
public static function registerCustomAlgorithm($name, $regexp, $callback)
|
||||
{
|
||||
self::$_custom[$name] = array('regexp' => $regexp, 'callback' => $callback);
|
||||
Rhymix\Framework\Password::addAlgorithm($name, $regexp, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the list of hashing algorithms supported by this server
|
||||
* @return array
|
||||
*/
|
||||
public function getSupportedAlgorithms()
|
||||
{
|
||||
$retval = array();
|
||||
if(function_exists('hash_hmac') && in_array('sha256', hash_algos()))
|
||||
{
|
||||
$retval['pbkdf2'] = 'pbkdf2';
|
||||
}
|
||||
if(version_compare(PHP_VERSION, '5.3.7', '>=') && defined('CRYPT_BLOWFISH'))
|
||||
{
|
||||
$retval['bcrypt'] = 'bcrypt';
|
||||
}
|
||||
$retval['md5'] = 'md5';
|
||||
return $retval;
|
||||
return Rhymix\Framework\Password::getSupportedAlgorithms();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the best hashing algorithm supported by this server
|
||||
* @return string
|
||||
*/
|
||||
public function getBestAlgorithm()
|
||||
{
|
||||
$algos = $this->getSupportedAlgorithms();
|
||||
return key($algos);
|
||||
return Rhymix\Framework\Password::getBestSupportedAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the currently selected hashing algorithm
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentlySelectedAlgorithm()
|
||||
{
|
||||
if(function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$algorithm = $config->password_hashing_algorithm;
|
||||
if(strval($algorithm) === '')
|
||||
{
|
||||
$algorithm = 'md5'; // Historical default for XE
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$algorithm = 'md5';
|
||||
}
|
||||
return $algorithm;
|
||||
return Rhymix\Framework\Password::getDefaultAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the currently configured work factor for bcrypt and other adjustable algorithms
|
||||
* @return int
|
||||
*/
|
||||
public function getWorkFactor()
|
||||
{
|
||||
if(function_exists('getModel'))
|
||||
{
|
||||
$config = getModel('member')->getMemberConfig();
|
||||
$work_factor = $config->password_hashing_work_factor;
|
||||
if(!$work_factor || $work_factor < 4 || $work_factor > 31)
|
||||
{
|
||||
$work_factor = 8; // Reasonable default
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$work_factor = 8;
|
||||
}
|
||||
return $work_factor;
|
||||
return Rhymix\Framework\Password::getWorkFactor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a hash using the specified algorithm
|
||||
* @param string $password The password
|
||||
* @param string $algorithm The algorithm (optional)
|
||||
* @return string
|
||||
*/
|
||||
public function createHash($password, $algorithm = null)
|
||||
{
|
||||
if($algorithm === null)
|
||||
{
|
||||
$algorithm = $this->getCurrentlySelectedAlgorithm();
|
||||
}
|
||||
if(!array_key_exists($algorithm, $this->getSupportedAlgorithms()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$password = trim($password);
|
||||
|
||||
switch($algorithm)
|
||||
{
|
||||
case 'md5':
|
||||
return md5($password);
|
||||
|
||||
case 'pbkdf2':
|
||||
$iterations = pow(2, $this->getWorkFactor() + 5);
|
||||
$salt = $this->createSecureSalt(12, 'alnum');
|
||||
$hash = base64_encode($this->pbkdf2($password, $salt, 'sha256', $iterations, 24));
|
||||
return 'sha256:'.sprintf('%07d', $iterations).':'.$salt.':'.$hash;
|
||||
|
||||
case 'bcrypt':
|
||||
return $this->bcrypt($password);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::hashPassword($password, $algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if a password matches a hash
|
||||
* @param string $password The password
|
||||
* @param string $hash The hash
|
||||
* @param string $algorithm The algorithm (optional)
|
||||
* @return bool
|
||||
*/
|
||||
public function checkPassword($password, $hash, $algorithm = null)
|
||||
{
|
||||
if($algorithm === null)
|
||||
{
|
||||
$algorithm = $this->checkAlgorithm($hash);
|
||||
}
|
||||
|
||||
$password = trim($password);
|
||||
|
||||
switch($algorithm)
|
||||
{
|
||||
case 'md5':
|
||||
return md5($password) === $hash || md5(sha1(md5($password))) === $hash;
|
||||
|
||||
case 'mysql_old_password':
|
||||
return (class_exists('Context') && substr(Context::getDBType(), 0, 5) === 'mysql') ?
|
||||
DB::getInstance()->isValidOldPassword($password, $hash) : false;
|
||||
|
||||
case 'mysql_password':
|
||||
return $hash[0] === '*' && substr($hash, 1) === strtoupper(sha1(sha1($password, true)));
|
||||
|
||||
case 'pbkdf2':
|
||||
$hash = explode(':', $hash);
|
||||
$hash[3] = base64_decode($hash[3]);
|
||||
$hash_to_compare = $this->pbkdf2($password, $hash[2], $hash[0], intval($hash[1], 10), strlen($hash[3]));
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash[3]);
|
||||
|
||||
case 'bcrypt':
|
||||
$hash_to_compare = $this->bcrypt($password, $hash);
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash);
|
||||
|
||||
default:
|
||||
if($algorithm && isset(self::$_custom[$algorithm]))
|
||||
{
|
||||
$hash_callback = self::$_custom[$algorithm]['callback'];
|
||||
$hash_to_compare = $hash_callback($password, $hash);
|
||||
return $this->strcmpConstantTime($hash_to_compare, $hash);
|
||||
}
|
||||
if(in_array($algorithm, hash_algos()))
|
||||
{
|
||||
return $this->strcmpConstantTime(hash($algorithm, $password), $hash);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::checkPassword($password, $hash, $algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check the algorithm used to create a hash
|
||||
* @param string $hash The hash
|
||||
* @return string
|
||||
*/
|
||||
function checkAlgorithm($hash)
|
||||
{
|
||||
foreach(self::$_custom as $name => $definition)
|
||||
{
|
||||
if(preg_match($definition['regexp'], $hash))
|
||||
{
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
|
||||
{
|
||||
return 'bcrypt';
|
||||
}
|
||||
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
|
||||
{
|
||||
return 'pbkdf2';
|
||||
}
|
||||
elseif(strlen($hash) === 32 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'md5';
|
||||
}
|
||||
elseif(strlen($hash) === 40 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha1';
|
||||
}
|
||||
elseif(strlen($hash) === 64 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
elseif(strlen($hash) === 96 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
elseif(strlen($hash) === 128 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
elseif(strlen($hash) === 16 && ctype_xdigit($hash))
|
||||
{
|
||||
return 'mysql_old_password';
|
||||
}
|
||||
elseif(strlen($hash) === 41 && $hash[0] === '*')
|
||||
{
|
||||
return 'mysql_password';
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$algos = Rhymix\Framework\Password::checkAlgorithm($hash);
|
||||
return count($algos) ? $algos[0] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check the work factor of a hash
|
||||
* @param string $hash The hash
|
||||
* @return int
|
||||
*/
|
||||
function checkWorkFactor($hash)
|
||||
{
|
||||
if(preg_match('/^\$2[axy]\$([0-9]{2})\$/', $hash, $matches))
|
||||
{
|
||||
return intval($matches[1], 10);
|
||||
}
|
||||
elseif(preg_match('/^sha[0-9]+:([0-9]+):/', $hash, $matches))
|
||||
{
|
||||
return max(0, round(log($matches[1], 2)) - 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Rhymix\Framework\Password::checkWorkFactor($hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a cryptographically secure random string to use as a salt
|
||||
* @param int $length The number of bytes to return
|
||||
* @param string $format hex or alnum
|
||||
* @return string
|
||||
*/
|
||||
public function createSecureSalt($length, $format = 'hex')
|
||||
{
|
||||
// Find out how many bytes of entropy we really need
|
||||
switch($format)
|
||||
{
|
||||
case 'hex':
|
||||
$entropy_required_bytes = ceil($length / 2);
|
||||
break;
|
||||
case 'alnum':
|
||||
case 'printable':
|
||||
$entropy_required_bytes = ceil($length * 3 / 4);
|
||||
break;
|
||||
default:
|
||||
$entropy_required_bytes = $length;
|
||||
}
|
||||
|
||||
// Cap entropy to 256 bits from any one source, because anything more is meaningless
|
||||
$entropy_capped_bytes = min(32, $entropy_required_bytes);
|
||||
|
||||
// Find and use the most secure way to generate a random string
|
||||
$entropy = false;
|
||||
$is_windows = (defined('PHP_OS') && strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
|
||||
if(function_exists('random_bytes')) // PHP 7
|
||||
{
|
||||
$entropy = random_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('openssl_random_pseudo_bytes') && (!$is_windows || version_compare(PHP_VERSION, '5.4', '>=')))
|
||||
{
|
||||
$entropy = openssl_random_pseudo_bytes($entropy_capped_bytes);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && (!$is_windows || version_compare(PHP_VERSION, '5.3.7', '>=')))
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_DEV_URANDOM);
|
||||
}
|
||||
elseif(function_exists('mcrypt_create_iv') && $is_windows)
|
||||
{
|
||||
$entropy = mcrypt_create_iv($entropy_capped_bytes, MCRYPT_RAND);
|
||||
}
|
||||
elseif(!$is_windows && @is_readable('/dev/urandom'))
|
||||
{
|
||||
$fp = fopen('/dev/urandom', 'rb');
|
||||
if (function_exists('stream_set_read_buffer')) // This function does not exist in HHVM
|
||||
{
|
||||
stream_set_read_buffer($fp, 0); // Prevent reading several KB of unnecessary data from urandom
|
||||
}
|
||||
$entropy = fread($fp, $entropy_capped_bytes);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// Use built-in source of entropy if an error occurs while using other functions
|
||||
if($entropy === false || strlen($entropy) < $entropy_capped_bytes)
|
||||
{
|
||||
$entropy = '';
|
||||
for($i = 0; $i < $entropy_capped_bytes; $i += 2)
|
||||
{
|
||||
$entropy .= pack('S', rand(0, 65536) ^ mt_rand(0, 65535));
|
||||
}
|
||||
}
|
||||
|
||||
// Mixing (see RFC 4086 section 5)
|
||||
$output = '';
|
||||
for($i = 0; $i < $entropy_required_bytes; $i += 32)
|
||||
{
|
||||
$output .= hash('sha256', $entropy . $i . rand(), true);
|
||||
}
|
||||
|
||||
// Encode and return the random string
|
||||
switch($format)
|
||||
{
|
||||
case 'hex':
|
||||
return substr(bin2hex($output), 0, $length);
|
||||
case 'binary':
|
||||
return substr($output, 0, $length);
|
||||
case 'printable':
|
||||
$salt = '';
|
||||
for($i = 0; $i < $length; $i++)
|
||||
{
|
||||
$salt .= chr(33 + (crc32(sha1($i . $output)) % 94));
|
||||
}
|
||||
return $salt;
|
||||
case 'alnum':
|
||||
default:
|
||||
$salt = substr(base64_encode($output), 0, $length);
|
||||
$replacements = chr(rand(65, 90)) . chr(rand(97, 122)) . rand(0, 9);
|
||||
return strtr($salt, '+/=', $replacements);
|
||||
}
|
||||
return Rhymix\Framework\Security::getRandom($length, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a temporary password using the secure salt generator
|
||||
* @param int $length The number of bytes to return
|
||||
* @return string
|
||||
*/
|
||||
public function createTemporaryPassword($length = 16)
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
$source = base64_encode($this->createSecureSalt(64, 'binary'));
|
||||
$source = strtr($source, 'iIoOjl10/', '@#$%&*-!?');
|
||||
$source_length = strlen($source);
|
||||
for($i = 0; $i < $source_length - $length; $i++)
|
||||
{
|
||||
$candidate = substr($source, $i, $length);
|
||||
if(preg_match('/[a-z]/', $candidate) && preg_match('/[A-Z]/', $candidate) &&
|
||||
preg_match('/[0-9]/', $candidate) && preg_match('/[^a-zA-Z0-9]/', $candidate))
|
||||
{
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Rhymix\Framework\Password::getRandomPassword($length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate the PBKDF2 hash of a string using a salt
|
||||
* @param string $password The password
|
||||
* @param string $salt The salt
|
||||
* @param string $algorithm The algorithm (optional, default is sha256)
|
||||
* @param int $iterations Iteration count (optional, default is 8192)
|
||||
* @param int $length The length of the hash (optional, default is 32)
|
||||
* @return string
|
||||
*/
|
||||
public function pbkdf2($password, $salt, $algorithm = 'sha256', $iterations = 8192, $length = 24)
|
||||
{
|
||||
if(function_exists('hash_pbkdf2'))
|
||||
{
|
||||
return hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = '';
|
||||
$block_count = ceil($length / strlen(hash($algorithm, '', true))); // key length divided by the length of one hash
|
||||
for($i = 1; $i <= $block_count; $i++)
|
||||
{
|
||||
$last = $salt . pack('N', $i); // $i encoded as 4 bytes, big endian
|
||||
$last = $xorsum = hash_hmac($algorithm, $last, $password, true); // first iteration
|
||||
for($j = 1; $j < $iterations; $j++) // The other $count - 1 iterations
|
||||
{
|
||||
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
|
||||
}
|
||||
$output .= $xorsum;
|
||||
}
|
||||
return substr($output, 0, $length);
|
||||
}
|
||||
$hash = Rhymix\Framework\Security::pbkdf2($password, $salt, $algorithm, $iterations, $length);
|
||||
$hash = explode(':', $hash);
|
||||
return base64_decode($hash[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate the bcrypt hash of a string using a salt
|
||||
* @param string $password The password
|
||||
* @param string $salt The salt (optional, auto-generated if empty)
|
||||
* @return string
|
||||
*/
|
||||
public function bcrypt($password, $salt = null)
|
||||
{
|
||||
if($salt === null)
|
||||
{
|
||||
$salt = '$2y$'.sprintf('%02d', $this->getWorkFactor()).'$'.$this->createSecureSalt(22, 'alnum');
|
||||
}
|
||||
return crypt($password, $salt);
|
||||
return Rhymix\Framework\Security::bcrypt($password, $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two strings in constant time
|
||||
* @param string $a The first string
|
||||
* @param string $b The second string
|
||||
* @return bool
|
||||
*/
|
||||
function strcmpConstantTime($a, $b)
|
||||
{
|
||||
$diff = strlen($a) ^ strlen($b);
|
||||
$maxlen = min(strlen($a), strlen($b));
|
||||
for($i = 0; $i < $maxlen; $i++)
|
||||
{
|
||||
$diff |= ord($a[$i]) ^ ord($b[$i]);
|
||||
}
|
||||
return $diff === 0;
|
||||
return Rhymix\Framework\Security::compareStrings($a, $b);
|
||||
}
|
||||
}
|
||||
/* End of file : Password.class.php */
|
||||
|
|
|
|||
|
|
@ -3,161 +3,14 @@
|
|||
|
||||
class Purifier
|
||||
{
|
||||
|
||||
private $_cacheDir;
|
||||
private $_htmlPurifier;
|
||||
private $_config;
|
||||
private $_def;
|
||||
|
||||
public function __construct()
|
||||
public static function getInstance()
|
||||
{
|
||||
$this->_checkCacheDir();
|
||||
$this->_setConfig();
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function getInstance()
|
||||
{
|
||||
if(!isset($GLOBALS['__PURIFIER_INSTANCE__']))
|
||||
{
|
||||
$GLOBALS['__PURIFIER_INSTANCE__'] = new Purifier();
|
||||
}
|
||||
return $GLOBALS['__PURIFIER_INSTANCE__'];
|
||||
}
|
||||
|
||||
private function _setConfig()
|
||||
{
|
||||
$this->_config = HTMLPurifier_Config::createDefault();
|
||||
$this->_config->set('HTML.TidyLevel', 'light');
|
||||
$this->_config->set('Output.FlashCompat', TRUE);
|
||||
$this->_config->set('HTML.SafeObject', TRUE);
|
||||
$this->_config->set('HTML.SafeEmbed', TRUE);
|
||||
$this->_config->set('HTML.SafeIframe', TRUE);
|
||||
$this->_config->set('URI.SafeIframeRegexp', $this->_getWhiteDomainRegexp());
|
||||
$this->_config->set('Cache.SerializerPath', $this->_cacheDir);
|
||||
$this->_config->set('Attr.AllowedFrameTargets', array('_blank'));
|
||||
//$allowdClasses = array('emoticon');
|
||||
//$this->_config->set('Attr.AllowedClasses', $allowdClasses);
|
||||
$this->_def = $this->_config->getHTMLDefinition(TRUE);
|
||||
}
|
||||
|
||||
private function _setDefinition(&$content)
|
||||
{
|
||||
// add attribute for edit component
|
||||
$editComponentAttrs = $this->_searchEditComponent($content);
|
||||
if(is_array($editComponentAttrs))
|
||||
{
|
||||
foreach($editComponentAttrs AS $k => $v)
|
||||
{
|
||||
$this->_def->addAttribute('img', $v, 'CDATA');
|
||||
$this->_def->addAttribute('div', $v, 'CDATA');
|
||||
}
|
||||
}
|
||||
|
||||
// add attribute for widget component
|
||||
$widgetAttrs = $this->_searchWidget($content);
|
||||
if(is_array($widgetAttrs))
|
||||
{
|
||||
foreach($widgetAttrs AS $k => $v)
|
||||
{
|
||||
$this->_def->addAttribute('img', $v, 'CDATA');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search attribute of edit component tag
|
||||
* @param string $content
|
||||
* @return array
|
||||
*/
|
||||
private function _searchEditComponent($content)
|
||||
{
|
||||
preg_match_all('!<(?:(div)|img)([^>]*)editor_component=([^>]*)>(?(1)(.*?)</div>)!is', $content, $m);
|
||||
|
||||
$attributeList = array();
|
||||
if(is_array($m[2]))
|
||||
{
|
||||
foreach($m[2] as $key => $value)
|
||||
{
|
||||
unset($script, $m2);
|
||||
$script = " {$m[2][$key]} editor_component={$m[3][$key]}";
|
||||
|
||||
if(preg_match_all('/([a-z0-9_-]+)="([^"]+)"/is', $script, $m2))
|
||||
{
|
||||
foreach($m2[1] as $value2)
|
||||
{
|
||||
//SECISSUE check style attr
|
||||
if($value2 == 'style')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$attributeList[] = $value2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($attributeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search edit component tag
|
||||
* @param string $content
|
||||
* @return array
|
||||
*/
|
||||
private function _searchWidget(&$content)
|
||||
{
|
||||
preg_match_all('!<(?:(div)|img)([^>]*)class="zbxe_widget_output"([^>]*)>(?(1)(.*?)</div>)!is', $content, $m);
|
||||
|
||||
$attributeList = array();
|
||||
if(is_array($m[3]))
|
||||
{
|
||||
$content = str_replace('<img class="zbxe_widget_output"', '<img src="" class="zbxe_widget_output"', $content);
|
||||
|
||||
foreach($m[3] as $key => $value)
|
||||
{
|
||||
if (preg_match_all('/([a-z0-9_-]+)="([^"]+)"/is', $m[3][$key], $m2))
|
||||
{
|
||||
foreach($m2[1] as $value2)
|
||||
{
|
||||
//SECISSUE check style attr
|
||||
if($value2 == 'style')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$attributeList[] = $value2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_unique($attributeList);
|
||||
}
|
||||
|
||||
private function _getWhiteDomainRegexp()
|
||||
{
|
||||
$oEmbedFilter = EmbedFilter::getInstance();
|
||||
$whiteIframeUrlList = $oEmbedFilter->getWhiteIframeUrlList();
|
||||
|
||||
$whiteDomains = array();
|
||||
foreach($whiteIframeUrlList as $domain)
|
||||
{
|
||||
$whiteDomains[] = preg_quote($domain, '%');
|
||||
}
|
||||
return '%^https?://(' . implode('|', $whiteDomains) . ')%';
|
||||
}
|
||||
|
||||
private function _checkCacheDir()
|
||||
{
|
||||
// check htmlpurifier cache directory
|
||||
$this->_cacheDir = _XE_PATH_ . 'files/cache/htmlpurifier';
|
||||
FileHandler::makeDir($this->_cacheDir);
|
||||
}
|
||||
|
||||
|
||||
public function purify(&$content)
|
||||
{
|
||||
$this->_setDefinition($content);
|
||||
$this->_htmlPurifier = new HTMLPurifier($this->_config);
|
||||
|
||||
$content = $this->_htmlPurifier->purify($content);
|
||||
$content = Rhymix\Framework\Filters\HTMLFilter::clean($content);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ class Security
|
|||
* Action target variable. If this value is null, the method will use Context variables
|
||||
* @var mixed
|
||||
*/
|
||||
var $_targetVar = NULL;
|
||||
public $_targetVar = NULL;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param mixed $var Target context
|
||||
* @return void
|
||||
*/
|
||||
function __construct($var = NULL)
|
||||
public function __construct($var = NULL)
|
||||
{
|
||||
$this->_targetVar = $var;
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ class Security
|
|||
* separate the owner(object or array) and the item(property or element) using a dot(.)
|
||||
* @return mixed
|
||||
*/
|
||||
function encodeHTML(/* , $varName1, $varName2, ... */)
|
||||
public function encodeHTML(/* , $varName1, $varName2, ... */)
|
||||
{
|
||||
$varNames = func_get_args();
|
||||
if(count($varNames) < 0)
|
||||
|
|
@ -109,7 +109,7 @@ class Security
|
|||
* @param array $name
|
||||
* @return mixed
|
||||
*/
|
||||
function _encodeHTML($var, $name = array())
|
||||
protected function _encodeHTML($var, $name = array())
|
||||
{
|
||||
if(is_string($var))
|
||||
{
|
||||
|
|
@ -183,46 +183,9 @@ class Security
|
|||
* @param string $xml
|
||||
* @return bool
|
||||
*/
|
||||
static function detectingXEE($xml)
|
||||
public static function detectingXEE($xml)
|
||||
{
|
||||
if(!$xml) return FALSE;
|
||||
|
||||
if(strpos($xml, '<!ENTITY') !== FALSE)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Strip XML declaration.
|
||||
$header = preg_replace('/<\?xml.*?\?'.'>/s', '', substr($xml, 0, 100), 1);
|
||||
$xml = trim(substr_replace($xml, $header, 0, 100));
|
||||
if($xml == '')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Strip DTD.
|
||||
$header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($xml, 0, 200), 1);
|
||||
$xml = trim(substr_replace($xml, $header, 0, 200));
|
||||
if($xml == '')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Confirm the XML now starts with a valid root tag. A root tag can end in [> \t\r\n]
|
||||
$root_tag = substr($xml, 0, strcspn(substr($xml, 0, 20), "> \t\r\n"));
|
||||
|
||||
// Reject a second DTD.
|
||||
if(strtoupper($root_tag) == '<!DOCTYPE')
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if(!in_array($root_tag, array('<methodCall', '<methodResponse', '<fault')))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
return !Rhymix\Framework\Security::checkXEE($xml);
|
||||
}
|
||||
}
|
||||
/* End of file : Security.class.php */
|
||||
|
|
|
|||
|
|
@ -3,40 +3,9 @@
|
|||
|
||||
class UploadFileFilter
|
||||
{
|
||||
private static $_block_list = array ('exec', 'system', 'passthru', 'show_source', 'phpinfo', 'fopen', 'file_get_contents', 'file_put_contents', 'fwrite', 'proc_open', 'popen');
|
||||
|
||||
public function check($file)
|
||||
{
|
||||
// TODO: 기능개선후 enable
|
||||
|
||||
return TRUE; // disable
|
||||
if (! $file || ! FileHandler::exists($file)) return TRUE;
|
||||
return self::_check ( $file );
|
||||
}
|
||||
|
||||
private function _check($file)
|
||||
{
|
||||
if (! ($fp = fopen ( $file, 'r' ))) return FALSE;
|
||||
|
||||
$has_php_tag = FALSE;
|
||||
|
||||
while ( ! feof ( $fp ) )
|
||||
{
|
||||
$content = fread ( $fp, 8192 );
|
||||
if (FALSE === $has_php_tag) $has_php_tag = strpos ( $content, '<?' );
|
||||
foreach ( self::$_block_list as $v )
|
||||
{
|
||||
if (FALSE !== $has_php_tag && FALSE !== strpos ( $content, $v ))
|
||||
{
|
||||
fclose ( $fp );
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose ( $fp );
|
||||
|
||||
return TRUE;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue