Friday, January 2, 2015

Microsoft 70-486: Implement a secure site with ASP.NET

Exam Objectives


Secure communication by applying SSL certificates; salt and hash passwords for storage; use HTML encoding to prevent cross-site scripting attacks (ANTI-XSS Library); implement deferred validation and handle unvalidated requests, for example, form, querystring, and URL; prevent SQL injection attacks by parameterizing queries; prevent cross-site request forgeries (XSRF)

Quick Overview of Training Materials


Hashing Passwords using ASP.NET's Crypto Class - MSDN Crypto Class 
Password management made easy in ASP.NET with the Crypto API
MSDN - Differences Between AntiXss.HtmlEncode and HttpUtility.HtmlEncode
owasp.org - ASP.NET Request Validation


Using SSL certificatates


To communicate securely over the web requires the use of encryption on the messages passed back and forth.  Encryption on the web is accomplised through the use of Transport Layer Security (TLS). Http traffic layered over SSL/TLS is called Https.  It would be all too easy to venture down the rabbit hole and explain in great detail how TLS establishes a secure connection, but the crypto minutia is likely a bit out of scope for the purposes of this discussion.  However, a basic understanding of the mechanisms used by TLS to secure the communication channel will make understanding the use of SSL certificates much easier.

TLS uses public key cryptography, which requires the distribution of a public key.  This public key is transmitted as part of an SSL certificate, along with other identifying information meant to establish trust that a server is who it is supposed to be (and not a malicious eavesdroper).  While it is possible to create self-signed certificates, browsers do not trust these certificates and will give users dire warning when they are encountered (this would be like me printing my own drivers license and saying "See, I am who I say I am... really!").  For a certificate to be trusted, it must be issued by a trusted Certificate Authority (CA).  When a secure connection is made using a trusted certificate, most browsers will display an indicator that the connection is secure.  In Chrome, clicking the green lock icon will reveal information regarding the details of the connection:


And this is what a self signed certificate will get you:



 So to fully take advantage of Https you need to have a certificate from a trusted CA (Symantec [GeoTrust, VeriSign], Comodo, GlobalSign, etc).  Getting a certificate involves first creating a Certificate Signing Request (CSR).  IIS has the capability of creating a CSR, and their are other tools available, including the linux command line using OpenSSL.  This is what it looks like in IIS:


The CSR itself isn't much to look at:



You'll transmit the CSR in one form or another to the CA you are getting your certificate from, and after they've performed whatever level of due diligence they'll send you your SSL certificate.  Once you have the certificate, you'll need to install it on the server hosting your site.  For .NET based web applications, that likely means IIS or Azure.

Installing SSL Certificate in IIS 8.0

DigiCert publishes some pretty straight forward instructions for installing a certificate in IIS 8 and 8.5 that I will quickly sum up here.  In IIS Manager, under Server Certificates, there is an option for "Complete Certificate Request" (you can see it just below "Create Certificate Request" in the screenshot above..).  Here you'll browse to the *.cer file that contains your certificate, add a friendly name to make management easier (it doesn't change anything in the certificate) and add the certificate to the Personal certificate store.  This installs the certificate to the server.  To use it in your site, you have to bind it.  Navigate to the site settings in IIS, go to bindings, and add a binding for https using port 443 and the created certificate. Boom, done.

Installing SSL Certificate in Azure (custom domain)

In Microsoft Azure, SSL is already enabled for sites using the *.azurewebsites.net domain name. There is literally nothing to do for these sites, https is baked in:



