Skip links

A method to assess ‘forgivable’ vs ‘unforgivable’ vulnerabilities

This section explains how we arrived at the Implementation Score for each of the top-level mitigations. They are presented in order of difficulty, starting with the easiest mitigations first.

Input Validation

The OWASP Cheat Sheet (OWASP, 2021) provides the following examples for input validation strategies:

Input Validation is a subset of the Libraries or Frameworks mitigation. However, its prevalence in the mitigation list means it’s worth listing separately.

Score Evidence
Cost 1 Low/no direct and indirect costs
Knowledge 1 Widely understood and widely available
Feasibility 1 Many implementations for most/all languages
Total 3 (Easy)

Output Encoding

Encoding and escaping are defensive techniques meant to stop injection attacks. Encoding (commonly called ‘output encoding’) involves translating special characters into some different but equivalent form that is no longer dangerous in the target interpreter, for example translating the < character into the &lt; string when writing to an HTML page.

Escaping involves adding a special character before the character/string to avoid it being misinterpreted, for example, adding a \ character before a " (double quote) character so that it is interpreted as text and not as closing a string.

Output Encoding is a subset of the Libraries or Frameworks mitigation. However, its prevalence in the mitigation list means it’s worth listing separately.

Score Evidence
Cost 1 Low/no direct and indirect costs
Knowledge 1 Widely understood and widely available
Feasibility 1 Many implementations for most/all languages
Total 3 (Easy)

Reduce the Attack Surface

Originating from the security sector, an ‘attack surface’ measure typically reflects the number of input points and output points that can be exploited by a potential attacker. A larger attack surface provides more places to attack, and more opportunities for developers to introduce weaknesses. In some cases, this measure may reflect other aspects of quality besides security. For example, a product with many inputs and outputs may require a large number of tests in order to improve code coverage. This can also include the use of functions, libraries, and frameworks.

The attack surface of an application is the union of code, interfaces, services, protocols, and practices available to all users, with a strong focus on what is accessible to unauthenticated users (Microsoft, 2019). Therefore, developers should look to reduce the exposure of the application at the earliest stages of development, specifically at the design stage. Further considerations should be given at the coding stage and finally at runtime.

Score Evidence
Cost 2 Removing code/functionality will take time and thus cost
Knowledge 1 Concept is widely known and understood
Feasibility 2 Technically feasible but challenges in justifying removal of functionality/features
Total 5 (Medium)

Enforcement by Conversion

This mitigation involves converting the input into a different, well-controlled representation. For example, in PHP, a common mechanism for avoiding SQL injection is to apply intval() to all numeric inputs, which guarantees that the generated value is a number. 

Score Evidence
Cost 1 Usually available as part of the language
Knowledge 3 Not widely used or understood
Feasibility 1 Does not rely upon any prerequisite
Total 5 (Medium)

Sandbox or Jail

Run the code in a ‘jail’ or similar sandbox environment that enforces strict boundaries between the process and the operating system. This may effectively restrict which files can be accessed in a particular directory, or which commands can be executed by the software. 

OS-level examples include the Unix chroot jail, AppArmor, and SELinux. In general, managed code may provide some protection. For example, java.io.FilePermission in the Java SecurityManager allows the software to specify restrictions on file operations. 

This may not be a feasible solution, and it only limits the impact to the operating system; the rest of the application may still be subject to compromise. 

Score Evidence
Cost 1 Usually part of the language/OS
Knowledge 2 Concept is known and partially understood
Feasibility 2 Technically feasible but with limitations
Total 5 (Medium)

Secure Programming

This is a large subject and typically is down to the developers. Examples of secure programming include:

  • when freeing pointers, be sure to set them to NULL once they are freed
  • if all pointers that could have been modified are sanity-checked previous to use, nearly all NULL pointer dereferences can be prevented
  • ensure that all protocols are strictly defined such that all out-of-bounds behaviour can be identified simply, and require strict conformance to the protocol
  • when deserializing data, populate a new object rather than just deserializing
  • explicitly define a final object() to prevent deserialization
  • use library calls rather than external processes to recreate the desired functionality
Score Evidence
Cost 2 Requires skilled developers
Knowledge 2 Concept is known and partially understood
Feasibility 2 Technically feasible with few technical prerequisites
Total 6 (Medium)

Compilation or Build Hardening

There are 4 areas to be examined when hardening the toolchain: configuration, preprocessor, compiler, and linker. Developers and their development environments are part of the software supply chain, so if their accounts get compromised, attackers get control over parts of this chain. Nowadays, many developers are working on these environments from their homes and can end up as entry points for malicious code or let attackers steal credentials to production services. Therefore, while ‘hardening’ used to mean securing a developer’s local computer, it now also means bolstering the security of the tools they need to do their work. These include source code management (SCM) tools, binary artifacts, and build/CI/CD pipelines.

