5 Secure Coding Guidelines to Keep in Mind
Great application security doesn’t happen overnight, but these 5 tips are a good start
Often as developers when we think about secure coding we’re thinking of security with a very limited mindset. We’re taught to be sure to sanitize input avoid storing passwords in the clear, etc. We may even be aware of XSS and SQL injection and what these attacks look like, but when it comes to coding and building secure apps, we may be prone to trust intuition or take our chances.
It doesn’t need to be this way. In fact, I believe in many cases considering security when you’re designing systems and coding can actually enhance the quality of your code. In traditional InfoSec training, security practitioners are introduced to the CIA Triad. The CIA Triad defines the overall goals of security in an enterprise: Confidentiality, Integrity, and Availability. I’ll look at each of these and how they relate to writing code.
Confidentiality is best embodied by the principal of least privilege. Basically, it means that only those who have need to access the data, and have sufficient privileges should be allowed to access it. Confidentiality is an entire field in itself that includes many different ideas, implementations, and principals. Generally, when we consider confidentiality we are talking about encryption of data in any of its states (process/transport/rest) or about access controls.
Integrity is the point of the triad that ensures that information is consistent, trusted, accurate, and not manipulated. Data integrity should ensure that data is only modified by authorized parties and that controls are in place to detect any unauthorized modifications.
In a world where SLAs are measured by the number of 9’s you can put after a decimal point, Developers and Ops personnel are already acutely aware of the importance of high availability. Whether it’s a DDOS attack, or an attacker leveraging expensive API calls to slow down an application — these attacks impact an application’s ability to function as expected for their anticipated users.
Secure Coding Tips
#1 Input validation
Often the first security guideline presented is input validation, and my opinion is no different. You should always include input validation as an item when design, implementing, and testing a new application.
When designing a service, you should be asking the question, what kind of input in valid and look for the smallest set of possibilities (accept JSON, or XML, but not both). For specific fields, like a number for example, do you expect integers, floats, positive only, min/max — you should figure all of this out ahead of time so you aren’t making decisions about valid input at implementation time. Plan to do validation at all interfaces, not just the one’s open to the public — you should limit your trust perimeter as much as possible.
When implementing, try to stay as strict as possible and look to adopt a well-know library for validation if possible (I like joi or tv4 for node projects). Also be aware of validation on the contents of files. Be wary of DoS type attacks like the Billion Laugh Attack which is often implemented as an XML bomb, but can also use YAML, or other attacks like a zip bomb.
Finally, when testing an application be sure to include tests against unexpected inputs. Fuzzing is often the term used for this type of testing and there are numerous tools for doing this kind of testing.
#2 Implicit deny
Similar to the more well known firewall rule, you should treat all input/actions as suspicious until you’ve done validation to show otherwise. This applies both to the way you write logical code, as well as how you treat clients calling your applications.
For example, in a micro-service environment, you should not assume just because another service within your platform is making a request to your service means that the client’s request is safe. You should always maintain a very small trust boundary, and do authentication and validation on all requests, regardless of their source.
This principal can also be applied to coding. You should always return
false by default when authorizing actions or validating input rather than returning
true unless the request is proven to be malformed or malicious.
#3 Use known good libraries and keep up-to-date
Whenever possible, use the latest stable version of your language of choice. Many developers have worked hard to fix security bugs in languages and so it’s always recommended to use the latest stable version.
Similarly, when it comes to using open source libraries, don’t just pick the first npm module you see on google. Do a bit of research and check reviews, outstanding issues, and any CVEs for your dependency libraries. There are also numerous free and commercial tools that can help you manage vulnerabilities in your dependencies.
Estimates vary, but I’ve seem estimates from the low 40’s to the upper 90’s for the percentage of production code that is comprised of open-source and third party libraries. The best coding in the world can’t save you if your underlying dependencies or language have nasty vulnerabilities.
Some tools for dependency checking include:
- ochrona — Commercial (free version available)
- snyk — Commercial (free version available)
- npm audit — Free (Formerly NSP)
- OWASP Dependency-check — Free
#4 Know when to retry and when to throw exceptions
There are two general rules I try to follow when error handling. First is to avoid doing general exception catchalls. These can hurt your service by allowing unexpected conditions to go unchecked. Further, in a well designed and understood application as a developer you should have some general idea what can go wrong during various processes, and you check explicitly check for those exception types.
Retrying when possible is also important to ensure availability. A well-designed application knows when a request can be retried and when an exception is unexpected and should be raised. This ties into exception handling and understanding what conditions are safe to retry and what conditions are unsafe or impossible to recover from.
#5 Build security into your process
This could be an entire post or series of its own, but the general idea is to bake security into your development, build, and deployment processes. This is also a core tenant of DevSecOps, where the goal is to build on the DevOps movement and shift more security into the developers domain by adding additional automation into our increasingly automated build and deployment pipelines.
There are many different types of tools which you can add to improve your security posture, but Dependency Analysis (Composition Analysis), Static application security testing (SAST), Dynamic application security test (DAST), and Container testing are just a few examples.
While achieving good application security is an endless journey that requires tooling, knowledge, and planning, getting started doesn’t have to feel like na insurmountable challenge. By keeping these guidelines in mind and striving for incremental improvements you can begin on your journey to publishing safer code.
Similar to writing bug-free code, it’s also impossible to write risk-free code. The best you can do is try to understand your code, apply security controls when possible, and never leave security as an after-thought.