OWASP Top 10 #6 – Security Misconfiguration

What Is It?

Security misconfiguration is anything which is considered insecure due to the settings or configuration upon a server.

This can be anything from debug settings being turned on or even out of date third party frameworks being used.

Custom Errors

Leaking of information about the implemented technology stack could be an invitation for hackers to probe a system further. With regards to an SQL injection attack, error messages can be used to view details about the database schema such as table names and then further be used to display the contents of a database table.

Error messages displayed to a user should contain information useful to the user only and nothing else. They should report to the user the minimal amount information for them to react to the error.

Any information such as details of the error or the trace stack should be hidden from the user.

We can disable the standard .NET ‘yellow’ error message which contains the trace stack and the details of the error on a site basis. This is done within the customErrors node of the web.config

<customErrors mode="On" defaultRedirect="Error.aspx" />

The mode attribute can contain the following values:

  • On
    • Errors are reported in full. This is the default value.
  • Off
    • Errors are not reported. The user is redirected to the page defined within the defaultRedirect attribute
  • RemoteOnly
    • Users viewing the site on their local host are reported the errors in full
    • Users not viewing the site on their local host are redirected to the page defined within the defaultRedirect attribute.

If no defaultRedirect is provided the user is presented with a http response code of 500. This in itself is bad as it highlights our site as a potential site worthy of more probing by a hacker. The status code is easily read by batch scripts probing sites.

Providing a defaultRedirect presents the user with the page defined by the attribute along with a http status code of 302 (temporary redirect). This unfortunately creates a query string parameter of aspxerrorpath set to the relative URL of the page which caused the error. This can also be easily used by batch scripts to determine if a page is an error page.

We can redirect to the error page without the indicating that there was an error by changing the RedirectMode from ResponseRedirect (default) to ResponseRewrite.

<customErrors mode="On" defaultRedirect="Error.aspx" RedirectMode="ResponseRewrite" />

With this set, the contents of the error page is simply set to the response of the request without a redirect. The http status code is set to 200 which is the OK code. The hacker would need to read the contents of the page to try and determine if the page is an error page. This in itself is harder and slower. Anything we can do to slow down the hacker reduces the amount of hacking they can do on our site.

Disabling Trace

Trace information allows collecting, viewing and analysis of debug information that is collected and cached by ASP.NET. The information can be viewed at the bottom of individual pages or within the trace viewer which is accessed by calling the Trace.axd page from the route directory of the web site.

Like any debug information it contains details of implemented technology and potentially sensitive information that might be logged by accident. There are many instances of security issues where personal information such as credit card details or user accounts used in database connection strings which are logged as part of the debugging.

Tracing should be disabled within the web.config for all production environments.

<trace enabled="false" />

Keep Frameworks/Third Party Code Up To Date

All third party frameworks should be kept up to date to ensure that you are protected from any security issues which have been fixed within them.

With the use of NuGet this can be achieved very easily.

Encrypting Sensitive Parts Of The Web.Config

Sensitive data such as user accounts, passwords and private keys that are contained within the web.config should be encrypted.

If they are accidentally exposed to the user during an error while accessing a page or logged into an audit table and viewed by someone who should not see them, their details will not be readable.

It is also a good idea to reduce the amount of people who have access to sensitive information.

You can encrypt a node of a web.config at the visual studio command prompt by the following:

aspnet_regiis -site "Site Name In IIS" -app "/" -pe "connectionStrings"

This will update the web.config replacing the sensitive information with an encrypted version.

The file can be decrypted with the following command:

aspnet_regiis -site "Site Name In IIS" -app "/" -pd "connectionStrings"

This process uses a key which is created based upon the servers MAC address and as such is unique to the server and therefore can only be decrypted upon the web server.

Using Config Transforms To Ensure Secure Configurations

It can be very easy to leave debug settings such as non custom errors and tracing within a web.config.

Transforms for debug and release can be used to ensure that only settings for the required release are used when that release target is used.

Use Web.Release.Config to transform the Web.Config for release:

  • Remove debug attributes
  • Add custom errors
  • Remove trace

These transforms are only run during a site publish.

Retail Mode

Retail mode can be used as a back stop to ensure that no web.config setting which is considered unsafe in a production environment is actually enabled. For example in retail mode, turning off custom errors or turning on tracing will have no effect.

This setting can be set in the web.config though it is recommended to set it within the machine.config of the web server.

<deployment retail="true" />

OWASP Top 10 #5 – Cross Site Request Forgery (CSRF)

