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 !