As it is used to say, today you can find an application really for everything. Whether you are typing a document on a computer or watching videos on a mobile screen. Each software had to be designed, programmed and also tested.
There are many different software products on the market for the same activities, and software vendors compete with their products in the number of features, functions and design, as well as in the speed of introducing innovations. Software production is therefore very fast and dynamic. However, such a rapid development has its drawbacks too. Software designers as well as programmers work under pressure and make mistakes, or they simplify their work when they don’t think about some areas or just don’t try hard enough. They also don’t test sufficiently the software they develop before its launching to the market. One of those neglected areas is certainly security too.
A number of vulnerabilities in programmes and applications is constantly growing, from small mobile-level applications to large software solutions in large organizations and public institutions. Attackers exploit flaws in the software to their advantage, and sometimes these flaws remain undetected for years.
However, the existence of security flaws in software is not a vicious circle. Secure programming can significantly reduce the number of critical vulnerabilities.
Therefore, the National Cyber Security Centre SK-CERT within the “programming week” of the European Cybersecurity Month brings some basic rules that lead to a safer product and should be adopted by every programmer.
This guide was to a significant extent inspired by the OWASP Secure Coding Practices, available at:
Area 1 – Design
Security starts with the design of the software we are going to programme. Together with the design of the functionality, we must pay attention to the following parameters.
Rule 1.1 – Security must already be included in the application design
At the beginning of the entire design, it is necessary to perform an analysis that includes all possible threats that may affect the final product. Determining all entry points into the application, defining attack surfaces and possible attack vectors form the basis for identifying possible vulnerabilities and bugs that could occur in the programming process.
For threat modelling, there is for example a STRIDE methodology that is suitable for use. It defines 6 basic threats:
- Spoofing of user’s identity
- Repudiation (deniability)
- Information disclosure
- Denial of service
- Elevation of privilege
There will be a separate article about the STRIDE methodology soon!
As part of basic security modelling, it is also necessary to understand security limitations, i.e. how measures against threats can affect the functionality of software.
Rule 1.2 – Security must be an essential part of software architecture
In order to define the software architecture it is necessary to follow several security recommendations.
First of all, it is necessary to adequately divide the responsibilities, especially by modular architecture, abstracting interfaces from algorithms (by defining API and commands that enforce API), as well as by separation of frontend, middleware and backend.
From a security perspective, it is risky to rely on your own code, and not just for large projects. We recommend using a reputable framework that is maintained and widely used. Fixing of potential vulnerabilities in such a framework is faster as it is publicly auditable by the security-conscious community. And what is important is that it’s not just your responsibility. For databases, we recommend using Objected-relation mapping (ORM) and avoiding the implementation of dynamic queries.
There is beauty in simplicity. And this also applies to programming. Clean and readable code, using short functions and reusing trusted components will give your programme clarity and easy orientation. Complex programming is more prone to errors.
For software architecture, we also recommend focusing on best practices and proven practice in the field of “defense-in-depth” and using multiple layers of security.
Rule 1.3 – Don’t forget about secure authentication
User login and identification have clear rules in security. First of all, handle authentication outside your code.
When logging in, ensure that users are allowed to use multi-factor authentication methods, best enforced.
A role must be assigned to each user, it means a set of permissions that allows the user to use the functions of your software. The best way is to create more roles with a minimum number of permissions, or to assign individual rights to each user.
To avoid brute force attacks, which are based on constant login attempts until they are correct, use mechanisms that prevent attempts to log in several times in a row.
If your software also includes a communication layer, use familiar and advanced protocols, such as TLS. You will avoid a number of problems connected with assuring the confidentiality and integrity of transmitted data, and you will also avoid replay attacks in which the attacker detects even an encrypted network communication, and then by replaying it he triggers the same action as the authorized user before.
Rule 1.4 – Pay attention to encryption
Right-hand rule: NEVER develop encryption or signing by yourself. The vast majority of programmers don’t have the mathematical knowledge to create a new, unique and secure encryption algorithm.
When applying cryptographic mechanisms, follow the Kerckhoffs principle. The system should be secure, even if everything is publicly known, either it is an encryption algorithm or a combination method of encrypted and unencrypted text.
The use of cryptographic mechanisms is literally vital for security. However, we recommend using publicly known crypto-libraries, which are maintained and audited on a regular basis by the community or their creators. Writing and maintaining own cryptographic mechanism is extremely time consuming and resource intensive, and it is for sure that it will contain security vulnerabilities and weaknesses. It is certainly safer to use commonly available algorithms. And also, do not create your own cryptographic protocols but rather use well-known cryptographic patterns. The same applies critically to random number generators – do not use system generators to generate random numbers.
If you choose a certain encryption algorithm, programme your product in such a way that it is possible to quickly and easily change the algorithm and its parameters. Any cryptographic mechanism can be compromised or contain so far undocumented vulnerabilities, and its replacement must be immediate in order to avoid the risk of sensitive data abuse. Documentation of used algorithms and their parameters across the source code is obvious.
There are also a few basic rules for working with passwords that increase the security of their transmission and storage. First of all, passwords must not be stored without encryption or encrypted with a symmetric cipher. They should always be kept in a hash form only. In order to prevent attacks that use pre-computed hash tables, password hashes should be treated with a long and randomly selected individual salt for each password. When encrypting, use slow hashing mechanisms, such as PBKDF2, bcrypt, scrypt, taking into account CPU-sensitive mechanisms, which, however, can be optimized via GPU or ASIC. When choosing a password by a user, make sure that the password meets all security requirements according to modern security recommendations (length – a minimum number of characters, without limitation of the maximum length, allowing all possible characters, implementing a “password strength meter”, allowing the use of phrases and so on). Enforcing users to change passwords too frequently can reduce+ the password security, especially users who don’t use a password management application. The use of default passwords must be disabled too, or to enforce the default password change at the first login and pre-set the expiration of the first login option with the need for manual administrative intervention (recovery of an inactive account).
Rule 1.5 – Segment the network and make a secure network design
Modern applications often consist of several components (for example frontend, middleware, backend, databases, browsers, storages, and more). When designing a secure network architecture, consider a suitable topology, consisting of several strictly separated network segments.
Network segmentation should ensure that by compromising one part, other components cannot be accessed or data intercepted (such as iSCSI data of other servers). We recommend separating the individual elements by stateful firewalls with a strictly defined policy (default drop), and if possible, performing a content inspection. Separate management networks from production ones.
Remember that a “perimeter is dead”; protection must be implemented throughout the network topology.
If physical devices are part of the solution, enable redundancy and scalability; these features need to be considered when designing software. Dimension the network architecture so that a DDoS attack on entry points does not jeopardise the internal communication and functionality of the solution as a whole.
Think of monitoring in the network design as well. The application can provide data for monitoring tools, for example in the form of an SNMP agent.
Rule 1.6 – Pay attention to security when designing data and storage
Data corruption can occur for several reasons, for example, due to a user error, attack, or a hardware failure.
The application must handle all possible errors correctly. Therefore, even a data outage from the database cannot cause that the application performs an unexpected function, or the user receives an error message containing internal implementation details.
The software should also allow backup and recovery. In software design, it is necessary to take them into account especially in multi-server solutions, as partial recovery may lead to data inconsistencies.
If a physical media gets damaged and needs to be discarded, it may still contain recoverable sensitive data. One of the measures is to store data in encrypted form.
If the application stores sensitive data, also remember to comply with the requirements of applicable legislation and add the option of “forgetting”.
Rule 1.7 – Your application must generate multiple types of logs
Use the built-in options of a selected platform or framework for logging. The application should generate several types of logs:
- operational logs – information about the normal operation of the application, which is not necessary for the end user. It is sufficient to store them locally to files. They must not contain any sensitive data, such as user passwords.
- security logs – information about security events. The application should be able to send them to an external monitoring system in a structured form, containing optimally:
- date and time
- IP address and port
- identity of a user
- all the details of the security incident
- user / transaction logs, audit trail – information about actions performed by users. They should be in a form accessible to regular users of the system, e.g. via the administration web interface for the application.
Area 2 – Development
Rule 2.1 – Understand the programming language you are using, also from a security point of view
Security doesn’t end with the design. Security principles from design must also be transformed into the process of software development, i.e. a real programming. In practice, it is necessary to focus on several principles of secure programming.
When using any programming language and its corresponding framework, focus on the best programming practice in its usage, as well as on the existence of known vulnerabilities in the language or framework and methods to mitigate them. The same applies to the environment in which you programme and individual dependencies. At the same time, it is good to follow the principles given in the following chapters.
Rule 2.2 – Validate the input data
Input validation SHOULD NOT be focused on protection against a certain type of attacks; its purpose is to have high-quality data for input.
Input validation should be done as soon as possible, preferably directly at the point where the data is received. Focus on the positive security, create whitelists instead of blacklists; for example create a list of allowed activities instead of forbidden. This will create clear rules that prohibit everything except what you explicitly allow.
Pay attention to validation by regular expressions. It is necessary to ensure that the regular expression covers the entire input from start to end, including any “cr” and “lf” characters that the user might send. Make sure that your regular expression does not have vulnerabilities resulting from the use of special characters in input.
Ideally, use the validation functions of the framework.
Rule 2.3 – Treat the output data so that the information does not leak
All outputs must be treated, adapted to particularities of the destination where they are sent. This process is called escaping. For example, for output to a CSV file, it is usually necessary to take care of commas, quotation marks and line breaks.
Unlike the input validation, escaping is done as late as possible, just before sending or saving of data. Focus especially on injection vulnerabilities – SQL injection, execution of shell commands, format violations when saving to files (TXT, XML, HTML, CSV), cross site scripting for output to the browser, as well as file names that may contain an executable code.
Do not create your own functions for escaping. Use a usual code; your own programming may cause not only a waste of time but also making mistakes that will ensure inactivity of your security functions.
Do not fall into a trap of a false sense of security that the platform or framework you are using is preventing sql injection or cross site scripting vulnerabilities. They can be used very often in a way that will allow these vulnerabilities.
Rule 2.4 – Pay attention to security of multi-threaded applications
A typical problem for multi-threaded applications is an access to shared variables that needs to be addressed from the very beginning with a good design. If the threads share only one variable and are easy to modify, use atomic types. However, once the logic is more complex, locking should be used.
Use synchronization language primitives for synchronizing. Nested locking requires to get the locks always in the same order, otherwise a deadlock may occur.
Pay attention to avoid the possibility of race conditions in your application.
Rule 2.5 – Apply the principle of minimum privileges
Follow the principles of “defense-in-depth” in both design and development and apply several layers of security. Also use the “Least privilege” principle, i.e. the minimum user’s rights in the application to perform only the activities needed in their role.
Rule 2.6 – Manage sessions securely
When managing sessions, use a trusted framework instead of the one you programme by yourself. As in other cases, you will avoid mistakes and the need to maintain such a framework in the future. Particularly critical is the exchange of session cookies at login and logout, prevention of replay attacks, time delay limitation of login attempts, mechanisms to prevent leakage of session cookies. All these issues are solved within known frameworks.
For an ID session, make sure that its length is at least 128 bits (16 bytes). Also, make sure that the entropy of the ID session value is at least 64 bits and is sufficiently random.
Critical data can never be shared or stored on a user’s side. Store them on a server side based on session cookies or access token.
Rule 2.7 – Manage files securely
When managing files, pay attention not to use paths and file names directly from the user. Save the original file name in the database and use only its secure identifier when working with the file. Validate the identifier like any other input.
Keep uploaded files out of the source code and ideally in a directory that is not directly accessible via a URL. If, for some reason, you have to use a web-accessible directory, make sure that the file names do not contain any user input at all, and the script execution is disabled in that directory.
It is appropriate to limit the size of transferred files. Also, ensure that your files are read-only.
Rule 2.8 – Treat errors
Errors also need control. Handle them safely and don’t provide them to the user, unless it is necessary. If the user needs to know the errors, avoid leakage of too much information.
Errors in registration, login or password recovery belong to special cases. These functions must be programmed to return the same error code in response to an attempt to log in as a non-existing user as well as an existing user with an incorrect password. This will prevent possible user enumeration. During registration, enumeration can be disabled using CAPTCHA.
Avoid known dangerous functions (such as a notoriously known “strcpy” in language C) and keep in mind classic bugs that can compromise the environment in which your programme runs, especially stack overflows, resource leakage, uninitialized variables and numeric errors.
Rule 2.9 – Test your application internally and externally
Testing is a matter of course for any safe development. This must be done in parts so that errors are detected at lower levels of programming. In testing, focus on previous bugs that were detected to exclude them in a new version. After the development is complete, arrange an independent audit in the form of reviewing the source code or penetration testing (both methods can be done manually or automatically).
Area 3 – Deployment
Rule 3.1 – Use updated and configured runtime architecture
For a runtime infrastructure, make sure that this environment is updated and well configured. When deploying a product, use automation tools (e.g. ansible) that can easily install the product along with its dependencies and accompanying libraries. A good practice is to use containers and container orchestration platforms (e.g. Docker and Kubernetes).
Rule 3.2 – Sign installation packages
Sign the installation packages because your signature will guarantee the user the software legitimacy. Remember to encrypt communications, especially web services and products.
Area 4 – Maintenance
Deployment of your product doesn’t mean the end of security. Only the real use of software will show errors or shortcomings not only in functionality, but also in security solutions that you have applied in your product.
Rule 4.1 – Issue regular security updates
Make regular updates of your product, and also as soon as you detect a security vulnerability or if a security vulnerability is fixed in one of the libraries, applications and other dependencies that your product uses.
Rule 4.2 – Monitor your application
We recommend that you monitor your product, and record and evaluate non-standard behaviour. Implement in the product the ability for the user to report non-standard behaviour, functionality problems, and so on.
Rule 4.3 – Pay attention to compatibility and trusted updates
Each update, whether routine or security, requires the product compatibility with the latest version of the operating system of the device on which it is going to be installed by the user. The same applies to components and containers. When updating your software, ensure that users can download update packages via trusted and secure path.
Rule 4.4 – Organize bug bounty programmes
A very good practice for detecting vulnerabilities and improving your product is “bug bounty programme”. It’s a kind of competition where a software manufacturer asks researchers or enthusiasts to detect vulnerabilities or bugs in their product, for a financial or other reward. The software manufacturer can thus independently determine the level of security of his product and at the same time motivate users to use this product.
More about vulnerability reporting and management can be found at https://www.sk-cert.sk/wp-content/uploads/2019/10/Vulnerability_reporting.pdf.
If you are interested in constant advancing in safe programming, certainly don’t miss the OWASP project, which provides valuable advice and guidance in the field of safe programming. You can find them at:
« Späť na zoznam