Remote Identification Of Kerio Control Systems
This step itself is not based on a vulnerability, however, it is a mandatory step during the overall attack. First, the attacker tricks a victim (from the internal network of the attacked company) to visit his malicious website. The website then performs the complete attack. For this the website must know the internal IP address of Kerio Control. Therefore, the first action is to somehow identify and obtain this IP address.
This can easily be done by adding images on the website which point to an image stored on Kerio Control. The perfect target for this is the logo stored at <KerioIP>:4081/nonauth/gfx/kerio_logo.gif. The attacker can add JavaScript event listeners for onload and onerror on his evil website to detect if the image was loaded or not. If the image was loaded, the attacker knows that the scanned IP address hosts Kerio Control.
A simple JavaScript scan script is shown below:
for (i = 1; i < 255; i++) {
ip_to_check = “192.168.20.” + i.toString();
my_image = document.createElement("img");
my_image.src = "https://" + ip_to_check +
":4081/nonauth/gfx/kerio_logo.gif";
my_image.onerror=kerio_not_alive; // callback
my_image.onload=kerio_alive; // callback
document.getElementById("invisible_area").appendChild(my_image);
}
For such an attack the attacker must know the internal IP range. This information can be obtained via different techniques (e.g. WebRTC leak, e-mail headers, misconfigured DNS server, information disclosure vulnerabilities on the website, social engineering) or can just be brute-forced (but this would add an additional delay to the overall attack).
Session Verification
For the first attack scenario at least a session as standard user is required (the second attack scenario requires that the victim is an administrator). The reason for this is that the vulnerability resides in code which can only be executed by authenticated users. Three situations can occur:
- The attacked victim is currently logged in as standard user (low privileges).
- The attacked victim is currently logged in as administrator (high privileges).
- The attacked victim is currently not logged in (no session).
Situation one and two are perfectly valid and can be used to obtain a reverse shell. Situation three is a problem because a valid session is mandatory for the attack. In such a case the attacker can start a remote brute-force attack via CSRF to identify weak credentials. This is even possible if no ports are open from the Internet because the complete attack is conducted via the web browser of the victim and the victim is connected to the internal network.
For such a brute-force attack the attacker needs a way to identify if the last tested credentials are valid or not. This is typically not possible because the attacker can’t read the response of a request which was sent to another domain / internal IP address (because of SOP – Same-Origin-Policy). For example, the attacker can send a valid login request to Kerio Control (because the login is not protected against CSRF), however, he can’t read the response and therefore doesn’t know if the credentials were valid or not.
One solution is to just assume that the credentials are valid and continue the exploit. If the exploit works, the credentials were valid. However, with this approach the complete attack would take too long and the victim may close the website before the attack finished.
A better solution is to use a side channel which bypasses SOP, namely image loading combined with JavaScript event handlers. This is possible because the URL to the user image only returns a valid image if the user is logged in. That means that the attacker can send a login request and after that embed an image pointing to <KerioIP>:4081/internal/photo with JavaScript event handlers installed for onloadand onerror. If the onerrorevent handler triggers, the credentials were wrong, if the onloadevent handler triggers, the credentials were valid.
The following HTML code demonstrates this:
<img src=”https://<Kerio-IP>:4081/internal/photo” onerror=not_logged_in(); onload=logged_in();></img>
Attack Vector One – Bypass Of CSRF Protections
The next step is to trigger the vulnerabilities within Kerio Control. For this two vulnerable scripts on the system will be exploited. The first script sets the unserialize input (we are going to abuse the PHP unserializefunction) and the second script actually invokes unserialize on the malicious input. Both scripts are stored inside the authenticated area which means that the scripts are protected by a CSRF protection. The CSRF protection should ensure that a remote attacker can’t force an action in the context of the attacked user session. In the case of the login request it was trivial because the login was just not protected by a CSRF protection.
In the case of the two scripts the CSRF protections must be bypassed, but it turns out, that this is trivial as well. The most common method is to use a XSS vulnerability, but for the first attack vector we will use another technique (the XSS technique will be described when discussing the second attack vector). Now we are going to directly exploit the vulnerable PHP code of Kerio Control.
The following PHP code can be found in the first vulnerable script:
1: $p_session->getCsrfToken(&$p_securityHash);
2: $p_postedHash = $_GET[‘k_securityHash’] || $_POST[‘k_securityHash’];
3: if (” == $p_postedHash || ($p_postedHash != $p_securityHash)) {
4: exit();
5: }
The problem with the above code is, that the code is PHP code. Luckily for us, sadly for the programmer, the code is not JavaScript code which means that it has a completely different behavior than the programmer may have intended (except if it was deliberately backdoored). At line 2 the programmer wants to get either the GET or POST parameter k_securityHash, but || is a logical operator in PHP. The result is therefore either the boolean value trueor false. At line 3 the programmer compares this result against the correct security hash (p_securityHash). The comparison is done by != , however, in PHP the operator != applies type juggling first (loose comparison). Correct would be a strict comparison via !==.
If we set k_securityHash to any value (either in GET or POST), we compare the boolean value truewith a string. PHP will then interpret the string as boolean which is always trueand then conclude that trueequals truewhich means the check will always be bypassed if we set k_securityHashto any value.
So the first CSRF protection bypass was trivial. Let’s now focus on the CSRF protection in the second script:
1: $p_session->getCsrfToken(&$p_securityHash);
2: $p_hash = $_GET[‘k_securityHash’];
3: …
4: if (!$p_session || (” == $p_hash && $p_hash != $p_securityHash)) {
5: $p_page = new p_Page();
6: $p_page->p_jsCode(‘window.top.location = “index.php”;’);
7: $p_page->p_showPageCode();
8: die();
9: }
Now line 2 doesn’t contain the problem with the logical operator, however, line 4 contains the problem with loose comparison again. But there is also another detail, which differs…
The first code used || as a condition link, whereas this time the code uses &&. The condition can only become trueif $p_hash (the GET parameter) is empty and the GET hash does not equal the correct hash. That means that it’s again enough to set k_securityHash to any value because then $p_hashis not empty.
Both CSRF check bypasses make the exploit working from the Internet to obtain reverse shells into the attacked company network.
Attack Vector One – The Exploitation
During analysis of Kerio Control we identified that Kerio uses a 6-year-old PHP binary (version 5.2.13). This version contains a countless number of publicly known vulnerabilities, including many memory corruptions leading to full code execution. To demonstrate the issue, we picked a commonly attacked PHP function – unserialize– to exploit Kerio Control. Unserialize can be called on user control input, however, the code is only available in the authenticated area (therefore the session verification step is required).
To exploit the function, we used CVE-2014-3515. The flaw exists because unserialize parses fields from the input, stores references to these fields, but doesn’t increase the reference count of these fields. As soon as the reference count from a field (or better a ZVAL, the internal data structure used to describe all kind of variables in PHP like integers, strings, objects or arrays) becomes zero, the ZVAL gets freed. Nearly all memory corruptions in unserialize work like this, only the method to decrement the reference count to zero (free the data) changes. In CVE-2014-3515 SplObjectStorage objects are used for this. When unserializing a SplObjectStorage object, a specific code path can be taken, which frees fields from the SplObjectStorage. However, the unserialize code added references to these fields (without incrementing the reference counter) and can therefore be used to access the freed data (the references became dangling pointers).
Let’s see this in action: