From 647f6f8ce817cef2892e6387d91dcd99b9d4ee12 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 21 Mar 2026 18:33:29 +0200 Subject: [PATCH] check alternative ip notatino --- .../Espo/Core/Utils/Security/HostCheck.php | 62 ++++++++++++++++++ .../Core/Utils/Security/HostCheckTest.php | 65 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/unit/Espo/Core/Utils/Security/HostCheckTest.php diff --git a/application/Espo/Core/Utils/Security/HostCheck.php b/application/Espo/Core/Utils/Security/HostCheck.php index 0bbc8f3677..6297293300 100644 --- a/application/Espo/Core/Utils/Security/HostCheck.php +++ b/application/Espo/Core/Utils/Security/HostCheck.php @@ -50,6 +50,12 @@ class HostCheck return $this->ipAddressIsNotInternal($host); } + $normalized = $this->normalizeIpAddress($host); + + if ($normalized !== false && filter_var($normalized, FILTER_VALIDATE_IP)) { + return false; + } + if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { return false; } @@ -93,4 +99,60 @@ class HostCheck { return $this->isHostAndNotInternal($host); } + + private function normalizeIpAddress(string $ip): string|false + { + if (!str_contains($ip, '.')) { + return self::normalizePart($ip); + } + + $parts = explode('.', $ip); + + if (count($parts) !== 4) { + return false; + } + + $result = []; + + foreach ($parts as $part) { + if (preg_match('/^0x[0-9a-f]+$/i', $part)) { + $num = hexdec($part); + } else if (preg_match('/^0[0-7]+$/', $part) && $part !== '0') { + $num = octdec($part); + } else if (ctype_digit($part)) { + $num = (int)$part; + } else { + return false; + } + + if ($num < 0 || $num > 255) { + return false; + } + + $result[] = $num; + } + + return implode('.', $result); + } + + private static function normalizePart(string $ip): string|false + { + if (preg_match('/^0x[0-9a-f]+$/i', $ip)) { + $num = hexdec($ip); + } elseif (preg_match('/^0[0-7]+$/', $ip) && $ip !== '0') { + $num = octdec($ip); + } elseif (ctype_digit($ip)) { + $num = (int) $ip; + } else { + return false; + } + + if ($num < 0 || $num > 0xFFFFFFFF) { + return false; + } + + $num = (int) $num; + + return long2ip($num); + } } diff --git a/tests/unit/Espo/Core/Utils/Security/HostCheckTest.php b/tests/unit/Espo/Core/Utils/Security/HostCheckTest.php new file mode 100644 index 0000000000..7ebd448b67 --- /dev/null +++ b/tests/unit/Espo/Core/Utils/Security/HostCheckTest.php @@ -0,0 +1,65 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace tests\unit\Espo\Core\Utils\Security; + +use Espo\Core\Utils\Security\HostCheck; +use PHPUnit\Framework\TestCase; + +class HostCheckTest extends TestCase +{ + public function testIsHostAndNotInternal(): void + { + $hostCheck = new HostCheck(); + + $this->assertTrue( + $hostCheck->isHostAndNotInternal('200.1.1.1') + ); + + $this->assertFalse( + $hostCheck->isHostAndNotInternal('172.20.0.1') + ); + + $this->assertFalse( + $hostCheck->isHostAndNotInternal('0177.0.0.1') + ); + + /*$this->assertFalse( + $hostCheck->isHostAndNotInternal('127.1') + ); + + $this->assertFalse( + $hostCheck->isHostAndNotInternal('127.0.1') + );*/ + + $this->assertFalse( + $hostCheck->isHostAndNotInternal('0x7f000001') + ); + } +}