Using certificates on custom domains is a little more involved.  Microsoft has published detailed instructions.  The readers digest version:  You must use Standard Mode (evidently Free, Shared, and Basic don't support SSL for custom domains...).  Under the website configuration, upload and bind the certificate.  Finally, modify the website configuration to enforce https.  Non-MVC .NET applications can use the URL Rewrite rule in the web.config, while MVC application can use the [RequireHttps] filter.

Using Https in the application

Using the URL Rewrite rule described in the Azure SSL instruction involved adding this rule to the web.config file:

  <system.webServer>
  <rewrite>
    <rules>
      <rule name="Force HTTPS" enabled="false">
        <match url="(.*)" ignoreCase="false"/>
      <conditions>
        <add input="{HTTPS}" pattern="off"/>
      </conditions>
        <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" 
                appendQueryString="true" redirectType="Permanent"/>
      </rule>
    </rules>
  </rewrite>
  </system.webServer>

In MVC, using the [RequireHttps] attribute gives fine grained control over what portions of the site will need to use https:

 
[RequireHttps]
public ActionResult About()
{
    ViewBag.Message = "Your application description page.";
 
    return View();
}
 

This may cause some wonky localhost behavior (I probably just didn't set it up to use the right port or something), but that's pretty much the meat of it.



Salt and Hash Passwords


For users to sign in to a secure website, it is necessary to compare the password they supply with the one stored in the database.  Storing these passwords as plain text would be incredibly insecure (if the data store is compromised then all the passwords would be available to an attacker), so it is necessary to disguise the actual password.  This is accomplished through the use of a hashing algorithm.  A cryptographic hash converts a string of characters into a fixed length series of bytes.

Hashing alone isn't enought to ensure that passwords are secure, however.  Attackers must take an extra step in calculating hashes for potential password matches. The dictionary attackbrute force attackrainbow tables are all attacks based on that basic premise: matching a computed hash with the compromised hash to determine the underlying plaintext.  One technique that can protect against these attacks is the use of a salt.  A salt is a random string that is concatenated to the password before it is hashed, and it ensures that even if two passwords are the same, they will have different hash values (as long as all the salts are unique, which they should be).  Here is a simple illustration of the difference:



The unsalted passwords are all vulnerable to attack by the same dictionary, whereas with the salted passwords, the attacker has to recalculate the entire dictionary for every single salt value.  It there are millions of values in the dictionary (which there would have to be for it to be of any value), this can get very computationally expensive.  This attack can be made even more expensive by increasing the computation cost of calculating the hash.  This is done by iteratively appending the salt and rehashing the password.  This technique is called key stretching, and while it incurs only a very small performance cost on the user (who only has to calculate one hash), it can increase the amount of effort required of an attacker exponentially.   The image below gives a very basic illustration of what the technique looks like.  In practice, there would be a minimum of 1000 iterations:


.NET includes multiple classes that can compute and verify password hashes.  Two of these are the Crypto.HashPassword() helper in System.Web.Helpers, and another is the PasswordHasher class in Microsoft.AspNet.Identity.  They are both pretty straight forward to use:

            
var pwd = "Password";
var hashedPwd = Crypto.HashPassword(pwd);
var passwordHasher = new PasswordHasher();
var otherHashedPwd = passwordHasher.HashPassword(pwd);
 
 
ViewBag.A_A = Crypto.VerifyHashedPassword(hashedPwd, pwd).ToString();
ViewBag.B_B = passwordHasher.VerifyHashedPassword(otherHashedPwd, pwd).ToString();
ViewBag.A_B = Crypto.VerifyHashedPassword(otherHashedPwd, pwd).ToString();
ViewBag.B_A = passwordHasher.VerifyHashedPassword(hashedPwd, pwd).ToString();
 
ViewBag.pwd = pwd;
ViewBag.hashedPwd = hashedPwd;
ViewBag.otherHashedPwd = otherHashedPwd;
 
return View();
 


The two hashes are different because each function generates a new random salt, but both use the exact same algorithm: RFC 2898 algorithm using a 128-bit salt, a 256-bit subkey, and 1000 iterations (from the MSDN article on Crypto.HashPassword).  Because they use the same underlying method to generate the hash, they are able to mutually verify keys, so they could be used interchangably in an application.  I tried to play around with the Rfc2898DeriveBytes Class directly, but it was a lot more involved than I anticipated, and I ditched the effort.



Use HTML encoding to prevent XSS attacks


For sites that allow user input to be displayed in the browser, cross site scripting (XSS) attacks are a possibility that must be protected against.  These attacks are carried out by placing <script> tags pointing to malicious code in the public facing elements, which can be persisted elements such as comments or reviews, or more ephimeral examples such as query variables in the url.  The aim of these attacks varies, but some examples are stealing sensitive information (login credentials, personal data), forcing redirects, or just about anything else that can be accomplished with JavaScript.  These attackes can be prevented by encoding input. So instead of the literal string <script>naughty script code</script>, it becomes &lt;script&gt;naughty script code&lt;/script&gt;, and instead of running the code, it will simply display the text content of the script.

According to Microsoft, the primary purpose of the HttpUtility.HtmlEncode method is to ensure that ASP.NET output does not break HTML; it's purpose is not necessarily security.  However, the AntiXssEncoder class is primarily designed for security.  To this end, it uses a white-list approach rather than a black-list, only allowing known safe characters to remain unencoded.  The AntiXss method is slightly less performant, and will work in multiple languages.

It is possible to set the AntiXssEncoder as the default for your application, and this has gotten steadily easier.  Phil Haack wrote in 2010 about doing this using a the HttpEncoder abstract base class, and Jon Galloway wrote in 2011 about doing it with version 4.1 which already included an encoder, so it required little more than adding the assembly to the project and changing the web.config file.  Since AntiXss can now be had in NuGet, it's as simple as installing it and setting the httpRuntime encoderType property:


    
  <system.web>
    <httpRuntime targetFramework="4.5" 
             encoderType="Microsoft.Security.Application.AntiXssEncoder, AntiXssLibrary"/>
  </system.web>
    



Deferred validation and handling unvalidated requests


ASP.NET automatically performs a validation on requests to determine if they contains potentially harmful content.  If a user tries to submit a form value with <script> tags, for example, the application will throw an HttpRequestValidationException (I threw this one by trying to insert <script>alert("HACKED")</script> in a bunch of fields on the NerdDinner demo):



The details of this behavior have changed through the development of the .NET framework.  Different behaviors can be achieved by changing the httpRuntime property requestValidationMode in the application's web.config file.

   
  <system.web>
    <authentication mode="None" />
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime requestValidationMode="2.0"/>
  </system.web>
   

The default value for this property is 4.0, which differs significantly from the previous version (2.0). Deferred lazy validation, which validates only the fields that are actually accessed, requires setting this property to 4.5.  In 4.0 ALL of the fields were validated for every request, and in 2.0 only page elements were validated.

Two attributes allow for finer grain control of which pages or fields are validated:  the  [ValidateInput] attribute and the [AllowHtml] attribute.  Setting [ValidateInput(false)] on a controller method will prevent validation on any of the submitted fields. [AllowHtml] is used to decorate properties on the model class and essentially tells the framework to skip validation during model binding (cause THAT doesn't expose you to security risks...), making it possible to validate some fields but not others on the same form.

Unvalidated data for all fields can be accessed via the HttpRequest.Unvalidated collection.

 
string UnvalidatedId = Request.Unvalidated["Id"];
 

This data should be used with caution, as it opens a potential security vulnerability is the data is not property validated and encoded.



Prevent SQL injection attacks


SQL Injection attacks are carried out by passing specifically formatted strings into a form or query string that are then passed to a SQL statement without being checked.  A simple example is a lookup query that uses the query string variable id like this:

string query = @"SELECT * FROM Items WHERE Id = " + Request.QueryString["Id"]

With this query, the URL querystring  ?Id=1;DELETE FROM Items; would delete everything out of the items table.  This is avoided by using parameterized queries.  With a parameterized query, the above string would like this:

string query = @"SELECT * FROM Items WHERE Id =@Id"

And the query string value is passed as a parameter to the query.  Both the EntityCommand and SqlCommand objects have a parameter collection.  Parameters added to these collections can be typed using the DbType enumeration, and strings passed to varchar fields will be stringified.  The above assignment may look like this with a parameter:

SqlCommand command = new SqlCommand(commandText, connection); command.Parameters.Add("@ID", SqlDbType.Int); 
command.Parameters["@ID"].Value = Request.QueryString["Id"];

If the query string doesn't parse to an int, the query won't execute.



Prevent Cross Site Request Forgeries


Cross Site Request Forgery (CSRF) is an attack that exploits the trust a website has for a user (or at least their browser).  Also known as session riding, the attack works by tricking a user with access priveledges to a site into submitting malicious data to the site.  The following visual aid might make it clearer:

              

  1. The malicious user sends an email to a user with access to the target site.  The email contains a link to a malicious website.
  2. The target user has a cookie with valid session information to the target website.  The target user clicks the link in the email, which navigates them to the malicious website.  
  3. Once on the malicious website, a post request is submitted to the target website using javascript.  This is effectively a request made by the target user and has the target user's session information in the cookie.
  4. The target site accepts the request thinking it is from the trusted user (due to the session information in the cookie)
To defend against the CSRF attack it is essential to determine that the browser that sent the request did so intentionally, at the request of the user.  This usually involves the use of a token or a challenge.  The most common mechanism is the Syncronizer Token Pattern.  With this pattern, a hidden field is included on the form with a crypographically strong random value.  When the user submits the form, the server checks that the token value included in the submitted form matches the value on the server.  In the above diagram, the malicious site would have no way of generating this token, so malicious requests to the target site would fail.

This is easily accomplished in .NET through the use of the [ValidateAntiForgeryToken] and HtmlHelper.AntiForgeryToken.  Under the covers, this impliments the Syncronizer Token Pattern as well as the Double Submit Cookie mechanism.  The token value is included in both the form values and the cookie, so anonymous users are still protected (because the form token can be compared to the cookie token).  The article on Asp.net goes into depth on the features and extensibility.  Here is an example from the Pluralsight OdeToFood MVC tutorial:

         
//
// POST: /Restaurant/Create
[HttpPost]
[Authorize(Roles="admin")]
[ValidateAntiForgeryToken]
public ActionResult Create(Restaurant restaurant)
{
    if (ModelState.IsValid)
    {
        _db.Add(restaurant);
        _db.SaveChanges();
        return RedirectToAction("Index");
    }
 
    return View(restaurant);
}
 

 
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)
 
    <fieldset>
        <legend>Restaurant</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        ...
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}
 

OWASP points out that it is crucial to protect against cross site scripting (XSS) attacks to ensure the efficacy of CSRF countermeasures.  A scripting attack that is able to extract valid tokens from users will be able to fool the token validation process.

2 comments:

  1. Thank you very much... dear Troy Whorten your weblog is better than exam ref book :) I study for 2 weeks on your blog awesome.I hope you have time to write some tips about exam 70-487.

    ReplyDelete
    Replies
    1. Thanks for the comments, I'm always glad to hear that my efforts were valuable to someone. I do plan on giving 70-487 the same treatment in the near future. I'm currently taking an Artificial Intelligence class on edX, so that is taking up all my time at the moment, but when I'm done with that (probably in 8-10 weeks), then I'll get started on 70-487.

      Delete