Tuesday, April 22, 2014

AppSensor and MVC: Unexpected HTTP Commands

So, I have decided to try and implement the some of the OWASP AppSensor controls inside an MVC project.  I will go into details about the end goal of this project in another post, but for now I am going to talk about the Unexpected HTTP Commands detection point.

This is a first stab.  Currently the only response is to log the request in the logs.

In MVC, the current set of http method controls are implemented as ActionMethodSelectorAttribute.  What this means is that you apply the control on a method by method basis.  This approach will probably be good when implementing other controls, but for this one, we need something more global.

This article has a really good explanation of action filters and how to use them.  The filter itself is quite simple, since all we are doing is comparing the reported http verb to a list of expected ones.



        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (!Enabled)
            {
                return;
            }

            var httpMethod = filterContext.HttpContext.Request.GetHttpMethodOverride();

            if (
                acceptedVerbs.Any(
                    x =>
                        x.Equals(httpMethod,
                            StringComparison.InvariantCultureIgnoreCase)))
            {
                return;
            }

            httpMethod = SanitizeHttpMethod(httpMethod);

            Logger.LogRequestException(filterContext.HttpContext.Request.Url.AbsolutePath,
                SecurityConstants.UnexpectedHttpCommands,
                httpMethod,
                filterContext.HttpContext.Request.UserHostAddress);

            filterContext.Result = new HttpNotFoundResult();
        }

A couple of notes.

1)  filterContext.HttpContext.Request.GetHttpMethodOverride() returns the http method as listed in the request.  Remember that this is untrusted code and should be sanitized before writing to the logs.

2)  Verbs can actually be restricted earlier in the pipeline.  Firstly, you can use Request Filtering to limit the accepted verbs.  This method works, but doesn't allow the application to handle the request and thus limit our ability to build in the response mechanisms.  Secondly, in the handler definition you can define the list of verbs to respond.  If you try to use a verb that isn't on this list, you will get an HTTP 405 error.  Both these mechanism are great (and log to the IIS logs) but they don't allow our application to handle and keep track of violations.  To bypass the handler definition, you can use * in the accepted verbs list.

The code is located in github. 


Tuesday, April 15, 2014

Some thoughts on Secure File Uploads (MVC / C#)

Recently, I was asked to take a look at a solution that involves receiving file uploads via an MVC application that would then be later processed by an internal system.  MVC already has built in functionality to receive files, so that was no problem.  But what about the security aspects of doing something like this?  Before I dive into some of the things you can do to secure your file transfer, I want to touch on a couple of things.

1)  Verifying files and file types is hard!  There is a project that is available that tries to keep track of the magic numbers for each file type.  A quick scroll through there and you will notice that in some cases, multiple extensions use the same magic numbers.  In other cases, different versions of the application use different file types and magic numbers.  It is a mess!

2)  The verifying that can be done in the application often isn't enough.  We have to remember that we cannot rely on the MVC application to solve all problems.  Even the server side checks that can be implemented are not full proof.  Furthermore, this only verifies that a file is of the type it claims to be (or that you are looking for).  One would still have the problem of ensuring the file was actually "safe" for consumption by the target application.  At the outset, this seems more of a problem for IDS/IPS/Virus Scan than it does the MVC application.

3)  This type of functionality should have already gone through some sort of security review well before it reaches the development team.  The risk of uploading files should be pointed out as well as all the compensating controls that are in place to help mitigate that risk.  For example, is this page authenticated?  Is there sufficient logging?  Is there and IDS/IPS in the network?  Do the files get virus scanned before being consumed?  Can the files be "detonated" on a segregated machine before being given to end users?

As usual, OWASP has a great write-up on this.  They cover a lot of the key points you would want to look at when building your application.

First thing is first, you have build your form/app to receive files.  This is easily done.  MVC4 has the concept of HttpPostedFileBase which is the type the file shows up as.  A couple of notes, you must make sure that the name of your input field matches that of the parameter input to your controller method.  Secondly, you must set the enctype of the form to multipart/form-data.  See this post for more info.

One last thing, the checks below take into account a lot of assumptions about the types of files you want to accept.  You will most probably tailor them to your specific example.  In my case, I am focusing on docx or xlsx files.

Check 1:  Is a file actually there?
Basically this is the equivalent to the ModelState.IsValid() method.  You want to make sure you are actually processing a file.

  if (file == null){
   return View("Error");
  }

Check 2:  File Size
One of the first checks you are going to want to do is around file size.  You can use the request limits feature of request filtering to limit the maximum size you would like to handle.  A couple of notes is that this setting is a global setting, and not page specific.  Further to this, you cannot set a "minimum" size.  You could check for file size in the code (just call length on the input stream), and this might be a valid check in your case.

