Achieving Telerik Remote Code Execution 100 Times Faster

22 min
I
May 18, 2022

Abstract‍

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. 

Introduction

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.

Hacking applications using Progress Telerik products

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.

The risk arising from CVE-2017-9248

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.

Slow and easily detectable public exploit

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.

Deep dive into CVE-2017-9248

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.

Figure 1: Dialog window creation based on URL parameters

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.

Table 1: Three possible error messages and their meaning

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. 

Figure 3: Different encrypted strings in the context of the chain of operations

Underlying assumptions for exploits

Before addressing the exploits, two underlying assumptions need to be clarified to ensure comparability between the exploits:

  • The team assumed that the secret key could consist of all 95 printable ASCII characters.
  • If no key is set by the user in the web.config, there are several fallback methods, which automatically generate a key with a length of 48. The team therefore assumed that the secret key has a default length of 48 characters.
  • To exploit the vulnerability, the exact version of Telerik UI is needed (e.g., 2017.3.913) and encrypted with the other parameters. For simplicity reasons, it was left out in the original study.

The researches analyzed 3 ways to exploit the vulnerability:

  1. Exploit 0: Trivial un-optimized Brute-Forcing
  2. Exploit 1: An Optimized First Workable Exploit
  3. Exploit 2: A Fully Optimized Exploit

Exploit 0: Trivial Brute-Forcing

The most trivial approach to brute-forcing the secret key consists of three nested loops: ‍

  1. Loop 1 loops over each of the 12 secret key blocks, consisting of 4 characters each (12).‍
  2. Loop 2 loops over each key character combination for each of these secret key blocks (95^4). ‍
  3. Loop 3 loops over all possible combinations for the plaintext (64^4) and encrypts each of these combinations with the key character combinations from loop 2.

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. 

Figure 4: Pseudo code of exploit 0

Exploit 1: A First Workable Exploit

The method SRLabs tried out at the very beginning consists of four main nested loops:

  1. Loop 1 loops over the total key length (48) to guess each key character individually.
  2. Loop 2 loops over all padding characters. Padding is necessary as base64 requires multiples of 4 as its input and the exploit code goes over each key character individually (e.g., when determining the second key character, two padding characters are needed). Padding (if correct) also helps to focus on one key character at a time.This means it helps triggering an error for a single character.
  3. Loop 3 loops over the key character set, which consists of all possible characters for the key.
  4. Loop 4 loops over the accuracy threshold, which consists of all possible characters that could appear in the (base64-encoded) plaintext (i.e., 64 characters). Within this last loop, the plaintext is created for which it is checked if the (so-far-guessed) key works. The assumption is that if the key is correct, any plaintext that is encrypt on the hackers side should be decryptable on the server side.

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

Figure 5: Pseudo code of exploit 1

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.

Exploit 2: The Fully Optimized Exploit

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

  1. Step 1: Choose three encrypted characters that grouped the key character set into three groups (“buckets”)
  2. Step 2: Determine all possible combinations of these encrypted characters and sorted them by likelihood
  3. Step 3: Use the sorted combinations of encrypted characters, to figure out which bucket the 4 characters of a key block belonged to
  4. Step 4: performed divide-and-conquer for each key character

Simplified example

  • Assume 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C,D, E, and F are the possible key characters
  • 0, 1, 2, 3, 4, 5, 6, 8, and 9 result in a positive error message when XOR-ed with the encrypted byte ‘0x??’ (simplified example, no specific byte is provided)
  • A, B, C, D, E, and F result in a negative error message
  • If ‘0x??’ is send to the server and returns a bad result, the possible keys characters are A, B, C, D, E, and F
  • In the next step an encrypted character is determined that returns a positive or negative result for half of the possible key characters
  • Continue divide-and-conquer until there is only one possible character
Step 1

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).

Table 2: Example XOR results

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).

Table 3: Key buckets and their detector bytes

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.

Step 2‍

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.

Table 4 Top 10 detector byte combinations sorted by their likelihoods
Step 3‍

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).

Figure 6: Decision tree for finding first valid combination of detector bytes
Step 4

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.

Key Takeaways

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:

  1. Understand the key concepts behind the problem and its implications: A very simple example is XOR. It is important not only to understand how it works, but also that a key character XOR-ed with 0x0 results in the same key character. This understanding allowed developing the concept around detector bytes in the first place.
  2. Think about how information can be generated before the exploitation: To start with as much information as possible and to reduce noisiness, it makes sense to think of potential simulations and how that could provide additional information for exploitation. In this case, the team determined how the target server would respond to specific input and made use of the knowledge about the behavior. This worked without sending a single request to the target server.
  3. Think about how information can be (re-)used during the exploitation: Optimization is essentially the clever use of available information. This means that all helpful information in answers from previous requests should flow into ensuing requests. instead of guessing paddings for each iteration again and again as done in exploit 1, the researchers only determined it once in the form of a working detector byte combination. This allowed them to remove one factor from the complexity calculation.
  4. Reduce the problem to its essence: While the other exploits loop over the key characters as well as the plaintext characters, encrypt both, and send them to the server, they directly used the encrypted strings as a start for finding the secret key. As a result, SRLabs was able to reduce the complexity of the problem.
  5. Decide on a suitable, algorithmic method: Broadly speaking, keys can be guessed through a procedure of exclusion or a procedure of confirmation. While exploit 1 attempted to get a confirmation for a guessed secret key, the optimized exploit tried to exclude wrong options of the secret key through divide-and-conquer. This requires fundamentals in data structures and algorithmics.