From 1511693cf6e892e1ed0a3637bf34246894f78fdc Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 29 Apr 2026 23:12:02 +0900 Subject: [PATCH 1/6] Regenerate session ID upon login and peridically RVE-2026-8 --- common/framework/Session.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/framework/Session.php b/common/framework/Session.php index 1a6e7242d..8eadeb4da 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -430,7 +430,8 @@ class Session if ($refresh_cookie) { self::destroyCookiesFromConflictingDomains(array(session_name())); - Cookie::set(session_name(), session_id(), $options); + //Cookie::set(session_name(), session_id(), $options); + session_regenerate_id(true); if (self::$_autologin_key = self::_getAutologinKey()) { self::setAutologinKeys(substr(self::$_autologin_key, 0, 24), substr(self::$_autologin_key, 24, 24)); From 8cfd66713cf944b20b8d71feb9013aff2651d7b2 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 29 Apr 2026 23:25:17 +0900 Subject: [PATCH 2/6] Refresh session when accessing admin page --- classes/module/ModuleObject.class.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index 687c07bad..d4ec19e79 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -210,9 +210,10 @@ class ModuleObject extends BaseObject return; } - // Set admin layout + // Special treatment for admin actions if(preg_match('/^disp[A-Z][a-z0-9\_]+Admin/', $this->act)) { + // Set admin layout if(config('view.manager_layout') === 'admin') { $this->setLayoutPath('modules/admin/tpl'); @@ -223,6 +224,16 @@ class ModuleObject extends BaseObject $oTemplate = new Rhymix\Framework\Template('modules/admin/tpl', '_admin_common.html'); $oTemplate->compile(); } + + // Refresh session + if (!isset($_SESSION['RHYMIX']['admin_accessed'])) + { + if (!headers_sent()) + { + $_SESSION['RHYMIX']['admin_accessed'] = \RX_TIME; + Rhymix\Framework\Session::refresh(true); + } + } } // Execute init From 9f1a3574c5dd36e6bb7d8ba78920dcdbd3aca275 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Wed, 29 Apr 2026 23:31:29 +0900 Subject: [PATCH 3/6] Remove SSO --- classes/context/Context.class.php | 1 - common/defaults/config.php | 1 - common/framework/Session.php | 101 ------------------ common/framework/parsers/ConfigParser.php | 1 - .../controllers/systemconfig/Domains.php | 1 - modules/admin/lang/en.php | 2 - modules/admin/lang/es.php | 1 - modules/admin/lang/fr.php | 1 - modules/admin/lang/ja.php | 2 - modules/admin/lang/ko.php | 2 - modules/admin/lang/tr.php | 2 - modules/admin/lang/zh-CN.php | 1 - modules/admin/lang/zh-TW.php | 1 - modules/admin/tpl/config_domains.html | 11 +- tests/unit/framework/SessionTest.php | 5 - 15 files changed, 1 insertion(+), 132 deletions(-) diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index a24ed24e3..6d4b65f75 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -347,7 +347,6 @@ class Context if (self::$_current_request->getRouteOption('enable_session')) { session_cache_limiter(''); - Rhymix\Framework\Session::checkSSO($site_module_info); Rhymix\Framework\Session::start(false); } if (self::$_current_request->getRouteOption('cache_control')) diff --git a/common/defaults/config.php b/common/defaults/config.php index 6742f7af5..5f149314b 100644 --- a/common/defaults/config.php +++ b/common/defaults/config.php @@ -150,7 +150,6 @@ return array( 'regexp' => '', ], 'use_rewrite' => true, - 'use_sso' => false, 'other' => [ 'proxy' => null, ], diff --git a/common/framework/Session.php b/common/framework/Session.php index 8eadeb4da..ec4b5275b 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -237,107 +237,6 @@ class Session } } - /** - * Check if this session needs to be shared with another site with SSO. - * - * This method uses more or less the same logic as XE's SSO mechanism. - * It may need to be changed to a more secure mechanism later. - * - * @param object $site_module_info - * @return void - */ - public static function checkSSO(object $site_module_info): void - { - // Abort if SSO is disabled, the visitor is a robot, or this is not a typical GET request. - if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET' || !config('use_sso') || UA::isRobot() || in_array(\Context::get('act'), array('rss', 'atom'))) - { - return; - } - - // Get the current site information. - $is_default_domain = ($site_module_info->domain_srl == 0); - if (!$is_default_domain) - { - $current_domain = $site_module_info->domain; - $current_url = URL::getCurrentUrl(); - $default_domain = \ModuleModel::getDefaultDomainInfo(); - $default_url = \Context::getDefaultUrl($default_domain); - } - - // Step 1: if the current site is not the default site, send SSO validation request to the default site. - if(!$is_default_domain && !\Context::get('sso_response') && $_COOKIE['sso'] !== md5($current_domain)) - { - // Set sso cookie to prevent multiple simultaneous SSO validation requests. - Cookie::set('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); - header('Location:' . URL::modifyURL($default_url, array('sso_request' => $sso_request))); - exit; - } - - // Step 2: receive and process SSO validation request at the default site. - if($is_default_domain && \Context::get('sso_request')) - { - // Get the URL of the origin site - $sso_request = Security::decrypt(\Context::get('sso_request')); - if (!$sso_request || !preg_match('!^https?://!', $sso_request)) - { - \Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400); - exit; - } - if (!URL::isInternalUrl($sso_request) || !URL::isInternalURL($_SERVER['HTTP_REFERER'] ?? '')) - { - \Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400); - exit; - } - - // Encrypt the session ID. - self::start(true); - $sso_response = Security::encrypt(session_id()); - - // Redirect back to the origin site. - header('Location: ' . URL::modifyURL($sso_request, array('sso_response' => $sso_response))); - self::close(); - exit; - } - - // Step 3: back at the origin site, set session ID to be the same as the default site. - if(!$is_default_domain && \Context::get('sso_response')) - { - // Check SSO response - $sso_response = Security::decrypt(\Context::get('sso_response')); - if ($sso_response === false) - { - \Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_RESPONSE', 400); - exit; - } - - // Check that the response was given by the default site (to prevent session fixation CSRF). - if(isset($_SERVER['HTTP_REFERER']) && !URL::isInternalURL($_SERVER['HTTP_REFERER'])) - { - \Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_RESPONSE', 400); - exit; - } - - // Set the session ID. - session_id($sso_response); - self::start(true, false); - - // Finally, redirect to the originally requested URL. - header('Location: ' . URL::getCurrentURL(array('sso_response' => null))); - self::close(); - exit; - } - } - /** * Create the data structure for a new Rhymix session. * diff --git a/common/framework/parsers/ConfigParser.php b/common/framework/parsers/ConfigParser.php index a12e3b882..2ed1e019b 100644 --- a/common/framework/parsers/ConfigParser.php +++ b/common/framework/parsers/ConfigParser.php @@ -242,7 +242,6 @@ class ConfigParser $config['file']['umask'] = Storage::recommendUmask(); $config['mobile']['enabled'] = ($db_info->use_mobile_view ?? 'N') === 'N' ? false : true; $config['use_rewrite'] = ($db_info->use_rewrite ?? 'N') === 'Y' ? true : false; - $config['use_sso'] = ($db_info->use_sso ?? 'N') === 'Y' ? true : false; // Copy other configuration. unset($db_info->master_db, $db_info->slave_db); diff --git a/modules/admin/controllers/systemconfig/Domains.php b/modules/admin/controllers/systemconfig/Domains.php index b5ac5260e..737882e9e 100644 --- a/modules/admin/controllers/systemconfig/Domains.php +++ b/modules/admin/controllers/systemconfig/Domains.php @@ -205,7 +205,6 @@ class Domains extends Base // Save system config. Config::set('url.unregistered_domain_action', $vars->unregistered_domain_action); - Config::set('use_sso', $vars->use_sso === 'Y'); if (!Config::save()) { throw new Exception('msg_failed_to_save_config'); diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php index 19e7231a0..2a0e9a795 100644 --- a/modules/admin/lang/en.php +++ b/modules/admin/lang/en.php @@ -370,8 +370,6 @@ $lang->site_default_color_scheme_options = array( 'light' => 'Light mode only', 'dark' => 'Dark mode only', ); -$lang->use_sso = 'Use SSO?'; -$lang->about_use_sso = 'Logging into one domain will automatically log the user into all domains.
Do not rely on this feature, as it will be removed in the future.'; $lang->about_arrange_session = 'Do you want to clean up old session data?'; $lang->cmd_clear_session = 'Session cleanup'; $lang->save = 'Save'; diff --git a/modules/admin/lang/es.php b/modules/admin/lang/es.php index c8c184018..4045df919 100644 --- a/modules/admin/lang/es.php +++ b/modules/admin/lang/es.php @@ -41,7 +41,6 @@ $lang->about_server_ports = '80 de HTTP, HTTPS al puerto 443 si se utiliza otro $lang->about_db_session = 'This setting will use PHP session used for authentication as DB. For the Websites which do not use web server frequently, you can uncheck this setting to improve response time. However, session DB will make it difficult to get current users, so you cannot use related functions.'; $lang->trash = 'Basura'; $lang->timezone = 'Huso horario'; -$lang->about_use_sso = 'SSO will enable users to sign in just once for both default and virtual site. You will need this only if you are using virtual sites.'; $lang->modify = 'Modificar'; $lang->ftp_form_title = 'Datos de conexión para FTP'; $lang->ftp = 'FTP'; diff --git a/modules/admin/lang/fr.php b/modules/admin/lang/fr.php index 65fd137c0..1d5c25865 100644 --- a/modules/admin/lang/fr.php +++ b/modules/admin/lang/fr.php @@ -43,7 +43,6 @@ $lang->thumbnail_crop = 'Rogner'; $lang->thumbnail_ratio = 'Proportion'; $lang->trash = 'Poubelle'; $lang->timezone = 'Fuseau horaire'; -$lang->about_use_sso = 'SSO will enable users to sign in just once for both default and virtual site. You will need this only if you are using virtual sites.'; $lang->modify = 'Modifier'; $lang->ftp = 'FTP'; $lang->msg_ftp_no_directory = 'Succeed to connect to the host via FTP. However, can not read any directory list informaiton. Check the server configurations.'; diff --git a/modules/admin/lang/ja.php b/modules/admin/lang/ja.php index aa3840809..e7fd3512d 100644 --- a/modules/admin/lang/ja.php +++ b/modules/admin/lang/ja.php @@ -138,8 +138,6 @@ $lang->allow_use_favicon = 'ファビコン設定'; $lang->about_use_favicon = '16 x 16 サイズの*.ico ファイルのみ登録できます。'; $lang->allow_use_mobile_icon = '待受画面のアイコン設定'; $lang->detail_use_mobile_icon = '57 x 57 、または114 x 114 サイズの*.png ファイルのみ登録できます。'; -$lang->use_sso = 'SSOを使用'; -$lang->about_use_sso = 'ユーザが一度のログインで基本サイトと仮想サイトに同時にログインされる機能です。 仮想サイトの機能を使用してない場合、設定する必要がありません。'; $lang->about_arrange_session = 'セッションを整理しますか?'; $lang->cmd_clear_session = 'セッション整理'; $lang->save = '保存'; diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php index 5c0f39257..2b837f792 100644 --- a/modules/admin/lang/ko.php +++ b/modules/admin/lang/ko.php @@ -366,8 +366,6 @@ $lang->site_default_color_scheme_options = array( 'light' => '밝은 색상 고정', 'dark' => '어두운 색상 고정', ); -$lang->use_sso = 'SSO 사용'; -$lang->about_use_sso = '한 번만 로그인하면 모든 도메인에 로그인되도록 합니다.
이 기능은 폐기 예정이니 의존하지 마시기 바랍니다.'; $lang->about_arrange_session = '세션을 정리하시겠습니까?'; $lang->cmd_clear_session = '세션 정리'; $lang->save = '저장'; diff --git a/modules/admin/lang/tr.php b/modules/admin/lang/tr.php index 23193db70..dff074816 100644 --- a/modules/admin/lang/tr.php +++ b/modules/admin/lang/tr.php @@ -100,8 +100,6 @@ $lang->allow_use_favicon = 'Favicon\'u kullanmak istiyor musunuz?'; $lang->about_use_favicon = '16 x 16 boyutunda*.Ico dosyalar yüklenebilir.'; $lang->allow_use_mobile_icon = 'Mobil Ana Ekran Simgesini kullanmak istiyor musunuz?'; $lang->detail_use_mobile_icon = '57 x 57 veya 114 x 114 boyutu*.Png dosyalar yüklenebilir.'; -$lang->use_sso = 'SSO kullanmak ister misiniz?'; -$lang->about_use_sso = 'SSO kullanıcıları, geçreli ya da sanal siteye bir kere kayıt olmakla, ikisinden de yararlandıracaktır. Bu, size sadece sanal websiteler kullandığınız durumda lazım olacaktır.'; $lang->about_arrange_session = 'Bu oturumu temizlemek istiyor musunuz?'; $lang->cmd_clear_session = 'Oturum temizleme'; $lang->save = 'Kayıt'; diff --git a/modules/admin/lang/zh-CN.php b/modules/admin/lang/zh-CN.php index 6dd7b7d92..c19d359b9 100644 --- a/modules/admin/lang/zh-CN.php +++ b/modules/admin/lang/zh-CN.php @@ -96,7 +96,6 @@ $lang->allow_use_favicon = '是否启用自定义favicon?'; $lang->about_use_favicon = '请上传16*16像素的*.ico文件.'; $lang->allow_use_mobile_icon = '是否启用移动版屏幕图标?'; $lang->detail_use_mobile_icon = '请上传57*57像素或者114*114像素的*.png文件.'; -$lang->use_sso = '是否启用通行证SSO?'; $lang->about_arrange_session = '你想要整理session吗?'; $lang->cmd_clear_session = '整理sessioon'; $lang->save = '保存'; diff --git a/modules/admin/lang/zh-TW.php b/modules/admin/lang/zh-TW.php index 0cb6bbe0e..f88d17117 100644 --- a/modules/admin/lang/zh-TW.php +++ b/modules/admin/lang/zh-TW.php @@ -58,7 +58,6 @@ $lang->thumbnail_crop = '裁切'; $lang->thumbnail_ratio = '比例'; $lang->trash = '垃圾'; $lang->timezone = '時區'; -$lang->about_use_sso = '此功能可讓用戶只需登入一次即可訪問多個網站。 使用虛擬網站,這將會是很重要的功能。'; $lang->move = '搬移'; $lang->modify = '修改'; $lang->restore = '復原'; diff --git a/modules/admin/tpl/config_domains.html b/modules/admin/tpl/config_domains.html index a78addf9d..6554ffc77 100644 --- a/modules/admin/tpl/config_domains.html +++ b/modules/admin/tpl/config_domains.html @@ -6,7 +6,7 @@

{$XE_VALIDATOR_MESSAGE}

- +
@@ -103,15 +103,6 @@
-
- -
- - -
-

{$lang->about_use_sso}

-
-
diff --git a/tests/unit/framework/SessionTest.php b/tests/unit/framework/SessionTest.php index e0675dfc6..af8a6dcd8 100644 --- a/tests/unit/framework/SessionTest.php +++ b/tests/unit/framework/SessionTest.php @@ -98,11 +98,6 @@ class SessionTest extends \Codeception\Test\Unit $this->assertTrue(Rhymix\Framework\Session::isStarted()); } - public function testCheckSSO() - { - $this->assertNull(Rhymix\Framework\Session::checkSSO(new stdClass)); - } - public function testRefresh() { $_SERVER['REQUEST_METHOD'] = 'GET'; From f438a91cd677df6813ad9afec3140b1b145f50d0 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 22:44:14 +0900 Subject: [PATCH 4/6] Treat samesite attribute as a string, not integer --- common/framework/Session.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/framework/Session.php b/common/framework/Session.php index ec4b5275b..2a77a7fb9 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -79,7 +79,7 @@ class Session ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_strict_mode', 1); - ini_set('session.cookie_samesite', $samesite ? 1 : 0); + ini_set('session.cookie_samesite', $samesite); session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); session_name($session_name = Config::get('session.name') ?: session_name()); @@ -948,7 +948,7 @@ class Session $path = Config::get('session.path') ?: ini_get('session.cookie_path'); $secure = (\RX_SSL && config('session.use_ssl')) ? true : false; $httponly = Config::get('session.httponly') ?? true; - $samesite = config('session.samesite'); + $samesite = config('session.samesite') ?: ''; return array($lifetime, $refresh, $domain, $path, $secure, $httponly, $samesite); } From 7f1a61fb83ab8bb6641223f19b77a6b95dc8053d Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 23:00:55 +0900 Subject: [PATCH 5/6] Clean up session refresh handling (dedicated timer, don't refresh in non-GET request, etc.) --- classes/module/ModuleObject.class.php | 2 +- common/framework/Session.php | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index d4ec19e79..df397e020 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -211,7 +211,7 @@ class ModuleObject extends BaseObject } // Special treatment for admin actions - if(preg_match('/^disp[A-Z][a-z0-9\_]+Admin/', $this->act)) + if(preg_match('/^disp(?:Admin[A-Z]|[A-Z][a-z0-9\_]+Admin)/', $this->act)) { // Set admin layout if(config('view.manager_layout') === 'admin') diff --git a/common/framework/Session.php b/common/framework/Session.php index 2a77a7fb9..1891a0e8b 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -113,7 +113,11 @@ class Session } // Check if the session needs to be refreshed. - if (!$must_create && !isset($_SESSION['RHYMIX']['domains'][$alt_domain]['started']) || $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] < time() - $refresh_interval) + if (!$must_create && (!isset($_SESSION['RHYMIX']['last_refresh']) || $_SESSION['RHYMIX']['last_refresh'] < time() - $refresh_interval)) + { + $must_refresh = true; + } + if (!$must_create && isset($_SESSION['RHYMIX']['next_refresh']) && $_SESSION['RHYMIX']['next_refresh'] === true) { $must_refresh = true; } @@ -127,8 +131,9 @@ class Session } // If this is not a GET request, do not refresh now. - if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET') + if ($must_refresh && (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET')) { + $_SESSION['RHYMIX']['next_refresh'] = true; $must_refresh = false; } @@ -250,6 +255,8 @@ class Session $_SESSION['RHYMIX'] = array(); $_SESSION['RHYMIX']['login'] = false; $_SESSION['RHYMIX']['last_login'] = false; + $_SESSION['RHYMIX']['last_refresh'] = time(); + $_SESSION['RHYMIX']['next_refresh'] = false; $_SESSION['RHYMIX']['autologin_key'] = false; $_SESSION['RHYMIX']['ipaddress'] = $_SESSION['ipaddress'] = \RX_CLIENT_IP; $_SESSION['RHYMIX']['useragent'] = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; @@ -319,7 +326,10 @@ class Session ); // Update the domain initialization timestamp. - $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] = time(); + if (!isset($_SESSION['RHYMIX']['domains'][$alt_domain]['started'])) + { + $_SESSION['RHYMIX']['domains'][$alt_domain]['started'] = time(); + } if (!isset($_SESSION['RHYMIX']['domains'][$alt_domain]['trusted'])) { $_SESSION['RHYMIX']['domains'][$alt_domain]['trusted'] = 0; @@ -328,6 +338,8 @@ class Session // Refresh the main session cookie and the autologin key. if ($refresh_cookie) { + $_SESSION['RHYMIX']['last_refresh'] = time(); + $_SESSION['RHYMIX']['next_refresh'] = false; self::destroyCookiesFromConflictingDomains(array(session_name())); //Cookie::set(session_name(), session_id(), $options); session_regenerate_id(true); From 019950c8a8b135f0c6336705b4b7187bbcf46937 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Thu, 30 Apr 2026 23:13:05 +0900 Subject: [PATCH 6/6] Prevent unnecessary double cookie refresh when logging in as admin --- classes/module/ModuleObject.class.php | 4 ++-- common/framework/Session.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index df397e020..c0861660d 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -226,9 +226,9 @@ class ModuleObject extends BaseObject } // Refresh session - if (!isset($_SESSION['RHYMIX']['admin_accessed'])) + if (!isset($_SESSION['RHYMIX']['admin_accessed']) && !headers_sent()) { - if (!headers_sent()) + if (!isset($_SESSION['RHYMIX']['last_refresh']) || $_SESSION['RHYMIX']['last_refresh'] < time() - 10) { $_SESSION['RHYMIX']['admin_accessed'] = \RX_TIME; Rhymix\Framework\Session::refresh(true); diff --git a/common/framework/Session.php b/common/framework/Session.php index 1891a0e8b..4cf729897 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -446,7 +446,7 @@ class Session if ($refresh) { self::checkLoginStatusCookie(); - return self::refresh(true); + return $_SESSION['RHYMIX']['next_refresh'] = true; } else {