Why Secure Design Matters ? Secure Approach to Session Validation on Modern Frameworks (Django Solution)

Why Secure Design Matters ? Secure Approach to Session Validation on Modern Frameworks (Django Solution)

September 8, 2019 14:30 Mehmet Ince Application Security


I've been doing security researches on softwares for a quite long time. During these researchs, I often find myself in a situation where in I think about the state of mind of developers, problems that occur during developments and core problems of nature of software crafting teams. Thinking about these questions always lead me to realize possible software bugs.

People developers are tend to make mistakes by the nature of human being. Mistakes made by developers usually end up with software bugs. If there is a software bug, security researchers always try to take an advantage of this bugs and convert it to a software vulnerability. For that reason, I always start my research by defining a places in softwares where things left to the developer's initiative. One of good the example for that 'places', which also main focus of this post, can be a session validations.

How does the application validate the session of user ?

It's very simple questions yet the answer is vary.. Let me give you couple of answers that I've seen in my life. I'll try to give a source code examples with different programming languages. But this post does not focus on technology stack, it focus the approach. So programming language shouldn't make any difference ?

#1 - Approaches in Controllers

Yes, as you know, you can easily check whether user has a valid session or not within controller.

class UserController extends Controller
{
    public function show($id)
    {
        if (! Auth::check()) {
            // The user is NOT. logged in...
        }
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

Of course, this approach is silly. But there is a lot of developers out there who are doing this... At some point, I believe, almost every single developers remember Don't Repeat Yourself rule and try to get ride off that repetitive code. First solution that pops in minds is usage of decorators. Instead of calling piece of code that check existence of session, you can define it as a function and you can call it as a decorator.

@flask_app.route('/log_browser/validate', methods=['POST'])
@login_required
def user(request, pk):
    # Do stuff in here
    return User.query.get(pn)

Good. But don't you feel like still repeating yourself ? %90 of endpoints you have in your application will require a valid session. Why would you call decorator every single time ? On the other hand, what will happen if you forget to call decorator ? 1,5 year ago I've found a 0day Remote Code Execution vulnerability because developer has forgotten to call one specific decorator :) (Click here to read full story about that 0day vulnerability.)

In order to less repeat yourself, second approach you can follow is create a two different controller as follow.

class PubliclyAccessibleController extends Controller{
    function __construct(){
        if (! Auth::check()) {
            return redirect_to_login();
        }
    }
}

class AuthenticatedController extends Controller{
    
}

class UserController extends AuthenticatedController
{
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

This is a better approach for sure. But it has couple of downsides. If you want to have publicly accessible endpoint, which means anonymous user can interact with it, you must inherit PubliclyAccessibleController. That may looks like right at the first glance but I've seen that approach cause a trouble too. Think about engineering cultures of developer teams. What will happen if you have a new developer on your team ? Do you believe that new people joined to the team will know that they should inherit AuthenticatedController ? More and more questions pops in my mind about documentation, code reviews etc etc.

Solutions must be improved.

#2 - Approaches in Middleware/Filter

I believe everyone know and/or feels that the problem should be taken care off before controllers. Before execution of controller, we have routing definitions where we can try to solve that problem.

You have routes where you define path mapping to the controllers. Some frameworks defines routing in xml configurations while others have them in code base. Theoretically, by using capabilities of frameworks we can validate session before execution of the controller.

Route::group(['middleware' => 'auth'], function () {
    // Logout
    Route::get('admin/platform/create', 
    // Article index
    Route::get("admin/article", 'AdminController@allArticle');
    // Article create and store
    Route::get('admin/article/create', 'ArticleController@create');
});

Any of the given route in the list requested by a user, framework executes middleware named as auth. Within the auth middleware we perform session validation. So that we can validation session before execution of the controller. You don't need to think about session decorators or controller inheritance anymore. But you have to put route in a proper routing list every single time in order to have session validation, which is still a task to done by developers.

Instead of waiting stuff from developer in order to have a session validation on new endpoint, we should change our software design to enforce authentication by default! So that developers should work for to have anonymously accessible endpoint.

Let's Force Authentication By Default !

So far, I've tried to explain why we should change our design by giving different approaches and their problems.

  • We must forcefully enable session validation for every endpoint.
  • Developers must do something to make it publicly accessible instead of making it authentication protected !
  • We should do this in filter/middlewares.

I've developed a small Django package. It's quite small one. Once you get the idea, you can develop the same one for different language/frameworks easily. (https://github.com/mmetince/django-forceauth)

I've created one decorator and one mixing.

def publicly_accessible_endpoint(view_func):
    return view_func

class PubliclyAccessibleEndpointMixin:
    publicly_accessible_endpoint = None

They do literally nothing. But they provide you the way to tell middleware that the endpoint want to be anonymously accessible !

Now we have middleware where we have the magic.

from django.conf.global_settings import LOGIN_URL
from django.http import HttpResponseRedirect
from django.utils.deprecation import MiddlewareMixin

class ForceAuthenticationMiddleware(MiddlewareMixin):
    """
    Bla bla bla
    """
    def process_view(self, request, view_func, view_args, view_kwargs):
        # If it's authenticated user we don't have to do anything.
        if request.user.is_authenticated:
            return None
        # We need to decide that view is function or class. Easiest way to do it check existince
        # of view_class attribute of view_func. While __global__ exist on every object,
        # Class-based-views only have view_class.
        if hasattr(view_func, 'view_class'):
            if not hasattr(view_func.view_class, 'publicly_accessible_endpoint'): return HttpResponseRedirect(LOGIN_URL)
        else:
            if 'publicly_accessible_endpoint' not in view_func.__globals__: return HttpResponseRedirect(LOGIN_URL)

That middleware is being executed before view call. It checks attributes of mapped view. If the view doesn't have 'publicly_accessible_endpoint' attribute, which they can be given by calling above decorator and mixing, it redirect user back to login without continue to execution.

For example, lets say you have a one function-based-view. If you call publicly_accessible_endpoint decorator, it will the middleware that don't do session validation for that endpoint .

@publicly_accessible_endpoint
def user(request):
    ...
    return render(request, 'user', context={})

Same can be done easily for Class-Based-Views.

class AuthLoginView(LoginView, PubliclyAccessibleEndpointMixin):
    pass

Thanks for reading !

secure coding application
CATEGORIES Network Operating System Application Security Database Tools Research Advisories Android Threat Intel
TAGS Cyber Intelligence Botnet Organized Cyber-Crime Cyber Espionage tunneling dns ssh icmp wireless wpa wep wpa2 ddos dos udp tcp flood network wpad netbios mitm responder windows llmnr pivoting routing metasploit advisory 0day malware fud crypter backdoors binary reversing patching antidetection privilege escalation shellcode encoder decoder EMET exploit bypass ransomware packer multi-stage stager in-memory reflective bof iat hook assembly self-defence burp sql injection sqlmap secure coding application php phishing word office exploit 1day alienvault nas rce research storage IOT linux siem securityonion android reverse