What Is It?

Before we talk about CSRF it is important to understand that all cookies created by a domain are sent back to that domain during page requests regardless which domain the page originated from. This includes cookies which contain authentication session cookies.

Hackers can simply change the action attribute of a form to be that of the domain/URL they are trying to breach. An unsuspecting site would only ensure that the authentication session token is valid. It would therefore be possible to use the users open session to perform malicious actions such as saving or reading data from a database.

How Can I Protect My Self From It?

Protection from CSRF attacks has a simple solution. Send another cryptically secure token to the user along with the authentication session token. This token is sent both on the page being generated as well as a cookie.

Upon receiving a post back from a page, the server simply reads the CSRF token from the page and the cookie and ensures that they are identical.

As only a page generated from the site would contain the token, any fake page posting back to the server will be missing the correct CSRF token.

MVC

The MVC helper method AntiForgeyToken generates a CSRF token and persists it in a cookie and a hidden input element on the page.

using (Html.BeginForm())
{
  @Html.AntiForgeyToken()
}

Validation against a CSRF attack is made by decorating the controller’s action method with the ValidateAntiForgeryToken attribute.

[HttpPost]
[Authorise]
[ValidateAntiForgeryToken]
public ActionResult Index(McModel bigMac)
{
}

If a CSRF attack is caught a System.Mvc.HttpAntiForgeryException is thrown and the user is presented with a 500 status code.

ASP.NET Web Forms

A page written in ASP.NET web forms would traditionally need to manually perform the CSRF token, its persistence and checks to ensure they are identical when the page is posted back.

However we can now make use of the AntiForgery class.

https://msdn.microsoft.com/en-us/library/system.web.helpers.antiforgery%28v=vs.111%29.aspx

Within the aspx page a call to the GrtHtml method would generate the CSRF token which is then persisted within a cookie and an input element upon the page.

<%= System.Web.Helpers.AntiForgery.GetHtml() %>

Validation against a CSRF attack would then be performed during a page post back with the Validation method.

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack) {
        AntiForgery.Validate();
    }
}

Securing Cookies

Http Only Cookies

Using AntiForgery.GetHtml or @Html.AntiForgeyToken should create cookies which are secured for access by the server only and not via a client side script.

However where a more manual approach has been made to CSRF protection it is important to ensure that the CSRF token cannot be read from by a client side script. If this is not the case then a malicious page could read the token and simply add it to its form data before posting back to the server; circumventing our protection.

Cookies can be set as HttpOnly via a property during their creation:

var cookie = new HttpCookie("MyCookies", DateTime.Now.ToString());
cookie.HttpOnly = true;

Cookies can be set as HttpOnly globally for a website via the web.config:

<httpCookies domain="String" httpOnlyCookies="true" />

Note is it probably wise to set httpOnlyCookies to true regardless of your CSRF approach. Any information stored within a cookie which could be readable on the client side is probably a bad idea.

SSL Only Cookies

Where cookies contain sensitive information such as CSRF or authentication session token, it would be wise to enforce their transportation via SSL or HTTPS, as such preventing people from spying on the network traffic and therefore stealing them.

This can be done globally for a site within the web.config:

<httpCookies domain="String" httpOnlyCookies="true" requireSSL="true" />

However this approach might not always be possible. We can conditionally determine when to enforce SSL communication for cookies by checking the forms authentication settings and the connection for their status of secure connections. The individual cookie can then then be secured for SSL communication only via the Secure property.

var cookie = new HttpCookie("MyCookies", DateTime.Now.ToString());
cookie.HttpOnly = true;

if( FormsAuthentication.RequireSSL && Request.IsSecureConnection) {
    cookies.Secure = true;
}

OWASP Top 10 #4 – Insecure Direct Object References

Direct Object Reference

A direct object reference is a key or id which is used identify a piece or set of data. The data would typically be a record within a a table of a database.

They can be:

  • Patterns such as incrementing integers/ids
  • Natural keys such as a username or email address.
  • Discoverable data such as a national insurance number or a social security number.

What Is it?

An insecure direct object reference is when a user accesses a direct object reference when they are not permitted to.

This can be from an unsecured page and an unauthenticated user. It can also come from a secured page and authenticated user, where there is simply insufficient checks that they can view the data they have requested.

Quite often the user guesses or enumerates a direct object reference either manually or by an automated script.

If the data should only be visible to a subset of people then any requests to that data should be validated that they are permitted to see that data. Simply relying on not displaying links to the user is not secure enough.

