Sorted By: Tag (security)

ASP.NET Core Authentication in a Load Balanced Environment with HAProxy and Redis

Token based authentication is a fairly common way of authenticating a user for an HTTP application. However, handling this in a load balanced environment has always involved extra caring. In this post, I will show you how this is handled in ASP.NET Core by demonstrating it with HAProxy and Redis through the help of Docker.
2016-11-28 23:31
Tugberk Ugurlu


Token based authentication is a fairly common way of authenticating a user for an HTTP application. ASP.NET and its frameworks had support for implementing this out of the box without much effort with different type of authentication approaches such as cookie based authentication, bearer token authentication, etc. ASP.NET Core is a no exception to this and it got even better (which we will see in a while).

However, handling this in a load balanced environment has always involved extra caring as all of the nodes should be able to read the valid authentication token even if that token has been written by another node. Old-school ASP.NET solution to this is to keep the Machine Key in sync with all the nodes. Machine key, for those who are not familiar with it, is used to encrypt and decrypt the authentication tokens under ASP.NET and each machine by default has its own unique one. However, you can override this and put your own one in place per application through a setting inside the Web.config file. This approach had its own problems and with ASP.NET Core, all data protection APIs have been revamped which cleared a room for big improvements in this area such as key expiration and rolling, key encryption at rest, etc. One of those improvements is the ability to store keys in different storage systems, which is what I am going to touch on in this post.

The Problem

Imagine a case where we have an ASP.NET Core application which uses cookie based authentication and stores their user data in MongoDB, which has been implemented using ASP.NET Core Identity and its MongoDB provider.

1-ok-wo-lb

This setup is all fine and our application should function perfectly. However, if we put this application behind HAProxy and scale it up to two nodes, we will start seeing problems like below:

System.Security.Cryptography.CryptographicException: The key {3470d9c3-e59d-4cd8-8668-56ba709e759d} was not found in the key ring.
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked)
   at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData)
   at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken)

Let’s look at the below diagram to understand why we are having this problem:

2-not-ok-w-lb

By default, ASP.NET Core Data Protection is wired up to store its keys under the file system. If you have your application running under multiple nodes as shown in above diagram, each node will have its own keys to protect and unprotect the sensitive information like authentication cookie data. As you can guess, this behaviour is problematic with the above structure since one node cannot read the protected data which the other node protected.

The Solution

As I mentioned before, one of the extensibility points of ASP.NET Core Data Protection stack is the storage of the data protection keys. This place can be a central place where all the nodes of our web application can reach out to. Let’s look at the below diagram to understand what we mean by this:

4-ok-w-lb

Here, we have Redis as our Data Protection key storage. Redis is a good choice here as it’s a well-suited for key-value storage and that’s what we need. With this setup, it will be possible for both nodes of our application to read protected data regardless of which node has written it.

Wiring up Redis Data Protection Key Storage

With ASP.NET Core 1.0.0, we had to write the implementation by ourselves to make ASP.NET Core to store Data Protection keys on Redis but with 1.1.0 release, the team has simultaneously shipped a NuGet package which makes it really easy to wire this up: Microsoft.AspNetCore.DataProtection.Redis. This package easily allows us to swap the data protection storage destination to be Redis. We can do this while we are configuring services as part of ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    // sad but a giant hack :(
    // https://github.com/StackExchange/StackExchange.Redis/issues/410#issuecomment-220829614
    var redisHost = Configuration.GetValue<string>("Redis:Host");
    var redisPort = Configuration.GetValue<int>("Redis:Port");
    var redisIpAddress = Dns.GetHostEntryAsync(redisHost).Result.AddressList.Last();
    var redis = ConnectionMultiplexer.Connect($"{redisIpAddress}:{redisPort}");

    services.AddDataProtection().PersistKeysToRedis(redis, "DataProtection-Keys");
    services.AddOptions();

    // ...
}

I have wired it up exactly like this in my sample application in order to show you a working example. It’s an example taken from ASP.NET Identity repository but slightly changed to make it work with MongoDB Identity store provider.

Note here that configuration values above are specific to my implementation and it doesn’t have to be that way. See these lines inside my Docker Compose file and these inside my Startup class to understand how it’s being passed and hooked up.

The sample application can be run on Docker through Docker Compose and it will get a few things up and running:

  • Two nodes of the application
  • A MongoDB instance
  • A Redis instance

Image

You can see my docker-compose.yml file to understand how I hooked things together:

mongo:
    build: .
    dockerfile: mongo.dockerfile
    container_name: haproxy_redis_auth_mongodb
    ports:
      - "27017:27017"

redis:
    build: .
    dockerfile: redis.dockerfile
    container_name: haproxy_redis_auth_redis
    ports:
      - "6379:6379"