Build hardening includes using automatic buffer overflow detection mechanisms. These are offered by certain compilers or compiler extensions, e.g. Microsoft Visual Studio /GS flag, Fedora/Red Hat FORTIFY_SOURCE GCC flag, StackGuard, and ProPolice, which provide various mechanisms including canary-based detection and range/index checking. D3-SFCV (Stack Frame Canary Validation) from D3FEND (MITRE, 2023) discusses canary-based detection in detail. Other examples include the Control-flow Enforcement Technology (CET) Shadow Stack computer processor feature. It provides capabilities to defend against return-oriented programming (ROP) based attacks. 

Score Evidence
Cost 1 Usually available as part of the tool chain
Knowledge 2 Not widely understood
Feasibility 3 Impact on performance and usually specific to compiler or architecture
Total 6 (Medium)

Separation of Privilege

Separation of privilege, also called privilege separation, refers to both the ‘segmentation of user privileges across various, separate users and accounts’ (Microsoft, 2021), and the compartmentalisation of privileges across various application or system sub-components, tasks, and processes.        

A different, more generic description is that multiple conditions need to be met in order to gain access to a given process or object. A control could be a permission, for example. 

Score Evidence
Cost 2 Will take time and thus cost
Knowledge 2 Concept is widely known and understood
Feasibility 2 Technically feasible (see XP to Vista implementation) but requires prerequisites
Total 6 (Medium)

Libraries or Frameworks

The difference between a library and a framework can be summarised as follows:

  • for a library, your application code calls the library code
  • for a framework, your application code is called by the framework

The general advice is to use the latest version of a vetted library or framework to fulfil the functionality that is required. This is largely to avoid duplication, development time and in some cases to avoid creating your own implementation of a complex function such as crypt. The main security assumption is that the library or framework would not allow a weakness to occur, or provides constructs that make weakness easier to avoid.

As with the choice of programming language, the choice of library or framework also has several considerations, including:

  • size and complexity – ideally you would want to use as lightweight library / framework as possible (to avoid bloatware etc).
  • performance requirements
  • use automated bundle tracking, e.g. to manage the latest version or to check for large updates
  • impact to web accessibility
  • backward compatibility
  • licensing
  • documentation
  • correctly implemented
  • updates
  • security
  • support / community / vendor support – some libraries are backed by vendors or communities who invest time and money into keeping libraries up to date and secure

However, there are significant implications of migrating between frameworks. It is typically non-trivial, time-consuming, can often be fraught with complex technical challenges and can result in a lot of code to be re-written (LinkedIn, 2023; Opsview, 2023).

Score Evidence
Cost 2 Licensing costs
Knowledge 2 Current level of knowledge as highlighted by JetBrains (2022)
Feasibility 2 Performance and security impact
Total 6 (Medium)

Secure Architecture and Design

Ensuring the product is securely architected and designed should be done at the beginning of the product development. Redesign of the product’s security architecture and/or design, or to retrospectively add core security mitigations, is expensive and difficult.

Examples include:

  • divide the product into anonymous, normal, privileged, and administrative areas
  • refactor the product so that you do not have to dynamically generate code
Score Evidence
Cost 2 A critical, core process of software development
Knowledge 3 Not widely used or understood
Feasibility 2 Technically feasible with few technical prerequisites
Total 7 (Hard)

Language Selection

The choice of programming language will have a profound impact on the security of the application. While modern languages, such as Rust and Swift, are both type-safe and memory-safe, languages such as C, C++, and assembly do not offer full type and memory safety for developers.

In theory an engineer has a choice of programming languages to develop in. However, there are a number of constraints:

  1. The ecosystem, what support is there, will it be around for a long time?
  2. What is the environment / platform for the project (web, mobile, embedded device, OS application, etc.)?
  3. What are the specific requirements for libraries, features, and tools for the programming language?
  4. What is the performance consideration and is the languages suitable to accommodate this performance?
  5. Is the developer able to code in this language?

At the beginning of a new project, developers should choose a type-safe and memory-safe programming language. The State of Developer Ecosystem 2022 survey (JetBrains, 2022) of 29,269 developers from around the world highlighted that one out of every two developers is planning to adopt a new language. The top choices for next languages are Go, Rust, Kotlin, TypeScript, and Python. This highlights that the current level of knowledge is assessed at level 2.

However, there are a vast majority of even modern applications and operating systems written in C and C++. As an example, Chrome announced that they will soon support third-party Rust libraries (Google Security Blog, 2023). But Chrome has millions of lines of C++ and it will take a long time to migrate. This would mean the technical feasibility of retrospectively applying this mitigation is assessed at level 3.

Score Evidence
Cost 3 Listed constraints would increase costs
Knowledge 2 Current level of knowledge as highlighted by JetBrains, 2022
Feasibility 3 Could be limited by requirements or time taken if retrospectively applying this mitigation (Google Security Blog, 2023)
Total 8 (Hard)

Source