Secure Coding Practices
Updated: Oct 14, 2022
This article first appeared on Snapt.
I previously addressed the need to design secure applications and test for security during development. However, despite all good intentions, it is all too easy to focus on solving the immediate functional problem and ignore other objectives like security when writing code.
Thankfully, many powerful security scanning applications can help identify the most common mistakes we make as developers, leading to security issues.
Still, we shouldn't become reliant on tools. We also need to ensure that we can better understand what it takes to develop securely.
These secure coding best practices should help.
General Coding Practices
Regardless of the type of programming you are doing, the following principles are helpful to implement in your coding practices.
Use tested, approved, and managed code rather than creating new unmanaged code for common tasks.
Use your operating system's built-in APIs for system-level tasks. Do not allow your application to issue commands directly to the operating system, especially through application-initiated command shells.
Use checksums or hashes to verify the integrity of interpreted code, libraries, executables, and configuration files.
Use locking to prevent multiple simultaneous requests on a shared resource or use a synchronization mechanism to avoid race conditions.
Protect shared variables and resources from inappropriate concurrent access.
Explicitly initialize all your variables and other data stores during declaration or before the first use.
Avoid calculation errors by understanding your programming language's underlying representation and how it interacts with numeric calculation. Pay close attention to byte size discrepancies, precision, signed/unsigned distinctions, truncation, conversion and casting between types, "not-a-number" calculations, and how your language handles numbers too large or too small for your programming language's underlying representation.
Do not pass user-supplied data to any dynamic execution function.
Implement safe updating. If the application will use automatic updates, then use cryptographic signatures for your code and ensure your download clients verify those signatures. Use encrypted channels to transfer the code from the host server.
User input presents the most significant risk to your code. Malicious users will do their best to expose sensitive information through your applications' input methods, for example, by using code or SQL injections. Here's what you can do about it.
Centralize your input validation
Create a centralized input validation routine for your application. This method will streamline and standardize security across your application. It also allows your security professionals to simplify the security testing process.
Within this one program, you can now include all the critical mechanisms that will help to ensure all user input is sufficiently validated.
Ensure all validation failures result in input rejection.
Specify proper character sets, such as UTF-8, for all input sources.
Encode data to a common character set before validating (Canonicalize).
Verify that header values in both requests and responses contain only ASCII characters.
Validate data from redirects. An attacker might submit malicious content directly to the redirect's target, circumventing application logic and any validation performed before the redirect.
Validate for the expected data types.
Check for null bytes (%00).
Check for new line characters (%0d, %0a, \r, \n).
Check for “dot-dot-slash" (../ or ..\) path alteration characters. In cases where UTF-8 extended character set encoding is supported, address alternative representation like: %c0%ae%c0%ae/. Use canonicalization to address double encoding or other forms of obfuscation attacks.
Output encoding translates special characters into a different but equivalent form so that they will not be read as malicious code by any part of the software system.
If you allow any potentially hazardous characters as input, implement output encoding and secure task-specific APIs, and account for the use of that data throughout the application.
Examples of common hazardous characters include: < > " ' % ( ) & + \ \' \"
Encode all characters unless they are known to be safe for the intended interpreter.
Contextually sanitize all data returned to the client that originated outside the application's trust boundary. HTML entity encoding is one example but does not work in all cases.
Contextually sanitize all output of untrusted data to queries for SQL, XML, and LDAP.
Sanitize all output of untrusted data to operating system commands.
By implementing general security practices, input validation, and output encoding, you can help to make your application code secure during development, making testing and auditing easier.