Writing secure code involves adopting a set of software development best practices, and a change of attitude and culture within development teams. This minimizes the risk of security vulnerabilities and helps protect applications from malicious attackers.
By incorporating these practices into the software creation process, developers are able to create more secure applications that are able to withstand the increasingly sophisticated attacks of the modern technology ecosystem.
There are several key principles that software engineers can follow to write more secure code, both technically and culturally. By doing so, development teams can reduce the likelihood of security vulnerabilities and protect their applications from cyberattacks. Some of the most important best practices include input validation and sanitization, error handling, logging, output encoding, access control, encryption, and hashing.
In this article, we’ll explore each of these practices in more detail and provide examples of how to implement them in your code. By following these guidelines, you can ensure that your code is secure and that your applications are safe from potential threats.
Understanding the Threat Landscape
In order to write code securely, it is essential to understand the threat landscape and the types of attacks that the application may be susceptible to. Threats to software security can come in many forms, a very common one being malicious actors seeking to steal data, disrupt business operations, exploit vulnerabilities for financial gain, and a variety of other motives. An extremely relevant activity, which can help in understanding this scenario, is Threat Modeling. We are not going to talk about threat modeling directly in this article, but there is already a lot of content on the topic on the Conviso blog.
Common issues such as SQL Injection, Information Disclosure and Cross-Site Scripting (XSS) can be avoided by applying good secure coding practices.
On Best Practices
Input Validation and Sanitization
Input validation and sanitization are critical components of secure coding practices. Input validation refers to the process of verifying that user input is valid and meets expected format and constraints. In contrast, sanitization involves cleaning and filtering user input to remove potentially harmful or malicious data. Both techniques are important to prevent Injection attacks, which involve exploiting input fields to inject malicious content into an application.
To illustrate how validating and sanitizing inputs can be applied in practice, let’s consider an example:
The above code snippet introduces a critical Command Injection vulnerability, which could allow attackers to execute malicious commands on the host operating system. The vulnerability occurs due to the direct use of user input in constructing a system command, without any proper validation or sanitization. In this example, an attacker is able to concatenate a command such as ‘; rm -rf /;, and delete all files on the server.
A common approach to sanitizing inputs is to remove any characters that might be used to inject additional commands or modify the behavior of the original command. In the following example, a regular expression is used to identify some shell metacharacters ( ; , &&, | ) within the user input and then replaces them with an empty string.
This technique helps ensure that the executed command is as expected and reduces the likelihood of a successful Command Injection attack. However, it is important to note that sanitizing inputs is only part of the security strategy and other measures, such as using secure methods and functions, must also be implemented to protect the application against potential threats.
Furthermore, it is critical to apply validation and sanitization of inputs not only to user input fields, but also to any input data received from external sources such as APIs or databases.
Having an effective error handling strategy is a good practice that brings many benefits to the security of an application. In addition to helping to avoid security flaws and vulnerabilities, proper error handling ensures a smooth and seamless experience for users, even in the face of unexpected situations.
On the other hand, an inadequate error handling strategy can raise a series of security problems, as it can expose sensitive internal information about the application. One of the most common risks related to improper error handling is Information Disclosure, which can reveal confidential information to unauthorized users.
Below, we have an example of an error message that was not properly handled and was displayed to the user with various internal system information. The error message contains technical details about the database and the language used by the application.
To avoid this type of problem, it is crucial to ensure that the application handles all possible types of errors. This can be done by creating a custom, generic error type, which will replace the standard error messages.
When creating a custom error type, it’s important to consider the possible types of errors that the application might encounter and create a hierarchy that reflects those types. For example, an e-commerce application may have a generic error class called AppException, which is extended by more specific classes such as PaymentErrorException and AuthenticationErrorException, and each different error type can bring different information, helping to protect the sensitive data involved. in application operations.
Logging is an important aspect of writing secure code as it helps developers monitor and detect security incidents and vulnerabilities in their applications. Proper logging practices involve capturing relevant information about user interactions, system events and errors that occur in the application. By logging this information, developers can gain insight into how their application is being used and detect unusual behavior that could indicate an attack. Furthermore, logs can be used to track down the root cause of errors and help developers diagnose and fix bugs in the application.
Security Logging and Monitoring Failures is a category featured on the OWASP Top 10 list, meaning failures related to logging and monitoring are highly common. In addition to its presence in the OWASP Top 10, this practice is also listed in another highly relevant project, the OWASP ASVS. In ASVS, several security requirements are raised that help guide developers during the implementation of logs in the system. The presence of the Logging practice in these projects helps to demonstrate the relevance that the practice has for application security.
When logging, it’s important to follow best practices, such as defining a clear logging structure, limiting the amount of sensitive information stored in the logs, and ensuring that the logs are encrypted and protected from unauthorized access.
Below is an example of a log message that has a well-defined structure.
By using structured logs, developers can easily search and analyze the relevant data, and make integrations with different types of tools, facilitating the detection of security incidents and the diagnosis of problems in the application.
Output encoding is the practice of converting data to a secure format before displaying it to users. This is important because attackers can inject malicious code into an application and potentially gain control of the user’s browser. By encoding the output data, we can prevent this type of attack, known as cross-site scripting (XSS).
There are several types of output encoding, because browsers handle HTML, JS and CSS in different ways. Using the wrong encoding method can introduce vulnerability points in the application.
In this example, the encode() function is used to encode the output string, replacing special characters with their corresponding HTML entity codes.
By encoding user-supplied data before displaying it in the application, developers can make sure that any malicious code becomes harmless.
Criptografia e Hashing
Encryption and hashing are important techniques used in secure coding to protect sensitive information such as passwords, credit card numbers, and other types of personal data. Encryption involves using mathematical algorithms to scramble data so that it can only be read by someone who has the key to decrypt it. Hashing, on the other hand, is a one-way process that takes data and generates a fixed-length string of characters that cannot be reversed to get the original data.
Cryptography is a key technique for ensuring data confidentiality, i.e. the ability to keep sensitive information private and accessible only by authorized persons. On the other hand, hashing is an important technique to ensure data integrity, that is, the ability to guarantee that data has not been modified or corrupted during the transmission or storage process.
To ensure the highest possible security for data, developers should use strong and reliable algorithms such as AES and SHA-256. It is very important to avoid implementing custom algorithms as they can introduce unknown weaknesses and vulnerabilities.
In addition, carefully managing cryptographic keys is crucial to ensuring the security of encrypted data. Keys must be securely stored and changed regularly to prevent intruders from gaining access to sensitive information.
Overall, cryptography and hashing are powerful tools for protecting sensitive information and must be implemented with great care and consideration in applications.
Access Control is the process of ensuring that users or entities are given only the appropriate level of access to resources on a system. This includes things like authentication and authorization, as well as mechanisms for enforcing permissions and managing roles. When properly implemented, access control can help prevent unauthorized access to sensitive information and actions, such as viewing or modifying data, deleting files, or running code.
There are several best practices for implementing access control, such as role-based access (RBAC) and the principle of least privilege. Least privilege means giving users only the access rights they need to perform their tasks and nothing more. RBAC involves assigning permissions to users based on their roles and responsibilities within the system, with each role having specific access permissions.
Implementing access control requires a thorough understanding of the flow of data, resources, and user roles within the application. It is also important to regularly review and update access control policies to ensure they remain effective and aligned with the organization’s needs.
By implementing robust access control measures, developers can prevent unauthorized access to confidential information, protect against malicious attacks, and maintain the confidentiality, integrity, and availability of their applications.
Secure code: an ongoing process
While there is no single solution that guarantees complete security, applying a set of best practices can greatly reduce the risk of vulnerabilities and strengthen application resiliency.
Writing secure code is an ongoing process. It is essential to minimally follow good practices throughout the development process, such as those mentioned in this article, to ensure that the code delivered to users is secure.
Regular security audits, vulnerability assessments and penetration tests can help identify and mitigate potential weaknesses in the codebase. Ultimately, while the responsibility for writing secure code rests with the developer, the security of the application as a whole is a shared responsibility, and it is essential to view security as a fundamental aspect of the software development process.