Software security - introduction

In this module, you will become familiar with software vulnerabilities and the techniques used to identify and prevent them or reduce their impact.

Software can be considered secure when it meets a defined or stated security requirement. This includes requirements for confidentiality, integrity, and availability of system operation and data.

Consider, for example, a social media application. The following requirements may be set for it:

  • A user can modify only their own data. (Integrity)
  • Only the user’s friends can see their data. (Confidentiality)
  • The software must be available 99.9% of the time. (Availability)

Requirements may conflict with each other: for example, an integrity requirement may be to shut down the system in the event of an attack, but then the system is not available.

A security incident is a situation in which a system fails to achieve a security objective. A vulnerability or security flaw can, in turn, be defined as something that causes a security incident. This assumes that a security objective has been defined. In practice, many systems do not have clearly defined security objectives, or they are ambiguous. Security objectives may also be deliberately weakened, for example to improve usability and accessibility.

Thus, there are also vulnerabilities that are not tied to security objectives. Well-defined security objectives do cover such implementation-specific vulnerabilities, but this is not always the case, and implementation-specific vulnerabilities are still considered real vulnerabilities.

Select the correct options

EU General Data Protection Regulation requirements for software

As already described in an earlier module, the EU General Data Protection Regulation in particular regulates the requirements for software that processes personal data within the EU. These must therefore be taken into account in software development. When developing new programs that process personal data and when maintaining existing ones, it is extremely important that developers and their supervisors are aware of the regulation’s requirements for data protection. Senior management in the organisation should also understand that compliance with the regulation requires resources and that the organisation is financially liable for potential non-compliance.

If a program is available within the EU, the GDPR imposes numerous requirements on the software, such as

  • Requesting consent in accordance with legal requirements.
  • Preparing for reporting personal data breaches, including collecting and managing log data and notifying those whose personal data has been breached.
  • Preparing for user data access requests. For these, an automatic report of the user’s personal data can be generated.
  • Preparing to fulfil accountability obligations.
  • Minimising the personal data processed.
  • Deleting personal data when it is no longer needed and there is no legal basis for retaining it.
  • Preparing for the processing of special categories of data or avoiding collecting such data.
  • Conducting a data protection impact assessment when required by the regulation.
Due to data protection legislation, the following must be taken into account when designing and implementing software:

Classification of vulnerabilities

Vulnerabilities can be classified in different ways, such as by prevalence, severity of consequences, classification of consequences, and the difficulty of exploitation. An agreed classification provides a commonly understood view of vulnerabilities. CVE (Common Vulnerabilities and Exposures) is one common way to classify vulnerabilities, as is CWE (Common Weakness Enumeration). In Finland, vulnerabilities are reported and coordinated by the Cyber Security Centre’s CERT function, which operates under the Finnish Transport and Communications Agency.

The following is a classification of vulnerabilities inspired by CyBOK material. There are many different classifications, and this one is not exhaustive. The aim is to describe the most common implementation-specific vulnerabilities.

Memory management vulnerabilities (advanced)

Programming requires memory allocation, and some vulnerabilities are related to it. Languages without automatic memory management, such as C and C++, are particularly susceptible. If a programmer uses memory outside the allocated memory area, a buffer overflow may occur, in which case behaviour is undefined. The program is vulnerable even if the operating system prevents the overflow.

For example, if space has been allocated for an array of 10 elements, but the program writes 11 or more, this may lead to a vulnerability in the aforementioned programming languages. Exploitation of the vulnerability is based on modifying the memory addresses following the array so that the attacker’s own code is executed. For example, program execution may jump into the array, though more advanced attacks also exist. The situation is dangerous if the attacker can fill the overflowing memory via, for example, a web form, a URL, or other input. Protections may have been implemented in the compiler or system, but these should not be relied upon; instead, the program must be implemented securely.

Other vulnerabilities may also be related to memory use. Memory must be allocated when its required amount varies during program execution and is not known in advance. An example of a vulnerability is when a program frees a block of memory but still references the same released memory area, which an attacker may now be able to modify.

A memory management issue can also arise when a program allocates memory that is accessible to other programs. Examples include not clearing allocated memory, the use of temporary storage, browser cookies, environment variables, or cache. Another resource may modify or read the memory. Memory allocation and deallocation may be performed correctly as such, but if the stored data is not overwritten, confidential information such as passwords, payment details, or personal data may be exposed.

Memory management vulnerabilities can be prevented, for example, through code reviews, testing, and proper program design, such as using arrays only through interfaces or safe function calls. This may, however, come at the cost of performance.

The choice of programming language can help prevent vulnerabilities: for example, Java, Python, Perl, Rust, and Ruby include automatic memory management, meaning that buffer overflows and memory allocation do not cause problems in the same way. It is not always possible to choose the language, but vulnerabilities can also be mitigated through compiler choice and settings. These also include protections that detect and prevent vulnerabilities. However, this does not provide complete protection, as not all errors can be detected.

Input validation vulnerabilities (advanced)

Programs often need to construct new structures from input during execution, such as queries. Examples include SQL queries and HTML processing. For example, when a user logs in to a system, the username and password hash may be retrieved from a database using SQL queries. Similarly, when searching for information via a web form, the search may be carried out using SQL queries. A problem arises if the user can input SQL commands that the program interprets as part of an SQL query. SQL commands may be complex, and in the worst case such a vulnerability can lead to full system compromise, while even in milder cases it may allow retrieval of data from the database or cause a denial of service.

It is the programmer’s responsibility to ensure the secure handling of SQL data. It is often possible to prevent user input from being interpreted as SQL queries. On the other hand, if such prevention is not implemented, a vulnerability exists.