How Can I Protect Against It?

Validate Access

The first point to address is to validate that the user has permission to access the data.

For example; is the person permitted to see the financial records of the bank account in question?

Quite often the validation checks are placed around populating the UI to navigate to the data but no checks are placed around querying the data.

Indirect Reference Maps

A normal approach to view database records on a web page is to expose the table fields and their direct object references on the URL

https://www.MySite/Foo.aspx?id=1

Here an attacker can simply increment the id to try and view other peoples Foo which are simple incrementing ids or identity indexes generated automatically by the database.

An indirect reference provides an abstraction between what is publicly observable in the URL and what is used to uniquely identify a record within a database.

A dictionary is used to store a map between the id and a randomly generated cryptically secure identifier. We then pass these secure identifier to the user as part of URLs which they can request.

https://www.MySite/Foo.aspx?id=ASWERDFMJSDGJSERSASKAWEOSDSDF

These keys should also be mapped to the user’s session to protect against the ids being leaked:

The following code shows how to generate a cryptically safe unique id using the RNGCryptoServiceProvider class.

using System.Security.Cryptography;

using( var rng = new RNGCryptoServiceProvider() )
{
    map[id] = HttpServerUtility.UrlTokenEncode(rng.GetBytes(new byte[32]));
}

// Store the map within the users session
HttpContext.Current.Session["Map"] = map;

In the example above it would be best to only create one instance of the RNGCryptoServiceProvider class regardless of how many direct object references are being made. Consider creating a class which implements IDisposable which calls Dispose upon the instance of RNGCryptoServiceProvider within it’s Dispose method.

Obfuscation via non-discoverable surrogate keys

A surrogate key is a set of fields which when used together can uniquely identify a record. These are harder to guess than a single column which has a distinctive pattern such as an identify index which increments by one each time.

Surrogate keys would require more space to index and are also slower, it is a trade off between performance and security.

A random unique identifier such as a GUID is harder to guess but does not perform as well as an integer when used to search upon. Also for tables such as logs and audits which are used to generate a high number records, the indexes will become fragmented and as such will have more overhead for de-fragmenting them regularly.

OWASP Top 10 # 1 – Injection Attacks

Injection

What is it?

An injection attack is where a program, database or interpreter is tricked into executing code or performing functionality which was not intended to be run.

Though SQL injection is probably the most commonly known type of injection attack, it is a vulnerability that can affect many technologies;

  • SQL
  • XPath
  • LDAP
  • Applications / Operating Systems

SQL injection is when a query or command parameter which should contain a simple variable value actually contains additional syntax which is inadvertently executed by the database.

How does it happen?

For many web applications a URL such as http://www.TheFooSite.com/MyDatabaseTable?Id=1 would translated into a query such as:

Select * From MyDatabaseTable Where Id = 1

The integrity of the value of the id has come from the user and as such can contain potentially any malicious payload.

For example, we can change the sql query to return all data within the table:

http://www.TheFooSite.com/MyDatabaseTable?Id=1 or 1=1

Alternatively we could make the database throw an error which could potentially propagate to the user and show our technology stack, this in turn would invite any potential hacker to start a more in depth probe:

http://www.TheFooSite.com/MyDatabaseTable?Id=x

Additionally we could append on queries which query the schema tables such sysobjects and could start to expose our database structure to the user. This could then be used to append queries which exposes the potentially sensitive data contained within these tables such as credit card numbers and identity numbers:

http://www.TheFooSite.com/MyDatabaseTable?Id=SELECT name FROM master..sysobjects WHERE xtype = 'U';

Untrusted data sources

Any information which comes from the user, third party or external entity should be considered as untrusted and therefore potentially dangerous.

The sources of untrusted data are numerous:

  • From the user
    • In the URL via a query string or route parameter
    • Posted via a form
  • From the browser
    • In cookies
    • In the request headers
  • Web services calls
  • Database
  • Files such as XML, XSD, CSV

In fact anything which does not come directly from our running source code should be treated as potentially malicious. That includes data and services from within the bounds of our company as well as data entered directly from our users, even if they are our colleagues.

How can I protect myself from this?

Security is all about addressing the security concern at multiple levels; this way if one level is breached there are more levels which can catch the breach.

The principle of least privilege

The principle of least privilege is about giving any entity that requires permissions to perform a job, the minimum level of permissions it requires to perform that job.

Why grant access for the user account which is used by a web application to query an entire database, delete data, modify schema or even grant administrator level access? By granting the minimum level of permissions you dramatically reduced the level of damage that a breach can perform.

