mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-04 17:21:39 +09:00
Implement SameSite cookie policy
- 세션 쿠키, 세션 보안키 등에 SameSite 속성을 적용할 수 있는 기능 추가 (시스템 설정 -> 보안 설정) - 일반적인 사이트는 Lax를 권장함, PG사 연동 오류 등의 경우 None 사용 - None 사용시 크롬 80부터는 SSL 전용 세션으로 지정해야 함 - Rhymix\Framework\Session에서 쿠키 관련 루틴들 정리 - PHP 7.3 이상, 7.2 이하 버전으로 나누어 처리
This commit is contained in:
parent
e33d8569bc
commit
04bb0493c7
6 changed files with 134 additions and 31 deletions
|
|
@ -74,14 +74,24 @@ class Session
|
|||
}
|
||||
|
||||
// Set session parameters.
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
$alt_domain = $domain ?: preg_replace('/:\\d+$/', '', strtolower($_SERVER['HTTP_HOST']));
|
||||
$ssl_only = (\RX_SSL && config('session.use_ssl')) ? true : false;
|
||||
ini_set('session.gc_maxlifetime', $lifetime + 28800);
|
||||
ini_set('session.use_cookies', 1);
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
session_set_cookie_params($lifetime, $path, $domain, $ssl_only, $ssl_only);
|
||||
if ($samesite)
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '7.3', '>='))
|
||||
{
|
||||
ini_set('session.cookie_samesite', $samesite);
|
||||
}
|
||||
else
|
||||
{
|
||||
$path = ($path ?: '/') . '; SameSite=' . $samesite;
|
||||
}
|
||||
}
|
||||
session_set_cookie_params($lifetime, $path, $domain, $secure, $secure);
|
||||
session_name($session_name = Config::get('session.name') ?: session_name());
|
||||
|
||||
// Get session ID from POST parameter if using relaxed key checks.
|
||||
|
|
@ -307,7 +317,14 @@ class Session
|
|||
if(!$is_default_domain && !\Context::get('sso_response') && $_COOKIE['sso'] !== md5($current_domain))
|
||||
{
|
||||
// Set sso cookie to prevent multiple simultaneous SSO validation requests.
|
||||
setcookie('sso', md5($current_domain), 0, '/', $domain, !!config('session.use_ssl'), true);
|
||||
self::_setCookie('sso', md5($current_domain), array(
|
||||
'expires' => 0,
|
||||
'path' => '/',
|
||||
'domain' => null,
|
||||
'secure' => !!config('session.use_ssl'),
|
||||
'httponly' => true,
|
||||
'samesite' => config('session.samesite'),
|
||||
));
|
||||
|
||||
// Redirect to the default site.
|
||||
$sso_request = Security::encrypt($current_url);
|
||||
|
|
@ -508,23 +525,16 @@ class Session
|
|||
public static function destroy()
|
||||
{
|
||||
// Get session parameters.
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
|
||||
// Delete all cookies.
|
||||
self::_setKeys();
|
||||
self::destroyAutologinKeys();
|
||||
setcookie(session_name(), 'deleted', time() - 86400, $path, $domain, false, false);
|
||||
setcookie('xe_logged', 'deleted', time() - 86400, $path, $domain, false, false);
|
||||
setcookie('xeak', 'deleted', time() - 86400, $path, $domain, false, false);
|
||||
setcookie('sso', 'deleted', time() - 86400, $path, $domain, false, false);
|
||||
self::_unsetCookie(session_name(), $path, $domain);
|
||||
self::_unsetCookie('xe_logged', $path, $domain);
|
||||
self::_unsetCookie('xeak', $path, $domain);
|
||||
self::_unsetCookie('sso', $path, $domain);
|
||||
self::destroyCookiesFromConflictingDomains(array('xe_logged', 'xeak', 'sso'), $domain === null);
|
||||
unset($_COOKIE[session_name()]);
|
||||
unset($_COOKIE['rx_autologin']);
|
||||
unset($_COOKIE['rx_sesskey1']);
|
||||
unset($_COOKIE['rx_sesskey2']);
|
||||
unset($_COOKIE['xe_logged']);
|
||||
unset($_COOKIE['xeak']);
|
||||
unset($_COOKIE['sso']);
|
||||
|
||||
// Clear session data.
|
||||
$_SESSION = array();
|
||||
|
|
@ -1047,7 +1057,9 @@ class Session
|
|||
$refresh = Config::get('session.refresh') ?: 300;
|
||||
$domain = self::getDomain();
|
||||
$path = Config::get('session.path') ?: ini_get('session.cookie_path');
|
||||
return array($lifetime, $refresh, $domain, $path);
|
||||
$secure = (\RX_SSL && config('session.use_ssl')) ? true : false;
|
||||
$samesite = config('session.samesite');
|
||||
return array($lifetime, $refresh, $domain, $path, $secure, $samesite);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1089,27 +1101,35 @@ class Session
|
|||
protected static function _setKeys()
|
||||
{
|
||||
// Get session parameters.
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
$alt_domain = $domain ?: preg_replace('/:\\d+$/', '', strtolower($_SERVER['HTTP_HOST']));
|
||||
$lifetime = $lifetime ? ($lifetime + time()) : 0;
|
||||
$ssl_only = (\RX_SSL && config('session.use_ssl')) ? true : false;
|
||||
|
||||
$options = array(
|
||||
'expires' => $lifetime,
|
||||
'path' => $path,
|
||||
'domain' => $domain,
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => $samesite,
|
||||
);
|
||||
|
||||
// Set or destroy the HTTP-only key.
|
||||
if (isset($_SESSION['RHYMIX']['keys'][$alt_domain]['key1']))
|
||||
{
|
||||
setcookie('rx_sesskey1', $_SESSION['RHYMIX']['keys'][$alt_domain]['key1'], $lifetime, $path, $domain, $ssl_only, true);
|
||||
self::_setCookie('rx_sesskey1', $_SESSION['RHYMIX']['keys'][$alt_domain]['key1'], $options);
|
||||
$_COOKIE['rx_sesskey1'] = $_SESSION['RHYMIX']['keys'][$alt_domain]['key1'];
|
||||
}
|
||||
else
|
||||
{
|
||||
setcookie('rx_sesskey1', 'deleted', time() - 86400, $path, $domain);
|
||||
self::_unsetCookie('rx_sesskey1', $path, $domain);
|
||||
unset($_COOKIE['rx_sesskey1']);
|
||||
}
|
||||
|
||||
// Set the HTTPS-only key.
|
||||
if (\RX_SSL && isset($_SESSION['RHYMIX']['keys'][$alt_domain]['key2']))
|
||||
{
|
||||
setcookie('rx_sesskey2', $_SESSION['RHYMIX']['keys'][$alt_domain]['key2'], $lifetime, $path, $domain, true, true);
|
||||
$options['secure'] = true;
|
||||
self::_setCookie('rx_sesskey2', $_SESSION['RHYMIX']['keys'][$alt_domain]['key2'], $options);
|
||||
$_COOKIE['rx_sesskey2'] = $_SESSION['RHYMIX']['keys'][$alt_domain]['key2'];
|
||||
}
|
||||
|
||||
|
|
@ -1118,6 +1138,63 @@ class Session
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cookie (for compatibility with PHP < 7.3)
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _setCookie($name, $value, array $options = [])
|
||||
{
|
||||
$name = strval($name);
|
||||
$value = strval($value);
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.3', '>='))
|
||||
{
|
||||
$result = setcookie($name, $value, $options);
|
||||
}
|
||||
else
|
||||
{
|
||||
$expires = $options['expires'] ?? 0;
|
||||
$path = $options['path'] ?? null;
|
||||
$domain = $options['domain'] ?? null;
|
||||
$secure = $options['secure'] ?? null;
|
||||
$httponly = $options['httponly'] ?? null;
|
||||
$samesite = $options['samesite'] ?? '';
|
||||
if ($samesite)
|
||||
{
|
||||
$path = ($path ?: '/') . '; SameSite=' . $samesite;
|
||||
}
|
||||
$result = setcookie($name, $value, $expires, $path, $domain, $secure, $httponly);
|
||||
}
|
||||
|
||||
if ($result)
|
||||
{
|
||||
$_COOKIE[$name] = $value;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset cookie.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $path (optional)
|
||||
* @param string $domain (optional)
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _unsetCookie($name, $path = null, $domain = null)
|
||||
{
|
||||
$result = setcookie($name, 'deleted', time() - (86400 * 366), $path, $domain, false, false);
|
||||
if ($result)
|
||||
{
|
||||
unset($_COOKIE[$name]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set autologin key.
|
||||
*
|
||||
|
|
@ -1128,16 +1205,23 @@ class Session
|
|||
public static function setAutologinKeys($autologin_key, $security_key)
|
||||
{
|
||||
// Get session parameters.
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
$lifetime = time() + (86400 * 365);
|
||||
$ssl_only = (\RX_SSL && config('session.use_ssl')) ? true : false;
|
||||
$samesite = config('session.samesite');
|
||||
|
||||
// Set the autologin keys.
|
||||
if ($autologin_key && $security_key)
|
||||
{
|
||||
setcookie('rx_autologin', $autologin_key . $security_key, $lifetime, $path, $domain, $ssl_only, true);
|
||||
self::_setCookie('rx_autologin', $autologin_key . $security_key, array(
|
||||
'expires' => $lifetime,
|
||||
'path' => $path,
|
||||
'domain' => $domain,
|
||||
'secure' => $secure,
|
||||
'httponly' => true,
|
||||
'samesite' => $samesite,
|
||||
));
|
||||
|
||||
self::destroyCookiesFromConflictingDomains(array('rx_autologin'), $domain === null);
|
||||
$_COOKIE['rx_autologin'] = $autologin_key . $security_key;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
|
@ -1154,7 +1238,7 @@ class Session
|
|||
public static function destroyAutologinKeys()
|
||||
{
|
||||
// Get session parameters.
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
|
||||
// Delete the autologin keys from the database.
|
||||
if (self::$_autologin_key)
|
||||
|
|
@ -1169,7 +1253,7 @@ class Session
|
|||
}
|
||||
|
||||
// Delete the autologin cookie.
|
||||
setcookie('rx_autologin', 'deleted', time() - 86400, $path, $domain, false, false);
|
||||
self::_unsetCookie('rx_autologin', $path, $domain);
|
||||
self::destroyCookiesFromConflictingDomains(array('rx_autologin'), $domain === null);
|
||||
unset($_COOKIE['rx_autologin']);
|
||||
return $result;
|
||||
|
|
@ -1235,12 +1319,12 @@ class Session
|
|||
return false;
|
||||
}
|
||||
|
||||
list($lifetime, $refresh_interval, $domain, $path) = self::_getParams();
|
||||
list($lifetime, $refresh_interval, $domain, $path, $secure, $samesite) = self::_getParams();
|
||||
foreach ($cookies as $cookie)
|
||||
{
|
||||
foreach ($conflict_domains as $conflict_domain)
|
||||
{
|
||||
setcookie($cookie, 'deleted', time() - 86400, $path, $conflict_domain);
|
||||
self::_unsetCookie($cookie, $path, $conflict_domain);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -689,6 +689,7 @@ class adminAdminController extends admin
|
|||
|
||||
Rhymix\Framework\Config::set('admin.allow', array_values($allowed_ip));
|
||||
Rhymix\Framework\Config::set('admin.deny', array_values($denied_ip));
|
||||
Rhymix\Framework\Config::set('session.samesite', preg_replace('/[^a-zA-Z]/', '', $vars->use_samesite));
|
||||
Rhymix\Framework\Config::set('session.use_keys', $vars->use_session_keys === 'Y');
|
||||
Rhymix\Framework\Config::set('session.use_ssl', $vars->use_session_ssl === 'Y');
|
||||
Rhymix\Framework\Config::set('session.use_ssl_cookies', $vars->use_cookies_ssl === 'Y');
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ class adminAdminView extends admin
|
|||
Context::set('remote_addr', RX_CLIENT_IP);
|
||||
|
||||
// Session and cookie security settings
|
||||
Context::set('use_samesite', Rhymix\Framework\Config::get('session.samesite'));
|
||||
Context::set('use_session_keys', Rhymix\Framework\Config::get('session.use_keys'));
|
||||
Context::set('use_session_ssl', Rhymix\Framework\Config::get('session.use_ssl'));
|
||||
Context::set('use_cookies_ssl', Rhymix\Framework\Config::get('session.use_ssl_cookies'));
|
||||
|
|
|
|||
|
|
@ -164,6 +164,9 @@ $lang->use_server_push = 'Use HTTP/2 Server Push';
|
|||
$lang->use_gzip = 'gzip Compression';
|
||||
$lang->delay_session = 'Delay session start';
|
||||
$lang->about_delay_session = 'To improve performance when using a caching proxy server such as Varnish, do not issue sessions to visitors until they log in.<br>Selecting this option may interfere with autologin, and visitor counts may become inaccurate.';
|
||||
$lang->use_samesite = 'SameSite attribute';
|
||||
$lang->use_samesite_empty = 'Do not use';
|
||||
$lang->about_use_samesite = 'Set the SameSite attribute for session cookies and session keys.<br>Lax is the recommended setting for most sites. You may need to use None if you are having difficulties integrating with external services such as payment gateways.<br>However, None is only valid when used with SSL-only sessions.';
|
||||
$lang->use_session_keys = 'Use session security keys';
|
||||
$lang->about_use_session_keys = 'Use additional security keys to guard against session theft. This setting is highly recommended if you don\'t use SSL-only sessions.<br>This setting may cause some users to become logged out.';
|
||||
$lang->use_session_ssl = 'Use SSL-only session';
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@ $lang->use_server_push = 'Server Push 사용';
|
|||
$lang->use_gzip = 'gzip 압축';
|
||||
$lang->delay_session = '세션 시작 지연';
|
||||
$lang->about_delay_session = 'Varnish 등의 프록시 캐싱 서버 사용시 성능 개선을 위해, 로그인하지 않은 사용자에게는 인증 세션을 부여하지 않습니다.<br>이 옵션을 사용하면 자동 로그인이 되지 않으며, 방문자 수 집계가 정확하게 이루어지지 않을 수 있습니다.';
|
||||
$lang->use_samesite = 'SameSite 속성 사용';
|
||||
$lang->use_samesite_empty = '표기하지 않음';
|
||||
$lang->about_use_samesite = '세션 쿠키, 보안키 등에 적용되는 SameSite 속성을 선택할 수 있습니다.<br>대부분의 사이트에는 Lax를 권장하며, 최신 브라우저에서 결제 연동 등에 문제가 있는 경우 None을 선택하시기 바랍니다.<br>단, None을 선택할 경우 SSL 전용 세션을 사용하여야 정상 작동합니다.';
|
||||
$lang->use_session_keys = '세션 보안키 사용';
|
||||
$lang->about_use_session_keys = '세션 탈취를 방지하기 위한 보안키를 사용합니다. SSL 전용 세션을 사용하지 않을 경우 반드시 보안키를 사용하시기를 권장합니다.<br>사용자 환경에 따라 로그인이 풀리는 문제가 발생할 수 있습니다.';
|
||||
$lang->use_session_ssl = 'SSL 전용 세션 사용';
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@
|
|||
<p class="x_help-block">{$lang->about_admin_ip_deny}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->use_samesite}</label>
|
||||
<div class="x_controls">
|
||||
<label for="use_samesite_strict" class="x_inline"><input type="radio" name="use_samesite" id="use_samesite_strict" value="Strict" checked="checked"|cond="$use_samesite === 'Strict'" /> Strict</label>
|
||||
<label for="use_samesite_lax" class="x_inline"><input type="radio" name="use_samesite" id="use_samesite_lax" value="Lax" checked="checked"|cond="$use_samesite === 'Lax'" /> Lax</label>
|
||||
<label for="use_samesite_none" class="x_inline"><input type="radio" name="use_samesite" id="use_samesite_none" value="None" checked="checked"|cond="$use_samesite === 'None'" /> None</label>
|
||||
<label for="use_samesite_empty" class="x_inline"><input type="radio" name="use_samesite" id="use_samesite_empty" value="" checked="checked"|cond="!$use_samesite" /> {$lang->use_samesite_empty}</label>
|
||||
<br />
|
||||
<p class="x_help-block">{$lang->about_use_samesite}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="x_control-group">
|
||||
<label class="x_control-label">{$lang->use_session_keys}</label>
|
||||
<div class="x_controls">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue