Creating a File Relay (proxy) using ASP.Net - Part 1 - Serving files.

In this series of posts I will walk you through implementing an ASP.Net based file relay mechanism using aspx pages. This will allow you to serve files hosted in the back-office (behind a firewall) from a frontend webserver.

To implement this solution we need to build two separate webpages, one for the backend server and one for the frontend server.

  • GetFile.aspx – The backend page which will surface the requested file to the frontend server.
  • RelayFile.aspx – The frontend page which will forward (relay/proxy) the request to the backend server.

In this first part I will show you how to send a file using an aspx page.

Implementing GetFile.aspx

The first thing we need to do is create the new aspx page and name it GetFile.aspx.

The Page Markup

Now let’s go ahead and remove all the content from the GetFile.aspx page, except for the Page directive; The purpose of this page is to return to contents of another file, so all the html markup is superfluous.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GetFile.aspx.cs" Inherits="Backend.GetFile" %>

The Code Behind

With the markup out of the way, let’s focus on the actual code behind the page. The only method we will need to implement is Page_Load.

1. Ensure a clean response – We don’t want anything from the aspx processing pipeline to interfere with the result

Response.ClearHeaders();
Response.ClearContent();

2. Determine the target file – In this example the filepath will be passed with the querystring.

string filePath = (Request.QueryString["filePath"] ?? "").Trim();

3. Determine and set the response type – This will allow the browser to display the file correctly. Also, make sure you only serve expected file types! This feature circumvents the basic webserver file security mechanisms, avoid leaking your secrets to the world!

string contentType = null;
string extension = Path.GetExtension(filePath).ToLowerInvariant();
switch (extension)
{
   case ".csv":
      contentType = "text/csv";
      break;
   case ".mp4":
      contentType = "video/mp4";
      break;
   case ".pdf":
      contentType = "application/pdf";
      break;
   default:
      // Unsupported filetype
      // There are serious security implications with
      // serving up files from a webserver. Make sure
      // you do not open yourself up to an attack!
      throw new HttpException(403, "Forbidden."); 
      // Use an empty ContentType if the type is unknown
      //contentType = "";
      //break;
}
   
//Set the ContentType.
Response.ContentType = contentType;

4. Set the response content length so the client knows how much data to expect.

var fileInfo = new FileInfo(filePath);
Response.AddHeader("Content-Length", fileInfo.Length.ToString());

5. Write the file contents to the response stream.

Response.WriteFile(filePath);

6. Flush the output buffers and end the response.

Response.End();

 

It’s a Wrap!

These 6 simple steps (7 if you count the page markup change) are all you need to implement a simple page to serve files from the filesystem. In the next part we will have a look at making this code more production ready by adding error checking and handling.

 

The Complete Page_Load Method

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:     // Clear headers and content to ensure a clean response
   4:     Response.ClearHeaders();
   5:     Response.ClearContent();
   6:   
   7:     // get target file path from querystring
   8:     string filePath = (Request.QueryString["filePath"] ?? "").Trim();
   9:     Debug.WriteLine("GetFile: filePath: '" + filePath + "'");
  10:   
  11:     // determine content type
  12:     string contentType = null;
  13:     string extension = Path.GetExtension(filePath).ToLowerInvariant();
  14:     switch (extension)
  15:     {
  16:        case ".csv":
  17:           contentType = "text/csv";
  18:           break;
  19:        case ".mp4":
  20:           contentType = "video/mp4";
  21:           break;
  22:        case ".pdf":
  23:           contentType = "application/pdf";
  24:           break;
  25:        default:
  26:           // Unsupported filetype
  27:           // There are serious security implications with
  28:           // serving up files from a webserver. Make sure
  29:           // you do not open yourself up to an attack!
  30:           throw new HttpException(403, "Forbidden.");
  31:        // Use an empty ContentType if the type is unknown
  32:        //contentType = "";
  33:        //break;
  34:     }
  35:   
  36:     //Set the ContentType.
  37:     Response.ContentType = contentType;
  38:     Debug.WriteLine("Mapped extension '" + extension 
  39:            + "' to content type '" + contentType + "'.");
  40:   
  41:     // determine and set file length
  42:     var fileInfo = new FileInfo(filePath);
  43:     Response.AddHeader("Content-Length", fileInfo.Length.ToString());
  44:   
  45:     //Write the file directly to the HTTP content output stream.
  46:     Response.WriteFile(filePath);
  47:   
  48:     // flush and end the response
  49:     Response.End();
  50:  }

How To Detect If The Command Prompt Is Running Elevated

As I was setting up my Console2 shell tabs I was curious if running Console2 as an administrator would transfer the elevated privileges token to the tabs as well.

Turns out detecting this was not as straightforward as I thought it would be!

TL;DR

If you need to know how to detect if the command prompt is running elevated (or your script) use the following command:

whoami /groups
If the output contains these lines the process is running elevated:
Mandatory Label\High Mandatory Level Label            S-1-16-12288
                    Mandatory group, Enabled by default, Enabled group

The Long Answer

With the addition of User Account Control to Windows Vista the platform gained integrity levels – an integrity level indicates how much an application can be trusted to perform  actions on the system, e.g. accessing files or the registry and interacting with other processes. By adding this additional security feature to the OS it now has another indicator to help isolate (sandbox) programs and prevent them from going rogue on your system. Very cool!

The following integrity levels are supported:

  • Untrusted – processes that are logged on anonymously are automatically designated as Untrusted
  • Low – The Low integrity level is the level used by default for interaction with the Internet. As long as Internet Explorer is run in its default state, Protected Mode, all files and processes associated with it are assigned the Low integrity level. Some folders, such as the Temporary Internet Folder, are also assigned the Low integrity level by default.
  • Medium – Medium is the context that most objects will run in. Standard users receive the Medium integrity level, and any object not explicitly designated with a lower or higher integrity level is Medium by default.
  • High – Administrators are granted the High integrity level. This ensures that Administrators are capable of interacting with and modifying objects assigned Medium or Low integrity levels, but can also act on other objects with a High integrity level, which standard users can not do.
  • System – As the name implies, the System integrity level is reserved for the system. The Windows kernel and core services are granted the System integrity level. Being even higher than the High integrity level of Administrators protects these core functions from being affected or compromised even by Administrators.
  • Installer – The Installer integrity level is a special case and is the highest of all integrity levels. By virtue of being equal to or higher than all other WIC integrity levels, objects assigned the Installer integrity level are also able to uninstall all other objects.

 

For more info see the Windows Integrity Mechanism Design.

My Latest Track