Don’t give the account db_owner access; this can read, update, delete, truncate and drop any table as well as execute any stored procedures and modify the schema.

A user account should be given access to:

  • Read from only the individual table(s) required
  • Write to only the individual table(s) required
  • Execute only the required stored procedures required

Any account accessed by a web application should probably never be given access to modify any schema, read or write to any schema or system tables and should probably not even be given access to physically delete data from any tables.

If you need to delete data, consider marking records as deleted by a status field and archiving off the records by a system task running in the background.

Where different levels of access is required between types of user account groups or roles, consider having multiple accounts. For example a public account used by public user groups and an admin account which is used by back office staff.

Please note: just because the user role is ‘admin’ it does not mean they should be given anything but the bare minimum permissions within the database.

In line SQL parametrisation

Most issues from SQL injection are from parameters being attached to the query or command via string concatenation. Take the following example which concatenates a parameter taken from the user via a URL query string parameter.

var id = Request.QueryString["Id"];
var sqlCommand = "SELECT * FROM MyTable WHERE id = " + id;

The data within the parameter is run as is within the database allowing the data within the parameter to break out of its parameter scope and into a query scope.

For example “1 or 1=1” will be searched upon as if it has two parts of the predicate.

By parametrising the query, the data contained within the parameter will stay within the scope of the parameter; here we will try and search for an id which is equal to “1 or 1=1” which at some level will either retrieve no data or throw an error when trying to convert between data types.

The following shows how we can parametrise an line query:

var id = Request.QueryString["id"];

var sqlCommand = "SELECT * FROM Foo WHERE Id = @id";

using (var connection = new SqlConnection(connString))
{
using (var command = new SqlCommand(sqlString, connection))
{
command.Parameters.Add("@d", SqlDbType.VarChar).Value = id;
command.Connection.Open();
var data = command.ExecuteReader();
}
}

We now have an error thrown within by the database as it cannot convert the string “1 or 1=1” into an integer; the data type of the id field.

Stored procedure Parametrisation

The problem with in line parametrised queries is that you have to remember to do them. By using stored procedures you are enforced to perform this step.

var id = Request.QueryString["id"];

var sqlString = "GetFoo";
using (var conn = new SqlConnection(connString))
{
using (var command = new SqlCommand(sqlString, conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@id", SqlDbType.VarChar).Value = id;
command.Connection.Open();
var data = command.ExecuteReader();
}
}

This will cause a similar issue that our bad string cannot be converted to an integer.

White listing untrusted data

Where possible, all untrusted data should always be validated against a white list of known safe values.

A white list states a set of rules which defines the data. Any incoming data must be validated against these rules to be considered safe. Failure of the validation should interpret the data as an expected attack.

There are multiple ways of performing white list:

  • Type conversion
    • Integer, date, GUID
  • Regular expression
    • Email address, phone number, name
  • Enumerated list of known good values
    • Titles, countries and colours for example

The following shows how we can use the try parse function to white list our id parameter by trying to convert it to an integer.

int idString;
if(!int.TryParse(idString, out id))
{
throw new Exception("The id is not a valid integer.");
}

Note: any errors should be handled, where required audited and then the user should be displayed a human readable message which gives nothing away about our implemented technology stack or in-depth details of the error.

Now that we have the parameter as integer we can add our parameter into the command as an integer:

command.Parameters.Add("@id", SqlDbType.Int).Value = id;

Any suspect attacks now should be caught way before the database which is a good place to be.

ORM’s

ORM’s should automatically parametrise any SQL.

The only thing to note is that when using ORM’s it is often required to drop to native SQL for performance reasons. When doing this care should be made to ensure all untrusted data is parametrised using the steps above.

Injection through stored procedures

Care should be taken when using stored procedures that they are not themselves concatenating parameters before calling exec.

declare @query varchar(max)
set @query = 'select * from dbo.MyTable where MyField like ''%'+ @parameter + '%''
EXEC(@query)

The stored procedure sp_executesql allows parametrising sql statements which are held as string.

declare @query varchar(max)
set @query = 'select * from dbo.Foo where MyField like ''%'+ @myFieldParameter + '%''

EXEC sp_executesql @query, N'@search varchar(50)', @myFieldParameter=@parameter

Automated SQL injection tools

There are a number of tools which can automate the hacking process, allowing people with next to no technical experience to perform automated attacks or to test the strength of web applications.

Best SQL Injection Tools