SW Development Security Best Practices
After reading Secure Design Best Practices, this page describes some weaknesses around concepts that are frequently used or encountered in software development environments. This includes all aspects of the software development lifecycle including implementation.
Accordingly, this view can align closely with the perspectives of architects, developers, educators, and assessment vendors. Software developers (including architects, designers, coders, and testers) use this view to better understand potential mistakes that can be made in specific areas of their software application.
Note: some concepts can be found, and applied, also to architectural design. The source code examples, although written in programming languages that you may not be familiar with (or making references to OS different from Windows or even specific technologies), should be easy to understand and expected to provide a general idea about the bad practice.
See References for more technical details about the Software Development concepts in Common Weakness Enumeration (CWE).
Audit
-
Be careful about the output from the application that is written to logs: the product can generate an error message that discloses sensitive information about its environment, users, or associated data. This is independent of possible security measures applied to the repository of the logs or during the transmission of the message. Also, be careful about improper Output Neutralization (see Improper Neutralization) or the omission of relevant information.
Example 1
The following web application code attempts to read an integer value from a request object. If the parseInt call fails, then the input is logged with an error message indicating what happened.
If a user submits the string "twenty-one" for val, the following entry is logged:
- INFO: Failed to parse val=twenty-one
However, if an attacker submits the string "twenty-one%0a%0aINFO:+User+logged+out%3dbadguy", the following entry is logged:
- INFO: Failed to parse val=twenty-one
- INFO: User logged out=badguy
Clearly, attackers can use this same mechanism to insert arbitrary log entries. See References to CWE for more details about possible neutralizations.
Example 2
This code only logs failed login attempts when a certain limit is reached. If an attacker knows this limit, they can stop their attack from being discovered by avoiding the limit.
Authentication
-
Provide a detailed description of the SW implementation and focus on effective Test cases, reviewed by cyber security engineers.
-
The product shall perform authentication for critical functions, for functionality that requires a provable user identity or consumes a significant number of resources.
Example 1
In the following Java example, the method createBankAccount is used to create a BankAccount object for a bank management application.
However, there is no authentication mechanism to ensure that the user creating this bank account object has the authority to create new bank accounts. The following Java code includes a boolean variable and method for authenticating a user. If the user has not been authenticated then the createBankAccount will not create the bank account object.
-
The product shall implement sufficient measures to prevent multiple failed authentication attempts within a short time frame.
Example 2
In the following C/C++ example the validateUser method opens a socket connection, reads a username and password from the socket and attempts to authenticate the username and password.
The validateUser method will continuously check for a valid username and password without any restriction on the number of authentication attempts made. The method should limit the number of authentication attempts made to prevent brute force attacks as in the following example code.
Authorization
-
Perform any authorization check for giving access to the resources after parsing and doing canonicalization of data input (see References about canonicalization).
-
The product does not properly compartmentalize or isolate functionality, processes, or resources that require different privilege levels, rights, or permissions. This may also be considered to be addressed by architectural requirements.
Example 1
Single sign-on technology is intended to make it easier for users to access multiple resources or domains without having to authenticate each time. While this is highly convenient for the user and attempts to address problems with psychological acceptability, it also means that a compromise of a user's credentials can provide immediate access to all other resources or domains.
-
A common weakness that can exist is that access controls or policies are not granular enough. Carefully evaluate the given access permissions, which resources can be accessed and if all resources are covered by the expected access control mechanism.
Bad Coding
-
The purpose of package scope (Information Hiding principle) is to prevent accidental access by other parts of a program.
Example 1
The following example (although in Java) should demonstrate the weakness. Data can be accessed (lack of confidentiality) or modified (lack of integrity) by anyone outside the class boundary.
-
A protection mechanism relies, to a large extent, on the evaluation of a single condition or the integrity of a single object or entity to decide on granting access to restricted resources or functionality. Consider more factors possibly when access is granted.
-
The product uses a protection mechanism that relies on the existence or values of inputs. When security decisions such as authentication and authorization are made based on the values of these inputs, attackers can bypass the security of the software. Without sufficient encryption, integrity checking, or other input validation mechanisms, any input cannot be trusted.
-
The source code contains comments that do not accurately describe the developer's decision to use a given structure or object instead of another one. Do not add comments if the code is already self-explaining, add comments to help an external reviewer understand the developer’s implementation choices. Also, to make a review easier and quicker, ensure that comments accurately describe or explain aspects of the portion of the code with which the comment is associated.
-
The product allocates a reusable resource without imposing any restrictions on the size or number of resources that can be allocated. Programmers must be careful to ensure that resources are not consumed too quickly, too easily or for a long time (preventing others from accessing them).
Complexity Issues
-
The product contains modules in which one module has references that cycle back to itself, i.e., there are circular dependencies. This issue can prevent the product from running reliably. If the relevant code is reachable by an attacker, then this reliability problem might introduce a vulnerability.
-
A source code file has too many lines of code. This issue makes it more difficult to understand and/or maintain the product, which indirectly affects security by making it more difficult or time-consuming to find and/or fix vulnerabilities. It also might make it easier to introduce vulnerabilities. CISQ (https://www.it-cisq.org ) recommends a default threshold value of 1000.
Concurrency Issues
-
The product checks the state of a resource before using that resource, but the resource's state can change between the check and the use in a way that invalidates the results of the check. This weakness can be security-relevant when an attacker can influence the state of the resource between check and use. This can happen with shared resources such as files, memory, or even variables in multithreaded programs. It is called ‘TOCTOU’ (Time-of-check Time-of-use).
Example 1
The program performs certain file operations on behalf of the current user and uses access checks to perform operations that should otherwise be unavailable to the current user. The program uses the access() system call to check if the person running the program has permission to access the specified file before it opens the file and performs the necessary operations.The call to access() behaves as expected, and returns 0 if the user running the program has the necessary permissions to write to the file, and -1 otherwise. However, because both access() and fopen() operate on filenames rather than on file handles, there is no guarantee that the file variable still refers to the same file on disk when it is passed to fopen() that it did when it was passed to access().
If an attacker replaces a file after the call to access() with a symbolic link to a different file, the program will use its own (maybe root) privileges to operate on the file even if it is a file that the attacker would otherwise be unable to modify. By tricking the program into performing an operation that would otherwise be impermissible, the attacker has gained elevated privileges. This type of vulnerability is not limited to programs with root privileges.
See https://cwe.mitre.org/data/definitions/367.html for potential mitigations.
Key Management Errors
-
Performing a key exchange will preserve the integrity of the information sent between two entities, but this will not guarantee that the entities are who they claim they are. Depending on the context, consider implementing mutual authentication.
-
Nonces are often bundled with a key in a communication exchange to produce a new session key for each exchange. Ensure that, if used, they are used for the present occasion and only once by the protocol you intend to use.
Example 1
This code takes a password, concatenates it with a nonce, and then encrypts it before sending it over a network:
Because the nonce used is always the same, an attacker can impersonate a trusted party by intercepting and resending (Reply attach) the encrypted password. This attack avoids the need to learn the unencrypted password.
Data Integrity Issues
- The product uses encryption of inputs that should not be visible by an external actor, but the product does not use integrity checks to detect if those inputs have been modified. Do not forget the distinction (and the requirements you have) about encryption and integrity of data.
Documentation Issues
- The product does not have documentation that represents how it is designed. It can make it more difficult and time-consuming to detect and/or fix vulnerabilities.
File Handling Issues
-
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname (example: “..” or “/”) that can cause the pathname to resolve to a location that is outside of the restricted directory. This is typically known as a 'Path Traversal' attack (see https://owasp.org/www-community/attacks/Path_Traversal ).
Example 1
The following code could be for a social networking application in which each user's profile information is stored in a separate file. All files are stored in a single directory.
While the programmer intends to access files such as "/users/cwe/profiles/alice" or "/users/cwe/profiles/bob", there is no verification of the incoming user parameter. An attacker could provide a string such as:
../../../etc/passwd
The program would generate a profile pathname like this:
/users/cwe/profiles/../../../etc/passwd
When the file is opened, the operating system resolves the "../" during path canonicalization (see References about canonicalization) and accesses this file:
/etc/passwd
As a result, the attacker could read the entire text of the password file. Note that the example also contains an error message information leak. See https://cwe.mitre.org/data/definitions/22.html for details.
-
The product receives an external path to identify its own file or directory, but it does not properly check whether it is really a valid path of the file system structure of the product or the access permissions are the expected ones. See the example below about path validation:
Example 2
This program is intended to execute a command that lists the contents of a restricted directory and then performs other actions. Assume that it runs with setuid privileges to bypass the permissions check by the operating system.
This code may look harmless at first since both the directory and the command are set to fixed values that the attacker can't control. The attacker can only see the contents for DIR, which is the intended program behavior. Finally, the programmer is also careful to limit the code that executes with raised privileges. So far so good.
However, because the program does not modify the PATH environment variable, the following attack would work:
- The user sets the PATH to reference a directory under the attacker's control, such as "/my/dir/"
- The attacker creates a malicious program called "ls", and puts that program in /my/dir
- The user executes the program
- When system() is executed, the shell consults the PATH to find the "ls" program
- The program finds the attacker's malicious program, "/my/dir/ls". It doesn't find "/bin/ls" because PATH does not contain "/bin/"
- The program executes the attacker's malicious program with the raised privileges
Information Management Errors
-
The product stores sensitive information in cleartext. Because the information is stored in cleartext (i.e., unencrypted), attackers could potentially read it. Even if the information is encoded in a way that is not human-readable, certain techniques could determine which encoding is being used, and then decode the information. Use strong encryption algorithms and access control mechanisms.
Do not forget access control mechanisms in this context. -
The product transmits sensitive or security-critical data in cleartext in a communication channel that can be sniffed and/or modified by unauthorized actors. Before transmitting, use strong encryption algorithms and/or data integrity checks.
Do not forget authentication mechanisms too in this context.
Initialization and Cleanup Errors
-
The product does not exit, or otherwise modify its operation when security-relevant errors occur during initialization, such as when a configuration file has a format error, which can cause the product to execute in a less secure fashion than intended by the administrator.
Example 1
The following code intends to limit certain operations to the administrator only:
If the application is unable to extract the state information - say, due to a database timeout - then the uid to be regarded as equivalent to "0" in the conditional, allowing the original user to perform administrator actions.
Even if the attacker cannot directly influence the state data, unexpected errors could cause incorrect privileges to be assigned to a user just by accident.
-
The product does not properly "clean up" and remove temporary or supporting resources (e.g., memory allocated) after they have been used. For example, if you use an operator to deallocate the memory occupied by the variable, then that memory area might be assigned to another application but, until then, the previous content may be still in memory and be read.
Example 2
Stream resources in a Java application should be released in a final block, otherwise, an exception is thrown before the call to close() would result in an unreleased I/O resource. In the example below, the close() method is called in the try block (incorrect).
Data Validation Issues
-
The product validates input before applying (‘early validation’) protection mechanisms that modify the input, which could allow an attacker to bypass the validation via dangerous inputs that only arise after the modification. The product needs to validate data at the proper time after data has been canonicalized.
Example 1
The following code attempts to validate a given input path by checking it against an allowlist and then returning the canonical path (see References about canonicalization). In this specific case, the path is considered valid if it starts with the string "/safe_dir/".
The problem with the above code is that the validation step occurs before canonicalization occurs. An attacker could provide an input path of "/safe_dir/../" that would pass the validation step. However, the canonicalization process sees the double dot as a traversal to the parent directory and hence when canonicized the path would become just "/".
To avoid this problem, validation should occur after canonicalization takes place. In this case, canonicalization occurs during the initialization of the File object. The code below fixes the issue.
Memory Buffer Errors
-
The product writes to a buffer using an index or pointer that references a memory location prior to the beginning (or past the end) of the buffer.
Example 1
In the following C/C++ example, a utility function is used to trim trailing whitespace from a character string. The function copies the input string to a local character string and uses a while statement to remove the trailing whitespace by moving backward through the string and overwriting the whitespace with a NUL character.
However, this function can cause a buffer to underwrite if the input character string contains all whitespace. On some systems, the while statement will move backward past the beginning of a character string and will call the isspace() function on an address outside of the bounds of the local buffer.
-
The product reads data past the end, or before the beginning, of the intended buffer.
Example 2
In the following code, the method retrieves a value from an array at a specific array index location that is given as an input parameter to the method:
However, this method only verifies that the given array index is less than the maximum length of the array but does not check for the minimum value CWE-839. This will allow a negative value to be accepted as the input array index, which will result in an out-of-bounds read CWE-125 and may allow access to sensitive memory. The input array index should be checked to verify that is within the maximum and minimum range required for the array CWE-129. In this example, the if statement should be modified to include a minimum range check, as shown below.
Numeric Errors
-
The product performs a calculation that can produce an integer overflow or wraparound when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.
Example 1
The following code excerpt from OpenSSH 3.3 demonstrates a classic case of integer overflow:If nresp has the value 1073741824 and sizeof(char*) has its typical value of 4, then the result of the operation nrespsizeof(char) overflows, and the argument to xmalloc() will be 0.
Most malloc() implementations will happily allocate a 0-byte buffer, causing the subsequent loop iterations to overflow the heap buffer response.
-
The product subtracts one value from another, such that the result is less than the minimum allowable integer value, which produces a value that is not equal to the correct result.
Example 2
This code performs a stack allocation based on a length calculation.
Since a and b are declared as signed ints, the "a - b" subtraction gives a negative result (-1). However, since len is declared to be unsigned, len is cast to an extremely large positive number (on 32-bit systems - 4294967295). As a result, the buffer buf[len] declaration uses an extremely large size to allocate on the stack, very likely more than the entire computer's memory space.
Miscalculations usually will not be so obvious. The calculation will either be complicated or the result of an attacker's input to attain the negative value.
Pointer Issues
-
The product attempts to return a memory resource to the system, but it calls the wrong release function or calls the appropriate release function incorrectly.
Example 1
In this example, the programmer dynamically allocates a buffer to hold a string and then searches for a specific character. After completing the search, the programmer attempts to release the allocated memory and return SUCCESS or FAILURE to the caller.
Note: for simplification, this example uses a hard-coded "Search Me!" string and a constant string length of 20.
However, if the character is not at the beginning of the string, or if it is not in the string at all, then the pointer will not be at the start of the buffer when the programmer frees it.
Instead of freeing the pointer in the middle of the buffer, the programmer can use an indexing pointer to step through the memory or abstract the memory calculations by using array indexing.
-
The product obtains a value from an untrusted source, converts this value to a pointer, and dereferences the resulting pointer. An attacker can supply a pointer for memory locations that the product is not expecting.
There are several variants of this weakness, including but not necessarily limited to:
- The untrusted value is directly invoked as a function call.
- Inadvertently accepting the value from an untrusted control sphere when it did not have to be accepted as input at all.
This might occur when the code was originally developed to be run by a single user in a non-networked environment, and the code is then ported to or otherwise exposed to a networked environment.
Privilege Issues
-
Two distinct privileges, roles, capabilities, or rights can be combined in a way that allows an entity to perform unsafe actions that would not be allowed without that combination. A user can be given or gain access rights of another user. This can give the user unauthorized access to sensitive information including the access information of another user.
Example 1
This code allows someone with the role of "ADMIN" or "OPERATOR" to reset a user's password. The role of "OPERATOR" is intended to have fewer privileges than an "ADMIN", but still be able to help users with small issues such as forgotten passwords.
This code does not check the role of the user whose password is being reset. An Operator to gain Admin privileges by resetting the password of an Admin account and taking control of that account.
-
The elevated privilege level required to perform operations such as chroot() should be dropped immediately after the operation is performed.
Example 2
The following code calls chroot() to restrict the application to a subset of the filesystem below APP_HOME in order to prevent an attacker from using the program to gain unauthorized access to files located elsewhere. The code then opens a file specified by the user and processes the contents of the file.
Constraining the process inside the application's home directory before opening any files is a valuable security measure. However, the absence of a call to setuid() with some non-zero value means the application is continuing to operate with unnecessary root privileges.
Any successful exploit carried out by an attacker against the application can now result in a privilege escalation attack because any malicious operations will be performed with the privileges of the superuser. If the application drops to the privilege level of a non-root user, the potential for damage is substantially reduced.
-
The product mixes trusted and untrusted data in the same data structure or structured message. A trust boundary can be thought of as a line drawn through a program. On one side of the line, data is untrusted. On the other side of the line, data is assumed to be trustworthy. The purpose of validation logic is to allow data to safely cross the trust boundary - to move from untrusted to trusted. A trust boundary violation occurs when a program blurs the line between what is trusted and what is untrusted. By combining trusted and untrusted data in the same data structure, it becomes easier for programmers to mistakenly trust unvalidated data.
Example 3
The following code accepts an HTTP request and stores the username parameter in the HTTP session object before checking to ensure that the user has been authenticated.
Without well-established and maintained trust boundaries, programmers will inevitably lose track of which pieces of data have been validated and which have not. This confusion will eventually allow some data to be used without first being validated.
Resource Management Errors
-
The product constructs the name of a file or other resource using input from an upstream component, but it does not restrict or incorrectly restrict the resulting name. This may produce resultant weaknesses. For instance, if the names of these resources contain scripting characters, a script may get executed in the client's browser if the application ever displays the name of the resource on a dynamically generated web page.
Alternately, if the resources are consumed by some application parser, a specially crafted name can exploit some vulnerability internal to the parser, potentially resulting in the execution of arbitrary code on the server machine. The problems will vary based on the context of usage of such malformed resource names and whether vulnerabilities are present or assumptions are made by the targeted technology that would make code execution possible.
-
The product allows user input to control or influence paths or file names that are used in filesystem operations. An attacker could refer to OS system files (not product files) and leverage how the product operates on files.
User Session Errors
-
The product does not sufficiently enforce boundaries between the states of different sessions, causing data to be provided to, or used by the wrong session.
Example 1
The following Servlet stores the value of a request parameter in a member field and then later echoes the parameter value to the response output stream.
While this code will work perfectly in a single-user environment if two users access the Servlet at approximately the same time, the two request handler threads interleave in the following way:
Thread 1: assign "Dick" to name
Thread 2: assign "Jane" to name
Thread 1: print "Jane, thanks for visiting!"
Thread 2: print "Jane, thanks for visiting!"Thereby showing the first user the second user's name.
-
According to the Web Application Security Consortium (WASC), "Insufficient Session Expiration is when a website permits an attacker to reuse old session credentials or session IDs for authorization".
Example 2
The following snippet was taken from a J2EE web.xml deployment descriptor in which the session-timeout parameter is explicitly defined (the default value depends on the container). In this case, the value is set to -1, which means that a session will never expire.
Although the example refers to web applications, it is reported to point out session management issues, in particular about reusing previous session data.