CVE-2026-43634
Description
HestiaCP versions 1.2.0 through 1.9.4 contain an IP spoofing vulnerability that allows unauthenticated remote attackers to bypass authentication security controls by supplying an arbitrary IP address in the CF-Connecting-IP HTTP header without verifying the request originated from Cloudflare's network. Attackers can exploit this to circumvent fail2ban brute-force protection, bypass per-user IP allowlists, and poison authentication audit logs by spoofing trusted IP addresses on each request.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
HestiaCP 1.2.0-1.9.4 trusts the CF-Connecting-IP header without verification, allowing unauthenticated IP spoofing to bypass security controls.
Vulnerability
HestiaCP versions 1.2.0 through 1.9.4 trust the CF-Connecting-IP HTTP header without verifying that the request originated from Cloudflare's network [1]. This allows an unauthenticated remote attacker to supply an arbitrary IP address in that header, which is then used for authentication logging, fail2ban checks, and per-user IP allowlists [1][2]. The code in web/inc/main.php directly used $_SERVER["HTTP_CF_CONNECTING_IP"] without validation [2].
Exploitation
An attacker can send HTTP requests to the HestiaCP web interface (port 8083) with a crafted CF-Connecting-IP header set to any IP address [1]. No authentication is required. The attacker can spoof a trusted IP (e.g., an administrator's IP) to bypass fail2ban brute-force protection and per-user IP allowlists [1]. The spoofed IP is recorded in authentication audit logs, masking the attacker's real address [1].
Impact
Successful exploitation allows the attacker to circumvent authentication security controls: fail2ban will not block the attacker's real IP, IP allowlists are bypassed, and audit logs are poisoned with false IP addresses [1]. This can facilitate further attacks, such as brute-force password guessing or combined with other vulnerabilities (e.g., CVE-2026-43633) for remote code execution [1]. The attacker's real IP remains hidden from logs and fail2ban.
Mitigation
The fix has been merged into the main branch in commit f381e294500f671cf12716c638afd0bfde901f88, which introduces a get_real_user_ip() function that validates the CF-Connecting-IP header using the cloudflare-ip-validator library [2][4]. As of the publication date (2026-05-19), no official release containing the fix has been published [1]. Until a patched version is released, administrators should disable the web terminal (v-delete-sys-web-terminal) and restrict access to port 8083 to trusted IPs at the firewall level [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1f381e294500fStop trusting unauthenticated proxy headers (#5273)
6 files changed · +50 −88
web/delete/log/auth/index.php+1 −6 modified@@ -17,12 +17,7 @@ check_return_code($return_var, $output); unset($output); -$ip = $_SERVER["REMOTE_ADDR"]; -if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { - if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) { - $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; - } -} +$ip = get_real_user_ip(); $v_ip = quoteshellarg($ip); $user_agent = $_SERVER["HTTP_USER_AGENT"]; $v_user_agent = quoteshellarg($user_agent);
web/inc/composer.json+2 −1 modified@@ -2,6 +2,7 @@ "require": { "phpmailer/phpmailer": "7.0.2", "hestiacp/phpquoteshellarg": "1.1.0", - "robthree/twofactorauth": "3.0.3" + "robthree/twofactorauth": "3.0.3", + "divinity76/cloudflare-ip-validator": "1.0.0" } }
web/inc/composer.lock+38 −1 modified@@ -4,8 +4,45 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "086d256282e0f8f84760be89c386abb2", + "content-hash": "d75dd082b587a517bf30598cfbcefb90", "packages": [ + { + "name": "divinity76/cloudflare-ip-validator", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/divinity76/cloudflare-ip-validator.git", + "reference": "557a95ef8db59192a6688a30316440e73403644f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/divinity76/cloudflare-ip-validator/zipball/557a95ef8db59192a6688a30316440e73403644f", + "reference": "557a95ef8db59192a6688a30316440e73403644f", + "shasum": "" + }, + "require": { + "php": ">=8.0" + }, + "suggest": { + "ext-filter": "Used for additional input validation in your application stack" + }, + "type": "library", + "autoload": { + "psr-4": { + "Divinity76\\CloudflareIpValidator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Unlicense" + ], + "description": "Fast Cloudflare IP validator for PHP", + "support": { + "issues": "https://github.com/divinity76/cloudflare-ip-validator/issues", + "source": "https://github.com/divinity76/cloudflare-ip-validator/tree/v1.0.0" + }, + "time": "2026-03-23T21:11:01+00:00" + }, { "name": "hestiacp/phpquoteshellarg", "version": "v1.1.0",
web/inc/helpers.php+7 −39 modified@@ -1,4 +1,5 @@ <?php +use Divinity76\CloudflareIpValidator\CloudflareIpValidator; use function Hestiacp\quoteshellarg\quoteshellarg; # Return codes @@ -84,7 +85,7 @@ function exit_code_to_http_code(int $exit_code, int $default = 400): int { } function check_local_ip($addr) { - if (in_array($addr, [$_SERVER["SERVER_ADDR"], "127.0.0.1"])) { + if (in_array($addr, [$_SERVER["SERVER_ADDR"], "127.0.0.1"], true)) { return true; } else { return false; @@ -93,46 +94,13 @@ function check_local_ip($addr) { function get_real_user_ip() { $ip = $_SERVER["REMOTE_ADDR"]; - if (isset($_SERVER["HTTP_CLIENT_IP"]) && !check_local_ip($_SERVER["HTTP_CLIENT_IP"])) { - if (filter_var($_SERVER["HTTP_CLIENT_IP"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_CLIENT_IP"]; - } - } - - if ( - isset($_SERVER["HTTP_X_FORWARDED_FOR"]) && - !check_local_ip($_SERVER["HTTP_X_FORWARDED_FOR"]) - ) { - if (filter_var($_SERVER["HTTP_X_FORWARDED_FOR"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_X_FORWARDED_FOR"]; - } - } - - if (isset($_SERVER["HTTP_FORWARDED_FOR"]) && !check_local_ip($_SERVER["HTTP_FORWARDED_FOR"])) { - if (filter_var($_SERVER["HTTP_FORWARDED_FOR"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_FORWARDED_FOR"]; - } - } - - if (isset($_SERVER["HTTP_X_FORWARDED"]) && !check_local_ip($_SERVER["HTTP_X_FORWARDED"])) { - if (filter_var($_SERVER["HTTP_X_FORWARDED"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_X_FORWARDED"]; - } - } - - if (isset($_SERVER["HTTP_FORWARDED"]) && !check_local_ip($_SERVER["HTTP_FORWARDED"])) { - if (filter_var($_SERVER["HTTP_FORWARDED"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_FORWARDED"]; - } - } - if ( - isset($_SERVER["HTTP_CF_CONNECTING_IP"]) && - !check_local_ip($_SERVER["HTTP_CF_CONNECTING_IP"]) + !empty($_SERVER["HTTP_CF_CONNECTING_IP"]) && + filter_var($_SERVER["HTTP_CF_CONNECTING_IP"], FILTER_VALIDATE_IP) && + filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP) && + CloudflareIpValidator::isCloudflareIp($_SERVER["REMOTE_ADDR"]) ) { - if (filter_var($_SERVER["HTTP_CF_CONNECTING_IP"], FILTER_VALIDATE_IP)) { - $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; - } + $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; } // Handling IPv4-mapped IPv6 address
web/inc/main.php+1 −24 modified@@ -37,30 +37,7 @@ function destroy_sessions() { $i = 0; // Saving user IPs to the session for preventing session hijacking -$user_combined_ip = ""; -if (isset($_SERVER["REMOTE_ADDR"])) { - $user_combined_ip = $_SERVER["REMOTE_ADDR"]; -} -if (isset($_SERVER["HTTP_CLIENT_IP"])) { - $user_combined_ip .= "|" . $_SERVER["HTTP_CLIENT_IP"]; -} -if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) { - $user_combined_ip .= "|" . $_SERVER["HTTP_X_FORWARDED_FOR"]; -} -if (isset($_SERVER["HTTP_FORWARDED_FOR"])) { - $user_combined_ip .= "|" . $_SERVER["HTTP_FORWARDED_FOR"]; -} -if (isset($_SERVER["HTTP_X_FORWARDED"])) { - $user_combined_ip .= "|" . $_SERVER["HTTP_X_FORWARDED"]; -} -if (isset($_SERVER["HTTP_FORWARDED"])) { - $user_combined_ip .= "|" . $_SERVER["HTTP_FORWARDED"]; -} -if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { - if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) { - $user_combined_ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; - } -} +$user_combined_ip = get_real_user_ip(); if (!isset($_SESSION["user_combined_ip"])) { $_SESSION["user_combined_ip"] = $user_combined_ip;
web/login/index.php+1 −17 modified@@ -113,25 +113,9 @@ function authenticate_user($user, $password, $twofa = "") { unset($_SESSION["login"]); if (verify_csrf($_POST, true)) { $v_user = quoteshellarg($user); - $ip = $_SERVER["REMOTE_ADDR"]; + $ip = get_real_user_ip(); $user_agent = $_SERVER["HTTP_USER_AGENT"]; - if ( - !empty($_SERVER["HTTP_CF_CONNECTING_IP"]) && - filter_var( - $_SERVER["HTTP_CF_CONNECTING_IP"], - FILTER_VALIDATE_IP, - FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6, - ) - ) { - $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; - } - - // Handling IPv4-mapped IPv6 address - if (strpos($ip, ":") === 0 && strpos($ip, ".") > 0) { - $ip = substr($ip, strrpos($ip, ":") + 1); // Strip IPv4 Compatibility notation - } - $v_ip = quoteshellarg($ip); $v_user_agent = quoteshellarg($user_agent);
Vulnerability mechanics
Root cause
"The `get_real_user_ip()` function unconditionally trusted the `CF-Connecting-IP` HTTP header without verifying that the request actually originated from Cloudflare's IP ranges."
Attack vector
An unauthenticated attacker sends HTTP requests to the HestiaCP web interface with a spoofed `CF-Connecting-IP` header containing an arbitrary IP address. Because the original code in `get_real_user_ip()` (in `web/inc/helpers.php`) accepted this header without validating that `$_SERVER["REMOTE_ADDR"]` belonged to Cloudflare, the attacker can impersonate any trusted IP. This bypasses fail2ban rate-limiting (which tracks the spoofed IP), circumvents per-user IP allowlists, and injects false IPs into authentication audit logs [patch_id=624071]. No authentication or prior access is required; the attack is launched over the network against any exposed HestiaCP login or API endpoint.
Affected code
The vulnerability resides in the `get_real_user_ip()` function in `web/inc/helpers.php`, which previously accepted `HTTP_CF_CONNECTING_IP` without verifying the origin. The same flawed logic was duplicated in `web/inc/main.php`, `web/login/index.php`, and `web/delete/log/auth/index.php`, all of which directly read `HTTP_CF_CONNECTING_IP` without any Cloudflare IP validation [patch_id=624071].
What the fix does
The patch introduces the `divinity76/cloudflare-ip-validator` library and adds a call to `CloudflareIpValidator::isCloudflareIp($_SERVER["REMOTE_ADDR"])` inside `get_real_user_ip()` [patch_id=624071]. Now the `CF-Connecting-IP` header is only accepted when the connecting IP (`REMOTE_ADDR`) is confirmed to be a legitimate Cloudflare IP address. Additionally, the patch consolidates all IP-resolution logic into the single `get_real_user_ip()` function and removes the duplicated, ad-hoc header parsing that existed in `main.php`, `login/index.php`, and `delete/log/auth/index.php`, ensuring consistent enforcement of the Cloudflare-origin check across the entire application.
Preconditions
- networkThe HestiaCP web interface must be reachable over the network (no prior authentication needed).
- inputThe attacker can craft arbitrary HTTP headers, specifically the CF-Connecting-IP header.
Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/hestiacp/hestiacp/commit/f381e294500f671cf12716c638afd0bfde901f88nvd
- github.com/hestiacp/hestiacp/issues/5229nvd
- github.com/hestiacp/hestiacp/pull/5273nvd
- mercuryiss.com.au/hestiacp-unauthenticated-rce-ip-spoofing-cve-2026-43633-cve-2026-43634nvd
- www.vulncheck.com/advisories/hestiacp-ip-spoofing-via-cf-connecting-ip-headernvd
News mentions
0No linked articles in our index yet.