OWASP Top 10 For .NET Developers
OWASP Top 10 For .NET Developers
OWASP Top 10 For .NET Developers
Troy Hunt
2|
OWASP Top 10 for .NET developers by Troy Hunt is licensed under a Creative Commons .Attribution 3.0 Unported License.
3 | Contents
Contents
Contents ............................................................................................................................ 3 Foreword.........................................................................................................................11 The OWASP Top 10 Application Security Risks ....................................................12
A1 Injection ..................................................................................................................................... 12 A2 Cross-Site Scripting (XSS) ....................................................................................................... 12 A3 Broken Authentication and Session Management ............................................................... 12 A4 Insecure Direct Object References......................................................................................... 12 A5 Cross-Site Request Forgery (CSRF) ....................................................................................... 12 A6 Security Misconfiguration ........................................................................................................ 13 A7 Insecure Cryptographic Storage .............................................................................................. 13 A8 - Failure to Restrict URL Access ................................................................................................ 13 A9 - Insufficient Transport Layer Protection ................................................................................. 13 A10 Unvalidated Redirects and Forwards ................................................................................... 13
4 | Contents
What made this possible? .................................................................................................................. 22 Validate all input against a whitelist ................................................................................................. 23 Parameterised stored procedures ...................................................................................................... 24 Named SQL parameters .................................................................................................................... 26 LINQ to SQL...................................................................................................................................... 27 Applying the principle of least privilege .......................................................................................... 28 Getting more creative with HTTP request headers ....................................................................... 32 Summary .............................................................................................................................................. 33 References ............................................................................................................................................ 34
5 | Contents
6 | Contents
Implementing access control ............................................................................................................ 85 Using an indirect reference map ....................................................................................................... 87 Avoid using discoverable references ................................................................................................ 89 Hacking the Australian Tax Office .................................................................................................. 90 Insecure direct object reference, Apple style .................................................................................. 91 Insecure direct object reference v. information leakage contention ........................................... 92 Summary .............................................................................................................................................. 93 Resources ............................................................................................................................................. 94
7 | Contents
Keep your frameworks up to date .................................................................................................. 116 Customise your error messages ...................................................................................................... 121 Get those traces under control ....................................................................................................... 127 Disable debugging ............................................................................................................................ 131 Request validation is your safety net dont turn it off! ............................................................. 134 Encrypt sensitive configuration data.............................................................................................. 135 Apply the principle of least privilege to your database accounts ............................................... 137 Summary ............................................................................................................................................ 141 Resources ........................................................................................................................................... 142
8 | Contents
9 | Contents
Always use SSL for forms authentication ..................................................................................... 212 Ask MVC to require SSL and link to HTTPS .............................................................................. 220 Time limit authentication token validity ........................................................................................ 221 Always serve login pages over HTTPS .......................................................................................... 222 Try not to redirect from HTTP to HTTPS ................................................................................... 224 HTTP strict transport security ........................................................................................................ 228 Dont mix TLS and non-TLS content ........................................................................................... 231 Sensitive data still doesnt belong in the URL ............................................................................... 234 The (lack of) performance impact of TLS .................................................................................... 235 Breaking TLS ..................................................................................................................................... 236 Summary ............................................................................................................................................ 236
Part 10: Unvalidated Redirects and Forwards, 12 Dec 2011 .............................. 238
Defining unvalidated redirects and forwards ................................................................................ 238 Anatomy of an unvalidated redirect attack ................................................................................... 239 What made this possible? ................................................................................................................ 242 Taking responsibility ........................................................................................................................ 243 Whitelists are still important ........................................................................................................... 243 Implementing referrer checking ..................................................................................................... 245 Obfuscation of intent ....................................................................................................................... 247 Unvalidated redirects contention.................................................................................................... 248 Summary ............................................................................................................................................ 249
10 | Contents
11 | Foreword
Foreword
Without actually realising it at the time, writing this series has turned out to be one of the best professional moves Ive made in the last decade and a half of writing software for the web. First of all, it got me out of a bit of a technical rut; as I found myself moving into roles which focussed more on technology strategy and less on building code something that tends to happen when a career progresses I felt a void developing in my professional life. Partly it was a widening technical competency gap that comes from not practicing your art as frequently, but partly it was the simple fact that building apps is downright enjoyable. As I progressed in the series, I found it increasingly filling the void not just in my own technical fulfilment, but in the software community. In fact this has been one of the most fulfilling aspects of writing the posts; having fantastic feedback in the comments, over Twitter and quite often directly via personal emails. These posts have now made their way into everything from corporate standards to tertiary education material and thats a very pleasing achievement indeed. Perhaps most significantly though, writing this series allowed me to carve out a niche; to find something that gels with my personality that tends to want to be a little non-conformist and find the subversive in otherwise good honest coding. That my writing has coincided with a period where cyber security has gained so much press through many high-profile breaches has been fortuitous, at least it has been for me. Finally, this series has undoubtedly been the catalyst for receiving the Microsoft MVP award for Developer Security. Ive long revered those who achieved MVP status and it was not something I expected to append to my name, particularly not as I wrote less code during the day. By collating all these posts into an eBook I want to give developers the opportunity to benefit from the work that Ive enjoyed so much over the last 19 and a bit months. So take this document and share it generously; email it around, put it into your development standards, ask your team to rote learn it whatever just so long as it helps the Microsoft ASP.NET community build excellent and secure software. And above all, do as I have done and have fun learning something new from this series. Enojy! Troy Hunt Microsoft MVP Developer Security troyhunt.com | [email protected] | @troyhunt
A6 Security Misconfiguration
Good security requires having a secure configuration defined and deployed for the application, frameworks, application server, web server, database server, and platform. All these settings should be defined, implemented, and maintained as many are not shipped with secure defaults. This includes keeping all software up to date, including all code libraries used by the application.
Moving on, this is going to be a 10 part process. In each post Im going to look at the security risk in detail, demonstrate where possible how it might be exploited in a .NET web application and then detail the countermeasures at a code level. Throughout these posts Im going to draw as much information as possible out of the OWASP publication so each example ties back into an open standard. Heres what Im going to cover: 1. Injection 2. Cross-Site Scripting (XSS) 3. Broken Authentication and Session Management 4. Insecure Direct Object References 5. Cross-Site Request Forgery (CSRF) 6. Security Misconfiguration 7. Insecure Cryptographic Storage 8. Failure to Restrict URL Access 9. Insufficient Transport Layer Protection 10. Unvalidated Redirects and Forwards
and continue to learn new facts about application security on a daily basis. This really is a serious discipline within the software industry and should not be approached casually.
Worked examples
Im going to provide worked examples of both exploitable and secure code wherever possible. For the sake of retaining focus on the security concepts, the examples are going to be succinct, direct and as basic as possible. So heres the disclaimer: dont expect elegant code, this is going to be elemental stuff written with the sole intention of illustrating security concepts. Im not even going to apply basic practices such as sorting SQL statements unless it illustrates a security concept. Dont write your production ready code this way!
Defining injection
Lets get started. Im going to draw directly from the OWASP definition of injection: Injection flaws, such as SQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attackers hostile data can trick the interpreter into executing unintended commands or accessing unauthorized data. The crux of the injection risk centres on the term untrusted. Were going to see this word a lot over coming posts so lets clearly define it now: Untrusted data comes from any source either direct or indirect where integrity is not verifiable and intent may be malicious. This includes manual user input such as form data, implicit user input such as request headers and constructed user input such as query string variables. Consider the application to be a black box and any data entering it to be untrusted.
OWASP also includes a matrix describing the source, the exploit and the impact to business:
Threat Agents Attack Vectors Exploitability EASY
Consider anyone who can send untrusted data to the system, including external users, internal users, and administrators. Attacker sends simple text-based attacks that exploit the syntax of the targeted interpreter. Almost any source of data can be an injection vector, including internal sources.
Business Impact
Injection flaws occur when an application sends untrusted data to an interpreter. Injection flaws are very prevalent, particularly in legacy code, often found in SQL queries, LDAP queries, XPath queries, OS commands, program arguments, etc. Injection flaws are easy to discover when examining code, but more difficult via testing. Scanners and fuzzers can help attackers find them.
Consider the business value of the affected data and the platform running the interpreter. All data could be stolen, modified, or deleted. Could your reputation be harmed?
Most of you are probably familiar with the concept (or at least the term) of SQL injection but the injection risk is broader than just SQL and indeed broader than relational databases. As the weakness above explains, injection flaws can be present in technologies like LDAP or theoretically in any platform which that constructs queries from untrusted data.
For the sake of simplicity and illustration, lets assume were going to construct a SQL statement in C# using a parameter passed in a query string and bind the output to a grid view. In this case its the good old Northwind database driving a product page filtered by the beverages category which happens to be category ID 1. The web application has a link directly to the page where the CategoryID parameter is passed through in a query string. Heres a snapshot of what the Products and Customers (well get to this one) tables look like:
In this scenario, the CategoryID query string is untrusted data. We assume it is properly formed and we assume it represents a valid category and we consequently assume the requested URL and the sqlString variable end up looking exactly like this (Im going to highlight the untrusted data in red and show it both in the context of the requested URL and subsequent SQL statement):
Products.aspx?CategoryID=1 SELECT * FROM Products WHERE CategoryID = 1
Of course much has been said about assumption. The problem with the construction of this code is that by manipulating the query string value we can arbitrarily manipulate the command executed against the database. For example:
Products.aspx?CategoryID=1 or 1=1 SELECT * FROM Products WHERE CategoryID = 1 or 1=1
Obviously 1=1 always evaluates to true so the filter by category is entirely invalidated. Rather than displaying only beverages were now displaying products from all categories. This is interesting, but not particularly invasive so lets push on a bit:
Products.aspx?CategoryID=1 or name=''
When this statement runs against the Northwind database its going to fail as the Products table has no column called name. In some form or another, the web application is going to return an error to the user. It will hopefully be a friendly error message contextualised within the layout of the website but at worst it may be a yellow screen of death. For the purpose of where were going with injection, it doesnt really matter as just by virtue of receiving some form of error message weve quite likely disclosed information about the internal structure of the application, namely that there is no column called name in the table(s) the query is being executed against. Lets try something different:
Products.aspx?CategoryID=1 or productname='' SELECT * FROM Products WHERE CategoryID = 1 or productname=''
This time the statement will execute successfully because the syntax is valid against Northwind so we have therefore confirmed the existence of the ProductName column. Obviously its easy to put this example together with prior knowledge of the underlying data schema but in most cases data models are not particularly difficult to guess if you understand a little bit about the application theyre driving. Lets continue:
Products.aspx?CategoryID=1 or 1=(select count(*) from products) SELECT * FROM Products WHERE CategoryID = 1 or 1=(select count(*) from products)
With the successful execution of this statement we have just verified the existence of the Products tables. This is a pretty critical step as it demonstrates the ability to validate the existence of individual tables in the database regardless of whether they are used by the query driving the page or not. This disclosure is starting to become serious information leakage we could potentially leverage to our advantage.
So far weve established that SQL statements are being arbitrarily executed based on the query string value and that there is a table called Product with a column called ProductName. Using the techniques above we could easily ascertain the existence of the Customers table and the CompanyName column by fairly assuming that an online system facilitating ordering may contain these objects. Lets step it up a notch:
Products.aspx?CategoryID=1;update products set productname = productname SELECT * FROM Products WHERE CategoryID = 1;update products set productname = productname
The first thing to note about the injection above is that were now executing multiple statements. The semicolon is terminating the first statement and allowing us to execute any statement we like afterwards. The second really important observation is that if this page successfully loads and returns a list of beverages, we have just confirmed the ability to write to the database. Its about here that the penny usually drops in terms of understanding the potential ramifications of injection vulnerabilities and why OWASP categorises the technical impact as severe. All the examples so far have been non-destructive. No data has been manipulated and the intrusion has quite likely not been detected. Weve also not disclosed any actual data from the application, weve only established the schema. Lets change that.
Products.aspx?CategoryID=1;insert into products(productname) select companyname from customers SELECT * FROM Products WHERE CategoryID = 1;insert into products (productname) select companyname from customers
So as with the previous example, were terminating the CategoryID parameter then injecting a new statement but this time were populating data out of the Customers table. Weve already established the existence of the tables and columns were dealing with and that we can write to the Products table so this statement executes beautifully. We can now load the results back into the browser:
Products.aspx?CategoryID=500 or categoryid is null SELECT * FROM Products WHERE CategoryID = 500 or categoryid is null
The unfeasibly high CategoryID ensures existing records are excluded and we are making the assumption that the ID of new records defaults to null (obviously no default value on the
column in this case). Heres what the browser now discloses note the company name of the customer now being disclosed in the ProductName column:
Just this one piece of simple validation has a major impact on the security of the code. It immediately renders all the examples further up completely worthless in that none of the malicious CategoryID values match the regex and the program will exit before any SQL execution occurs.
An integer is a pretty simple example but the same principal applies to other data types. A registration form, for example, might expect a first name form field to be provided. The whitelist rule for this field might specify that it can only contain the letters a-z and common punctuation characters (be careful with this there are numerous characters outside this range that commonly appear in names), plus it must be within 30 characters of length. The more constraints that can be placed around the whitelist without resulting in false positives, the better. Regular expression validators in ASP.NET are a great way to implement field level whitelists as they can easily provide both client side (which is never sufficient on its own) and server side validation plus they tie neatly into the validation summary control. MSDN has a good overview of how to use regular expressions to constrain input in ASP.NET so all you need to do now is actually understand how to write a regex. Finally, no input validation story is complete without the infamous Bobby Tables:
CREATE PROCEDURE GetProducts @CategoryID INT AS SELECT * FROM dbo.Products WHERE CategoryID = @CategoryID GO GRANT EXECUTE ON GetProducts TO NorthwindUser GO
There are a couple of native defences in this approach. Firstly, the parameter must be of integer type or a conversion error will be raised when the value is passed. Secondly, the context of what this procedure and by extension the invoking page can do is strictly defined and secured directly to the named user. The broad reader and writer privileges which were earlier granted in order to execute the dynamic SQL are no longer needed in this context. Moving on the .NET side of things:
var conn = new SqlConnection(connString); using (var command = new SqlCommand("GetProducts", conn)) { command.CommandType = CommandType.StoredProcedure; command.Parameters.Add("@CategoryID", SqlDbType.Int).Value = catID; command.Connection.Open(); grdProducts.DataSource = command.ExecuteReader(); grdProducts.DataBind(); }
This is a good time to point out that parameterised stored procedures are an additional defence to parsing untrusted data against a whitelist. As we previously saw with the INT data type declared on the stored procedure input parameter, the command parameter declares the data type and if the catID string wasnt an integer the implicit conversion would throw a System.FormatException before even touching the data layer. But of course that wont do you any good if the type is already a string! Just one final point on stored procedures; passing a string parameter and then dynamically constructing and executing SQL within the procedure puts you right back at the original dynamic SQL vulnerability. Dont do this!
What this will give us is a piece of SQL that looks like this:
exec sp_executesql N'SELECT * FROM Products WHERE CategoryID = @CategoryID',N'@CategoryID int',@CategoryID=1
There are two key things to observe in this statement: 1. The sp_executesql command is invoked 2. The CategoryID appears as a named parameter of INT data type This statement is only going to execute if the account has data reader permissions to the Products table so one downside of this approach is that were effectively back in the same data layer security model as we were in the very first example. Well come to securing this further shortly.
The last thing worth noting with this approach is that the sp_executesql command also provides some query plan optimisations which although are not related to the security discussion, is a nice bonus.
LINQ to SQL
Stored procedures and parameterised queries are a great way of seriously curtailing the potential damage that can be done by SQL injection but they can also become pretty unwieldy. The case for using ORM as an alternative has been made many times before so I wont rehash it here but I will look at this approach in the context of SQL injection. Its also worthwhile noting that LINQ to SQL is only one of many ORMs out there and the principals discussed here are not limited purely to one of Microsofts interpretation of object mapping. Firstly, lets assume weve created a Northwind DBML and the data layer has been persisted into queryable classes. Things are now pretty simple syntax wise:
var dc = new NorthwindDataContext(); var catIDInt = Convert.ToInt16(catID); grdProducts.DataSource = dc.Products.Where(p => p.CategoryID == catIDInt); grdProducts.DataBind();
From a SQL injection perspective, once again the query string should have already been assessed against a whitelist and we shouldnt be at this stage if it hasnt passed. Before we can use the value in the where clause it needs to be converted to an integer because the DBML has persisted the INT type in the data layer and thats what were going to be performing our equivalency test on. If the value wasnt an integer wed get that System.FormatException again and the data layer would never be touched. LINQ to SQL now follows the same parameterised SQL route we saw earlier, it just abstracts the query so the developer is saved from being directly exposed to any SQL code. The database is still expected to execute what from its perspective, is an arbitrary statement:
exec sp_executesql N'SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued] FROM [dbo].[Products] AS [t0] WHERE [t0].[CategoryID] = @p0',N'@p0 int',@p0=1
There was some discussion about the security model in the early days of LINQ to SQL and concern expressed in terms of how it aligned to the prevailing school of thought regarding secure database design. Much of the reluctance related to the need to provide accounts connecting to SQL with reader and writer access at the table level. Concerns included the risk of SQL injection as well as from the DBAs perspective, authority over the context a user was able to operate in moved from their control namely within stored procedures to the application developers control. However with parameterised SQL being generated and the application developers now being responsible for controlling user context and access rights it was more a case of moving cheese than any new security vulnerabilities.
This is a good case for being a little more selective about the accounts were using and the rights they have. Quite frequently, a single SQL account is used by the application. The problem this introduces is that the one account must have access to perform all the functions of the application which most likely includes reading and writing data from and to tables you simply dont want everyone accessing.
Lets go back to the first example but this time well create a new user with only select permissions to the Products table. Well call this user NorthwindPublicUser and it will be used by activities intended for the general public, i.e. not administrative activates such as managing customers or maintaining products.
Now lets go back to the earlier request attempting to validate the existence of the Customers table:
Products.aspx?CategoryID=1 or 1=(select count(*) from customers)
In this case Ive left custom errors off and allowed the internal error message to surface through the UI for the purposes of illustration. Of course doing this in a production environment is never a good thing not only because its information leakage but because the original objective of verifying the existence of the table has still been achieved. Once custom errors are on therell be no external error message hence there will be no verification the table exists. Finally and most importantly - once we get to actually trying to read or write unauthorised data the exploit will not be successful. This approach does come with a cost though. Firstly, you want to be pragmatic in the definition of how many logons are created. Ending up with 20 different accounts for performing different functions is going to drive the DBA nuts and be unwieldy to manage. Secondly, consider the impact on connection pooling. Different logons mean different connection strings which mean different connection pools. On balance, a pragmatic selection of user accounts to align to different levels of access is a good approach to the principle of least privilege and shuts the door on the sort of exploit demonstrated above.
Note the preference Firefox has delivered in this case is en-gb. The developer can now access this attribute in code:
var language = HttpContext.Current.Request.UserLanguages[0]; lblLanguage.Text = "The browser language is: " + language;
The language is often used to localise content on the page for applications with multilingual capabilities. The variable weve assigned above may be passed to SQL Server possibly in a concatenated SQL string - should language variations be stored in the data layer.
But what if a malicious request header was passed? What if, for example, we used the Fiddler Request Builder to reissue the request but manipulated the header ever so slightly first:
Weve looked enough at where an exploit can go from here already, the main purpose of this section was to illustrate how injection can take different attack vectors in its path to successful execution. In reality, .NET has far more efficient ways of doing language localisation but this just goes to prove that vulnerabilities can be exposed through more obscure channels.
Summary
The potential damage from injection exploits is indeed, severe. Data disclosure, data loss, database object destruction and potentially limitless damage to reputation. The thing is though, injection is a really easy vulnerability to apply some pretty thorough defences against. Fortunately its uncommon to see dynamic, parameterless SQL strings constructed in .NET code these days. ORMs like LINQ to SQL are very attractive from a productivity perspective and the security advantages that come with it are eradicating some of those bad old practices. Input parsing, however, remains a bit more elusive. Often developers are relying on type conversion failures to detect rogue values which, of course, wont do much good if the expected type is already a string and contains an injection payload. Were going to come back to input parsing again in the next part of the series on XSS. For now, lets just say that not parsing input has potential ramifications well beyond just injection vulnerabilities.
I suspect securing individual database objects to different accounts is not happening very frequently at all. The thing is though, its the only defence you have at the actual data layer if youve moved away from stored procedures. Applying the least privilege principle here means that in conjunction with the other measures, youve now erected injection defences on the input, the SQL statement construction and finally at the point of its execution. Ticking all these boxes is a very good place to be indeed.
References
1. 2. 3. 4. SQL Injection Attacks by Example SQL Injection Cheat Sheet The Curse and Blessings of Dynamic SQL LDAP Injection Vulnerabilities
But is XSS really that threatening? Isnt it just a tricky way to put alert boxes into random websites by sending someone a carefully crafted link? No, its much, much more than that. Its a serious vulnerability that can have very broad ramifications.
Defining XSS
Lets go back to the OWASP definition: XSS flaws occur whenever an application takes untrusted data and sends it to a web browser without proper validation and escaping. XSS allows attackers to execute scripts in the victims browser which can hijack user sessions, deface web sites, or redirect the user to malicious sites. So as with the injection vulnerability, were back to untrusted data and validation again. The main difference this time around is that theres a dependency on leveraging the victims browser for the attack. Heres how it manifests itself and what the downstream impact is:
Threat Agents Attack Vectors Security Weakness Technical Impacts Business Impact
As with the previous description about injection, the attack vectors are numerous but XSS also has the potential to expose an attack vector from a database, that is, data already stored within the application. This adds a new dynamic to things because it means the exploit can be executed will after a system has already been compromised.
normally a request for the browser to perform an activity outside the intended scope of the web application. In the context of security, this will often be an event with malicious intent. Heres the use case were going to work with: Our sample website from part 1 has some links to external sites. The legal folks want to ensure there is no ambiguity as to where this website ends and a new one begins, so any external links need to present the user with a disclaimer before they exit. In order to make it easily reusable, were passing the URL via query string to a page with the exit warning. The page displays a brief message then allows the user to continue on to the external website. As I mentioned in part 1, these examples are going to be deliberately simple for the purpose of illustration. Im also going to turn off ASP.NET request validation and Ill come back around to why a little later on. Heres how the page looks:
You can see the status bar telling us the link is going to take us off to http://www.asp.net/ which is the value of the Url parameter in the location bar. Code wise its pretty simple with the ASPX using a literal control:
<p>You are now leaving this site - we're no longer responsible!</p> <p><asp:Literal runat="server" ID="litLeavingTag" /></p>
This works beautifully plus its simple to build, easy to reuse and seemingly innocuous in its ability to do any damage. Of course we should have used a native hyperlink control but this approach makes it a little easier to illustrate XSS. So what happens if we start manipulating the data in the query string and including code? Im going to just leave the query string name and value in the location bar for the sake of succinctness, look at what happens to the continue link now:
It helps when you see the parameter represented in context within the HTML:
<p><a href=http://www.asp.net>xss>continue</a></p>
So whats happened is that weve managed to close off the opening <a> tag and add the text xss by ending the hyperlink tag context and entered an all new context. This is referred to as injecting up.
The code then attempts to close the tag again which is why we get the greater than symbol. Although this doesnt appear particularly threatening, what weve just done is manipulated the markup structure of the page. This is a problem, heres why:
Whoa! What just happened? Weve lost the entire header of the website! By inspecting the HTML source code of the page I was able to identify that a CSS style called header is applied to the entire top section of the website. Because my query string value is being written verbatim to the source code I was able to pass in a redefined header which simply turned it off. But this is ultimately just a visual tweak, lets probe a little further and attempt to actually execute some code in the browser:
Lets pause here because this is where the penny usually drops. What we are now doing is actually executing arbitrary code JavaScript in this case inside the victims browser and well
outside the intended scope of the application simply by carefully constructing the URL. But of course from the end users perspective, they are browsing a legitimate website on a domain they recognise and its throwing up a JavaScript message box. Message boxes are all well and good but lets push things into the realm of a truly maliciously formed XSS attack which actually has the potential to do some damage:
[ click to enlarge ]
Inspecting the HTML source code disclosed the ID of the log in link and it only takes a little bit of JavaScript to reference the object and change the target location of the link. What weve got now is a website which, if accessed by the carefully formed URL, will cause the log in link to take the user to an arbitrary website. That website may then recreate the branding of the original (so as to keep up the charade) and include username and password boxes which then save the credentials to that site. Bingo. User credentials now stolen.
This example was made easier because of the native framework validation for the URL. Of course there are many examples where you do need to get your hands a little dirtier and actually write a regex against an expected pattern. It may be to validate an integer, a GUID (although of course we now have a native Guid.TryParse in .NET 4) or a string value that needs to be within an accepted range of characters and length. The stricter the whitelist is without returning false positives, the better. The other thing Ill touch on again briefly in this post is that the validate all input mantra really does mean all input. Weve been using query strings but the same rationale applies to form data, cookies, HTTP headers etc, etc. If its untrusted and potentially malicious, it gets validated before doing anything with it.
Request validation is the .NET frameworks native defence against XSS. Unless explicitly turned off, all ASP.NET web apps will look for potentially malicious input and throw the error above along with an HTTP 500 if detected. So without writing a single line of code, the XSS exploits we attempted earlier on would never occur. However, there are times when request validation is too invasive. Its an effective but primitive control which operates by looking for some pretty simple character patterns. But what if one of those character patterns is actually intended user input?
A good use case here is rich HTML editors. Often these are posting markup to the server (some of them will actually allow you to edit the markup directly in the browser) and with request validation left on the post will never process. Fortunately though, we can turn off the validation within the page directive of the ASPX:
<%@ Page Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="LeavingSite.aspx.cs" Inherits="Web.LeavingSite" Title="Leaving Site" ValidateRequest="false" %>
Alternatively, request validation can be turned off across the entire site within the web.config:
<pages validateRequest="false" />
Frankly, this is simply not a smart idea unless there is a really good reason why youd want to remove this safety net from every single page in the site. I wrote about this a couple of months back in Request Validation, DotNetNuke and design utopia and likened it to turning off the electronic driver aids in a high performance car. Sure, you can do it, but youd better be damn sure you know what youre doing first. Just a quick note on ASP.NET 4; the goalposts have moved a little. The latest framework version now moves the validation up the pipeline to before the BeginRequest event in the HTTP request. The good news is that the validation now also applies to HTTP requests for resources other than just ASPX pages, such as web services. The bad news is that because the validation is happening before the page directive is parsed, you can no longer turn it off at the page level whilst running in .NET 4 request validation mode. To be able to disable validation we need to ask the web.config to regress back to 2.0 validation mode:
<httpRuntime requestValidationMode="2.0" />
The last thing Ill say on request validation is to try and imagine its not there. Its not an excuse not to explicitly validate your input; its just a safety net for if you miss a fundamental piece of manual validation. The DotNetNuke example above is a perfect illustration of this; it ran for quite some time with a fairly serious XSS flaw in the search page but it was only exploitable because they'd turned off request validation site wide. Dont turn off .NET request validation anywhere unless you absolutely have to and even then, only do it on the required pages.
This is a pretty common scene; enter your name and email and youll get a friendly, personalised response when youre done. The problem is, oftentimes that string in the thank you message is just the input data directly rewritten to the screen:
var name = txtName.Text; var message = "Thank you " + name; lblSignupComplete.Text = message;
This means we run the risk of breaking out of the data context and entering the code context, just like this:
Given the output context is a web page, we can easily encode for HTML:
var name = Server.HtmlEncode(txtName.Text); var message = "Thank you " + name; lblSignupComplete.Text = message;
Which will give us a totally different HTML syntax with the tags properly escaped:
Thank you Troy <i>Hunt</i>
And consequently we see the name being represented in the browser precisely as it was entered into the field:
So the real XSS defence here is that any text entered into the name field will now be rendered precisely in the UI, not precisely in the code. If we tried any of the strings from the earlier exploits, theyd fail to offer any leverage to the attacker. Output encoding should be performed on all untrusted data but its particularly important on free text fields where any whitelist validation has to be fairly generous. There are valid use cases for allowing angle brackets and although a thorough regex should exclude attempts to manufacture HTML tags, the output encoding remains invaluable insurance at a very low cost.
One thing you need to keep in mind with output encoding is that it should be applied to untrusted data at any stage in its lifecycle, not just at the point of user input. The example above would quite likely store the two fields in a database and redisplay them at a later date. The data might be exposed again through an administration layer to monitor subscriptions or the name could be included in email notifications. This is persisted or stored XSS as the attack is actually stored on the server so every single time this data is resurfaced, it needs to be encoded again.
Lets try this with the italics example from earlier on:
Obviously this isnt what we want to see as encoded HTML simply doesnt play nice with JavaScript they both have totally different encoding syntaxes. Of course it could also get a lot worse; the characters that could be leveraged to exploit JavaScript are not necessarily going to be caught by HTML encoding at all and if they are, they may well be encoded into values not suitable in the JavaScript context. This brings us to the Anti-XSS library.
Anti-XSS
JavaScript output encoding is a great use case for the Microsoft Anti-Cross Site Scripting Library also known as Anti-XSS. This is a CodePlex project with encoding algorithms for HTML, XML, CSS and of course, JavaScript.
A fundamental difference between the encoding performed by Anti-XSS and that done by the native HtmlEncode method is that the former is working against a whitelist whilst the latter to a blacklist. In the last post I talked about the differences between the two and why the whitelist approach is the more secure route. Consequently, the Anti-XSS library is a preferable choice even for HTML encoding. Moving onto JavaScript, lets use the library to apply proper JavaScript encoding to the previous example:
var name = AntiXss.JavaScriptEncode(txtName.Text, false); var message = "Thank you " + name; var alertScript = "<script>alert('" + message + "');</script>"; ClientScript.RegisterClientScriptBlock(GetType(), "ThankYou", alertScript);
Well now find a very different piece of syntax to when we were encoding for HTML:
<script>alert('Thank you Troy \x3ci\x3eHunt\x3c\x2fi\x3e');</script>
And well actually get a JavaScript alert containing the precise string entered into the textbox:
Using an encoding library like Anti-XSS is absolutely essential. The last thing you want to be doing is manually working through all the possible characters and escape combinations to try and write your own output encoder. Its hard work, it quite likely wont be comprehensive enough and its totally unnecessary. One last comment on Anti-XSS functionality; as well as output encoding, the library also has functionality to render safe HTML by removing malicious scripts. If, for example, you have an application which legitimately stores markup in the data layer (could be from a rich text editor), and it is to be redisplayed to the page, the GetSafeHtml and GetSafeHtmlFragment methods will sanitise the data and remove scripts. Using this method rather than HtmlEncode means hyperlinks, text formatting and other safe markup will functionally render (the behaviours will work) whilst the nasty stuff is stripped.
SRE
Another excellent component of the Anti-XSS product is the Security Runtime Engine or SRE. This is essentially an HTTP module that hooks into the pre-render event in the page lifecycle and encodes server controls before they appear on the page. You have quite granular control over which controls and attributes are encoded and its a very easy retrofit to an existing app.
Firstly, we need to add the AntiXssModule reference alongside our existing AntiXssLibrary reference. Next up well add the HTTP module to the web.config:
<httpModules> <add name="AntiXssModule" type="Microsoft. Security.Application.SecurityRuntimeEngine.AntiXssModule"/> </httpModules>
The final step is to create an antixssmodule.config file which maps out the controls and attributes to be automatically encoded. The Anti-XSS installer gives you the Configuration Generator for SRE which helps automate the process. Just point it at the generated website assembly and it will identify all the pages and controls which need to be mapped out:
The generate button will then allow you to specify a location for the config file which should be the root of the website. Include it in the project and take a look:
<Configuration> <ControlEncodingContexts> <ControlEncodingContext FullClassName="System.Web.UI.WebControls.Label" PropertyName="Text" EncodingContext="Html" />
</ControlEncodingContexts> <DoubleEncodingFilter Enabled="True" /> <EncodeDerivedControls Enabled="True" /> <MarkAntiXssOutput Enabled="False" Color="Yellow" /> </Configuration>
Ive removed a whole lot of content for the purpose of demonstration. Ive left in the encoding for the text attribute of the label control and removed the 55 other entries that were created based on the controls presently being used in the website. If we now go right back to the first output encoding demo we can run the originally vulnerable code which didnt have any explicit output encoding:
var name = txtName.Text; var message = "Thank you " + name; lblSignupComplete.Text = message;
And hey presto, well get the correctly encoded output result:
This is great because just as with request validation, its an implicit defence which looks after you when all else fails. However, just like request validation you should take the view that this is only a safety net and doesnt absolve you of the responsibility to explicitly output encode your responses. SRE is smart enough not to double-encode so you can happily run explicit and implicit encoding alongside each other. It will also do other neat things like apply encoding on control attributes derived from the ones youve already specified and allow encoding suppression on specific pages or controls. Finally, its a very easy retrofit to existing apps as its a no-code solution. This is a pretty compelling argument for people trying to patch XSS holes without investing in a lot of re-coding.
URL written to href attribute of Yes <a> tag Name written to HTML N/A Yes N/A
This is a great little model to apply to new app development but its also an interesting one to run over existing ones. Try mapping out the flow of your data in the format and see if it makes it back out to a UI without proper encoding. If the XSS stats are to be believed, youll probably be surprised by the outcome.
Another factor allowing a lot of potential for XSS to slip through is URL shorteners. The actual address behind http://bit.ly/culCJi is usually not disclosed until actually loaded into the browser. Obviously this activity alone can deliver the payload and the victim is none the wiser until its already loaded (if they even realise then). This section wouldnt be complete without at least mentioning social engineering. Constructing malicious URLs to exploit vulnerable sites is one thing, tricking someone into following them is quite another. However the avenues available to do this are almost limitless; spam mail, phishing attempts, social media, malware and so on and so on. Suffice to say the URL needs to be distributed and there are ample channels available to do this. The reality is the payload can be delivered through following a link from just about anywhere. But of course the payload is only of value when the application is vulnerable. Loaded URLs manipulated with XSS attacks are worthless without a vulnerable target.
This particular implementation is not without its issues though. There are numerous examples of where the filter doesnt quite live up to expectations and can even open new vulnerabilities which didnt exist in the first place. However, the action taken by browser manufacturers is really incidental to the action required by web application developers. Even if IE8 implemented a perfect XSS filter model wed still be looking at many years before older, more vulnerable browsers are broadly superseded. Given more than 20% of people are still running IE6 at the time of writing, now almost a 9 year old browser, were in for a long wait before XSS is secured in the client.
Summary
We have a bit of a head start with ASP.NET because its just so easy to put up defences against XSS either using the native framework defences or with freely available options from Microsoft. Request validation, Anti-XSS and SRE are all excellent and should form a part of any security conscious .NET web app. Having said that, none of these absolve the developer from proactively writing secure code. Input validation, for example, is still absolutely essential and its going to take a bit of effort to get right in some circumstances, particularly in writing regular expression whitelists.
However, if youre smart about it and combine the native defences of the framework with securely coded application logic and apply the other freely available tools discussed above, youll have a very high probability of creating an application secure from XSS.
Resources
1. XSS Cheat Sheet 2. Microsoft Anti-Cross Site Scripting Library V1.5: Protecting the Contoso Bookmark Page 3. Anti-XSS Library v3.1: Find, Fix, and Verify Errors (Channel 9 video) 4. A Sneak Peak at the Security Runtime Engine 5. XSS (Cross Site Scripting) Prevention Cheat Sheet
Business Impact
Developers frequently build custom authentication and session management schemes, but building these correctly is hard. As a result, these custom schemes frequently have flaws in areas such as logout, password management, timeouts, remember me, secret question, account update, etc. Finding such flaws can sometimes be difficult, as each implementation is unique.
Consider the business value of the affected data or application functions. Also consider the business impact of public exposure of the vulnerability.
The first thing youll notice in the info above is that this risk is not as clearly defined as something like injection or XSS. In this case, the term broken is a bit of a catch-all which defines a variety of different vulnerabilities, some of which are actually looked at explicitly and in depth within some of the other Top 10 such as transport layer security and cryptographic storage.
Persistence between requests is equally simple: ASP maintains session state by providing the client with a unique key assigned to the user when the session begins. This key is stored in an HTTP cookie that the client sends to the server on each request. The server can then read the key from the cookie and re-inflate the server session state. So cookies help persist the session by passing it to the web server on each request, but what happens when cookies arent available (theres still a school of belief by some that cookies are a threat to privacy)? Most commonly, well see session IDs persisted across requests in the URL. ASP.NET even has the capability to do this natively using cookieless session state. Before looking at the cookieless session approach, lets look at how ASP.NET handles things natively. Say we have a really, really basic logon page:
The simple version of whats happening is as follows (its easy to imagine the ASPX structure so Ill include only the code-behind here):
var username = txtUsername.Text; var password = txtPassword.Text; // Assume successful authentication against an account source... Session["Username"] = username; pnlLoginForm.Visible = false; pnlLoginSuccessful.Visible = true;
Were not too worried about how the user the user is being authenticated for this demo so lets just assume its been successful. The account holders username is getting stored in session state and if we go to Page 2 well see it being retrieved:
If we look at our cookies for this session (Cookies.aspx just enumerates all cookies for the site and outputs name value pairs to the page), heres what we see:
Because the data is stored in session state and because the session is specific to the clients browser and persisted via a cookie, well get nothing if we try hitting the path in another browser (which could quite possibly be on another machine):
Now lets make it more interesting; lets assume we want to persist the session via the URL rather than via cookies. ASP.NET provides a simple cookieless mode configuration via the web.config:
<system.web> <sessionState cookieless="true" /> </system.web>
Whoa! What just happened?! Check out the URL. As soon as we go cookieless, the very first request embeds the session ID directly into a re-written URL (sometimes referred to as URL mangling). Once we login, the link to Page 2 persists the session ID in the hyperlink (assuming its a link to a relative path):
Once we arrive at Page 2, the behaviour is identical to the cookie based session implementation:
Heres where everything starts to go wrong; if we take the URL for Page 2 complete with session ID and fire it up another browser, heres what happens:
MinRequiredNonAlphanumericCharacters Gets the minimum number of special characters that must be present in a valid password. MinRequiredPasswordLength PasswordAttemptWindow PasswordStrengthRegularExpression Provider Gets the minimum length required for a password. Gets the time window between which consecutive failed attempts to provide a valid password or password answer are tracked. Gets the regular expression used to evaluate a password. Gets a reference to the default membership provider for the application.
Gets a collection of the membership providers for the ASP.NET application. Gets a value indicating whether the default membership provider requires the user to answer a password question for password reset and retrieval. Specifies the number of minutes after the last-activity date/time stamp for a user during which the user is considered online.
Using the native .NET implementation also means controls such as the LoginView are available. This is a great little feature as it takes a lot of the legwork and potential for insecure implementations out of the process. Heres how it looks straight out of the box in a new ASP.NET Web Application template:
<asp:LoginView ID="HeadLoginView" runat="server" EnableViewState="false"> <AnonymousTemplate> [ <a href="~/Account/Login.aspx" id="HeadLoginStatus" runat="server">Log In</a> ] </AnonymousTemplate> <LoggedInTemplate> Welcome <span class="bold"><asp:LoginName ID="HeadLoginName" runat="server" /></span>! [ <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Log Out" LogoutPageUrl="~/" /> ] </LoggedInTemplate> </asp:LoginView>
Beyond the LoginView control theres also a series of others available right out of the box (see the Visual Studio toolbox to the right). These are all pretty common features used in many applications with a login facility and in times gone by, these tended to be manually coded. The things is, now that we have these controls which are so easily implemented and automatically integrate with the customisable role provider, there really arent any good reasons not to use them.
The important message here is that .NET natively implements a great mechanism to authenticate your users and control the content they can access. Dont attempt to roll your own custom authentication and session management schemes or build your own controls; Microsoft has done a great job with theirs and by leveraging the provider model have given you the means to tailor it to suit your needs. Its been done right once dont attempt to redo it yourself without very good reason!
The shorter the session expiration, the shorter the window where an exploit can occur. Of course this also increases the likelihood of a session expiring before the user would like (they stopped browsing to take a phone call or grab some lunch), and forcing users to re-authenticate does have a usability impact. The session timeout can be manually adjusted back in the web.config. Taking into consideration the balance of security and usability, an arbitrary timeout such as 10 minutes may be selected:
<system.web> <sessionState timeout="10" /> </system.web>
Of course there are also times when we want to expire the session much earlier than even a few minutes of inactivity. Giving users the ability to elect when their session expires by manually logging out gives them the opportunity to reduce their session risk profile. This is important whether youre running cookieless session or not, especially when you consider users on a shared PC. Using the LoginView and LoginStatus controls mentioned earlier on makes this a piece of cake. In a similar strain to session timeouts, you dont want to be reusing session IDs. ASP.NET wont do this anyway unless you change SessionStateSection.RegenerateExpiredSessionId to true and youre running cookieless. The session timeout issue is interesting because this isnt so much a vulnerability in the technology as it is a risk mitigation strategy independent of the specific implementation. In this regard Id like to reinforce two fundamental security concepts that are pervasive right across this blog series: 1. App security is not about risk elimination, its about risk mitigation and balancing this with the practical considerations of usability and project overhead. 2. Not all app security measures are about plugging technology holes; encouraging good social practices is an essential component of secure design.
1. In storage via persistent encryption at the data layer, preferably as a salted hash. 2. During transit via the proper use of SSL. Both of these will be addressed in subsequent posts Insecure Cryptographic Storage and Insufficient Transport Layer Protection respectively so I wont be drilling down into them in this post. Suffice to say, any point at which passwords are not encrypted poses a serious risk to broken authentication.
This is a piece of cake in the ASP.NET world as we have the PasswordStrength control in the AJAX Control Toolkit:
<asp:TextBox ID="txtPassword" runat="server" TextMode="Password" /> <ajaxToolkit:PasswordStrength ID="PS" runat="server" TargetControlID="txtPassword" DisplayPosition="RightSide" StrengthIndicatorType="Text" PreferredPasswordLength="10" PrefixText="Strength:" TextCssClass="TextIndicator_txtPassword" MinimumNumericCharacters="0" MinimumSymbolCharacters="0"
Of course this alone wont enforce password strength but it does make compliance (and above) a little easier. For ensuring compliance, refer back to the MinRequiredNonAlphanumericCharacters, MinRequiredPasswordLength and PasswordStrengthRegularExpression properties of the membership provider. Beyond the strength of passwords alone, theres also the issue of secret questions and their relative strength. Theres mounting evidence to suggest this practice often results in questions that are too easily answered but rather than entering into debate as to whether this practice should be used at all, lets look at whats required to make it as secure as possible. Firstly, avoid allowing users to create their own. Chances are youll end up with a series of very simple, easily guessed questions based on information which may be easily accessible (the Sarah Palin incident from a couple of years back is a perfect example). Secondly, when creating default secret questions and youll need a few to choose from - dont fall for the same trap. Questions such as Whats your favourite colour are too limited in scope and Where did you go to school can easily be discovered via social networking sites. Ideally you want to aim for questions which result in answers with the highest possible degree of precision, are stable (they dont change or are forgotten over time) and have the broadest possible range of answers which would be known and remembered - by the narrowest possible audience. A question such as What was the name of your favourite childhood toy is a good example.
What this leaves us with is password resets. Im going to delve into this deeper in a dedicated password recovery post later on but for now, lets work to the following process: 1. Initiate the reset process by requesting the username and secret answer (to the secret question, of course!) of the account holder. 2. Provide a mechanism for username recovery by entering only an email address. Email the result of a recovery attempt to the address entered, even if it wasnt a valid address. Providing an immediate confirmation response via the UI opens up the risk of email harvesting for valid users of the system. 3. Immediately suspend the ability to login with the existing password once the reset process has been initiated. 4. Email a unique, tokenised URL rather than generating a password. Ensure the URL is unique enough not to be guessed, such as a GUID specific to this instance of the password reset. 5. Allow the URL to be used only once and only within a finite period of time, such as an hour, to ensure it is not reused. 6. Apply the same password strength rules (preferably reuse the existing, secure process) when creating the new password. 7. Email a notification to the account holder immediately once the change is complete. Obviously do not include the new password in this email! 8. Dont automatically log the user in once the password is changes. Divert them to the login page and allow them to authenticate as usual, albeit with their new password. This may seem a little verbose but its a minor inconvenience for users engaging in a process which should happen very infrequently. Doing password recovery wrong is a recipe for disaster; it could literally serve up credentials to an attacker on a silver plate. In terms of implementation, once again the membership provider does implement an EnablePasswordReset property and a RequiresQuestionAndAnswer property which can be leveraged to achieve the reset functionality.
The ability to remember credentials or automate the logon process is a convenience. It takes out a little of the manual labour the user would otherwise perform and hopefully lowers that barrier to them frequently returning just a little bit. The problem is though, that convenience cuts both ways because that same convenience may now be leveraged by malicious parties. So we come back around to this practical versus secure conundrum. The more secure route is to simply not implement a remember me feature on the website. This is a reasonable balance for, say, a bank where there could be serious dollars at stake. But then you have the likes of just about every forum out there plus Facebook, Twitter, YouTube etc who all do implement this feature simply because of the convenience and stickiness it offers. If youre going to implement this feature, do it right and use the native login control which will implement its own persistent cookie. Microsoft explains this feature well: By default, this control displays user name and password fields and a Remember me next time check box. If the user selects this check box, a persistent authentication cookie is created and the user's browser stores it on the user's hard disk. Then again, even they go on to warn about the dangers of a persistent cookie: To prevent an attacker from stealing an authentication cookie from the client's computer, you should generally not create persistent authentication cookies. To disable this feature, set the DisplayRememberMe property of the Login control to false. What you absolutely, positively dont want to be doing is storing credentials directly in the cookie and then pulling them out automatically on return to the site.
Automatic completion of credentials goes a little bit further than just what you implement in your app though. Consider the browsers ability to auto-complete form data. You really dont want login forms behaving like this:
Granted, this is only the username but consider the implications for data leakage on a shared machine. But of course beyond this we also have the browsers (or third party addons) desire to make browsing the site even easier with save your password style functionality:
Mozilla has a great summary of how to tackle this in How to Turn Off Form Autocompletion: The easiest and simplest way to disable Form and Password storage prompts and prevent form data from being cached in session history is to use the autocomplete form element attribute with value "off" Just turn off the autocomplete attribute at the form level and youre done:
<form id="form1" runat="server" autocomplete="off">
My app doesnt have any sensitive data does strong authentication matter?
Yes, it actually matters a lot. You see, your authentication mechanism is not just there to protect your data, it also must protect your customers identities. Identity and access management implementations which leak customer information such as their identities even just their email address are not going to shine a particularly positive light on your app. But the bigger problem is this; if your app leaks customer credentials you have quite likely compromised not only your own application, but a potentially unlimited number of other web applications. Let me explain; being fallible humans we have this terrible habit of reusing credentials in multiple locations. Youll see varying reports of how common this practice really is, but the assertion that 73% of people reuse logins would have to be somewhere in the right vicinity. This isnt your fault, obviously, but as software professionals we do need to take responsibly for mitigating the problem as best we can and beginning by keeping your customers credentials secure regardless of what theyre protecting is a very important first step.
Summary
This was never going to be a post with a single message resulting in easily actionable practices. Authentication is a very broad subject with numerous pitfalls and its very easy to get it wrong, or at least not get it as secure as it could - nay should - be. Having said that, there are three key themes which keep repeating during the post: 1. Consider authentication holistically and be conscious of its breadth. It covers everything from credential storage to session management. 2. Beware of the social implications of authentication people share computers, they reuse passwords, they email URLs. You need to put effort into protecting people from themselves. 3. And most importantly, leverage the native .NET authentication implementation to the full extent possible. Youll never, ever be 100% secure (heck, even the US military doesnt always get it right!), but starting with these objectives will make significant inroads into mitigating your risk.
Resources
1. Membership and Role Providers in ASP.NET 2.0 2. The OWASP Top Ten and ESAPI Part 8 Broken Authentication and Session Management 3. GoodSecurityQuestions.com (yes, theres actually a dedicated site for this!) 4. How To: Use Forms Authentication with SQL Server in ASP.NET 2.0 5. Session Attacks and ASP.NET
Lets look at how OWASP defines how people get in and exploit the vulnerability and what the impact of that might be:
Threat Agents Attack Vectors Exploitability EASY
Consider the types of users of your system. Do any users have only partial access to certain types of system data? Attacker, who is an authorized system user, simply changes a parameter value that directly refers to a system object to another object the user isnt authorized for. Is access granted?
Business Impact
Applications frequently use the actual name or key of an object when generating web pages. Applications dont always verify the user is authorized for the target object. This results in an insecure direct object reference flaw. Testers can easily manipulate parameter values to detect such flaws and code analysis quickly shows whether authorization is properly verified.
Consider the business value of the exposed data. Also consider the business impact of public exposure of the vulnerability.
This explanation talks a lot about parameters which are a key concept to understand in the context of direct object vulnerabilities. Different content is frequently accessible through the same implementation, such as a dynamic web page, but depending on the context of the parameters, different access rules might apply. Just because you can hit a particular web page doesnt mean you should be able to execute it in any context with any parameter.
sometimes of a sensitive nature. This creates an entirely new attack vector so its a good one to illustrate here. Heres how the page looks (Ive started out with the base Visual Studio 2010 web app hence the styling):
After clicking the button, the customer details are returned and written to the page:
Assuming were an outside party not (yet) privy to the internal mechanism of this process, lets do some discovery work. Im going to use Firebug Lite for Google Chrome to see if this is actually pulling data over HTTP or simply populating it from local variables. Hitting the button again exposes the following information in Firebug:
Here we can see that yes, the page is indeed making a post request to an HTTP address ending in CustomerService.svc/GetCustomer. We can also see that a parameter with the name customerId and value 3 is being sent with the post. If we jump over to the response tab, we start to see some really interesting info:
{"d":{"__type":"Customer:#Web","Address":"3 Childers St","CustomerID":3,"Email":"[email protected]","FirstName":"Bruce","Postcode":"3 000","State":"VIC","Suburb":"Melbourne"}}
Here we have a nice JSON response which shows that not only are we retrieving the customers name and email address, were also retrieving what appears to be a physical address. But so far, none of this is a problem. Weve legitimately logged on as a customer and have retrieved our own data. Lets try and change that.
What I now want to do is re-issue the same request but with a different customer ID. Im going to do this using Fiddler which is a fantastic tool for capturing and reissuing HTTP requests. First, Ill hit the Get my details button again and inspect the request:
Here we see the request to the left of the screen, the post data in the upper right and the response just below it. This is all consistent with what we saw in Firebug, lets now change that.
Ive flicked over to the Request Builder tab then dragged the request from the left of the screen onto it. What we now see is the request recreated in its entirety, including the customer ID. Im going to update this to 4:
With a new request now created, lets hit the Execute button then switch back to the inspectors view and look at the response:
When we look at the response, we can now clearly see a different customer has been returned complete with their name and address. Because the customer ID is sequential, I could easily script the request and enumerate through n records retrieving the private data of every customer in the system. Bingo. Confidential data exposed.
This exploit was made even easier by the fact that the customers ID was an integer; autoincrementing it is both logical and straight forward. Had the ID been a type that didnt hold predictable values, such as a GUID, it would have been a very different exercise as I would had to have known the other customers ID and could not have merely guessed it. Having said that, key types are not strictly what this risk sets out to address but its worth a mention anyway.
Add some intro text and a button to fire the service call:
<p>You can retrieve your customer details using the button below.</p> <input type="button" value="Get my details" onclick="return GetCustomer()" />
Note: the first parameter of the GetCustomer method is retrieved dynamically. The implementation behind the GetCustomerId method is not important in the context of this post, although it would normally be returned based on the identity of the logged on user. And finally, some controls to render the output to:
<div id="customerDetails" style="visibility: hidden;"> <h2>My details</h2> Name: <span id="customerName"></span><br /> Email: <span id="customerEmail"></span> </div>
No problems here, all of this is fine as were not actually doing any work with the customer details. What we want to do is take a look inside the customer service. Because we adhere to good service orientated architecture principals, were assuming the service is autonomous and not tightly coupled to any single implementation of it. As such, the authorisation work needs to happen within the service. The service is just a simple AJAX-enabled WCF service item in the ASP.NET web application project. Heres how it looks:
[OperationContract] public Customer GetCustomer(int customerId) { var dc = new InsecureAppDataContext(); return dc.Customers.Single(e => e.CustomerID == customerId); }
There are a number of different ways we could secure this; MSDN magazine has a nice overview of Authorisation in WCF-Based Services which is a good place to start. There are a variety of elegant mechanisms available closely integrated with the authorisation model of ASP.NET pages but rather than going down that route and introducing the membership provider into this post, lets just look at a bare basic implementation:
[OperationContract] public Customer GetCustomer(int customerId) { if (!CanCurrentUserAccessCustomer(customerId)) { throw new UnauthorizedAccessException(); }
Thats it. Establish an identity, validate access rights then run the service otherwise bail them out. The implementation behind the CanCurrentUserAccessCustomer method is inconsequential, the key message is that there is a process validating the users right access the customer data before anything is returned.
Lets bring this back to our original app. Were going to map the original customer ID integer to a GUID, store the lookup in a dictionary and then persist it in a session variable. The data type isnt particularly important so long as its unique, GUIDs just make it really easy to generate unique IDs. By keeping it in session state we keep the mapping only accessible to the current user and only in their current session. Well need two publicly facing methods; one to get a direct reference from the indirect version and another to do the reverse. Well also add a private method to create the map in the first place:
public static class IndirectReferenceMap { public static int GetDirectReference(Guid indirectReference) { var map = (Dictionary<Guid, int>)HttpContext.Current.Session["IndirMap"]; return map[indirectReference]; } public static Guid GetIndirectReference(int directReference) { var map = (Dictionary<int, Guid>)HttpContext.Current.Session["DirMap"]; return map == null ? AddDirectReference(directReference) : map[directReference]; } private static Guid AddDirectReference(int directReference) { var indirectReference = Guid.NewGuid(); HttpContext.Current.Session["DirMap"] = new Dictionary<int, Guid> { {directReference, indirectReference } }; HttpContext.Current.Session["IndirMap"] = new Dictionary<Guid, int> { {indirectReference, directReference } }; return indirectReference; } }
This is pretty fast and easy it wont handle scenarios such as trying the get the direct reference before the map is created or handle any other errors that occur but its a good, simple implementation to demonstrate the objective. All we need to do now is translate the reference backwards and forwards in the appropriate places.
First we create it when constructing the AJAX syntax to call the service (its now a GUID hence the encapsulation in quotes):
service.GetCustomer('<%= IndirectReferenceMap. GetIndirectReference(GetCustomerId()) %>', onSuccess, null, null);
Then we map it back in the service definition. We need to change the method signature (the ID is now a GUID), then translate it back to the original, direct reference before going any further:
public Customer GetCustomer(Guid indirectId) { var customerId = IndirectReferenceMap.GetDirectReference(indirectId);
Substituting the customerId parameter for any other value wont yield a result as its now an indirect reference which needs a corresponding map in the dictionary stored in session state. Even if the actual ID of another customer was known, nothing can be done about it.
was an integer and it was simply incremented then passed back to the service. The same could be said for natural keys being used as object references; if theyre discoverable, youre one step closer to an exploit. This approach could well be viewed as another example of security through obscurity and on its own, it would be. The access controls are absolutely essential and an indirect reference map is another valuable layer of defence. Non-discoverable references are a not a replacement for either of these. The fact is though, there are other good reasons for using object references such as GUIDs in a system design. There are also arguments against them (i.e. the number of bytes they consume), but where an application implements a globally unique reference pattern there is a certain degree of implicit security that comes along with it.
The final thing to understand from the iPad / AT&T incident is that as innocuous as it might seem, this is a serious breach with very serious repercussions. Yes, its only email addresses, but its disclosure is both an invasion of privacy and a potential risk to the persons identity in the future. If youre in any doubt of the seriousness of an event like this, this one sentence should put it in perspective: The FBI has confirmed that it has opened an investigation into the iPad breach and Gawker Media can confirm that it has been contacted by the agency.
have Information Leakage and Improper Error Handling but it didnt make the cut for 2010. In this post theres an observation from Jeremiah Grossman to the effect of: Information leakage is not a vulnerability, but the effects of an exploited vulnerability. Many of the OWASP Top 10 may lead to information leakage. The difference is probably a little semantic, at least in the context of demonstrating insecure code, as the effect is a critical driver for addressing the cause. The comments following the link above demonstrate sufficient contention that Im happy to sit on the fence, avoid the pigeon holing and simply talk about how to avoid it both the vulnerability and the fallout through writing secure code.
Summary
This risk is another good example of where security needs to get applied in layers as opposed to just a single panacea attempting to close the threat door in one go. Having said that, the core issue is undoubtedly the access control because once thats done properly, the other defences are largely redundant. The discoverable references suggestion is one of those religious debates where everyone has their own opinion on natural versus surrogate keys and when the latter is chosen, what type it should be. Personally, I love the GUID where its length is not prohibitive to performance or other aspects of the design because it has so many other positive attributes. As for indirect reference maps, theyre a great security feature, no doubt, Id just be a little selective about where theyre applied. Theres a strong argument for them in say, the banking sector, but Id probably skip the added complexity burden in less regulated environments in deference to getting that access control right. The reason things went wrong for the ATO and for AT&T is that they simply screwed up every single layer! If the Aussie tax office and the largest mobile carrier in the US can make this mistake, is it any wonder this risk is so pervasive?!
Resources
1. 2. 3. 4. ESAPI Access Reference Map Insecure Direct Object Reference Choosing a Primary Key: Natural or Surrogate? 10 Reasons Websites get hacked
A bunch of different sites all presently authenticated to and sitting idly by waiting for your next HTTP instruction to update your status, accept your credit card or email your friends. And then theres all those sites which, by virtue of the ubiquitous remember me checkbox, dont appear open in any browser sessions yet remain willing and able to receive instruction on your behalf. Now, remember also that HTTP is a stateless protocol and that requests to these sites could originate without any particular sequence from any location and assuming theyre correctly formed, be processed without the application being any the wiser. What could possibly go wrong?!
Heres how OWASP defines the attack and the potential ramifications:
Threat Agents Attack Vectors Security Weakness Detectability EASY Technical Impacts Impact MODERATE
Attackers can cause victims to change any data the victim is allowed to change or perform any function the victim is authorized to use. Consider the business value of the affected data or application functions. Imagine not being sure if users intended to take these actions. Consider the impact to your reputation.
Business Impact
CSRF takes advantage of web applications that allow attackers to predict all the details of a particular action. Since browsers send credentials like session cookies automatically, attackers can create malicious web pages which generate forged requests that are indistinguishable from legitimate ones. Detection of CSRF flaws is fairly easy via penetration testing or code analysis.
Theres a lot of talk about trickery going on here. Its actually not so much about tricking the user to issue a fraudulent request (their role can be very passive), rather its about tricking the browser and theres a whole bunch of ways this can happen. Weve already looked at XSS as a means of maliciously manipulating the content the browser requests but theres a whole raft of other ways this can happen. Im going to show just how simple it can be.
This is a pretty vanilla ASP.NET Web Application template with an application services database in which Ive registered as Troy. Once I successfully authenticate, heres what I see:
When I enter a new status value (something typically insightful for social media!), and submit it, theres an AJAX request to a WCF service which receives the status via POST data after which an update panel containing the grid view is refreshed:
From the perspective of an external party, all the information above can be easily discovered because its disclosed by the application. Using Fiddler we can clearly see the JSON POST data containing the status update:
And we can clearly see a series of additional JavaScript files required to tie it all together:
What we cant see externally (but could easily test for), is that the user must be authenticated in order to post a status update. Heres whats happening behind the WCF service:
[OperationContract] public void UpdateStatus(string statusUpdate) { if (!HttpContext.Current.User.Identity.IsAuthenticated) { throw new ApplicationException("Not logged on"); }
var dc = new VulnerableAppDataContext(); dc.Status.InsertOnSubmit(new Status { StatusID = Guid.NewGuid(), StatusDate = DateTime.Now, Username = HttpContext.Current.User.Identity.Name, StatusUpdate = statusUpdate }); dc.SubmitChanges(); }
This is a very plain implementation but it clearly illustrates that status updates only happen for users with a known identity after which the update is recorded directly against their username. On the surface of it, this looks pretty secure, but theres one critical flaw Lets create a brand new application which will consist of just a single HTML file hosted in a separate IIS website. Imagine this is a malicious site sitting anywhere out there on the web. Its totally independent of the original site. Well call the page Attacker.htm and stand it up on a separate site on port 84. What we want to do is issue a status update to the original site and the easiest way to do this is just to grab the relevant scripts from above and reconstruct the behaviour. In fact we can even trim it down a bit:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script src="http://localhost:85/ScriptResource.axd?d=4sSlXLx8QpYnLirlbD... <script src="http://localhost:85/ScriptResource.axd?d=oW55T29mrRoDmQ0h2E... <script src="http://localhost:85/StatusUpdateService.svc/jsdebug" type="... <script language="javascript" type="text/javascript"> // <![CDATA[ var service = new Web.StatusUpdateService(); var statusUpdate = "hacky hacky"; service.UpdateStatus(statusUpdate, null, null, null); // ]]> </script> </head>
Ultimately, this page is comprised of two external script resources and a reference to the WCF service, each of which is requested directly from the original site on port 85. All we need then is for the JavaScript to actually call the service. This has been trimmed down a little to drop the onSuccess method as we dont need to do anything after it executes. Now lets load that page up in the browser:
Ok, thats pretty much what was expected but has the vulnerable app actually been compromised? Lets load it back up and see how our timeline looks:
Whats now obvious is that simply by loading a totally unrelated webpage our status updates have been compromised. I didnt need to click any buttons, accept any warnings or download any malicious software; I simply browsed to a web page. Bingo. Cross site request forgery complete.
This becomes a little clearer when you compare the two requests. Take a look at a diff between the two raw requests (both captured with Fiddler), and check out how similar they are (legitimate on the left, malicious on the right). The differences are highlighted in red:
As you can see on line 13, the cookie with the session ID which persists the authentication between requests is alive and well. Obviously the status update on line 15 changes and as a result, so does the content length on line 10. From the apps perspective this is just fine because its obviously going to receive different status updates over time. In fact the only piece of data giving the app any indication as to the malicious intent of the request is the referrer. More on that a bit later. What this boils down to in the context of CSRF is that because the request was predictable, it was exploitable. That one piece of malicious code we wrote is valid for every session of every user and its equally effective across all of them.
Lets start with creating a method in the page which allows the token to be requested. Its simply going to try to pull the token out of the users session state and if it doesnt exist, create a brand new one. In this case, our token will be a GUID which has sufficient uniqueness for our purposes and is nice and easy to generate. Heres how it looks:
protected string GetToken() { if (Session["Token"] == null) { Session["Token"] = Guid.NewGuid(); } return Session["Token"].ToString(); }
Well now make a very small adjustment in the JavaScript which invokes the service so that it retrieves the token from the method above and passes it to the service as a parameter:
function UpdateStatus() { var service = new Web.StatusUpdateService(); var statusUpdate = document.getElementById('txtStatusUpdate').value; var token = "<%= GetToken() %>"; service.UpdateStatus(statusUpdate, token, onSuccess, null, null); }
Finally, lets update the service to receive the token and ensure its consistent with the one stored in session state. If its not, were going to throw an exception and bail out of the process. Heres the adjusted method signature and the first few lines of code:
[OperationContract] public void UpdateStatus(string statusUpdate, string token) { var sessionToken = HttpContext.Current.Session["Token"]; if (sessionToken == null || sessionToken.ToString() != token) { throw new ApplicationException("Invalid token"); }
Now lets run the original test again and see how that request looks:
This seems pretty simple, and it is. Have a think about whats happening here; the service is only allowed to execute if a piece of information known only to the current users session is persisted into the request. If the token isnt known, heres what ends up happening (Ive passed No idea! from the attacker page in the place of the token):
Yes, the token can be discovered by anyone who is able to inspect the source code of the page loaded by this particular user and yes, they could then reconstruct the service request above
with the correct token. But none of that is possible with the attack vector illustrated above as the CSRF exploit relies purely on an HTTP request being unknowingly issued by the users browser without access to this information.
Lets compare that to the requests created by exactly the process in Chrome 7, again with the legitimate request on the left and the malicious request on the right:
These are now fundamentally different requests. Firstly, the HTTP POST has gone in favour of an HTTP OPTIONS request intended to return the HTTP methods supported by the server. Weve also got an Access-Control-Request-Method entry as well as an Access-Control-RequestHeaders and both the cookie and JSON body are missing. The other thing not shown here is the response. Rather than the usual HTTP 200 OK message, an HTTP 302 FOUND is returned with a redirect to /Account/Login.aspx?ReturnUrl=%2fStatusUpdateService.svc%2fUpdateStatus. This is happening because without a cookie, the application is assuming the user is not logged in and is kindly sending them over to the login page.
This all links back to the XMLHttpRequest API (XHR) which allows the browser to make a client-side request to an HTTP resource. This methodology is used extensively in AJAX to enable fragments of data to be retrieved from services without the need to post the entire page back and process the request on the server side. In the context of this example, its used by the AJAX-enabled WCF service and encapsulated within one of the script resources we added to the attacker page. Now, the thing about XHR is that surprise, surprise, different browsers handle it in different fashions. Prior to Chrome 2 and Firefox 3.5, these browsers simply wouldnt allow XHR requests to be made outside the scope of the same-origin policy meaning the attacker app would not be able to make the request with these browsers. However since the newer generation of browsers arrived, cross-origin XHR is permissible but with the caveat that its
execution is not denied by the app. The practice of these cross-site requests has become known as cross-origin resource sharing (CORS). Theres a great example of how this works in the saltybeagle.com CORS demonstration which shows a successful CORS request where you can easily see whats going on under the covers. This demo makes an HTTP request via JavaScript to a different server passing a piece of form data with it (in this case, a Name field). Heres how the request looks in Fiddler:
OPTIONS http://ucommbieber.unl.edu/CORS/cors.php HTTP/1.1 Host: ucommbieber.unl.edu Connection: keep-alive Referer: http://saltybeagle.com/cors/ Access-Control-Request-Method: POST Origin: http://saltybeagle.com Access-Control-Request-Headers: X-Requested-With, Content-Type, Accept Accept: */* User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Note how similar the structure is to the example of the vulnerable app earlier on. Its an HTTP OPTIONS request with a couple of new access control request headers. Only this time, the response is very different:
HTTP/1.1 200 OK Date: Sat, 30 Oct 2010 23:57:57 GMT Server: Apache/2.2.14 (Unix) DAV/2 PHP/5.3.2 X-Powered-By: PHP/5.3.2 Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: X-Requested-With Access-Control-Max-Age: 86400 Content-Length: 0 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=utf-8
This is what would be normally be expected, namely the Access-Control-Allow-Methods header which tells the browser its now free to go and make a POST request to the secondary server. So it does:
POST http://ucommbieber.unl.edu/CORS/cors.php HTTP/1.1 Host: ucommbieber.unl.edu Connection: keep-alive Referer: http://saltybeagle.com/cors/ Content-Length: 9 Origin: http://saltybeagle.com X-Requested-With: XMLHttpRequest
Content-Type: application/x-www-form-urlencoded Accept: */* User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 name=Troy
Now test that back to back with Internet Explorer 8 and theres only one request with an HTTP POST and of course one response with the expected result. The browser never checks if its allowed to request this resource from a location other than the site which served the original page. Of course none of the current crop of browsers will protect against a GET request structured something like this: http://localhost:85/StatusUpdateService.svc/UpdateStatus?statusUpdate=Hey,%20I'm%20eating%20 my%20breakfast%20now! Its viewed as a simple hyperlink and the CORS concept of posting and sharing data across sites wont apply. This section has started to digress a little but the point is that there is a degree of security built into the browser in much the same way as browsers are beginning to bake in protection from other exploits such as XSS, just like IE8 does. But of course vulnerabilities and workarounds persist and just like when considering XSS vulnerabilities in an application, developers need to be entirely proactive in protecting against CSRF. Any additional protection offered by the browser is simply a bonus.
even within a single securely authenticated session. Then of course you also have multiple machines exposing the same externally facing IP address by virtue of shared gateways such as youd find in a corporate scenario. Its a totally pointless and fatally flawed defence.
Summary
The thing thats a little scary about CSRF from the users perspective is that even though theyre securely authenticated, an oversight in the app design can lead to them not even an attacker making requests they never intended. Add to that the totally indiscriminate nature of who the attack can compromise on any given site and combine that with the ubiquity of exposed HTTP endpoints in the Web 2.0 world (a term I vehemently dislike, but you get the idea), and there really is cause for extra caution to be taken. The synchroniser token pattern really is a cinch to implement and the degree of randomness it implements significantly erodes the predictability required to make a CSRF exploit work properly. For the most part, this would be sufficient but of course theres always reauthentication if that added degree of request authenticity is desired. Finally, this vulnerability serves as a reminder of the interrelated, cascading nature of application exploits. CSRF is one those which depends on some sort of other exploitable hole to begin with whether that be SQL injection, XSS or plain old social engineering. So once again we come back to the layered defence approach where security mitigation is rarely any one single defence but rather a series of fortresses fending off attacks at various different points of the application.
Resources
1. Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet 2. The Cross-Site Request Forgery (CSRF/XSRF) FAQ 3. HttpHandler with cross-origin resource sharing support
Lets look at how OWASP sees the vulnerability and potential fallout:
Threat Agents Attack Vectors Exploitability EASY
Consider anonymous external attackers as well as users with their own accounts that may attempt to compromise the system. Also consider insiders wanting to disguise their actions. Attacker accesses default accounts, unused pages, unpatched flaws, unprotected files and directories, etc. to gain unauthorized access to or knowledge of the system.
Business Impact
Security misconfiguration can happen at any level of an application stack, including the platform, web server, application server, framework, and custom code. Developers and network administrators need to work together to ensure that the entire stack is configured properly. Automated scanners are useful for detecting missing patches, misconfigurations, use of default accounts, unnecessary services, etc.
The system could be completely compromised without you knowing it. All your data could be stolen or modified slowly over time. Recovery costs could be expensive.
Again, theres a wide range of app touch points here. Given that this series is for .NET developers, Im going to pointedly focus on the aspects of this vulnerability that are directly within our control. This by no means suggests activities like keeping operating system patches current is not essential, it is, but its (hopefully) a job thats fulfilled by the folks whose job it is to keep the OS layer ticking along in a healthy fashion.
Of course the concept of vulnerabilities in frameworks and the need to keep them current extends beyond just the third party product; indeed it can affect the very core of the .NET framework. It was only a couple of months ago that the now infamous padding oracle vulnerability in ASP.NET was disclosed and developers everywhere rushed to defend their sites. Actually the Microsoft example is a good one because it required software developers, not server admins, to implement code level evasive action whilst a patch was prepared. In fact there was initial code level guidance followed by further code level guidance and eventually followed by a patch after which all prior defensive work needed to be rolled back. The point with both the DNN and the Microsoft issues is that there needs to be a process to keep frameworks current. In a perfect world this would be well formalised, reliable, auditable monitoring of framework releases and speedy response when risk was discovered. Of course for many people, their environments will be significantly more casual but the objective is the same; keep the frameworks current! One neat way to keep libraries current within a project is to add them as a library package reference using NuGet. Its still very early days for the package management system previously known as NuPack but theres promise in its ability to address this particular vulnerability, albeit not the primary purpose it sets out to serve.
To get started, just jump into the Extension Manager in Visual Studio 2010 and add it from the online gallery:
That then allows you to find your favourite packages / libraries / frameworks:
Resulting in a project which now has all the usual NUnit bits (referenced to the assemblies stored in the packages folder at the root of the app), as well as a sample test and a packages.config file:
Anyway, the real point of all this in the context of security misconfiguration is that at any time we can jump back into the library package reference dialog and easily check for updates:
From a framework currency perspective, this is not only a whole lot easier to take updates when theyre available but also to discover them in the first place. Positive step forward for this vulnerability IMHO.
This brings us to the yellow screen of death, a sample of which Ive prepared below:
Im sure youve all seen this before but lets just pause for a bit and consider the internal implementation information being leaked to the outside world: 1. The expected behaviour of a query string (something we normally dont want a user manipulating) 2. The internal implementation of how a piece of untrusted data is handled (possible disclosure of weaknesses in the design) 3. Some very sensitive code structure details (deliberately very destructive so you get the idea)
4. The physical location of the file on the developers machine (further application structure disclosure) 5. Entire stack trace of the error (disclosure of internal events and methods) 6. Version of the .NET framework the app is executing on (discloses how the app may handle certain conditions) The mitigation is simple and pretty broadly known; its just a matter of turning custom errors on in the system.web element of the Web.config:
<customErrors mode="On" />
What the server is telling us in the response headers is that an internal server error an HTTP 500 has occurred. This in itself is a degree of internal information leakage as its disclosing that the request has failed at a code level. This might seem insignificant, but it can be considered low-hanging fruit in that any automated scanning of websites will quickly identify applications throwing internal errors are possibly ripe for a bit more exploration. Lets define a default redirect and well also set the redirect mode to ResponseRewrite so the URL doesnt change (quite useful for the folks that keep hitting refresh on the error page URL when the redirect mode is ResponseRedirect):
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx" />
A dedicated custom error page is a little thing, but it means those internal server errors are entirely obfuscated both in terms of the response to the user and the response headers. Of course from a usability perspective, its also a very good thing.
I suspect one of the reasons so many people stand up websites with Yellow Screens of Death still active has to do with configuration management. They may well be aware of this being an undesirable end state but its simply slipped through the cracks. One really easy way of mitigating against this insecure configuration is to set the mode to RemoteOnly so that error stack traces still bubble up to the page on the local host but never on a remote machine such as a server:
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx" />
But what about when you really want to see those stack traces from a remote environment, such as a test server? A bit of configuration management is the way to go and config transforms are the perfect way to do this. Just set the configuration file for the target environment to turn custom errors off:
<customErrors xdt:Transform="SetAttributes(mode)" mode="Off" />
Thats fine for a test environment which doesnt face the public, but you never want to be exposing stack traces to the masses so how do you get this information for debugging purposes? Theres always the server event logs but of course youre going to need access to these which often isnt available, particularly in a managed hosting environment. Another way to tackle this issue is to use ASP.NET health monitoring and deliver error messages with stack traces directly to a support mailbox. Of course keep in mind this is a plain text medium and ideally you dont want to be sending potentially sensitive data via unencrypted email but its certainly a step forward from exposing a Yellow Screen of Death. All of these practices are pretty easy to implement but theyre also pretty easy to neglect. If you want to be really confident your stack traces are not going to bubble up to the user, just set the machine.config of the server to retail mode inside the system.web element:
<deployment retail="true" />
One last thing while Im here; as I was searching for material to go into another part of this post, I came across the site below which perfectly illustrates just how much potential risk you run by allowing the Yellow Screen of Death to make an appearance in your app. If the full extent of whats being disclosed below isnt immediately obvious, have a bit of a read about what the machineKey element is used for. Ouch!
Secondly, weve got information explicitly traced out via the Trace.(Warn|Write) statements, for example:
var adminPassword = ConfigurationManager.AppSettings["AdminPassword"]; Trace.Warn("The admin password is: " + adminPassword);
Granted, some of these examples are intentionally vulnerable but they illustrate the point. Just as with the previous custom errors example, the mitigation really is very straight forward. The easiest thing to do is to simply set tracing to local only in the system.web element of the Web.config:
<trace enabled="true" localOnly="true" />
As with the custom errors example, you can always keep it turned off in live environments but on in a testing environment by applying the appropriate config transforms. In this case, local only can remain as false in the Web.config but the trace element can be removed altogether in the configuration used for deploying to production:
<trace xdt:Transform="Remove" />
Finally, good old retail mode applies the same heavy handed approach to tracing as it does to the Yellow Screen of Death so enabling that on the production environment will provide that safety net if a bad configuration does accidentally slip through.
Disable debugging
Another Web.config setting you really dont want slipping through to customer facing environments is compilation debugging. Scott Gu examines this setting in more detail in his excellent post titled Dont run production ASP.NET Applications with debug=true enabled where he talks about four key reasons why you dont want this happening: 1. The compilation of ASP.NET pages takes longer (since some batch optimizations are disabled) 2. Code can execute slower (since some additional debug paths are enabled) 3. Much more memory is used within the application at runtime 4. Scripts and images downloaded from the WebResources.axd handler are not cached Hang on; does any of this really have anything to do with security misconfiguration? Sure, you dont want your production app suffering the sort of issues Scott outlined above but strictly speaking, this isnt a direct security risk per se. So why is it here? Well, I can see a couple of angles where it could form part of a successful exploit. For example, use of the DEBUG conditional compilation constant in order to only execute particular statements whilst were in debug mode. Take the following code block:
#if DEBUG Page.EnableEventValidation = false; #endif
Obviously in this scenario youre going to drop the page event validation whilst in debug mode. The point is not so much about event validation, its that there may be code written which is never expected to run in the production environment and doing so could present a security risk.
Of course it could also present a functionality risk; there could well be statements within the #if block which could perform actions you never want happening in a production environment. The other thing is that when debug mode is enabled, its remotely detectable. All it takes is to jump over to Fiddler or any other tool that can construct a custom HTTP request like so:
DEBUG / HTTP/1.1 Host: localhost:85 Accept: */* Command: stop-debug
But what can you do if you know debugging is enabled? Im going to speculate here, but knowing that debugging is on and knowing that when in debug mode the app is going to consume a lot more server resources starts to say possible service continuity attack to me. I tried to get some more angles on this from Stack Overflow and from the IT Security Stack Exchange site without getting much more than continued speculation. Whilst there doesnt seem to be a clear, known vulnerability even just a disclosure vulnerability its obviously not a state you want to leave your production apps in. Just dont do it, ok?! Last thing on debug mode; the earlier point about setting the machine in retail mode also disables debugging. One little server setting and custom errors, tracing and debugging are all sorted. Nice.
Ive kept custom errors off for the sake of showing the underlying server response and as you can see, its none too happy with the string I entered. Most importantly, the web app hasnt proceeded with processing the request and potentially surfacing the untrusted data as a successful XSS exploit. The thing is though, there are folks who arent real happy with ASP.NET poking its nose into the request pipeline so they turn it off in the system.web element of the Web.config:
<httpRuntime requestValidationMode="2.0" /> <pages validateRequest="false" />
Sidenote: changes to request validation in .NET4 means it needs to run in .NET2 request validation mode in order to turn it off altogether. If theres really a need to pass strings to the app which violate request validation rules, just turn it off on the required page(s):
<%@ Page ValidateRequest="false" %>
However, if youre going to go down this path, you want to watch how you handle untrusted data very, very carefully. Of course you should be following practices like validation against a whitelist and using proper output encoding anyway, youre just extra vulnerable to XSS exploits once you dont have the request validation safety net there. Theres more info on protecting yourself from XSS in OWASP Top 10 for .NET developers part 2: Cross-Site Scripting (XSS).
ID=MyUsername;Password=MyPassword"/> </connectionStrings>
Depending on how the database server is segmented in the network and what rights the account in the connection string has, this data could well be sufficient for any public user with half an idea about how to connect to a database to do some serious damage. The thing is though, encrypting these is super easy. At its most basic, encryption of connection strings or other elements in the Web.config, for that matter is quite simple. The MSDN Walkthrough: Encrypting Configuration Information Using Protected Configuration is a good place to start if this is new to you. For now, lets just use the aspnet_regiis command with a few parameters:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis -site "VulnerableApp" -app "/" -pe "connectionStrings"
What were doing here is specifying that we want to encrypt the configuration in the VulnerableApp IIS site, at the root level (no virtual directory beneath here) and that its the connectionStrings element that we want encrypted. Well run this in a command window on the machine as administrator. If you dont run it as an admin youll likely find it cant open the website. Heres what happens:
You can also do this programmatically via code if you wish. If we now go back to the connection string in the Web.config, heres what we find:
<connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider"> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm= "http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <KeyName>Rsa Key</KeyName> </KeyInfo> <CipherData> <CipherValue> Ousa3THPcqKLohZikydj+xMAlEJO3vFbMDN3o6HR0J6u28wgBYh3S2WtiF7LeU/ rU2RZiX0p3qW0ke6BEOx/RSCpoEc8rry0Ytbcz7nS7ZpqqE8wKbCKLq7kJdcD2O TKqSTeV3dgZN1U0EF+s0l2wIOicrpP8rn4/6AHmqH2TcE= </CipherValue> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue> eoIzXNpp0/LB/IGU2+Rcy0LFV3MLQuM/cNEIMY7Eja0A5aub0AFxKaXHUx04gj37nf7 EykP31dErhpeS4rCK5u8O2VMElyw10T1hTeR9INjXd9cWzbSrTH5w/QN5E8lq+sEVkq T9RBHfq5AAyUp7STWv4d2z7T8fOopylK5C5tBeeBBdMNH2m400aIvVqBSlTY8tKbmhl +amjiOPav3YeGw7jBIXQrfeiOq4ngjiJXpMtKJcZQ/KKSi/0C6lwj1s6WLZsEomoys= </CipherValue> </CipherData> </EncryptedData> </connectionStrings>
Very simple stuff. Of course keep in mind that the encryption needs to happen on the same machine as the decryption. Remember this when youre publishing your app or configuring config transforms. Obviously you also want to apply this logic to any other sensitive sections of the Web.config such as any credentials you may store in the app settings.
The problem, of course, is that if the account is compromised either by disclosure of the credentials or successful exploit via SQL injection, youve opened the door to the entire app. Never mind that someone was attacking a publicly facing component of the app and that the admin was secured behind robust authentication in the web layer, if the one account with broad access rights is used across all components of the app youve pretty much opened the floodgates. Back in OWASP Top 10 for .NET developers part 1: Injection I talked about applying the principal of least privilege: In information security, computer science, and other fields, the principle of least privilege, also known as the principle of minimal privilege or just least privilege, requires that in a particular abstraction layer of a computing environment, every module (such as a process, a user or a program on the basis of the layer we are considering) must be able to access only such information and resources that are necessary to its legitimate purpose.
From a security misconfiguration perspective, access rights which look like this are really not the way you want your app set up:
A single account used by public users with permissions to read any table and write to any table. Of course most of the time the web layer is going to control what this account is accessing. Most of the time.
If we put the least privilege hat on, the access rights start to look more like this:
This time the rights are against the NorthwindPublicUser account (the implication being there may be other accounts such as NorthwindAdminUser), and select permissions have explicitly been granted on the Products table. Under this configuration, an entirely compromised SQL account cant do any damage beyond just reading out some product data.
For example, if the app contained a SQL injection flaw which could otherwise be leveraged to read the Customers table, applying the principal of least privilege puts a stop to that pretty quickly:
Of course this is not an excuse to start relaxing on the SQL injection front, principals such as input validation and parameterised SQL as still essential; the limited access rights just give you that one extra layer of protection.
Summary
This is one of those vulnerabilities which makes it a bit hard to point at one thing and say There thats exactly what security misconfiguration is. Weve discussed configurations which range from the currency of frameworks to the settings in the Web.config to the access rights of database accounts. Its a reminder that building secure applications means employing a whole range of techniques across various layers of the application. Of course weve also only looked at mitigation strategies directly within the control of the .NET developer. As I acknowledged earlier on, the vulnerability spans other layers such as the OS and IIS as well. Again, they tend to be the domain of other dedicated groups within an organisation (or taken care of by your hosting provider), so accountability normally lies elsewhere. What I really like about this vulnerability (as much as a vulnerability can be liked!), is that the mitigation is very simple. Other than perhaps the principal of least privilege on the database account, these configuration settings can be applied in next to no time. New app, old app, its easy to do and a real quick win for security. Very good news for the developer indeed!
Resources
1. Deployment Element (ASP.NET Settings Schema) 2. Request Validation - Preventing Script Attacks 3. Walkthrough: Encrypting Configuration Information Using Protected Configuration
Business Impact
The most common flaw in this area is simply not encrypting data that deserves encryption. When encryption is employed, unsafe key generation and storage, not rotating keys, and weak algorithm usage is common. Use of weak or unsalted hashes to protect passwords is also common. External attackers have difficulty detecting such flaws due to limited access. They usually must exploit something else first to gain the needed access.
Consider the business value of the lost data and impact to your reputation. What is your legal liability if this data is exposed? Also consider the damage to your reputation.
From here we can see a number of different crypto angles coming up: Is the right data encrypted? Are the keys protected? Is the source data exposed by interfaces? Is the hashing weak? This is showing us that as with the previous six posts in this series, the insecure crypto risk is far more than just a single discrete vulnerability; its a whole raft of practices that must be implemented securely if cryptographic storage is to be done well.
Salting is a concept often related to hashing and it involves adding a random string to input text before the hashing process is executed. What this practice is trying to achieve is to add unpredictability to the hashing process such that the output is less regular and less vulnerable to a comparison of hashed common password against what is often referred to as a rainbow table. Youll sometimes also see the salt referred to as a nonce (number used once).
information. This is fine in circumstances where the end-to-end encryption and decryption process is handled in the one location such as where we may need to encrypt data before storing it then decrypt it before returning it to the user. So when all systems are under your control and you dont actually need to know who encrypted the content, symmetric is just fine. Symmetric encryption is commonly implemented by the AES algorithm. In asymmetric encryption we have different keys to encrypt and decrypt the data. The encryption key can be widely distributed (and hence known as a public-key), whilst the decryption key is kept private. We see asymmetric encryption on a daily basis in SSL implementations; browsers need access to the public-key in order to encrypt the message but only the server at the other end holds the private-key and consequently the ability to decrypt and read the message. So asymmetric encryption works just fine when were taking input from parties external to our own systems. Asymmetric encryption is commonly implemented via the RSA algorithm.
Aesthetics aside, this is a pretty common scenario. However, its whats behind the scenes that really count:
protected void SubmitButton_Click(object sender, EventArgs e) { var username = UsernameTextBox.Text; var sourcePassword = PasswordTextBox.Text; var passwordHash = GetMd5Hash(sourcePassword);
CreateUser(username, passwordHash); ResultLabel.Text = "Created user " + username; UsernameTextBox.Text = string.Empty; PasswordTextBox.Text = string.Empty; }
Where the magic really happens (or more aptly, the pain as well soon see), is in the GetMd5Hash function:
private static string GetMd5Hash(string input) { var hasher = MD5.Create(); var data = hasher.ComputeHash(Encoding.Default.GetBytes(input)); var builder = new StringBuilder(); for (var i = 0; i < data.Length; i++) { builder.Append(data[i].ToString("x2")); } return builder.ToString(); }
This is a perfectly valid MD5 hash function stolen directly off MSDN. I wont delve into the CreateUser function referenced above, suffice to say it just plugs the username and hashed password directly into a database using your favourite ORM. Lets start making it interesting and generate a bunch of accounts. To make it as realistic as possible, Im going to create 25 user accounts with usernames of User[1-25] and Im going to use these 25 passwords: 123456, password, rootkit, 111111, 12345678, qwerty, 123456789, 123123, qwertyui, letmein, 12345, 1234, abc123, dvcfghyt, 0, r00tk1t, , 1234567, 1234567890, 123, fuckyou, 11111111, master, aaaaaa, 1qaz2wsx
Why these 25? Because theyre the 25 most commonly used passwords as exposed by the recent rootkit.com attack. Heres how the accounts look:
Username User1 User2 User3 User4 User5 User6 User7 User8 User9 User10 User11 User12 User13 User14 User15 User16 User17 User18 User19 User20 User21 User22 User23 User24 User25 Password 123456 password rootkit 111111 12345678 qwerty 123456789 123123 qwertyui letmein 12345 1234 abc123 dvcfghyt 0 r00tk1t 1234567 1234567890 123 fuckyou 11111111 master aaaaaa 1qaz2wsx
So lets create all these via the UI with nice MD5 hashes then take a look under the covers in the database:
Pretty secure stuff huh? Well, no. Now having said that, everything above is just fine while the database is kept secure and away from prying eyes. Where things start to go wrong is when its exposed and theres any number of different ways this could happen. SQL injection attack, poorly protected backups, exposed SA account and on and on. Lets now assume that this has happened and the attacker has the database of usernames and password hashes. Lets save those hashes into a file called PasswordHashes.txt.
The problem with what we have above is that its vulnerable to attack by rainbow table (this sounds a lot friendlier than it really is). A rainbow table is a set of pre-computed hashes which in simple terms means that a bunch of (potential) passwords have already been passed through the MD5 hashing algorithm and are sitting there ready to be compared to the hashes in the database. Its a little more complex than that with the hashes usually appearing in hash chains which significantly decrease the storage requirements. Actually, theyre stored along with the result of reduction functions but were diving into unnecessary detail now (you can always read more about in How Rainbow Tables Work). Why use rainbow tables rather than just calculating the hashes on the fly? Its whats referred to as a time-memory trade-off in that it becomes more time efficient to load up a heap of precomputed hashes into memory off the disk rather than to plug different strings into the hashing algorithm then compare the output directly to the password database. It costs more time upfront to create the rainbow tables but then comparison against the database is fast and it has the added benefit of being reusable across later cracking attempts. There are a number of different ways of getting your hands on a rainbow table including downloading pre-computed ones and creating your own. In each instance, we need to remember that were talking about seriously large volumes of data which increase dramatically with the password entropy being tested for. A rainbow table of hashed four digit passwords is going to be miniscule in comparison to a rainbow table of up to eight character passwords with upper and lowercase letters and numbers. For our purposes here today Im going to be using RainbowCrack. Its freely available and provides the functionality to both create your own rainbow table and then run them against the password database. In creating the rainbow table you can specify some password entropy parameters and in the name of time efficiency for demo purposes, Im going to keep it fairly restricted. All the generated hashes will be based on password strings of between six and eight characters consisting of lowercase characters and numbers. Now of course we already know the passwords in our database and it just so happens that 80% of them meet these criteria anyway. Were we really serious about cracking a typical database of passwords wed be a lot more liberal in our password entropy assumptions but of course wed also pay for it in terms of computational and disk capacity needs.
There are three steps to successfully using RainbowCrack, the first of which is to generate the rainbow tables. Well call rtgen with a bunch of parameters matching the password constraints weve defined and a few other black magic ones better explained in the tutorial:
rtgen md5 loweralpha-numeric 6 8 0 3800 33554432 0
The first thing you notice when generating the hashes is that the process is very CPU intensive:
In fact this is a good time to reflect on the fact that the availability of compute power is a fundamental factor in the efficiency of a brute force password cracking exercise. The more variations we can add to the password dictionary and greater the speed with which we can do it, the more likely we are to have success. In fact theres a school of thought due to advances in quantum computing, the clock is ticking on encryption as we know it.
Back to RainbowCrack, the arduous process continues with updates around every 68 seconds:
Lets look at this for a moment in this command were generating over thirty three and a half million rainbow chains at a rate of about 3,800 a second which means about two and a half hours all up. This is on a mere 1.6 GHz quad core i7 laptop ok, not mere as a workhorse by todays standard but for the purpose of large computational work its not exactly cutting edge. Anyway, once the process is through we end up with a 512MB rainbow table sitting there on the file system. Now it needs a bit of post-processing which RainbowCrack refers to as a sorting process so we fire up the following command:
rtsort md5_loweralpha-numeric#6-8_0_3800x33554432_0.rt
This one is a quickie and it executes in a matter of seconds. But wait theres more! The rainbow table we generated then sorted was only for table and part index of zero (the fifth and eight parameters in the rtgen command related to the reduce function). Well do another five table generations with incrementing table indexes (this all starts to get very mathematical, have a read of Making a Faster Cryptanalytic Time-Memory TradeOff if you really want to delve into it). If we dont do this, the range of discoverable password hashes will be very small.
For the sake of time, well leave the part indexes and accept were not going to be able to break all the passwords in this demo. If you take a look at a typical command set for lower alphanumeric rainbow tables, youll see why were going to keep this a bit succinct. Lets put the following into a batch file, set it running then sleep on it:
rtgen rtgen rtgen rtgen rtgen rtsort rtsort rtsort rtsort rtsort md5 md5 md5 md5 md5 loweralpha-numeric loweralpha-numeric loweralpha-numeric loweralpha-numeric loweralpha-numeric 6 6 6 6 6 8 8 8 8 8 1 2 3 4 5 3800 3800 3800 3800 3800 33554432 33554432 33554432 33554432 33554432 0 0 0 0 0
Sometime the following day Now for the fun bit actually cracking the passwords from the database. Of course what we mean by this term is really just that were going to match the hashes against the rainbow tables, but that doesnt sound quite as interesting. This time Im going to fire up rcrack_gui.exe and get a bit more graphical for a change. Well start up by loading our existing hashes from the PasswordHashes.txt file:
Doing this will give us all the existing hashes loaded up but as yet, without the plaintext equivalents:
In order to actually resolve the hashes to plain text, well need to load up the rainbow tables as well so lets just grab everything in the directory where we created them earlier:
Now its getting interesting! RainbowCrack successfully managed to resolve eight of the password hashes to their plaintext equivalents. We could have achieved a much higher number closer to or equal to 20 had we computed more tables with wider character sets, length ranges and different part indexes (they actually talk about a 99.9% success rate), but after 15 hours of generating rainbow tables, I think the results so far are sufficient. The point has been made; the hashed passwords are vulnerable to rainbow tables.
This shows the real power of rainbow tables; yes, it took 15 hours to generate them in the first place but then we were moving through over twelve and a half million chains a second. But weve still only got hashes and some plain text equivalents, lets suck the results back into the database and join them all up:
of a string. Theres no key used in the algorithm to vary the output and it doesnt matter where the hash is generated. As such, it left us vulnerable to having our hashes compared against a large set with plain text equivalents which in this case was our rainbow tables. You might say Yes, but this only worked because there were obviously other systems which failed in order to first disclose the database, and youd be right. RainbowCrack is only any good once there have been a series of other failures resulting in data disclosure. The thing is though, its not an uncommon occurrence. I mentioned rootkit.com earlier on and its perfectly analogous to the example above as the accounts were just straight MD5 hashes with no salt. Reportedly, 44% of the accounts were cracked using a dictionary of about 10 M entries in less than 5 minutes. But there have also been other significant braches of a similar nature; Gawker late last year was another big one and then theres the mother of all customer disclosures, Sony (were getting somewhere near 100 million accounts exposed across numerous breaches now). The point is that breaches happen and the role of security in software is to apply layered defences. You dont just apply security principles at one point; you layer them throughout the design so that the compromise of one or two vulnerabilities doesnt bring the whole damn show crashing down. Getting back to our hashes, what we needed to do was to add some unpredictability to the output of the hash process. After all, the exploit only worked because we knew what to look for in that we could compare the database to pre-computed hashes.
Now, before you start thinking Hey, this sounds kind of risky, remember that because the salt is different for each user, if you wanted to start creating rainbow tables youd need to repeat the entire process for every single account. Its no longer possible to simply take a hashed password list and run it through a tool like RainbowCrack, at least not within a reasonable timeframe. So what does this change code wise? Well, the first thing is that we need a mechanism of generating some cryptographically strong random bytes to create our salt:
private static string CreateSalt(int size) { var rng = new RNGCryptoServiceProvider(); var buff = new byte[size]; rng.GetBytes(buff); return Convert.ToBase64String(buff); }
Well also want to go back to the original hashing function and make sure it takes the salt and appends it to the password before actually creating the hash:
private static string GetMd5Hash(string input, string salt) { var hasher = MD5.Create(); var data = hasher.ComputeHash(Encoding.Default.GetBytes(input + salt)); var builder = new StringBuilder(); for (var i = 0; i < data.Length; i++) { builder.Append(data[i].ToString("x2")); } return builder.ToString(); }
Dont fly off the handle about using MD5 just yet read on! In terms of tying it all together, the earlier button click event needs to create the salt (well make it 8 bytes), pass it to the hashing function and also pass it over to the method which is going to save the user to the data layer (remember we need to store the salt):
var username = UsernameTextBox.Text; var sourcePassword = PasswordTextBox.Text; var salt = CreateSalt(8);
Now lets recreate all those original user accounts and see how the database looks:
Excellent, now we have passwords hashed with a salt and the salt itself ready to recreate the process when a user logs on. Now lets try dumping this into a text file and running RainbowCrack against it:
Ah, thats better! Not one single password hash matched to the rainbow table. Of course theres no way there could have been a match (short of a hash collision); the source text was completely randomised via the salt. Just to prove the point, lets create two new users and call them Same1 and Same2, both with a password of Passw0rd. Heres how they look:
Totally different salts and consequently, totally different password hashes. Perfect. About the only thing we havent really touched on is the logon process for reasons explained in the next section. Suffice to say the logon method will simply pull back the appropriate record for the provided username then send the password entered by the user back to the GetMd5Hash function along with the salt. If the return value from that function matches the password hash in the database, logon is successful. But why did I use MD5 for all this? Hasnt it been discredited over and again? Yes, and were we to be serious about this wed use SHA (at the very least), but in terms of demonstrating the vulnerability of non-salted hashes and the use of rainbow tables to break them, its all pretty much of a muchness. If you were going to manage the salting and hashing process yourself, it would simply be a matter of substituting the MD5 reference for SHA. But even SHA has its problems, one of them being that its too fast. Now this sounds like an odd problem, dont we always want computational processes to be as fast as possible? The problem with speed in hashing processes is that the faster you can hash, the faster you can run a brute force attack on a hashed database. In this case, latency can actually be desirable; speed is exactly what you dont want in a password hash function. The problem is that access to fast processing is getting easier and easier which means you end up with situations like Amazon EC2 providing the perfect hash cracking platform for less than a cup of coffee. You dont want the logon process to grind to halt, but the difference between a hash computation going from 3 milliseconds to 300 milliseconds, for example, wont be noticed by the end user but has a 100 fold impact on the duration required to resolve the hash to plain text. This is one of the attractive attributes of bcrypt in that it uses the computationally expensive Blowfish algorithm. But of course latency can always be added to hashing process of other algorithms simply by iterating the hash. Rather than just passing the source string in, hashing it and storing the output in the database, iterative hashing repeats the process and consequently the latency - many
times over. Often this will be referred to as key stretching in that it effectively increases the amount of time required to brute force the hashed value. Just one final comment now that we have a reasonable understanding of whats involved in password hashing: You know those password reminder services which send you your password when you forget it? Or those banks or airlines where the operator will read your password to you over the phone (hopefully after ascertaining your identity)? Clearly theres no hashing going on there. At best your password is encrypted but in all likelihood its just sitting there in plain text. One thing is for sure though, it hasnt been properly hashed.
Between the provider and the controls, account functionality like password resets (note: not password retrieval!), minimum password criteria, password changes, account lockout after
subsequent failed attempts, secret question and answer and a few other bits and pieces are all supported right out of the box. In fact its so easy to configure you can have the whole thing up and running within 5 minutes including the password cryptography done right. The fastest way to get up and running is to start with a brand new ASP.NET Web Application:
Now we just create a new SQL database then run aspnet_regsql from the Visual Studio Command Prompt. This fires up the setup wizard which allows you to specify the server, database and credentials which will be used to create a bunch of DB objects:
If we now take a look in the database we can see a bunch of new tables:
And a whole heap of new stored procedures (no fancy ORMs here):
You can tell just by looking at both the tables and procedures that a lot of typical account management functionality is already built in (creating users, resetting passwords, etc.) The nuts and bolts of the actual user accounts can be found in the aspnet_Users and aspnet_Membership tables:
The only thing left to do is to point our new web app at the database by configuring the connection string named ApplicationServices then give it a run. On the login page well find a link to register and create a new account. Lets fill in some typical info:
The whole point of this exercise was to demonstrate how the membership provider handles cryptographic storage of the password so lets take a look into the two tables we mentioned earlier:
So there we go, a username stored along with a hashed password and the corresponding salt and not a single line of code written to do it! And by default its hashed using SHA1 too so no concern about poor old MD5 (it can be changed to more secure SHA variants if desired). There are two really important points to be made in this section: Firstly, you can save yourself a heap of work by leveraging the native functionality within .NET and the provider model gives you loads of extensibility if you want to extend the behaviour to bespoke requirements. Secondly, when it comes to security, the more stuff you can pull straight out of the .NET framework and avoid rolling yourself, the better. Theres just too much scope for error and unless youre really confident with what youre doing and have strong reasons why the membership provider cant do the job, stick with it.
Were now moving into the symmetric encryption realm and the most commonly used mechanism of implementing this within .NET is AES. There are other symmetric algorithms such as DES, but over time this has been proven to be quite weak so well steer away from this here. AES is really pretty straight forward:
Ok, all jokes aside, the details of the AES implementation (or other cryptographic implementations for that matter), isnt really the point. For us developers, its more about understanding which algorithms are considered strong and how to appropriately apply them. Whilst the above image is still front of mind, heres one really essential little piece of advice: dont even think about writing your own crypto algorithm. Seriously, this is a very complex piece of work and there are very few places which would require and indeed very few people who would be capable of competently writing a bespoke algorithm. Chances are youll end up with something only partially effective at best.
When it comes to symmetric encryption, there are two important factors we need in order to encrypt then decrypt: 1. An encryption key. Because this is symmetric encryption well be using the same key for data going in and data coming out. Just like the key to your house, we want to look after this guy and keep it stored safely (more on that shortly). 2. An initialisation vector, also known as an IV. The IV is a random piece of data used in combination with the key to both encrypt and decrypt the data. Its regenerated for each piece of encrypted data and it needs to be stored with the output of the process in order to turn it back into something comprehensible. If were going to go down the AES path were going to need at least a 128 bit key and to keep things easy, well generate it from a salted password. Well need to store the password and salt (well come back to how to do that securely), but once we have these, generating the key and IV is easy:
private void GetKeyAndIVFromPasswordAndSalt(string password, byte[] salt, SymmetricAlgorithm symmetricAlgorithm, ref byte[] key, ref byte[] iv) { var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt); key = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.KeySize / 8); iv = rfc2898DeriveBytes.GetBytes(symmetricAlgorithm.BlockSize / 8); }
Once we have the key and the IV, we can use the RijndaelManaged class to encrypt the string and bring back a byte array:
static byte[] Encrypt(string clearText, byte[] key, byte[] iv) { var clearTextBytes = Encoding.Default.GetBytes(clearText); var rijndael = new RijndaelManaged(); var transform = rijndael.CreateEncryptor(key, iv); var outputStream = new MemoryStream(); var inputStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write); inputStream.Write(clearTextBytes, 0, clearText.Length); inputStream.FlushFinalBlock(); return outputStream.ToArray(); }
Just one quick point on the above: we wrote quite a bit of boilerplate code which can be abstracted away by using the Cryptography Application Block in the Enterprise Library. The application block doesnt quite transforms the way cryptography is implemented, but it can make life a little easier and code a little more maintainable. Lets now tie it all together in a hypothetical implementation. Lets imagine we need to store a drivers license number for customers. Because its just a little proof of concept, well enter the license in via a text box, encrypt it then use a little LINQ to SQL to save it then pull all the licenses back out, decrypt them and write them to the page. All in code behind on a button click event (hey its a demo!):
protected void SubmitButton_Click(object sender, EventArgs e) { var key = new byte[16]; var iv = new byte[16]; var saltBytes = Encoding.Default.GetBytes(_salt); var algorithm = SymmetricAlgorithm.Create("AES"); GetKeyAndIVFromPasswordAndSalt(_password, saltBytes, algorithm, ref key, ref iv); var sourceString = InputStringTextBox.Text; var ciphertext = Encrypt(sourceString, key, iv); var dc = new CryptoAppDataContext(); var customer = new Customer { EncLicenseNumber = ciphertext, IV = iv }; dc.Customers.InsertOnSubmit(customer); dc.SubmitChanges();
var customers = dc.Customers.Select(c => Decrypt(c.EncLicenseNumber.ToArray(), key, c.IV.ToArray())); CustomerGrid.DataSource = customers; CustomerGrid.DataBind(); }
The data layer looks like this (we already know the IV is always 16 bytes, well assume the license ciphertext might be up to 32 bytes):
So this gives us the full cycle; nice plain text input, AES encrypted ciphertext stored as binary data types in the database then a clean decryption back to the original string. But where does the _password value come from? This is where things get a bit tricky
Key management
Heres the sting in the encryption tail looking after your keys. A fundamental component in the success of a cryptography scheme is being able to properly protect the keys, be that the single key for symmetric encryption or the private key for asymmetric encryption. Before I come back to actual key management strategies, here are a few encryption key 101 concepts: 1. Keep keys unique. Some encryption attack mechanisms benefit from having greater volumes of data encrypted with the same key. Mixing up the keys is a good way to add some unpredictability to the process. 2. Protect the keys. Once a key is disclosed, the data it protects can be considered as good as open. 3. Always store keys away from the data. It probably goes without saying, but if the very piece of information which is required to unlock the encrypted data the key is conveniently located with the data itself, a data breach will likely expose even encrypted data. 4. Keys should have a defined lifecycle. This includes specifying how they are generated, distributed, stored, used, replaced, updated (including any rekeying implications), revoked, deleted and expired. Getting back to key management, the problem is simply that protecting keys in a fashion where they cant easily be disclosed in a compromised environment is extremely tricky. Barry Dorrans, author of Beginning ASP.NET Security, summarised it very succinctly on Stack Overflow: Key Management Systems get sold for large amounts of money by trusted vendors because solving the problem is hard. So the usual ways of storing application configuration data go right out the window. You cant drop them into the web.config (even if its encrypted as thats easily reversed if access to the machine is gained), you cant put them it in the database as then youve got the encrypted data and keys stored in the same location (big no-no), so whats left? There are a few options and to be honest, none of them are real pretty. In theory, keys should be protected in a key vault which is akin to a physical vault; big and strong with very limited access. One route is to use a certificate to encrypt the key then store it in the Windows Certificate Store. Unfortunately a full compromise of the machine will quickly bring this route undone.
Another popular approach is to skip the custom encryption implementation and key management altogether and just go direct to the Windows Data Protection API (DPAPI). This can cause some other dramas in terms of using the one key store for potentially multiple tenants in the same environment and you need to ensure the DPAPI key store is backed up on a regular basis. There is also some contention that reverse engineering of DPAPI is possible, although certainly this is not a trivial exercise. But theres a more practical angle to be considered when talking about encryption and it has absolutely nothing to do with algorithms, keys or ciphers and its simply this: if you dont absolutely, positively need to hold data of a nature which requires cryptographic storage, dont
do it!
One approach to determining the necessity of cryptographic storage is to map data attributes against the risks associated with disclosure, modification and loss then assess both the seriousness and likelihood. For example, heres a mapping using a three point scale with one being low and three being high:
Data object Authentication credentials Credit card details Customer address Seriousness D 3 3 2 M 2 1 2 L 1 1 2 Likelihood D 2 2 2 M 1 2 1 L Storage / cryptography method
1 Plain text username, salted & hashed password 1 All symmetrically encrypted 1 Plain text
Disclosing a credit card is serious business but modifying or losing it is not quite as critical. Still, the disclosure impact is sufficient enough to warrant symmetric encryption even if the likelihood isnt high (plus if you want to be anywhere neat PCI compliant, you dont have a choice). A customers address, on the other hand, is not quite as serious although modification or loss may be more problematic than with a credit card. All in all, encryption may not be required but other protection mechanisms (such as a disaster recovery strategy), would be quite important. These metrics are not necessarily going to be the same in every scenario, the intention is to suggest that there needs to be a process behind the election of data requiring cryptographic storage rather than the simple assumption that everything needs to a) be stored and b) have the overhead of cryptography thrown at it. Whilst were talking about selective encryption, one very important concept is that the ability to decrypt persistent data via the application front end is constrained to a bare minimum. One thing you definitely dont want to do is tie the encryption system to the access control system. For example, logging on with administrator privileges should not automatically provide access to decrypted content. Separate the two into autonomous sub-components of the system and apply the principle of least privilege enthusiastically.
Summary
The thing to remember with all of this is that ultimately, cryptographic storage is really the last line of defence. Its all thats left after many of the topics discussed in this series have already failed. But cryptography is also far from infallible and weve seen both a typical real world
example of this and numerous other potential exploits where the development team could stand up and say Yes, we have encryption!, but in reality, it was done very poorly. But of course even when implemented well, cryptography is by no means a guarantee that data is secure. When even the NSA is saying theres no such thing as secure anymore, this becomes more an exercise of making a data breach increasingly difficult as opposed to making it impossible. And really thats the theme with this whole series; continue to introduce barriers to entry which whilst not absolute, do start to make the exercise of breaching a web applications security system an insurmountable task. As the NSA has said, we cant get secure but we can damn well try and get as close to it as possible.
Resources
1. OWASP Cryptographic Storage Cheat Sheet 2. Project RainbowCrack 3. Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes
They focus on entry points such as links and buttons being secured at the exclusion of proper access controls on the target resources, but it can be even simpler than that. Take a look at the vulnerability and impact and you start to get an idea of how basic this really is:
Threat Agents Attack Vectors Exploitability EASY
Anyone with network access can send your application a request. Could anonymous users access a private page or regular users a privileged page? Attacker, who is an authorised system user, simply changes the URL to a privileged page. Is access granted? Anonymous users could access private pages that arent protected.
Business Impact
Applications are not always protecting page requests properly. Sometimes, URL protection is managed via configuration, and the system is misconfigured. Sometimes, developers must include the proper code checks, and they forget. Detecting such flaws is easy. The hardest part is identifying which pages (URLs) exist to attack.
Consider the business value of the exposed functions and the data they process. Also consider the impact to your reputation if this vulnerability became public.
So if all this is so basic, whats the problem? Well, its also easy to get wrong either by oversight, neglect or some more obscure implementations which dont consider all the possible attack vectors. Lets take a look at unrestricted URLs in action.
Im not logged in at this stage so I get the [ Log In ] prompt in the top right of the screen. Youll also see Ive got Home and About links in the navigation and nothing more at this stage. Lets now log in:
Right, so now my username troyhunt appears in the top right and youll notice I have an Admin link in the navigation. Lets take a look at the page behind this:
All of this is very typical and from an end user perspective, it behaves as expected. From the code angle, its a very simple little bit of syntax in the master page:
if (Page.User.Identity.Name == "troyhunt") { NavigationMenu.Items.Add(new MenuItem { Text = "Admin", NavigateUrl = "~/Admin" }); }
The most important part in the context of this example is that I couldnt access the link to the admin page until Id successfully authenticated. Now lets log out:
Heres the sting in the tail lets now return the URL of the admin page by typing it into the address bar:
Now what we see is that firstly, Im not logged in because were back to the [ Log In ] text in the top right. Weve also lost the Admin link in the navigation bar. But of course the real problem is that weve still been able to load up the admin page complete with user accounts and activities we certainly wouldnt want to expose to unauthorised users. Bingo. Unrestricted URL successfully accessed.
In this example, the presence of an /Admin path is quite predictable and there are countless numbers of websites out there that will return a result based on this pattern. But it doesnt really matter what the URL pattern is if its not meant to be an open URL then it needs access controls. The practice of not securing an individual URL because of an unusual or unpredictable pattern is often referred to as security through obscurity and is most definitely considered a security anti-pattern.
So without a line of actual code (well classify the above as configuration rather than code), weve now secured the admin directory to me and me alone. But this now means weve got two definitions of securing the admin directory to my identity: the one we created just now and the earlier one intended to show the navigation link. This is where ASP.NET site-map security trimming comes into play.
For this to work we need a Web.sitemap file in the project which defines the site structure. What well do is move over the menu items currently defined in the master page and drop each one into the sitemap so it looks as following:
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode roles="*"> <siteMapNode url="~/Default.aspx" title="Home" /> <siteMapNode url="~/About.aspx" title="About" /> <siteMapNode url="~/Admin/Default.aspx" title="Admin" /> </siteMapNode> </siteMap>
After this well also need a site-map entry in the Web.config under system.web which will enable security trimming:
<siteMap enabled="true"> <providers> <clear/> <add siteMapFile="Web.sitemap" name="AspNetXmlSiteMapProvider" type="System.Web.XmlSiteMapProvider" securityTrimmingEnabled="true"/> </providers> </siteMap>
Finally, we configure the master page to populate the menu from the Web.sitemap file using a sitemap data source:
<asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false" IncludeStyleBlock="false" Orientation="Horizontal" DataSourceID="MenuDataSource" /> <asp:SiteMapDataSource ID="MenuDataSource" runat="server" ShowStartingNode="false" />
What this all means is that the navigation will inherit the authorisation settings in the Web.config and trim the menu items accordingly. Because this mechanism also secures the individual resources from any direct requests, weve just locked everything down tightly without a line of code and its all defined in one central location. Nice!
All we need to do to take advantage of this is to enable the role manager which is already in our project:
<roleManager enabled="true">
Now, we could easily just insert the new role into the aspnet_Roles table then add a mapping entry against my account into aspnet_UsersInRole with some simple INSERT scripts but the membership provider actually gives you stored procedures to take care of this:
EXEC dbo.aspnet_Roles_CreateRole '/', 'Admin' GO DECLARE @CurrentDate DATETIME = GETUTCDATE() EXEC dbo.aspnet_UsersInRoles_AddUsersToRoles '/', 'troyhunt', 'Admin', @CurrentDate GO
Even better still, because weve enabled the role manager we can do this directly from the app via the role management API which will in turn call the stored procedures above:
Roles.CreateRole("Admin"); Roles.AddUserToRole("troyhunt", "Admin");
The great thing about this approach is that it makes it really, really easy to hook into from a simple UI. Particularly the activity of managing users in roles in something youd normally expose through a user interface and the methods above allow you to avoid writing all the data access plumbing and just leverage the native functionality. Take a look through the Roles class and youll quickly see the power behind this. The last step is to replace the original authorisation setting using my username with a role based assignment instead:
<location path="Admin"> <system.web> <authorization> <allow roles="Admin" /> <deny users="*" /> </authorization> </system.web> </location>
And thats it! What I really like about this approach is that its using all the good work that already exists in the framework were not reinventing the wheel. It also means that by
leveraging all the bits that Microsoft has already given us, its easy to stand up an app with robust authentication and flexible, configurable authorisation in literally minutes. In fact I can get an entire website up and running with a full security model in less time than it takes me to go and grab a coffee. Nice!
Now lets create a new page in the root of the application and well call it UnrestrictedPage.aspx. Because the page isnt in the admin folder it wont inherit the authorisation setting we configured earlier. Lets now invoke the RemoveUserFromRole method which weve just protected with the principal permission and see how it goes:
Perfect, weve just been handed a System.Security.SecurityException which means everything stops dead in its tracks. Even though we didnt explicitly lock down this page like we did the admin directory, it still cant execute a fairly critical application function because weve locked it down at the declaration. You can also employ this at the class level:
[PrincipalPermission(SecurityAction.Demand, Role = "Admin")] public class RoleManager { public void RemoveUserFromRole(string userName, string role) { Roles.RemoveUserFromRole(userName, role); }
Think of this as a safety net; it shouldnt be required if individual pages (or folders) are appropriately secured but its a very nice backup plan!
Lets take a typical example where we want to protect a collection of PDF files so that only members of the Admin role can access them. All the PDFs will be placed in a PDFs folder and we protect them in just the same way as we did the Admin folder earlier on:
<location path="PDFs"> <system.web> <authorization> <allow roles="Admin" /> <deny users="*" /> </authorization> </system.web> </location>
If I now try to access a document in this path without being authenticated, heres what happens:
We can see via the ReturnUrl parameter in the URL bar that Ive attempted to access a .pdf file and have instead been redirected over to the login page. This is great as it brings the same authorisation model we used to protect our web pages right into the realm of files which
previously would have been processed in their own pipeline outside of any .NET-centric security model.
the same way known vulnerabilities might be and often include webcam endpoints searches, directory listings or even locating passwords. If its publicly accessible, chances are theres a Google search that can locate it. And while were here, all this goes for websites stood up on purely on an IP address too. A little while back I had someone emphatically refer to the fact that the URL in question was safe because Google wouldnt index content on an IP address alone. This is clearly not the case and is simply more security through obscurity. Other resources vulnerable to this sort of attack include files the application may depend on internally but that IIS will happily serve up if requested. For example, XML files are a popular means of lightweight data persistence. Often these can contain information which you dont want leaked so they also need to have the appropriate access controls applied.
Summary
This is really a basic security risk which doesnt take much to get your head around. Still, we see it out there in the wild so frequently (check out those Googledorks), plus its inclusion in the Top 10 shows that its both a prevalent and serious security risk. The ability to easily protect against this with the membership and role providers coupled with the IIS 7 integrated pipeline should make this a non-event for .NET applications we just shouldnt see it happening. However, as the Stack Overflow discussion shows, there are still many instances of developers rolling their own authentication and authorisation schemes when they simply dont need to. So save yourself the headache and leverage the native functionality, override it where needed, watch your AJAX calls and its not a hard risk to avoid.
Resources
1. How To: Use Membership in ASP.NET 2.0 2. How To: Use Role Manager in ASP.NET 2.0 3. ASP.NET Site-Map Security Trimming
HTTPS, SSL and TLS (well go into the differences between these shortly), are essential staples of website security. Without this assurance we have no confidence of who were talking to and if our communications both the data we send and the data we receive is authentic and has not been eavesdropped on. But unfortunately we often find sites lacking and failing to implement proper transport layer protection. Sometimes this is because of the perceived costs of implementation, sometimes its not knowing how and sometimes its simply not understanding the risk that unencrypted
communication poses. Part 9 of this series is going to clarify these misunderstandings and show to implement this essential security feature effectively within ASP.NET.
Business Impact
Applications frequently do not protect network traffic. They may use SSL/TLS during authentication, but not elsewhere, exposing data and session IDs to interception. Expired or improperly configured certificates may also be used. Detecting basic flaws is easy. Just observe the sites network traffic. More subtle flaws require inspecting the design of the application and the server configuration.
Consider the business value of the data exposed on the communications channel in terms of its confidentiality and integrity needs, and the need to authenticate both participants.
Obviously this has a lot to do with the ability to monitor network traffic, something were going to look at in practice shortly. The above matrix also hints at the fact that transport layer protection is important beyond just protecting data such as passwords and information returned on web pages. In fact SSL and TLS goes way beyond this.
SSL / TLS can be applied to a number of different transport layer protocols: FTP, SMTP and, of course, HTTP. HTTPS is Hypertext Transport Protocol Secure and is the implementation of TLS over HTTP. HTTPS is also the URI scheme of website addresses implementing SSL, that is its the prefix of an address such as https://www.americanexpress.com and implies the site will be loaded over an encrypted connection with a certificate that can usually be inspected in the browser. In using these three terms interchangeably, the intent is usually the same in that it refers to securely communicating over HTTP.
cryptographic storage. This website is a project Im currently building at asafaweb.com and for the purpose of this post, it wasnt making use of TLS. For this example, I have a laptop, an iPad and a network adaptor which supports promiscuous mode which simply means its able to receive wireless packets which may not necessarily be destined for its address. Normally a wireless adapter will only receive packets directed to its MAC address but as wireless packets are simply broadcast over the air, theres nothing to stop an adapter from receiving data not explicitly intended for it. A lot of built-in network cards dont support this mode, but $27 from eBay and an Alfa AWUSO36H solves that problem:
In this scenario, the iPad is an innocent user of the ASafaWeb website. Im already logged in as an administrator and as such I have the highlighted menu items below:
Whilst its not explicit on the iPad, this page has been loaded over HTTP. A page loaded over HTTPS displays a small padlock on the right of the tab:
The laptop is the attacker and it has no more rights than any public, non-authenticated user would. Consequently, its missing the administrative menu items the iPad had:
For a sense of realism and to simulate a real life attack scenario, Ive taken a ride down to the local McDonalds which offers free wifi. Both the laptop and the iPad are taking advantage of the service, as are many other customers scattered throughout the restaurant. The iPad has been assigned an IP address of 192.168.16.233 as confirmed by the IP Scanner app:
What were going to do is use the laptop to receive packets being sent across the wireless network regardless of whether it should actually be receiving them or not (remember this is our promiscuous mode in action). Windows is notoriously bad at running in promiscuous mode so Im running the BackTrack software in a Linux virtual machine. An entire pre-configured image can be downloaded and running in next to no time. Using the pre-installed airodump-ng software, any packets the wireless adapter can pick up are now being recorded:
What we see above is airodump-ng capturing all the packets it can get hold of between the BSSID of the McDonalds wireless access point and the individual devices connected to it. We can see the iPads MAC address on the second row in the table. The adapter connected to the laptop is just above that and a number of other customers then appear further down the list. As the capture runs, its streaming the data into a .cap file which can then be analysed at a later date. While the capture ran, I had a browse around the ASafaWeb website on the iPad. Remember, the iPad could be any public user it has absolutely no association to the laptop performing the capture. After letting the process run for a few minutes, Ive opened up the capture file in Wireshark which is a packet capture and analysis tool frequently used for monitoring and inspecting network traffic:
In this case, Ive filtered the traffic to only include packets sent over the HTTP protocol (you can see this in the filer at the top of the page). As you can see, theres a lot of traffic going backwards and forwards across a range of IP addresses. Only some of it such as the first 6 packets comes from my iPad. The rest are from other patrons so ethically, we wont be going anywhere near these. Lets filter those packets further so that only those originating from my iPad are shown:
Now we start to see some interesting info as the GET requests for the elmah link appear. By right clicking on the first packet and following the TCP stream, we can see the entire request:
This is where it gets really interesting: each request any browser makes to a website includes any cookies the website has set. The request above contains a number of cookies, including one called .ASPXAUTH. This cookie is used by the membership provider to persist the authenticated state of the browser across the non-persistent, stateless protocol that is HTTP.
On the laptop, Im running the Edit This Cookie extension in Chrome which enables the easy inspection of existing cookies set by a website. Heres what the ASafaWeb site has set:
Ignore the __utm prefixed cookies this is just Google Analytics. Whats important is that because this browser is not authenticated, theres no .ASPXAUTH cookie. But thats easily rectified simply by adding a new cookie with the same name and value as weve just observed from the iPad:
With the new authentication cookie set its simply a matter of refreshing the page:
Bingo. Insufficient transport layer protection has just allowed us to hijack the session and become an administrator.
Many people think of TLS as purely a means of encrypting sensitive user data in transit. For example, youll often see login forms posting credentials over HTTPS then sending the authenticated user back to HTTP for the remainder of their session. The thinking is that once the password has been successfully protected, TLS no longer has a role to play. The example
above shows that entire authenticated sessions need to be protected, not just the credentials in transit. This is a lesson taught by Firesheep last year and is arguably the catalyst for Facebook implementing the option of using TLS across authenticated sessions.
Microsoft maintains CAs in Windows under its Root Certificate Program which is accessible by Internet Explorer:
Of course the browser vendors also need to be able to maintain these lists. Every now and then new CAs are added and in extreme cases (such as DigiNotar recently), they can be removed thus causing any certificates issued by the authority to no longer be trusted by the browser and cause rather overt security warnings. As Ive written before, SSL is not about encryption. In fact it provides a number of benefits: 1. It provides assurance of the identity of the website (site verification). 2. It provides assurance that the content has not been manipulated in transit (data integrity). 3. It provides assurance that eavesdropping has not occurred in transit (data confidentiality). These days, getting hold of a certificate is fast, cheap and easily procured through domain registrars and hosting providers. For example, GoDaddy (who claim to be the worlds largest
provider of certificates), can get you started from $79 a year. Or you can even grab a free one from StartSSL who have now been added to the list of trusted CAs in the major browsers. Most good web hosts also have provisions for the easy installation of certificates within your hosting environment. In short, TLS is now very cheap and very easily configured. But of course the big question is What does network traffic protected by TLS actually look like? After applying a certificate to the ASafaWeb website and loading an authenticated page over HTTPS from my local network, it looks just like this:
The destination IP address in the filter is the one behind asfaweb.com and whilst the packets obviously identify their intended destination, they dont disclose much beyond that. In fact the TCP stream discloses nothing beyond the certificate details:
Of course wed expect this info to be sent in the clear, its just what youll find when inspecting the certificate in the browser:
Theres really not much more to show; each of the packets in the Wireshark capture are nicely encrypted and kept away from prying eyes, which is exactly what wed expect. One last thing on certificates; you can always create whats referred to as a self-signed certificate for the purposes of testing. Rather than being issued by a CA, a self-signed certificate is created by the owner so its legitimacy is never really certain. However, its a very easy way to test how your application behaves over HTTPS and what Ill be using in a number of the examples in this post. Theres a great little blog post from Scott Gu on Enabling SSL on IIS 7.0 Using Self-Signed Certificates which walks through the process. Depending on the browser, youll get a very ostentatious warning when accessing a site with a self-signed certificate:
But again, for test purposes, this will work just fine.
Here' we have the same website running locally over HTTPS using a self-signed certificate, hence the warning indicators in the URL bar:
This alone is fine, assuming of course it had a valid certificate. The problem though, is this:
There is one subtle difference on this screen the scheme is now HTTP. The problem though is that were still logged in. What this means is that the .ASPXAUTH cookie has been sent across the network in the clear and is open to interception in the same way I grabbed the one at McDonalds earlier on. All it takes is one HTTP request to the website whilst Im logged on even though I logged on over HTTPS and the session hijacking risk returns. When we inspect the cookie, the reason for this becomes clear:
The cookie is not flagged as being secure. The secure cookie attribute instructs the browser as to whether or not it should send the cookie over an HTTP connection. When the cookie is not decorated with this attribute, the browser will send it along with all requests to the domain which set it, regardless of whether the HTTP or HTTPS scheme is used. The mitigation for this within a forms authentication website in ASP.NET is to set the requireSSL property in the web.config to true:
<forms loginUrl="~/Account/LogOn" timeout="2880" requireSSL="true" />
After we do this, the secure property on the cookie is now set and clearly visible when we look at the cookies passed over the HTTPS scheme:
But go back to HTTP and the .ASPXAUTH cookie has completely disappeared all thats left is the cookie which persists the session ID:
What the secure cookie does is ensures that it absolutely, positively cannot be passed over the network in the clear. The session hijacking example from earlier on is now impossible to reproduce. It also means that you can no longer login over the HTTP scheme:
Lets assume this cookie is used to determine how many results I want returned on the Log page of the admin section. I can define this value via controls on the page and its persisted via a cookie. Im only ever going to need it on the admin page and as we now know, I can only access the admin page if already authenticated which, following the advice in the previous section, means Ill have a secure auth cookie. But it doesnt mean the ResultsPerPage cookie is secure:
Now of course the necessity for the cookie to be marked as secure is a factor of the information being protected within it. Whilst this cookie doesnt contain sensitive info, a better default position on a TLS-enabled site is to start secure and this can easily be configured via the web.config:
<httpCookies requireSSL="true" />
Once the requireSSL flag is set, we get the same protection that we got back in the forms authentication section for the auth cookie:
This is now a very different proposition as the cookie is afforded the same security as the auth cookie from earlier on. If the request isnt made over HTTPS, the cookie simply wont be sent over the network. But this setting means that every cookie can only be sent over HTTPS which means that even the ASP.NET_SessionId cookie is not sent over HTTP resulting in a new session ID for every request. In many cases this wont matter, but sometimes more granularity is required. What we can do is set the secure flag when the cookie is created rather than doing it globally in the web.config:
var cookie = new HttpCookie("ResultsPerPage", "50"); cookie.Secure = true; Response.Cookies.Add(cookie);
Whilst youd only really need to do this when its important to have other cookies which can be sent across HTTP, its nice to have the option. Just one more thing on cookies while were here, and its not really related to transport layer protection. If the cookie doesnt need to be requested by client-side script, make sure its flagged as HTTP only. When you look back at the cookie information in the screen grabs, you may have noticed that this is set for the .ASPXAUTH cookie but not for the cookie we created by code. Setting this to true offers protection against malicious client-side attacks such as XSS and its equally easy to turn on either across the entire site in the web.config:
Its cheap insurance and it means client script can no longer access the cookie. Of course there are times when you want to access the cookie via JavaScript but again, start locked down and open up from there if necessary.
In a case like the account controller (this is just the default one from a new MVC project), we dont want any of the actions to be served over HTTP as they include features for logging in, registering and changing passwords. This is an easy case for decorating the entire controller class but it can be used in just the same way against an action method if more granularity is required. Once we require HTTPS, any HTTP requests will be met with a 302 (moved temporarily) response and then the browser redirected to the secure version. We can see this sequence play out in Fiddler:
But its always preferable to avoid redirects as it means the browser ends up making an additional request, plus it poses some other security risks well look at shortly. A preferable approach is to link directly to the resource using the HTTPS scheme and in the case of linking to controller actions, its easy to pass in the protocol via one of the overloads:
Unfortunately the only available ActionLink overload which takes a protocol also has another four redundant parameters but regardless, the end result is that an absolute URL using the HTTPS scheme is emitted to the markup:
<a href="https://localhost/Account/LogOn">
Applying both these techniques gives the best of both worlds: Its easy to link directly to secure versions of actions plus your controller gets to play policeman and ensure that its not possible to circumvent HTTPS, either deliberately or by accident.
Whilst the default in a new ASP.NET app (either MVC or web forms) is 2,880 seconds (48 minutes), reducing this number to the minimum practical value offers a certain degree of security. Of course you then trade off usability, but thats often the balance we work with in security (two factor authentication is a great example of this). But even shorter timeouts leave a persistent risk; if the hijacker does get hold of the session, they can just keep issuing requests until theyre done with their malicious activities and theyll remain authenticated. One way of mitigating this risk but also at the cost of usability is to disable sliding expiration:
<forms loginUrl="~/Account/LogOn" timeout="2880" slidingExpiration="false" />
What this means is that regardless of whether the authenticated user keeps sending requests or not, the user will be logged out after the timeout period elapses once theyre logged in. This caps the window of session hijacking risk.
But the value of both these settings is greater when no TLS exists. Yes, sessions can still be hijacked when TLS is in place, but its an additional piece of security thats always nice to have in place.
In order to protect the credentials in transit, they then post to an HTTPS address:
<form id="headerLoginForm" action="https://www.singaporeair.com/kfHeaderLogin.form" method="post">
This method will encrypt the credentials before posting them, but theres one very major flaw in the design; its wide open to a man in the middle attack. An MITM attack works by a malicious party intercepting and manipulating the conversation between client and server. Earlier on I explained that one of the benefits offered by TLS was that it provides assurance that the content has not been manipulated in transit. Consider that in the following MITM scenario:
Because the login form was loaded over HTTP, it was open to modification by a malicious party. This could happen at many different points between the client and the server; the clients internet gateway, the ISP, the hosting provider, etc. Once that login form is available for modification, inserting, say, some JavaScript to asynchronously send the credentials off to an attackers website can be done without the victim being any the wiser. This is not the stuff of fiction; precisely this scenario was played out by the Tunisian government only a year ago: The Tunisian Internet Agency (Agence tunisienne d'Internet or ATI) is being blamed for the presence of injected JavaScript that captures usernames and passwords. The code has been discovered on login pages for Gmail, Yahoo, and Facebook, and said to be the reason for the recent rash of account hijackings reported by Tunisian protesters.
And: There is an upside however, as the embedded JavaScript only appears when one of the sites is accessed with HTTP instead of HTTPS. In each test case, we were able to confirm that Gmail and Yahoo were only compromised when HTTP was used. The mitigation for this risk is simply not to display login forms on pages which may be requested over HTTP. In a case like Singapore Airlines, either each page needs to be served over HTTPS or there needs to be a link to an HTTPS login page. You cant have it both ways. OWASP also refers to this specific risk in the TLS cheat sheet under Use TLS for All Login Pages and All Authenticated Pages: The login page and all subsequent authenticated pages must be exclusively accessed over TLS. The initial login page, referred to as the "login landing page", must be served over TLS. Failure to utilize TLS for the login landing page allows an attacker to modify the login form action, causing the user's credentials to be posted to an arbitrary location. Very clear indeed. But theres also a secondary flaw with loading a login form over HTTP then posting to HTTPS; theres no opportunity to inspect the certificate before sending sensitive data. Because of this, the authenticity of the site cant be verified until its too late. Actually, the user has no idea if any transport security will be employed at all and without seeing the usual browser indicators that TLS is present, the assumption would normally be that no TLS exists. Theres simply nothing visible to indicate otherwise.
But as you can see from the browser below, the response does not use the HTTP scheme at all, rather it comes back with the landing page (including login facility) over HTTPS:
Whats actually happening here is that Amex is receiving the HTTP request then returning an HTTP 301 (moved permanently) response and asking the browser to redirect to https://www.americanexpress.com/. We can see this in Fiddler with the request in the top half of the screen and the response at the bottom:
Because that first request is being made over HTTP its vulnerable to manipulation in the same way as the Tunisian example earlier on in that it can be modified in transit. In fact theres nothing stopping a malicious party who was able to manipulate the response from changing the redirect path (or any other part of the response) to something entirely different or just retuning an HTTP page with modified login controls (again, think back to Tunisia). All of this is simply because the request sequence started out over an insecure protocol. It was only a few years back that the risk this practice poses was brought into the spotlight by Moxie Marlinspike when he created SSL Strip. What Moxie showed us is the ease with which transport security can be entirely removed by a MITM simply intercepting that first
HTTP request then instead of allowing the redirect to HTTPS, sending the response back to the client in HTTP and then proxying requests back to the server over HTTPS. Unless explicitly looking for the presence of HTTPS (which most users wouldnt consciously do), the path has now been paved to observe credentials and other sensitive data being sent over plain old unencrypted HTTP. The video on the website is well worth a watch and shows just how easily HTTPS can be circumvented when you begin with a dependency on HTTP (also consider this in the context of the previous section about loading login forms over HTTP). In a perfect world, the solution is to never redirect; the site would only load if the user explicitly typed a URL beginning with the HTTPS scheme thus mitigating the threat of manipulation. But of course that would have a significant usability impact; anyone who attempted to access a URL without a scheme would go nowhere. Until recently, OWASP published a section titled Do not perform redirects from non-TLS to TLS login page (its still there, just flagged as removed). Their suggestion was as follows: It is recommended to display a security warning message to the user whenever the non-TLS login page is requested. This security warning should urge the user to always type "HTTPS" into the browser or bookmark the secure login page. This approach will help educate users on the correct and most secure method of accessing the application. Obviously this has a major usability impact; asking the user to go back up to their address bar and manually change the URL seems ludicrous in a world of hyperlinks and redirects. This, unfortunately, is why the HTTP to HTTPS redirect pattern will remain for some time yet, but at least developers should be aware of the risk. The only available mitigation is to check the validity of the certificate before providing your credentials:
and until this period has expired, the browser will automatically translate any HTTP requests into HTTPS versions with the same path. Enforcing HTTPS and supporting HSTS can easily be achieved in an ASP.NET app; its nothing more than a header. The real work is done on the browser end which then takes responsibility for not issuing HTTP requests to a site already flagged as "Strict-TransportSecurity". In fact the browser does its own internal version of an HTTP 301 but because were not relying on this response coming back over HTTP, its not vulnerable to the MITM attack we saw earlier. The HSTS header and forceful redirection to the HTTPS scheme can both easily be implemented in the Application_BeginRequest event of the global.asax:
protected void Application_BeginRequest(Object sender, EventArgs e) { switch (Request.Url.Scheme) { case "https": Response.AddHeader("Strict-Transport-Security", "max-age=300"); break; case "http": var path = "https://" + Request.Url.Host + Request.Url.PathAndQuery; Response.Status = "301 Moved Permanently"; Response.AddHeader("Location", path); break; } }
With this in place, lets take a look at HSTS in action. What Im going to do is set the link to the sites style sheet to explicitly use HTTP so it looks like this:
<link href="http://localhost/Content/Site.css" rel="stylesheet" type="text/css" />
Now heres what happens when I make an HTTP request to the site with Chrome:
There are three important things to note here: 1. Request 1: The HTTP request is responded to with an HTTP 301 redirecting me to the HTTPS scheme for the same resource. 2. Request 2: The HTTPS redirect from the previous point returns the "Strict-TransportSecurity" header in the response. 3. Request 6: This is to the style sheet which was explicitly embedded with an absolute link using the HTTP scheme but as we can see, the browser has converted this to use HTTPS before even issuing the request. Going back to the original example where packets sent over HTTP were sniffed, if the login had been over HTTPS and HSTS was used, it would have been impossible for the browser to issue requests over HTTP for the next 500 seconds even if explicitly asked to do so. Of course
this structure then disallows any content to be served over HTTP but in many cases, this is precisely the scenario youre looking to achieve. One final comment on HSTS, or rather the concept of forcing HTTPS requests; even when the "Strict-Transport-Security" header is not returned by the server, its still possible to ensure requests are only sent over HTTPS by using the HTTPS Everywhere plugin for Firefox. This plugin mimics the behaviour of HSTS and performs an in-browser redirect to the secure version of content for sites youve specified as being TLS-only. Of course the site still needs to support HTTPS in the first place, but where it does, the HTTPS Everywhere plugin will ensure all requests are issued across a secure connection. But ultimately this is only a mitigation you can perform as a user on a website, not as a developer.
But the more obvious problem is that this will very quickly be brought to the attention of users of the webpage. The implementation differs from browser to browser, but in the case of Chrome, heres what happens when content is mixed:
By striking out the padlock icon and the HTTPS scheme in the URL, the browser is sending a very clear warning to the user dont trust this site! The trust and confidence youve built with the user is very swiftly torn apart just by the inclusion of a single non-TLS asset on the page. The warning in the certificate info panel above is clear: youre requesting insecure resources and they cant be trusted to be authentic. And thats all it takes one asset. In Qantas case, we can easily see this by inspecting the content in Fiddler. Theres just a single request out of about 100 which is loaded over HTTP:
And what would justify sacrificing properly implemented TLS? Just one little Flash file promoting Secret Santa:
More likely than not its an oversight on their part and its something to remain vigilant about when building your apps. The bigger problem this poses is that once you start desensitising users to security warnings, theres a real risk that legitimate warnings are simply ignored and this very quickly erodes the value delivered by TLS. Whilst mixed HTTPS and HTTP content is an easily solvable issue when all the content is served from the one site, it remains a constant challenge when embedding content from external resources. In fact some people argue that this is one of the reasons why the web has not switched to SSL-only yet. For example, Google AdSense doesnt support SSL version of their ads. Not being able to display revenue generating advertising is going to be a deal-breaker for some sites and if they rely on embedding those ads on authenticated pages, some tough decisions and ultimately sacrifices of either security or dollars are going to need to be made. But its not all bad news and many external services do provide HTTPS alternatives to ensure this isnt a problem. For example, Google Analytics works just fine over HTTPS as does Twitters tweet button. Ironically that last link is presently returning mixed content itself:
It just goes to show that as basic as the concept is, even the big guys get it wrong.
In the example I showed how this meant the URL could then be reused elsewhere and the session hijacked. Transport layer security changes nothing in this scenario. Because the URL contains sensitive data it can still be handed off to another party either through social engineering or simple sharing and the session hijacked. OWASP also talks about keeping sensitive data out of the URL and identifies additional risks in the SSL cheat sheet. These risks include the potential caching of the page (including URL) on the users machine and the risk of the URL being passed in the referrer header when linking from one TLS site to another. Clearly the URL is not the right location to be placing anything thats either sensitive, or in the case of the session hijacking example above, could be used to perform malicious activity.
Breaking TLS
Like any defence we apply in information security, TLS itself is not immune from being broken or subverted. Weve looked at mechanisms to circumvent it by going upstream of secure requests and attacking at the HTTP level, but what about the certificate infrastructure itself? Only a few months back we saw how vulnerable TLS can be courtesy of DigiNotar. The Dutch certificate authority demonstrated that a systemic breakdown in their own internal security could pave the way for a malicious party to issue perfectly legitimate certificates for the likes of Google and Yahoo! This isnt the first time a CA has been compromised; Comodo suffered an attack earlier this year in the now infamous Comodo-gate incident in which one of their affiliates was breached and certificates issued for Skype and Gmail, among others. Around the same time as the DigiNotar situation, we also saw the emergence of BEAST, the Browser Exploit Against SSL/TLS. What BEAST showed us is that an inherent vulnerability in the current accepted version of TLS (1.0), could allow an attacker to decipher encrypted cookies from the likes of PayPal. It wasnt a simple attack by any means, but it did demonstrate that flaws exist in places that nobody expected could actually be exploited. But the reality is that there remains numerous ways to break TLS and it need not always involve the compromise of a CA. Does this make it insecure? No, it makes it imperfect but nobody is about to argue that it doesnt offer a significant advantage over plain old HTTP communication. To the contrary, TLS has a lot of life left and will continue to be a cornerstone of web security for many years to come.
Summary
Properly implementing transport layer protection within a web app is a lot of information to take on board and I didnt even touch on many of the important aspects of certificates themselves; encryption strength (128 bit, 256 bit), extended validation, protecting private keys, etc. Transport security remains one of those measures which whilst undoubtedly advantageous, is also far from fool proof. This comment from Moxie Marlinspike in the video on the SSL Strip page is testimony to how fragile HTTPS can actually be: Lots of times the security of HTTPS comes down to the security of HTTP, and HTTP is not secure
Whats the solution? Many people are saying responsibility should fall back to DNS so that sites which should only be served over secure connections are designated outside of the transport layer and thus less prone to manipulation. But then DNS is not fool proof. Ultimately we, as developers, can only work with the tools at our disposal and certainly there are numerous ways we can mitigate the risk of insufficient transport layer protection. But as with the other posts in this series, you cant get things perfect and the more you understand about the potential vulnerabilities, the better equipped you are to deal with them. As for the ASafaWeb website, youll now observe a free StartSSL certificate on the login page which, naturally, is loaded over TLS. Plus I always navigate directly to the HTTPS address by way of bookmark before authenticating. Its really not that hard.
Resources
1. OWASP Transport Layer Protection Cheat Sheet 2. HTTP Strict Transport Security has landed! 3. SSL Strip
Business Impact
Applications frequently redirect users to Such redirects Consider the other pages, or use internal forwards in may attempt to business value of a similar manner. Sometimes the install malware or retaining your target page is specified in an trick victims into users trust. unvalidated parameter, allowing disclosing attackers to choose the destination passwords or other What if they get page. sensitive owned by information. malware? Detecting unchecked redirects is easy. Unsafe forwards Look for redirects where you can set may allow access What if attackers the full URL. Unchecked forwards are control bypass. can access harder, since they target internal internal only pages. functions?
So were looking at a combination of untrusted data with trickery, or what we commonly know of as social engineering. The result of all this could be malware, data theft or other information disclosure depending on the objectives of the attacker. Lets take a look at how all this takes place.
There are a couple of noteworthy thing to point out; 1. The domain: lets assume we recognise and trust the fictitious mytrustedsite.com (Ive updated my hosts file to point to a local IIS website) and that seeing this host name in an address gives us confidence in the legitimacy of the site and its content. 2. The target URL of the hyperlink: you can see down in the status bar that it links off to a page called Redirect.aspx with a query string parameter named URL and a value of http://troyhunt.com Whats happening here is pretty self-explanatory, in fact thats the whole reason why detectability is so easy. Obviously once we click the link we expect to see something like this:
Now lets imagine weve seen a link to this domain through a channel such as Twitter. It might appear something like this:
As best as a casual observer can tell, this is a perfectly legitimate link. It establishes confidence and credibility as the domain name is recognisable; theres no reason to distrust it and for all intents and purposes, clicking on the link will load legitimate content on My Trusted Site. However:
See the problem? Its very subtle and indeed thats where the heart of the attack lies: The address bar shows that even though we clicked on a URL which clearly had the host name of mytrustedsite.com, were now on myuntrustedsite.com. Whats more, theres a logon form asking for credentials which youd naturally expect would be handled properly under the circumstances. Clearly this wont be the case in this instance. Bingo. An unvalidated redirect has just allowed us to steal someones credentials.
The attack was made more credible by the malicious site having a similar URL to the trusted one and the visual design being consistent (albeit both sample implementations). There is nothing that can be done about the similar URL or the consistent branding; all thats left is controlling the behaviour in the code above.
Taking responsibility
Before getting into remediation, theres an argument that the attack sequence above is not really the responsibility of the trusted site. After all, isnt it the malicious site which is stealing credentials? Firstly, the attack above is only one implementation of an unvalidated redirect. Once you can control where a legitimate URL can land an innocent user, a whole world of other options open up. For example, that could just as easily have been a link to a malicious executable. Someone clicks the link then gets prompted to execute a file. Again, theyre clicking a known, trusted URL so confidence in legitimacy is high. All the UAC in the world doesnt change that fact. The ability to execute this attack via your site is your responsibility because its your brand which cops the brunt of any fallout. Hey, I loaded a link from mytrustedsite.com now my PC is infected. Its not a good look and you have a vested interest in this scenario not playing out on your site.
In fact this is the first part of our whitelist validation because were confirming that the untrusted data conforms to the expected pattern of a URL. More on that back in part 2. But of course this wont stop the attack from earlier, even though it greatly mitigates the risk of XSS. What we really need is a whitelist of allowable URLs which the untrusted data can be validated against. This would exist somewhere in persistent storage such as an XML file or a SQL database. In the latter case, whitelist validation using Entity Framework would look something like this:
var db = new MyTrustedSiteEntities(); if (!db.AllowableUrls.Where(u => u.Url == url).Any()) { // Gracefully exit with a warning message }
This is pretty self-explanatory; if the URL doesnt exist in the database, the page wont process. At best, all an attacker can do is manipulate the query string with other URLs already in the whitelist, but of course assuming those URLs are trustworthy, theres no advantage to be gained. But theres also another approach we can take which provides a higher degree of obfuscation of the URL to be redirected to and rules out manipulation altogether. Back in part 4 I talked about insecure direct object references and showed the risk created by using internal identifiers in a publicly visible fashion. The answer was to use indirect reference maps which are simply a way of exposing a public identifier of no logical significance that resolved back to a private identifier internally within the app. For example, rather than placing a bank account number in a query string, a temporary and cryptographically random string could be used which then mapped back to the account internally thus stopping anyone from simply manipulating account numbers in the query string (i.e. incrementing them). In the case of unvalidated redirects, we dont need to have the URL in the query string, lets try it like this: http://mytrustedsite.com/Redirect.aspx?Id=AD420440-DB7E-4F16-8A61-72C9CEA5D58D The entire code would then look something like this:
var id = Request.QueryString["Id"]; Guid idGuid; if (!Guid.TryParse(id, out idGuid)) { // Gracefully exit with a warning message } var db = new MyTrustedSiteEntities(); var allowableUrl = db.AllowableUrls.SingleOrDefault(u => u.Id == idGuid); if (allowableUrl == null) { // Gracefully exit with a warning message } LogRedirect(allowableUrl.Url); Response.Redirect(allowableUrl.Url);
So were still validating the data type (not that much would happen with an invalid GUID anyway!) and were still checking it against a whitelist, the only difference is that theres a little more protection against manipulation and disclosure before actually resolving the ID to a URL.
This was captured using Fiddler and you can see here that the site which referred this request was our trusted site. Now lets look at that referrer from our malicious attack via Twitter:
The referrer address is Twitters URL shortener on the t.co domain. Our trusted website receives this header and consequently, it can read it and act on it accordingly. Lets try this:
var referrer = Request.UrlReferrer; var thisPage = Request.Url; if (referrer == null || referrer.Host != thisPage.Host) { // Gracefully exit with a warning message }
Thats a very simple fix that immediately rules out any further opportunity to exploit the unvalidated redirect risk. Of course it also means you can never deep link directly to the redirect page from an external resource but really, this isnt something youre normally going to want to do anyway.
Obfuscation of intent
Earlier on we looked at this URL: http://mytrustedsite.com/Redirect.aspx?Url=http://myuntrustedsite.com You only need to read the single query string parameter and the malicious intent pretty quickly becomes clear. Assuming, of course, you can see the full URL and it hasnt been chopped off as in the Twitter example from earlier, shouldnt it be quite easy for end users to identify that something isnt right? Lets get a bit more creative: http://mytrustedsite.com/Redirect.aspx?Foo=xLv8WUcipP6WQLnNyA6MQzyFfyFNqCcoe &Bar=deyZWmQ4dbRtFTEDWczt72D&Url=%68%74%74%70%3a%2f%2f%6D%79%75% 6E%74%72%75%73%74%65%64%73%69%74%65%2E%63%6F%6D&Foo2=CMVDnzwp Wzp3PtMFJUvCwX6bxr8ecFyy&Bar2=UYuu2XRcQUKzt3xYfemWHM6HNKt This will execute in exactly the same fashion as the previous URL but the intent has been obfuscated by a combination of redundant query string parameters which draw attention away from the malicious one combined with URL encoding the redirect value which makes it completely illegible. The point is that you cant expect even the most diligent users to spot a potential invalidated redirect attack embedded in a URL. Just in case this sounds very theoretical, its precisely the attack which was mounted against eBay some time back. In fact this particular attack mirrored my example from earlier on in terms of using an obfuscated URL with the eBay domain to then redirect to an arbitrary site with eBay branding and asked for credentials (note the URL). Take this address: http://cgi4.ebay.com/ws/eBayISAPI.dll?MfcISAPICommand=RedirectToDomain&DomainU rl=http%3A%2F%2F%32%31%31%2E%31%37%32%2E%39%36%2E%37%2FUpdateCente r%2FLogin%2F%3FMfcISAPISession%3DAAJbaQqzeHAAeMWZlHhlWXS2AlBXVShqAh QRfhgTDrferHCURstpAisNRqAhQRfhgTDrferHCURstpAisNRpAisNRqAhQRfhgTDrferH CUQRfqzeHAAeMWZlHhlWXh
And there you have it: unvalidated redirect being exploited in the wild.
of a small number of well-implemented and carefully monitored URL redirectors tend to outweigh the perceived risks. The actual use-case for Google allowing this practice isnt clear; its possible there is a legitimate reason for allowing it. Google also runs a vast empire of services consumed in all sorts of fashions and whilst there may be niche uses for this practice, the same can rarely be said of most web applications. Still, their defence of the practice also seems a little tenuous, especially when they claim a successful exploit depends on the fact that users will be not be attentive enough to examine the contents of the address bar after the navigation takes place. As weve already seen, similar URLs or those obfuscated with other query string parameters can easily fool even diligent users. Unvalidated redirects tend to occur more frequently than youd expect for such an easily mitigated risk. I found one on hp.com just last week, ironically whilst following a link to their WebInspect security tool: http://www.hp.com/cgibin/leaving_hp.cgi?cc=us&lang=en&exit_text=Go%20to%20troyhunt.com&area_text=Newsr oom&area_link=http://www.hp.com/hpinfo/newsroom/index.html&exit_link=http://troyhu nt.com Im not sure whether HP take the same stance as Google or not, but clearly this one doesnt seem to be worrying them (although the potential XSS risk of the exit_text parameter probably should).
Summary
Finishing the Top 10 with the lowest risk vulnerability that even Google doesnt take seriously is almost a little anticlimactic. But clearly there is still potential to use this attack vector to trick users into disclosing information or executing files with the assumption that theyre performing this activity on a legitimate site. Googles position shouldnt make you complacent. As with all the previous 9 risks Ive written about, security continues to be about applying layers of defence to your application. Frequently, one layer alone presents a single point of failure which can be avoided by proactively implementing multiple defences, even though holistically they may seem redundant.
Ultimately, unvalidated redirects are easy to defend against. Chances are your app wont even exhibit this behaviour to begin with, but if it does, whitelist validation and referrer checking are both very simple mechanisms to stop this risk dead in its tracks.
Resources
1. Open redirectors: some sanity 2. Common Weakness Enumeration: URL Redirection to Untrusted Site 3. Anti-Fraud Open Redirect Detection Service
251 | Index
Index
A
abstraction layer, 28, 138 access control, 77, 85, 93, 111, 176, 178, 184, 239 access reference map. See indirect reference map Access-Control-Request-Headers, 109, 111 Access-Control-Request-Method, 109, 111 Active Directory, 187 AdSense, 233 AES, 145, 146, 170, 171, 172, 173 airodump-ng, 201 AJAX, 70, 78, 86, 89, 96, 99, 110, 191, 194 Alfa AWUSO36H, 198 Amazon, 161 American Express, 224 AntiXSS, 45, 48, 49, 50, 52, 57, 58 Apple, 91 ASafaWeb, 199, 201, 204, 209, 237 aspnet_Membership, 166 aspnet_Users, 166 aspnet_UsersInRole, 188 aspnet_UsersInRoles_AddUsersToRoles, 188 ASPXAUTH, 203, 204, 214, 216, 217, 219 asymmetric encryption, 145, 146, 174, 197, 207 asymmetric-key, 145 AT&T, 91, 92, 93 ATO. See Australian Taxation Office attack vector, 33, 36, 105, 179 Australian Taxation Office, 90, 93 authentication, 59, 60, 62, 65, 66, 68, 69, 70, 73, 75, 77, 95, 103, 104, 108, 113, 114, 138, 143, 189, 191, 193, 194, 196, 205, 206, 212, 215, 218, 221, 234 autocomplete, 74 Browser Exploit Against SSL, 236 BSSID, 201
C
CA. See certificate authority Captcha, 113 certificate authority, 207, 208, 209, 211, 236 Chrome, 81, 109, 110, 111, 112, 204, 228, 229, 232 ciphertext, 144, 172, 173 code context, 36, 46 Common Weakness Enumeration, 250 Comodo, 236 connection pooling, 31 control tree, 127 cookie, 42, 55, 61, 62, 63, 64, 65, 68, 73, 95, 96, 103, 104, 108, 109, 113, 191, 203, 204, 205, 206, 214, 215, 216, 217, 218, 219, 220, 234, 236 cookieless session, 61, 65, 68, 69 CORS. See cross-origin resource sharing cross-origin resource sharing, 108, 111, 112, 114 cross-site request forgery, 15, 95, 96, 102, 104, 105, 108, 112, 113, 114 cross-site scripting, 33, 35, 36, 38, 40, 43, 44, 45, 47, 48, 54, 55, 56, 57, 58, 60, 65, 96, 105, 112, 114, 116, 134, 135, 175, 219, 238, 244, 249, See cross-site scripting cryptographic storage, 60, 70, 71, 143, 144, 146, 169, 175, 176, 177, 185, 198 cryptography application block, 172 CSRF. See cross-site request forgery CSS, 39, 49 custom errors, 31, 123, 125, 130, 131, 133, 135 customErrors, 123, 124, 125 CWE. See Common Weakness Enumeration
B
BackTrack, 201 Barry Dorrans, 174 bcrypt, 161 BEAST. See browser exploit against SSL Beginning ASP.NET Security, 174 BeginRequest, 44, 229 Bit.ly, 59 blacklist, 23, 49 Bobby Tables, 24 data context, 36, 46 data protection API, 175 db_datareader, 28 db_datawriter, 28 DBA, 28, 31 DBML, 27 defaultRedirect, 124, 125 DES, 145, 170 Developer Fusion, 65
252 | Index
DigiNotar, 208, 236 digital certificate, 207 direct object reference, 77, 78, 84, 89, 90, 91, 92, 191, 244 DisplayRememberMe, 73 DNN. See DotNetNuke DNS, 237 DotNetNuke, 44, 116, 117, 134 DPAPI. See data protection API
H
hash chain, 150 hash table, 60 HashAlgorithmType, 66 health monitoring, 125 Hewlet Packard, 249 Hotmail, 59 HSTS. See HTTP strict transport security HTML, 38, 39, 40, 44, 45, 46, 47, 48, 49, 50, 55, 96, 101, 191, 239 HtmlEncode, 46, 48, 49, 50, 55 HTTP 200 OK, 109 HTTP 301 MOVED PERMANENTLY, 225, 229, 230 HTTP 302 FOUND, 109, 242 HTTP 500 INTERNAL SERVER ERROR, 43, 124 HTTP strict transport security, 228, 229, 230, 231, 237 HTTP to HTTPS redirect, 227 httpCookies, 218, 220
E
eBay, 198, 247 EC2, 161 Edit This Cookie extension, 204 EnablePasswordReset, 66, 72 EnablePasswordRetrieval, 66 encoding, 41, 45, 47, 48, 49, 50, 52, 53, 54, 55, 56, 135, 186, 247 Enterprise Library, 172 Entity Framework, 244 ESAPI, 76, 94 Exif, 23 extended validation, 236 Extension Manager, 118
I
IETF, 228 IIS, 101, 136, 141, 191, 194, 211, 240 indirect reference map, 87, 90 information leakage, 93 initialisation vector, 171, 172, 173 injecting up, 38 input parsing, 33 integrated pipeline, 191, 194 Internet Explorer, 56, 57, 108, 112, 208, 228 IP Scanner app, 200 iPad, 91, 92, 198, 199, 200, 201, 202, 204 IsWellFormedUriString, 41, 243 IT security budget, 14 IV. See initialisation vector
F
Facebook, 59, 73, 113, 207, 223 FBI, 92 Fiddler, 32, 33, 82, 91, 99, 104, 111, 132, 191, 220, 225, 232, 246 Firebug, 81, 82 Firefox, 32, 110, 207, 228, 231 Firesheep, 207, 235 Flash, 233 FormatException, 25, 27 fuzzer, 17
G
Gawker, 92, 143, 145, 157 GetSafeHtml, 50 GetSafeHtmlFragment, 50 global.asax, 229 Gmail, 223, 224, 235, 236 GoDaddy, 208 GoodSecurityQuestions.com, 76 Google, 70, 77, 81, 193, 194, 204, 233, 235, 236, 248, 249 Googledork, 193
J
Java, 14 JavaScript, 39, 40, 48, 49, 50, 85, 100, 102, 106, 111, 220, 223, 224 JavaScriptEncode, 49 JPG, 23 jQuery, 78 JSON, 81, 99, 109, 191
253 | Index
K
key management, 174 key stretching, 162
ORM, 27, 33, 35, 147, 166 OWASP risk rating methodology, 238
P L
padding oracle, 117, 135, 145 password, 40, 59, 60, 62, 66, 67, 70, 71, 72, 73, 74, 75, 130, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 155, 156, 157, 158, 159, 160, 161, 162, 166, 169, 171, 172, 173, 176, 193, 194, 196, 206, 220, 223, 239 PasswordAttemptWindow, 66 PasswordStrengthRegularExpression, 66, 71 PayPal, 236 PDF, 192 Perl, 14 phishing, 56, 196, 238 PHP, 14, 111, 112 POST data, 99 principle of least privilege, 28, 31, 137, 138, 176 principle permission, 189 private key, 174 privileged account, 60 privileged page, 179 provider model, 66, 68, 169, 178, 193 public key, 145, 207
LDAP, 16, 17, 34 legacy code, 17 LINQ to SQL, 27, 33 Linux, 201 literal control, 37 LoginStatus, 67, 69 LoginView, 67, 69
M
MAC address, 198, 201 machineKey, 126 malware, 36, 56, 238, 239 man in the middle, 196, 223, 226, 229 markup, 39, 44, 50, 221 MaxInvalidPasswordAttempts, 66 McDonalds, 200, 201, 206, 214 MD5, 145, 147, 148, 150, 156, 157, 158, 161, 169 membership provider, 66, 67, 71, 72, 76, 86, 162, 169, 185, 187, 188, 191, 193, 197, 203, 206, 217 MinRequiredNonAlphanumericCharacters, 66, 71 MinRequiredPasswordLength, 66, 71 MITM. See man in the middle Moxie Marlinspike, 226, 236 Mozilla, 74, 111, 112 MSDN, 24, 86, 136, 147 MVC, 197, 220, 221 MVP, 11
Q
Qantas, 232 query string, 16, 18, 19, 21, 22, 27, 37, 38, 39, 40, 122, 134, 238, 240, 242, 243, 244, 247, 249
R
rainbow table, 145, 150, 151, 152, 153, 154, 155, 156, 157, 158, 160, 161, 177 RainbowCrack, 150, 151, 152, 155, 157, 158, 159, 177 reduction function, 150 referrer checking, 245, 250 regex. See regular expression regular expression, 24, 41, 42, 47 remember me, 60, 73, 95 request header, 16, 23, 32, 111, 238 request validation, 44, 134, 142 requestValidationMode, 44, 135 RequireHttps, 220 RequiresQuestionAndAnswer, 67, 72 requireSSL, 215, 218 response header, 230
N
Netscape, 197 nonce, 145 Northwind, 18, 20, 27 NSA, 177 NuGet, 117 NUnit, 120
O
obfuscate, 55 OpenID, 59
254 | Index
ResponseRedirect, 124 ResponseRewrite, 124, 125 REST, 178, 191 RFC3986, 41 RFP3986, 41 RFP3987, 41 Root Certificate Program, 208 rootkit.com, 143, 148, 157 RSA, 146
SSL Strip, 236 Stack Overflow, 59, 133, 174, 193, 194 stack trace, 125 StartSSL, 209, 237 stored procedure, 24, 25, 28, 34, 35, 137, 166, 188 Strict-Transport-Security header, 228, 229, 230, 231 symmetric encryption, 145, 170, 171, 174, 176 symmetric-key, 145 synchroniser token pattern, 105, 113, 114
S
Safari, 111, 112, 228 salted hash, 70, 71, 145, 157, 158, 159, 160, 161, 162, 169, 171, 172 saltybeagle.com, 111 Sarah Palin, 71 schema, 20, 21 Scott Allen, 66 Scott Gu, 131, 211 secret question, 71 secure cookie, 215, 216 Secure Sockets Layer. See TLS security runtime engine, 50, 52, 54, 57, 58 security through obscurity, 77, 90, 185, 194 security trimming, 185, 186, 193, 194 SecurityException, 190 self-signed certificate, 211, 213 server variables, 129 session fixation, 68 session hijacking, 68, 206, 212, 214, 216, 221, 235 session ID, 60, 61, 64, 65, 68, 69, 104, 196, 216, 219 session token, 59 SessionStateSection.RegenerateExpiredSessionId, 69 SHA, 145, 161, 169 sidejacking, 206 SIM card, 91 Singapore Airlines, 222, 224 Skype, 236 sliding expiration, 221 slidingExpiration, 221 social engineering, 15, 56, 105, 114, 235, 238, 239 Sony, 143, 157 sp_executesql, 26, 27 SQL, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 40, 76, 114, 138, 140, 141, 149, 164, 172, 244 SRE. See security runtime engine SSL. See transport layer security
T
TCP stream, 203, 209 threat model, 55 time-memory trade-off, 150 TLS. See transport layer security trace.axd, 130 tracing, 127, 130, 131, 133 transport layer security, 60, 70, 135, 146, 195, 196, 197, 198, 206, 207, 208, 209, 211, 212, 217, 218, 220, 221, 222, 223, 224, 226, 227, 231, 232, 233, 235, 236, 237 Tunisia, 223, 226 Twitter, 11, 73, 233, 241, 245, 246, 247
U
UAC, 243 UI, 31, 47, 52, 55, 72, 87, 148, 173, 188, 189, 191 unvalidated redirect, 238, 239, 242, 243, 247, 248 US military, 75 user agent, 91 UserIsOnlineTimeWindow, 67 UseUri, 234
V
validateRequest, 44, 135 validation, 23, 24, 36, 37, 42, 43, 44, 47, 54, 57, 131, 134, 135, 141, 238, 243, 244, 250 Visual Studio, 67, 79, 118, 164, 185
W
WCF, 86, 96, 99, 100, 102, 110, 191 Web 2.0, 114 web.config, 65, 123, 130, 131, 135, 136, 137, 141, 185, 186, 193 WebInspect, 249 WhiteHat Security, 14, 35
255 | Index
whitelist, 23, 24, 25, 27, 41, 42, 47, 49, 134, 135, 243, 244, 245, 250 wifi hotspot, 206 Windows certificate store, 174 Wireshark, 201, 211
Y
Yahoo, 223, 224, 236 yellow screen of death, 20, 122, 125, 126, 131 YouTube, 59, 73
X
XML, 49, 194, 244