diff --git a/app/code/core/Mage/Adminhtml/Helper/Data.php b/app/code/core/Mage/Adminhtml/Helper/Data.php
index 22e973f3c6a..6b1a9387b2b 100644
--- a/app/code/core/Mage/Adminhtml/Helper/Data.php
+++ b/app/code/core/Mage/Adminhtml/Helper/Data.php
@@ -16,6 +16,7 @@ class Mage_Adminhtml_Helper_Data extends Mage_Adminhtml_Helper_Help_Mapping
{
public const XML_PATH_ADMINHTML_ROUTER_FRONTNAME = 'admin/routers/adminhtml/args/frontName';
public const XML_PATH_USE_CUSTOM_ADMIN_URL = 'default/admin/url/use_custom';
+ public const XML_PATH_CUSTOM_ADMIN_URL = 'default/admin/url/custom';
public const XML_PATH_USE_CUSTOM_ADMIN_PATH = 'default/admin/url/use_custom_path';
public const XML_PATH_CUSTOM_ADMIN_PATH = 'default/admin/url/custom_path';
public const XML_PATH_ADMINHTML_SECURITY_USE_FORM_KEY = 'admin/security/use_form_key';
@@ -78,6 +79,20 @@ public static function getUrl($route = '', $params = [])
return Mage::getModel('adminhtml/url')->getUrl($route, $params);
}
+ /**
+ * Get custom admin URL (validated and normalized when saved via admin panel)
+ */
+ public static function getCustomAdminUrl(): string
+ {
+ $config = Mage::getConfig();
+
+ // Check if use custom admin URL is enabled
+ $useCustom = (int) $config->getNode(self::XML_PATH_USE_CUSTOM_ADMIN_URL);
+ return $useCustom
+ ? (string) $config->getNode(self::XML_PATH_CUSTOM_ADMIN_URL)
+ : '';
+ }
+
/**
* @return false|int
*/
diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php
index 1ac8007aa6a..780fc624f0b 100644
--- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php
+++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php
@@ -23,19 +23,71 @@ class Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom extends Mage_Core_
public const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url';
/**
- * Validate value before save
+ * Validate custom admin URL before save
*
* @return $this
+ * @throws Mage_Core_Exception
*/
protected function _beforeSave()
{
- $value = $this->getValue();
+ $value = trim($this->getValue());
+
+ // Empty is allowed (disabled custom admin URL)
+ if (empty($value)) {
+ $this->setValue($value);
+ return $this;
+ }
+
+ // Whitelist: only allow valid base URL characters
+ // Valid for base URL: letters, numbers, and URL-safe characters (: / . - _ [ ])
+ if (!preg_match('/^[a-zA-Z0-9:\/.\-_\[\]]+$/', $value)) {
+ Mage::throwException(
+ Mage::helper('adminhtml')->__('Custom Admin URL contains invalid characters.'),
+ );
+ }
+
+ // Parse the URL
+ $urlParts = parse_url($value);
+
+ if ($urlParts === false) {
+ Mage::throwException(
+ Mage::helper('adminhtml')->__('Invalid Custom Admin URL format.'),
+ );
+ }
+
+ // Must have protocol
+ if (!isset($urlParts['scheme'])) {
+ Mage::throwException(
+ Mage::helper('adminhtml')->__('Custom Admin URL must include protocol (http:// or https://).'),
+ );
+ }
+
+ // Only allow http and https
+ if (!in_array($urlParts['scheme'], ['http', 'https'])) {
+ Mage::throwException(
+ Mage::helper('adminhtml')->__('Custom Admin URL must use http:// or https:// protocol.'),
+ );
+ }
+
+ // Must have hostname
+ if (!isset($urlParts['host']) || empty($urlParts['host'])) {
+ Mage::throwException(
+ Mage::helper('adminhtml')->__('Custom Admin URL must include a hostname.'),
+ );
+ }
- if (!empty($value) && !str_ends_with($value, '}}')) {
- $value = rtrim($value, '/') . '/';
+ // Ensure trailing slash
+ if (!str_ends_with($value, '/')) {
+ $value .= '/';
}
$this->setValue($value);
+
+ // Set redirect flag if custom admin URL changed
+ if ($this->getOldValue() != $value) {
+ Mage::register('custom_admin_path_redirect', true, true);
+ }
+
return $this;
}
@@ -49,24 +101,24 @@ public function _afterSave()
$useCustomUrl = $this->getData('groups/url/fields/use_custom/value');
$value = $this->getValue();
- if ($useCustomUrl == 1 && empty($value)) {
+ // If use_custom is disabled OR value is empty, just save the value (don't update base URLs)
+ if ($useCustomUrl != 1 || empty($value)) {
return $this;
}
- if ($useCustomUrl == 1) {
- Mage::getConfig()->saveConfig(
- self::XML_PATH_SECURE_BASE_URL,
- $value,
- self::CONFIG_SCOPE,
- self::CONFIG_SCOPE_ID,
- );
- Mage::getConfig()->saveConfig(
- self::XML_PATH_UNSECURE_BASE_URL,
- $value,
- self::CONFIG_SCOPE,
- self::CONFIG_SCOPE_ID,
- );
- }
+ // If use_custom is enabled AND value is not empty, update base URLs
+ Mage::getConfig()->saveConfig(
+ self::XML_PATH_SECURE_BASE_URL,
+ $value,
+ self::CONFIG_SCOPE,
+ self::CONFIG_SCOPE_ID,
+ );
+ Mage::getConfig()->saveConfig(
+ self::XML_PATH_UNSECURE_BASE_URL,
+ $value,
+ self::CONFIG_SCOPE,
+ self::CONFIG_SCOPE_ID,
+ );
return $this;
}
diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php
index 9d43ad121f1..641e1af4f50 100644
--- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php
+++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php
@@ -54,6 +54,11 @@ protected function _afterSave()
);
}
+ // Set redirect flag if use custom admin URL changed
+ if ($this->getOldValue() != $value) {
+ Mage::register('custom_admin_path_redirect', true, true);
+ }
+
return $this;
}
}
diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
index 2a1cda98a61..e3aa3a8baec 100644
--- a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
+++ b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
@@ -36,12 +36,33 @@ protected function _getDefaultPath()
}
/**
- * dummy call to pass through checking
+ * Validate admin domain before routing
*
* @return bool
*/
protected function _beforeModuleMatch()
{
+ // Check if custom admin domain is configured
+ if ($adminUrl = Mage_Adminhtml_Helper_Data::getCustomAdminUrl()) {
+ $adminHost = parse_url($adminUrl, PHP_URL_HOST);
+ if (!$adminHost) {
+ // Should never happen - URL is validated when saved
+ // If it does, fail secure (possible database corruption/bypass)
+ Mage::log(
+ "Unable to parse custom admin URL host: {$adminUrl}. Access denied for security.",
+ Zend_Log::ERR,
+ 'system.log',
+ );
+ return false;
+ }
+
+ $currentHost = $this->getFront()->getRequest()->getHttpHost();
+ // Strip port for comparison (getHttpHost may include port)
+ $currentHost = preg_replace('/:\d+$/', '', $currentHost);
+
+ return strtolower($adminHost) === strtolower($currentHost);
+ }
+
return true;
}
diff --git a/app/locale/en_US/Mage_Adminhtml.csv b/app/locale/en_US/Mage_Adminhtml.csv
index dfe7fdfee1a..d30916eab80 100644
--- a/app/locale/en_US/Mage_Adminhtml.csv
+++ b/app/locale/en_US/Mage_Adminhtml.csv
@@ -281,6 +281,10 @@
"Current Configuration Scope:","Current Configuration Scope:"
"Current Month","Current Month"
"Custom","Custom"
+"Custom Admin URL contains invalid characters.","Custom Admin URL contains invalid characters."
+"Custom Admin URL must include a hostname.","Custom Admin URL must include a hostname."
+"Custom Admin URL must include protocol (http:// or https://).","Custom Admin URL must include protocol (http:// or https://)."
+"Custom Admin URL must use http:// or https:// protocol.","Custom Admin URL must use http:// or https:// protocol."
"Custom Variable ""%s""","Custom Variable ""%s"""
"Custom Variables","Custom Variables"
"Customer","Customer"
@@ -484,6 +488,7 @@
"Insert Variable...","Insert Variable..."
"Interactive","Interactive"
"Interface Locale: %s","Interface Locale: %s"
+"Invalid Custom Admin URL format.","Invalid Custom Admin URL format."
"Invalid Form Key. Please refresh the page.","Invalid Form Key. Please refresh the page."
"Invalid Import Service Specified","Invalid Import Service Specified"
"Invalid POST data (please check post_max_size and upload_max_filesize settings in your php.ini file).","Invalid POST data (please check post_max_size and upload_max_filesize settings in your php.ini file)."
diff --git a/errors/processor.php b/errors/processor.php
index 020d8517cb0..600b7632213 100644
--- a/errors/processor.php
+++ b/errors/processor.php
@@ -489,9 +489,7 @@ protected function _validate(): bool
*/
protected function _setSkin(string $value, ?stdClass $config = null)
{
- if (preg_match('/^[a-z0-9_]+$/i', $value)
- && is_dir($this->_indexDir . self::ERROR_DIR . '/' . $value)
- ) {
+ if (preg_match('/^[a-z0-9_]+$/i', $value) && is_dir($this->_errorDir . $value)) {
if (!$config && $this->_config) {
$config = $this->_config;
}