Similarly, in other input validation vulnerabilities, the programmer must be familiar with protection mechanisms. These are often simple, but they must be deliberately implemented in the program code. In other words, the programmer must remain vigilant and also follow the development of vulnerabilities.

One input-related vulnerability is incomplete mediation. A program forwards some data or a parameter to another program, which fails to perform checks that it would be capable of carrying out. The risk lies not only in errors made by the program forwarding the data, but also in the possibility that the program is under the attacker’s control. A simple example is an order sent by a browser to an online shop. Even if the customer has been authenticated, the receiving party—the server—must not trust, for example, the total amount included with the order; instead, it must calculate it itself and prevent an invalid order.

Application programming interface vulnerabilities (advanced)

An application programming interface (API) refers to the way software systems communicate with each other. For example, a program may call the interface of another program, in which case the called program performs pre-programmed operations based on the specifications provided by the calling program. For instance, on a mobile phone, an application may request the device’s location data, and by calling the phone’s API, this information can be obtained if the permissions of the software and the system allow it to be shared. Otherwise, the response from the interface indicates that the location data cannot be provided, and possibly explains the reason.

Almost all programs use one or more APIs. A software designer may make assumptions about the behaviour of an interface and therefore fail to prepare for exceptional behaviour, which can lead to a vulnerability.

There may also be vulnerabilities in the implementation of the API itself. It is important for the programmer to ensure that interfaces operate securely and to be prepared to update the called interface if vulnerabilities are discovered in it.

Correct use of APIs is particularly important when implementing cryptography, so that it is carried out in accordance with cryptographic standards and securely. In practice, the programmer must be aware of secure algorithms, their implementation and initialisation, different modes and their security, as well as the correct order of calls. Ready-made solutions should of course be used if they have already been implemented securely. Some interfaces handle matters correctly by default, but in others much depends on the programmer’s expertise. Some interfaces may still contain errors or use weak encryption, so when using cryptography it is necessary to ensure the security of both the interface and the implementation.

Race condition vulnerabilities (advanced)

When a program processes resources (e.g. files, memory, and databases) that are shared among concurrent actors, a vulnerability may arise in a race condition. This is described by the time-of-check-to-time-of-use (TOCTTOU). A classic example: “If a customer counts banknotes to you and you must turn away to write a receipt, you should count them again yourself before giving the receipt to the customer.” A corresponding situation is a more general integrity issue in access control: permissions are checked at one moment, and the corresponding operation is carried out somewhat later. If the permissions change in between, problems may occur. In the case of a database, the justification for writing a certain value into a field may disappear if another process changes the value first. Another example of a timing issue occurs when checking the validity of certificates via revocation lists. For this reason, revocation lists are updated quite frequently.

Race condition vulnerabilities can be avoided through good program design and implementation. Race conditions must be anticipated and handled according to good programming practices. For example, certain programming constructs prevent race conditions from arising. In concurrent systems, access control becomes important to ensure that race conditions do not cause problems. For example, after allocating a memory region, no other process should be allowed to write to it.

Side-channel vulnerabilities (advanced)

Side-channel vulnerabilities exploit additional information produced during the execution of the actual software. This information is typically noise that can nevertheless be analysed. For example, processor activity can be analysed using hardware or software. Data processed by the processor can be analysed by observing execution time or power consumption. If the content of a key affects the observed execution time or power consumption, it may be possible to determine the key or part of it.

Protection against side-channel attacks is achieved through proper software design and implementation that makes analysis difficult. For example, timing analysis can be prevented by implementing program instructions so that no timing differences depending on the encryption key arise during execution.

In addition to extracting information, side-channel attacks may also influence program execution. These are known as fault injection attacks. Faults can be induced, for example, by temperature changes, clock manipulation, or electromagnetic radiation. One example is the so-called RowHammer attack, where repeated memory accesses cause bit flips in adjacent rows of DRAM memory.

Comprehensive security requires complex definitions (advanced)

The above has discussed implementation-specific vulnerabilities. However, a system must be examined as a whole. For example, an implementation-specific vulnerability may not cause an overall problem if it has been mitigated elsewhere in the system. In such cases, layers of security have been built to prevent a single vulnerability from causing harm.

It is also possible that even if a system has no implementation-specific vulnerabilities, its security objective is still not met. It is important to consider security as a whole. A system can be divided into subsystems, whose security is also examined as part of the whole. For example: how is a problem in one subsystem handled at the system level, and how do the subsystems support the overall security objective?

The security objective for implementation-specific vulnerabilities is easy to define: it is either met or not met, for example, either a vulnerability exists or it does not. However, there are more complex security specifications. One of these is information flow security. In this case, security is examined from the perspective of data movement, and each data flow is assigned requirements for publicity and confidentiality. An example of such a requirement is that confidential input must not influence public output in any way. For example, querying a service for sales prices must not affect what other users see as publicly available prices in their own queries.

In information flow specifications, the repeatability of input must also be evaluated. For example, an attacker may repeat queries in slightly different forms. The information obtained in this way may be combined so that confidentiality requirements are violated.

Vulnerabilities are a subset of faults (advanced)

The concept of a vulnerability is useful, but it does not cover all aspects of software security. Software security can be improved through good programming practices, testing, and, especially when aiming for high quality requirements, also through formal verification. Vulnerabilities can be seen as a subset of faults. A fault, in turn, is an error in design or implementation. The taxonomy of faults, that is, their scientific classification, has been developed in research within the field of dependable computing.

Posting submission...