Check 3:  File Name
We have to remember here that the interpreted file name and content type is actually passed directly from the http request.  This means that it is untrusted client side input and must be validated.  You may want to run a series of checks, but the basics would be length of the file name and a regular expression match.  Here you will want to make some assumptions about the types of files/file names you will be getting.

  if (file.FileName.Length > 255)
  {
    return View("Error");
  }

  var regex = new Regex(@"^[\d\w -]+\.[\w]{4}");
  if (!regex.IsMatch(file.FileName))
  {
    return View("Error");
  }

  var acceptedExtensions = new List() {".docx", ".xlsx"};
  var fileExtension = Path.GetExtension(file.FileName);
  if (acceptedExtensions.All(x => !x.Equals(fileExtension)))
  {
    return View("Error");
  }


In my case, I am looking specifically for docx and xlsx files.  So I can take those assumptions and work with the code.

Check 4:  Content Type
Generally, content type is generated from extension present on the file name.  This behavior varies between browsers and potentially across OS platforms.  Never-the-less, it is information we can look at as a first check.  In the code below, I just compare it to a white-list.  You could take this farther by keeping a mapping of extension to accepted content type.

            var acceptedContentTypes = new List()
            {
                "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            };
            if (acceptedContentTypes.All(x => !x.Equals(file.ContentType)))
            {
                return View("Error");
            }

Check 5:  FindMimeFromData Call
Windows has a built in call that does a static check to determine 26 different mime types.  The problems with this are that you have to use interop.  This is the same check that is done in internet explorer, but the benefit is that server side you can control what parameters you call the program with. 

There are a few steps here to using this.  First you have to add the call definition to your code.


        [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)]
        private extern static System.UInt32 FindMimeFromData(
            System.UInt32 pBC,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl,
            [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer,
            System.UInt32 cbSize,
            [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed,
            System.UInt32 dwMimeFlags,
            out System.UInt32 ppwzMimeOut,
            System.UInt32 dwReserverd
        );

Next you have to get the header and call the function. This is a rough idea of what you would do.
            var buf = new byte[265];
            file.InputStream.Seek(0, SeekOrigin.Begin);
            if (file.InputStream.Length > 256)
            {
                file.InputStream.Read(buf, 0, 256);
            }
            else
            {
                file.InputStream.Read(buf, 0, (int)file.InputStream.Length);
            }

            System.UInt32 mimetype;
            var result = (int)FindMimeFromData(0, null, buf, 256, null, 0, out mimetype, 0);
            if (result != 0)
            {
                return View("Error");
            }
            var mimeTypePointer = new IntPtr(mimetype);
            var mimeType = Marshal.PtrToStringUni(mimeTypePointer);
            Marshal.FreeCoTaskMem(mimeTypePointer);

            if (mimeType == null || !mimeType.Equals("application/x-zip-compressed"))
            {
                return View("Error");
            }
In the case of open-xml format, the file that actually gets sent is a Zip file.  So you can look for it there.  There are some alternatives to calling out to the interop.  You could figure out the header bits for a zip file and code the check manually.  See mimedetector or filetypedetective.

So at this point, let us recap.  We have done some basic file name checks.  We have also done some basic content type checks, but this data is built from the file name, so it can't be trusted.  We have run a sever side check and from the header, we can determine that it is in fact a zip file.  Pretty weak!

At this point, we could now rip open the zip file and start to have a look at what is inside.

Check 6:  Zip Verification

The code would look something like this.

            var isValid = false;
            using (var archive = new ZipArchive(file.InputStream,ZipArchiveMode.Read))
            {
                foreach (var entry in archive.Entries)
                {
                    if (!entry.Name.Equals("[Content_Types].xml"))
                    {
                        continue;
                    }

                    using (var contentTypeFile = entry.Open())
                    {
                        var xElement = XElement.Load(contentTypeFile);
                        var elements = xElement.Elements();
                        
                        foreach (var element in elements)
                        {
                            if (element.FirstAttribute.Value.StartsWith("/word/") ||
                                element.FirstAttribute.Value.StartsWith("/xl/"))
                            {
                                isValid = true;
                                break;
                            }
                        }
                    }
                }
            }

            if (!isValid)
            {
                return View("Error");
            }
In .net 4.5, they finally introduced the ZipFile/ZipArchive functionality.  You can use it to open up a stream and check what is inside.  All I am doing in the code above looking for a specific file and checking that file for an attribute that I know needs to be there (as per the standard).  If you think about it, all I am doing is verifying that I have received a zip file (that I can open) and it has a file with some key words in it.  Once again, pretty weak stuff!

Conclusion

Where do you stop? It really depends on the needs of your application.  There is no good way to handle this.  We can start to decrease the risk by adding all of these extra checks, but we can't mitigate it completely.  The entire solution needs to take file upload into consideration.

Tuesday, April 8, 2014

CCSK Certified!

After dragging my feet for a long time, I finally decided to write the CCSK.

It was a pretty comprehensive exam, but if you go through the two documents, you should be fine.  I found that I finished the exam in about 30 minutes and had about an hour to go over the questions again.  In my initial attempt I skipped about 10 questions.

The biggest problem I found was the wording of some of the questions.  I found reading out-loud helped a lot as there are some subtle things to watch out for.

In any event, passed with 88%!  Time to take the rest of the week off!