A04 - 2021: Insecure Design


This vulnerability category is new in the 2021 version of the top 10. It focusses on the insecurities that creep into our code bases and applications. The general takeaway seems to be that we want to launch an open invitation to any developer, tester or manager to advocate better secure design practices, threat modeling and reference architectures.

What is it?

Insecure design is a really broad understanding which can cover many area's of software development. Whenever this issue type arises, we can see time and time again why secure design practices should be held in mind with great regards. This vulnerability category is used whenever a security control is absent. An example of this vulnerability type could be where users are not allowed to view each other's data but they can. The absence of business logic for the validation of the security rules falls unders this category.

Anything you can think of from being able to do stuff a user should not to a program giving wrong output in favour of the attacker but it's important to understand secure design is more than just a way of handling your software development. It is in fact a culture, mindset and methodology which are constantly evolving. It has been brought to life to ensure that the validity of our security measures is constantly evaluated and tested to prevent known attacks.

Secure design requires a secure development lifecycle, some form of secure design pattern or paved road component library or tooling, and threat modeling.

To speak of secure design we have to build security into our software development lifecycle together with some form of secure design patterns such as "Secure Factory" or "Secure Chain of Responsibility" for example. We will look some deeper into a few of these, but i can highly recommend you to do some more research as to what is out there.

Secure design patterns

We first need to make a distinction between the 3 main types of patterns we will be talking about today. We have Architectural-level patterns, Design-level patterns and Implementation-level patterns.

The Architectural-level patterns will discus any the interaction between higher level components. These patterns will be available in the least amount but they will have some more impactful and cost-efficient patterns as catching a bug as early as possible is always the cheapest way to fix it.

The Design-level patterns break down the higher level components and discus how to implement those chunks in a secure manner. They do not cross over different high-level components and never the integration of different high-level components.

The Implementation-level patterns will go over the lowest possible level of security issues and will usually be applicable to how we implement specific functions or methods of an application. They will address the programming specific implementations and are often linked to secure coding guidelines as well.

Secure coding guidelines are another set of patterns that describe how to safely code and implement the features of an application but we will not be going over them in this document.

Architectural-level patterns

Distrustful Decomposition

With this pattern we want to reduce any potential attack surface by grouping individual functions into programs which are separate from one another and which we all distrust equally. We then decrease the permissions of these modules as much as possible while still ensuring their functionality. The idea is to decrease the impact of an attack by limiting the scope to only that specific functionality. Attackers will often target high privilege systems which means an actual attack means big business. A compromised system often means the attacker can work his way to other pieces of information/data due to their high privileges. We can reduce this risk greatly by diminishing the privileges of separate components after segregation. Each program runs in its own process space with potentially separate user privileges.

Design-level patterns

Secure Factory

We all know what an object is, objects get created, read, updated or deleted and often when we create the coding for this, the security logic gets mixed up right into the business logic. This is a bad practice and the whole intent of this design pattern is to prevent this and to separate the security and business logic on the object level. In essence, we want to move all security related code to a security factory and when an object is requests, the request goes to the security factory which validates the credentials and returns the requested object.

Secure Builder Factory

This pattern is the same as the Secure factory but specifically focusses on complex object. A complex object is generally defined as a library object that is made up from many interrelated elements or digital objects [Arms 2000].

Secure Chain of Responsibility

Imagine a scenario in which a user is trying to show an invoice but depending on the users credentials he may see more or less information on the invoice. First of all the request comes in and gets handled by the invoice PDF generator for the sales rep. This should show the most amount of information and if the authorisation fails, the next step can be tried and the customer PDF generator can test the authorisation. This tiered system describes the secure chain of responsibilities.

The Implementation-level patterns

Secure Logger

One of the first things you learn as a developer is the importance of logging. This logging is not only important to good actors however, usually it will be full of information that hackers can use and sometimes abuse to gain access or pivote on our systems. To avoid this, we need to implement secure logging practices which dictate we send every line of logging to a secure logging framework (Such as syslog-ng which provides secure logging in a centralised manner). This will prevent attackers from gaining access to log files without knowing the decryption keys.

How to prevent it