webapp1:
    build: .
    dockerfile: app.dockerfile
    container_name: haproxy_redis_auth_webapp1
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_server.urls=http://0.0.0.0:6000
      - WebApp_MongoDb__ConnectionString=mongodb://mongo:27017
      - WebApp_Redis__Host=redis
      - WebApp_Redis__Port=6379
    links:
      - mongo
      - redis

webapp2:
    build: .
    dockerfile: app.dockerfile
    container_name: haproxy_redis_auth_webapp2
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_server.urls=http://0.0.0.0:6000
      - WebApp_MongoDb__ConnectionString=mongodb://mongo:27017
      - WebApp_Redis__Host=redis
      - WebApp_Redis__Port=6379
    links:
      - mongo
      - redis

app_lb:
    build: .
    dockerfile: haproxy.dockerfile
    container_name: app_lb
    ports:
      - "5000:80"
    links:
      - webapp1
      - webapp2

HAProxy is also configured to balance the load between two application nodes as you can see inside the haproxy.cfg file, which we copy under the relevant path inside our dockerfile:

global
  log 127.0.0.1 local0
  log 127.0.0.1 local1 notice

defaults
  log global
  mode http
  option httplog
  option dontlognull
  timeout connect 5000
  timeout client 10000
  timeout server 10000

frontend balancer
  bind 0.0.0.0:80
  mode http
  default_backend app_nodes

backend app_nodes
  mode http
  balance roundrobin
  option forwardfor
  http-request set-header X-Forwarded-Port %[dst_port]
  http-request set-header Connection keep-alive
  http-request add-header X-Forwarded-Proto https if { ssl_fc }
  option httpchk GET / HTTP/1.1\r\nHost:localhost
  server webapp1 webapp1:6000 check
  server webapp2 webapp2:6000 check

All of these are some details on how I wired up the sample to work. If we now look closely at the header of the web page, you should see the server name written inside the parenthesis. If you refresh enough, you will see that part alternating between two server names:

Image

Image

This confirms that our load is balanced between the two application nodes. The rest of the demo is actually very boring. It should just work as you expect it to work. Go to “Register” page and register for an account, log out and log back in. All of those interactions should just work. If we look inside the Redis instance, we should also see that Data Protection key has been written there:

docker run -it --link haproxy_redis_auth_redis:redis --rm redis redis-cli -h redis -p 6379
LRANGE DataProtection-Keys 0 10

Image

Conclusion and Going Further

I believe that I was able to show you what you need to care about in terms of authentication when you scale our your application nodes to multiple servers. However, do not take my sample as is and apply to your production application :) There are a few important things that suck on my sample, like the fact that my application nodes talk to Redis in an unencrypted fashion. You may want to consider exposing Redis over a proxy which supports encryption.

The other important bit with my implementation is that all of the nodes of my application act as Data Protection key generators. Even if I haven’t seen much problems with this in practice so far, you may want to restrict only one node to be responsible for key generation. You can achieve this by calling DisableAutomaticKeyGeneration like below during the configuration stage on your secondary nodes:

public void ConfigureServices(IServiceCollection services)
{
     services.AddDataProtection().DisableAutomaticKeyGeneration();
}

I would suggest determining whether a node is primary or not through a configuration value so that you can override this through an environment variable for example.

SQL Injection vs. Lethal Injection / Protection Against SQL Injection

SQL Injection and Lethal Injection... They are both dangerous and they can be easily fatal. But how? What is SQL Injection and how it can effect my project? The answers are in this blog post.
2011-01-29 18:37
Tugberk Ugurlu


lethal-injectionWriting a software, web application code is a real deal. It requires a good quality of talent on programing languages, appropriate analectic approach and most of all, a good prescience on your project. The things I have mentioned are very important and basic features of a qualified programmer.

I am not a student of a computer related course and I haven’t been but I support that educational background on computer science makes a difference on the quality of programmer. But the diploma or the course certificate is not enough. Little mistakes could be unforgivable in programming world and your diploma or certificate cannot get those mistakes back or cover them.

As for our topic, SQL injection is one of the most important topic on programming security. I have seen couple of developer’s “handy” work for last several months and I decided to write this blog post and I would like say all of the developers, with no offense;

Please, if you are creating a project with database structure, for the sake of programming, be aware of the SQL injection and its effects. It is not a shame that you haven’t heard of that term. What the shame is to write lines of codes creating the proper connection with your database without considering the effects of SQL injection !

NO OFEENSE !

What is SQL Injection?

Well, some of you might want to know what the SQL injection is. I won’t explore the world from scratch, so here is the clear explanation that I quoted from Wikipedia;

