A cryptographic vulnerability in the development software Telerik UI that was discovered in 2017 has long been considered impractical to exploit. Until now. Research by Security Research Labs (SRLabs) shows that this impracticability was only due to the unoptimized nature of the publicly available exploit. By using optimization strategies the team was able to unveil a practical exploit that could allow remote code execution within infrastructure. Learn how the 2017 Telerik vulnerability was successfully exploited in a large corporation in 2021, the optimization techniques deployed, which can also be applied to similar cryptographic issues in other software, and how to protect your infrastructure.
This post provides an understanding of the vulnerability and the original exploit. With that in mind, a new, optimized version of the exploit is developed. Finally, the key takeaways in relation to exploit optimization are summed up.
During a Red Team engagement at a Fortune500 company, the team found one Internet-facing vulnerability that seemed promising at one of the company’s subsidiaries: A cryptographic weakness in Telerik UI (CVE-2017-9248). The problem was that the publicly available exploit was inefficient, making it impossible to gain access in a timely and undetectable manner.
But different optimization strategies allowed the team to increase the speed of the exploit by ~100 for the average case (and ~250 for the Security Research Labs case, i.e., ~78k/314, and ~39k for the worst-case). It allowed the Red Team to gain a foothold and eventually move on to the headquarters.
Progress Telerik develops user interface (UI) controls and components for .NET and JavaScript frameworks, to build engaging apps. Telerik products are used by over 275,000 customers, like Visa, Microsoft, and Samsung. Only one product (Telerik UI for ASP.NET AJAX) is affected by this specific vulnerability.
In 2017 a cryptographic vulnerability in one of the components of the Telerik product was identified. The vulnerable component (DialogHandler) allows invoking file browsers (Web UI Dialog Windows) and receives plaintext as well as sensitive encrypted parameters through a URL.Two of these encrypted parameters define, for example, if files can be uploaded, and which file extensions are allowed. If a hacker can guess the secret (or gain access to it), a file browser can be invoked allowing them to upload files, enabling attackers to compromise vulnerable websites by uploading .aspx web shells. Even though it was already discovered in 2017, the vulnerability can still be found in the wild, even in recent engagements.
The team quickly found the exploit, that in theory would allow them to access the the companies infrastructure. However, the team cancelled the execution after realizing that the exploit was too slow to break the key, and created a lot of noise (note: if the key character set would be limited, e.g., time was not a problem in the simulations). In order to create a practical exploit, the team dove deeper into the vulnerability trying to optimize it.
The DialogHandler.aspx component can be described as a function that receives plaintext and sensitive encrypted parameters (encoded in base64) to invoke different types of dialog windows. Hackers generally are interested in the encrypted parameters, as they allow enabling uploads and specifying valid file extensions. They are specified in the "dp" argument of the request and go through several conversions until the plaintext arguments are derived (see Figure 1).The goal of the exploit is to figure out the secret key that is stored on the server and used in an XOR operation to decrypt parameters.
Sending different parameters to the server result in different error messages (note: error messages shown in the blog are abbreviations). These error messages can be used to guess the secret key.
Table 1 shows the three possible error messages, which are used to guess the secret key, and where these error messages can occur in the server-side chain of operations. The “Index out of range” error message is referred to as the positive error message, which occurs in step 4 and implies that the guessed key is a potential candidate (it does not imply that the key is correct). “No valid base64” indicates that it is wrong. “String is too short” is only listed here for completeness: since the encoded strings sent to the server are base64-encoded, they will always be a multiple of 4.
Figure 2 shows some examples of encoded strings sent to the server and the error message they return. For example, the error message for request 7 shows that the encoded string ABCF could be successfully base64-decoded (1) and XOR-ed with the still unknown secret key on the server-side (2) and resulted in a valid base64 string (3). As mentioned before, it is essential to understand that this might have happened by accident and does not mean that our guessed key is indeed correct.
Figure 2: Different encrypted strings and corresponding error messages
Figure 3 gives more detail of the chain of operations on the server-side. It visualizes the exchange between sending the encrypted string to the server and receiving the response from it.
Before addressing the exploits, two underlying assumptions need to be clarified to ensure comparability between the exploits:
The researches analyzed 3 ways to exploit the vulnerability:
The most trivial approach to brute-forcing the secret key consists of three nested loops:
Once there is a secret key block that returns a positive error message for all possible combinations of the plaintext, the correct secret key block was found.
This approach can take extremely long as it does not make use of available information. E.g., the approach has no way to find out which key character caused the decryption to fail in the first place. Therefore, it could require up to 12 * 95^4 * 64^4 requests, which is astronomically high.
The method SRLabs tried out at the very beginning consists of four main nested loops:
If the plaintext does not work, a new key character is chosen through loop 3 for the same padding. Once the exploit has gone through all key characters, it will go through the same procedure in the next padding. With a throttling of one second, this could take up to 2.37 years or 48 (number of characters in key) * 256 (number of padding characters) * 95 (number of characters in key character set) * 64 (accuracy iterations) requests.
In reality, the worst case is slightly faster since it is harder to find a valid padding for the first case because the number of padding characters decreases within each block. To find the first key character of a block, 3 padding characters are needed; to determine the third key character only 1 padding character is needed. Therefore, there are different worst-case scenarios depending on the number of padding characters needed
Exploit 1 uses several optimization strategies. It focuses on one character at the time to reduce the possibility space for each iteration by using padding, pre-defines the possible character sets for the secret key (e.g., hexadecimal only), and uses a sorted list of plaintext characters. But it is still not fully optimized, as information about working paddings from loop 2 are not reused and withdrawn for every new key character iteration.
SRLabs' research approach, assuming a throttling of one second, resulted in a worst-case duration of 32 minutes. The theory behind it is: Unlike the other exploits which loop over each possible key character and try to confirm whether they are correct, the team sent probes that helped exclude sets of possible key characters. For each possible key character and each encrypted character they determined offline if they would result in positive or negative error messages. The process had four Steps
First the detector bytes are need, which divide the key characters into buckets. A detector byte of a bucket always leads to a valid base 64 character if XOR-ed with characters of this specific bucket. Table 2.1-4 show the results of XOR combinations of different example detector bytes and key characters. The green color indicates that the XOR result is a valid base 64 character (i.e., positive error message).
As the goal is to find a positive error message quickly (to start to divide-and-conquer), the first detector byte should cover as many characters of the key character set as possible. For all 95 printable ASCII characters, three bytes were determined, which divide the key characters into 3 buckets (Table 3).
Note: If a password includes the “=” sign (which is a valid base64 character), there is a little trick to be made, which is not be covered here.
In the next step all possible combinations of detector bytes must be determined. Ordering them by likelihood minimizes the number of requests needed to find out the first valid combination. It needs to be ensured that each position of the 4 characters is first checked with the byte of the largest bucket (i.e., 0x0) and then gradually with bytes of smaller buckets (i.e., 0x6B and 0x8). The logic is as follows: if 0x0 raises a negative error message, the possible key characters are in the buckets covered by 0x6B or 0x8. It 0x6B then results in a negative error message, 0x8 should still be sent to verify the assumption that the key consists of printable ASCII characters. Table 4 shows the ordered detector bytes by likelihood.
Combinations of detector bytes determined and ordered in step 2 from the top onwards were sent to find valid buckets for each of the 4 characters that is being disclosed (e.g., {0x0, 0x0, 0x0, 0x0},{0x0, 0x0, 0x0, 0x6b}). Because each response of the server is considered for the next request, the team called this a decision tree and illustrated it in Figure 7. For example if a positive response was received in the third request (0x0 0x0 0x6B 0x0), they knew that the 1st, 2nd, and 4th character belong to the first bucket (all base64characters). The 3rd character belongs to the second bucket (seeFigure 7).
At this stage, the researchers had a valid detector byte combination for one block of the secret key (e.g., {0x6B,0x0, 0x0, 0x0}). They determined a divide-and-conquer byte for the first detector byte (i.e., 0x6B), for which half of the possible key characters from the bucket will return a positive error message and the other half a negative one.
A positive error message means that key character belongs to the first half, a negative error message means that key character belongs to the second half.
This will again create a decision tree analogous to the detector byte decision tree (Figure 7). This process is repeated until the key character is unambiguously identified. Then the entire process is repeated for the second detector byte (i.e., 0x0) with a different bucket.
In the worst-case scenario this would take 32 minutes (with a throttling of one second) based on 12 (number of blocks) * (81 (detector byte combinations) + 20 (max. tree depth per characters) * 4 (number of characters per block)) requests.
This research shows how the initially slow public exploit could be made practical by optimizing it. The SRLabs researchers summarized some optimization strategies they applied, that can also be used when developing other exploits: