Sunday 12 February 2017

Azure App Services Web App Antivirus - Concept

Introduction


Good web application antivirus solution needs to meet the following acceptance criteria:
  • Real-time inbound and outbound file scanning
  • Low latency, sub second performance
  • Transparent, no need to make changes to your web app
  • Requires no maintenance and is easy to setup

If you are using PaaS service such as Azure Web Apps your antivirus options are:

Option Limitation Installation Effort Maintenance Effort Cost Meets criteria
Firewall with ICAP integration Expense and ongoing maintenance High Medium High Yes
Send files to the antivirus API from your app Extra unnecessary development work Medium/High Medium Low No
Background service that scans your files Not real time, high possibility that viruses will be uploaded and downloaded Medium Low Low No
Reverse proxy binary content forwarding Scans only uploads Medium Low Low No
IIS Filter (this solution) PoC, not production ready Medium Low Low Yes

In depth exploration of different antivirus options is beyond the scope this article.


IIS Filter Solution, inspired by ModSecurity





This article will only cover the interesting parts of the solution, if you have questions please do comment.


AVFilter, File Upload/Download Filter


Filtering uploads and downloads is relatively straight forward thanks to IIS http modules, read more about them here.


Filtering Uploads


Here is an example of how you can detect file upload inside the http module:
 private void Context_PreRequestHandlerExecute(object sender, EventArgs e)
  {
      HttpApplication httpApplication = (HttpApplication)sender;
      HttpRequest httpRequest = httpApplication.Context.Request;
      
      if (httpRequest.RequestType == WebRequestMethods.Http.Post)
      {
          if (httpRequest.Files.Count != 0)
          {
              //Check files
          }
      }
  }
Take a look at the full implementation here.


Filtering Downloads


When it comes to intercepting downloads it’s not as simple. Unfortunately outgoing traffic is written to the System.Web.HttpResponseStreamFilterSink and this stream is write only, so you can’t read it. So the only option that I and Google can think of is to proxy the outgoing stream. To make this happen I have created proxy class that changes write only steam to write/read stream, I have conveniently called it ReadWriteProxyStream, take a look at the implementation here.

Now that we have ReadWriteProxyStream class, we need to hook it up response filter, this is done when web request is first received:
    private void Context_PreRequestHandlerExecute(object sender, EventArgs e)
    {
        HttpApplication httpApplication = (HttpApplication)sender;
        
        httpApplication.Context.Response.Filter = new ReadWriteProxyStream(httpApplication.Context.Response.Filter);
    }
Take a look at the full implementation here.

Before web response is returned back to user we need to check it, we can do this by reading the stream:
    private void Context_PreSendRequestContent(object sender, EventArgs e)
    {
        HttpApplication httpApp = (HttpApplication)sender;
        HttpResponse httpResponse = httpApp.Context.Response;
        
        if(httpResponse.ContentType == "application/octet-stream")
        {
            ReadWriteProxyStream filter = httpResponse.Filter as ReadWriteProxyStream;

            if (filter != null)
            {
                byte[] data = new byte[filter.Length];
                filter.Position = 0;
                filter.Read(data, 0, data.Length);

                //Check files
            }
        }
    }
Take a look at the full implementation here.


AVFileReceiver, Virus Scanning


For this proof of concept I have used Symantec virus scanner which comes with CLI, and to test it I have used EICAR test virus. EICAR test virus is harmless, it can’t actually do anything to your machine.

Symantec CLI will scan the specified file synchronously, so if the file is no longer there you know that it was not safe:
  using (Process process = Process.Start(
        "C:\\Program Files (x86)\\Symantec\\Symantec Endpoint Protection\\DoScan.exe",
        "/ScanFile " + filePath))
    {
        process.WaitForExit();
    }

    if (!File.Exists(filePath))
        throw new Exception("File was not safe!")
Take a look at the full implementation here.



Final Thoughts


If you like this concept then please do share and rate this blog post. If this post will generate enough interest I will productionise this concept. However, if you just can’t wait, then here is what you need to do to make this production ready:
  • Secure communication between AVFilter and AVFileReceiver
  • Secure AVFileReceiver access (authentication, network security group, etc)
  • Reduce VM maintenance through automation, use PowerShell files provided and place Cloud Service inside availability and upgrade group
  • Reduce memory consumption by intercepting binary responses only
  • Make debugging easier by logging exceptions
  • Improve performance by forwarding binary content using MTOM encoding to the AVFileReceiver and remove AVFilter temp storage
  • Improve scalability and responsiveness by using Async in the AVFileReceiver and AVFilter