SQL injection is a code injection technique that exploits a security vulnerability occurring in the database layer of an application. The vulnerability is present when user input is either incorrectly filtered for string literal escape characters embedded in SQL statements or user input is not strongly typed and thereby unexpectedly executed. It is an instance of a more general class of vulnerabilities that can occur whenever one programming or scripting language is embedded inside another. SQL injection attacks are also known as SQL insertion attacks.

 

sql-injection

So the definition supposed to clear the question marks but it might not. Let’s demonstrate.

Imagine that you have a web application running on the web and it aims to provide an user interface to your customers to view their account details.

The demo application is pretty small and we will only create 2 pages with one database table. Page 1 will be the wrong scenario and 2nd one will be the right.

In this application, we will see how the end user can easily display the sensetive data you migh have in your database.

"I would like to say this, in a nutshell, nobody (I mean a programmer who knows what he/she is doing) developed a kind of application for that kind of purpose but to demonstrate the topic, I have done something like that. The project is not supposed to be a real world example."

Our database structure looks like this;

 

 

sql-injection-demo-project-server-explorer-view-for-sql-database-structure

sql-injection-demo-project-data-view-for-sql-database-structure

I won’t dive into details, I will post the project code so your could download and dig it letter.

SQL Injectable Page 

I have used GridView to list the data and here is what the user page looks like;

sql-injection-demo-project-sql-injection-open-page-view

The code has been use to provide the data is as exactly below;

        protected void butn_click(object sender, EventArgs e) {

            GridView1.DataSource = DataProvider(txt1);
            GridView1.DataBind();
        }

        private static DataSet DataProvider(TextBox mytext) {

            string connectionString = WebConfigurationManager.ConnectionStrings["SampleConnectionString"].ConnectionString;
            string sql = "SELECT * FROM Customers WHERE ([TCKimlikNo] = '" + mytext.Text + "')";
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand(sql, con);
            cmd.CommandType = CommandType.Text;
            SqlDataAdapter MyAdapter = new SqlDataAdapter(cmd);
            DataSet ds = new DataSet("MyDs");
            MyAdapter.Fill(ds, "MyDs");

            return ds;
        }

“DataProvider()” static method connects to the database and executes some SQL against a SQL Server database that returns the number of rows where the user data supplied by the user matches a row in the database. If the result is one matching row, that row will be displayed as you can see;

 

sql-injection-demo-project-sql-injection-open-page-view-no-harm

Let’s put a break point on the 10th line and hit it;

 

sql-injection-demo-project-sql-injection-breakpoint-debug

 

The value supplied above for TCKimlikNo is 34265128731. As we can see in the image, the code works perfectly fine and the value is on the place that we wanted. Now let’s do some evil things;

 

sql-injection-demo-project-sql-injection-breakpoint-debug-evil

 

Now the query explains itself pretty clearly. The evil user put this;

hi’ or ‘1’ = ‘1

And the logic fits. Method will return all the rows inside the database table. Look at the result;

 

image

 

Boom, you have been hacked ! This is the SQL Injection my friends. This thing is easy to apply and the worse part, this mistake is being made often.

Here is a quote from Mike’s blog;

This is SQL Injection. Basically, additional SQL syntax has been injected into the statement to change its behavior. The single quotes are string delimiters as far as T-SQL is concerned, and if you simply allow users to enter these without managing them, you are asking for potential trouble.

What is the Prevention?

Easy ! Just do not create the world from scratch.

If you are a ASP.Net user, use parameters instead of hand made code. Review the following code and compare it with the previous one;

            string connectionString = WebConfigurationManager.ConnectionStrings["SampleConnectionString"].ConnectionString;
            string sql = "SELECT * FROM Customers WHERE ([TCKimlikNo] = @IDParameter)";
            SqlConnection con = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand(sql, con);
            cmd.CommandType = CommandType.Text;
            cmd.Parameters.Add("@IDParameter", SqlDbType.VarChar);
            cmd.Parameters["@IDParameter"].Value = mytext.Text;

            SqlDataAdapter MyAdapter = new SqlDataAdapter(cmd);
            DataSet ds = new DataSet("MyDs");
            MyAdapter.Fill(ds, "MyDs");

            return ds;

Done ! You will be good to go Smile But what happened there? Here is a good quote from Mike’s blog again;

Parameter Queries

Parameters in queries are placeholders for values that are supplied to a SQL query at runtime, in very much the same way as parameters act as placeholders for values supplied to a C# method at runtime. And, just as C# parameters ensure type safety, SQL parameters do a similar thing. If you attempt to pass in a value that cannot be implicitly converted to a numeric where the database field expects one, exceptions are thrown

Paramaters will protect your data if your building your project this way but another safe way is to use LINQ to SQL and Entity Framework to protect your project against SQL Injection.

Tags