This section provides an overview of how we determined the Implementation Score for each of the primary mitigations. The mitigations are organized according to their complexity, starting with those that are easiest to implement.
Input Validation
The OWASP Cheat Sheet (OWASP, 2021) details various strategies for input validation:
-
Utilization of data type validators that are natively provided by web application frameworks (such as Django Validators and Apache Commons Validators)
-
Validation against JSON Schema and XML Schema (XSD) for inputs in these formats.
-
Type conversion (for example, Integer.parseInt() in Java, int() in Python) with strict exception handling protocols.
Input Validation falls under the Libraries or Frameworks mitigation category. However, its significant presence in the mitigation list justifies a separate mention.
Score | Evidence | |
Cost | 1 | Minimal or no direct and indirect costs |
Knowledge | 1 | Well understood and widely available |
Feasibility | 1 | Available implementations for most programming languages |
Total | 3 (Easy) |
Output Encoding
Output encoding and escaping are critical defensive techniques designed to mitigate injection attacks. Output encoding translates special characters into a safe representation that is harmless to the target interpreter, such as converting the <
character to the <
string when adding content to an HTML page.
Escaping involves prepending a special character to prevent misinterpretation; for instance, adding a character before a
"
(double quote) so it is recognized as text rather than the end of a string.
Output Encoding is categorized under Libraries or Frameworks mitigation, yet its dominance in the security strategies warrants individual recognition.
Score | Evidence | |
Cost | 1 | Minimal or non-existent direct costs |
Knowledge | 1 | Broadly understood and readily available |
Feasibility | 1 | Numerous implementations exist for all programming languages |
Total | 3 (Easy) |
Reduce the Attack Surface
The term “attack surface” refers to the aggregate number of input and output points that may be vulnerable to exploitation by an attacker. A broader attack surface increases the potential for attack and the likelihood of introducing vulnerabilities. At times, this measure may also indicate other aspects of quality in addition to security. For instance, an application with numerous entry and exit points might necessitate extensive testing to enhance code coverage. This could also encompass the use of functions, libraries, and frameworks.
The attack surface is defined as the collective code, interfaces, services, protocols, and practices accessible to users, with a focus on what unauthenticated users can access (Microsoft, 2019). Thus, developers should aim to minimize the application’s exposure early in the development cycle, particularly during the design phase, and should continue to assess potential reductions during coding and runtime.
Score | Evidence | |
Cost | 2 | Removing functionalities will require time and incurr costs |
Knowledge | 1 | The concept is commonly recognized and understood |
Feasibility | 2 | Technically achievable, though justifying the removal of functionality may present challenges |
Total | 5 (Medium) |
Enforcement by Conversion
This approach entails converting input into a controlled and distinct representation. For instance, in PHP, one common method to prevent SQL injection is applying intval()
to numeric inputs, thereby ensuring the result is a number.
Score | Evidence | |
Cost | 1 | Typically integrated into the language |
Knowledge | 3 | Not widely adopted or well understood |
Feasibility | 1 | Does not depend on any prerequisites |
Total | 5 (Medium) |
Sandbox or Jail
This method involves executing code within a ‘sandbox’ or a similar constrained environment that enforces rigid boundaries between the process and the operating system. This setup can effectively limit which files may be accessed in a specific directory or the commands that the software can execute.
Examples on the operating system level include the Unix chroot jail, AppArmor, and SELinux. Generally, managed code can offer certain protections. For example, java.io.FilePermission
in Java SecurityManager allows software to impose constraints on file operations.
However, this solution may not always be sustainable, restricting only the operating system’s impact while other parts of the application may still be compromised.
Score | Evidence | |
Cost | 1 | Typically included with the language/OS |
Knowledge | 2 | The concept is partially known and understood |
Feasibility | 2 | Technically viable, yet poses limitations |
Total | 5 (Medium) |
Secure Programming
This expansive topic primarily falls within developers’ responsibilities. Examples of secure programming include:
- Setting pointers to NULL after freeing them
- Conducting sanity checks on all modifiable pointers before use to prevent NULL pointer dereferences
- Strictly defining protocols to easily identify all out-of-bounds behavior and adhering to the protocol’s specifications
- Creating a new object during data deserialization rather than merely deserializing it
- Explicitly defining a final object() to prevent deserialization
- Utilizing library calls instead of external processes to recreate desired functionalities
Score | Evidence | |
Cost | 2 | Requires skilled developers |
Knowledge | 2 | The concept is partially understood and known |
Feasibility | 2 | Technically practical with few prerequisites |
Total | 6 (Medium) |
Compilation or Build Hardening
Hardening the toolchain involves examining four critical areas: configuration, preprocessor, compiler, and linker. The development environments and developers themselves form a part of the software supply chain, so any compromise of their accounts can lead to attackers gaining control over segments of this supply chain. Many developers are now working from home, making them potential entry points for malicious code or the theft of credentials associated with production services. Thus, hardening now also encompasses strengthening the security of the tools required for their tasks. This includes source code management (SCM) tools, binary artifacts, and CI/CD pipelines.
Build hardening entails the use of automated mechanisms for buffer overflow detection, provided by certain compilers or compiler extensions, such as Microsoft Visual Studio /GS flag, and others that offer mechanisms for monitoring and validation. This also includes features like Control-flow Enforcement Technology (CET) Shadow Stack, which defends against return-oriented programming (ROP) attacks.
Score | Evidence | |
Cost | 1 | Typically included in the toolchain |
Knowledge | 2 | Not extensively understood |
Feasibility | 3 | May affect performance and typically depends on the compiler or architecture |
Total | 6 (Medium) |
Separation of Privilege
Separation of privilege, also called privilege separation, pertains to the division of user privileges across multiple separate accounts, as well as the compartmentalization of privileges among different application or system components, tasks, or processes.
A broader definition could state that multiple conditions must be satisfied to gain access to a specific process or object, where each control could refer to a permission.
Score | Evidence | |
Cost | 2 | Will incur time costs |
Knowledge | 2 | The concept is generally known and understood |
Feasibility | 2 | Technically feasible (illustrated by XP to Vista implementation) but requires certain prerequisites |
Total | 6 (Medium) |
Libraries or Frameworks
The distinction between a library and a framework can be summarized as:
- In a library, the application code invokes the library code.
- In a framework, the library code calls the application code.
It is generally recommended to use the latest version of an established library or framework to achieve the necessary functionality, primarily to reduce redundancy, save development time, and in some instances, to avoid constructing complex functionalities like cryptography from scratch. The underlying security assumption is that the library or framework will not introduce vulnerabilities or will provide elements that facilitate avoiding vulnerabilities.
As with the selection of a programming language, choosing a library or framework entails several considerations, including:
- Size and complexity—opt for a lightweight library or framework to avoid unnecessary bloat.
- Performance requirements.
- Utilize automated bundle tracking to manage updates.
- Assess the impact on web accessibility.
- Consider backward compatibility.
- Review licensing agreements.
- Evaluate documentation.
- Ensure correct implementation.
- Monitor for updates.
- Prioritize security features.
- Consider community/vendor support, as some libraries benefit from organizational commitment to security and updates.
However, migrating between frameworks brings significant implications. The process is often complex, time-consuming, and may involve substantial code rewriting (LinkedIn, 2023; Opsview, 2023).
Score | Evidence | |
Cost | 2 | May incur licensing costs |
Knowledge | 2 | Current knowledge levels as reported by JetBrains (2022) |
Feasibility | 2 | Performance and security impacts must be considered |
Total | 6 (Medium) |
Secure Architecture and Design
Securing the design and architecture of a product must take place from the project’s inception. Retrofitting security architecture or core design features later in development is costly and challenging.
Examples include:
- Segmenting the product into anonymous, regular, privileged, and administrative components.
- Refactoring the product to eliminate the need for dynamic code generation.
Score | Evidence | |
Cost | 2 | A fundamental aspect of software development |
Knowledge | 3 | Not widely utilized or understood |
Feasibility | 2 | Technically achievable with minimal prerequisites |
Total | 7 (Hard) |
Language Selection
Choosing the right programming language significantly influences an application’s security. While contemporary languages like Rust and Swift promote type-safety and memory safety, languages such as C, C++, and assembly fall short in this regard.
Developers often face constraints when selecting programming languages for their projects:
- The ecosystem: Is there adequate support, and will it be sustainable long-term?
- The target environment/platform: web, mobile, embedded device, or OS application?
- The specific requirements in terms of libraries, features, and tools offered by the language.
- Performance considerations: Is the language capable of meeting the performance demands?
- Are developers proficient in this language?
As projects begin, developers should prioritize selecting a type-safe and memory-safe programming language. The State of Developer Ecosystem 2022 survey from JetBrains, encompassing responses from 29,269 developers globally, indicated that half are considering adopting new languages. The top prospects for new languages include Go, Rust, Kotlin, TypeScript, and Python, reflecting that current knowledge levels are generally assessed at level 2.
However, many modern applications and operating systems are still built using C and C++. For instance, Google’s Chrome has announced plans to support third-party Rust libraries (Google Security Blog, 2023) while still being heavily reliant on millions of lines of C++. Transitioning this legacy entails significant technical complexities, evaluating the feasibility of implementing this mitigation would be categorized at level 3.
Score | Evidence | |
Cost | 3 | Identified constraints are likely to elevate costs |
Knowledge | 2 | Current knowledge levels per JetBrains, 2022 |
Feasibility | 3 | Could be constrained by requirements or time constraints in retroactive application (Google Security Blog, 2023) |
Total | 8 (Hard) |
Based on an article from ncsc.gov.uk: https://www.ncsc.gov.uk/report/a-method-to-assess-forgivable-vs-unforgivable-vulnerabilities