I never make exceptions. An exception disproves the rule.
—Sherlock Holmes in Sir Arthur Conan Doyle’s “The Sign of Four” (1890)
100% secure and reliable code is a unicorn.
I’ll think about security later.
In other words, achieving 100% secure code is wanted by everyone, but unfeasible. Every year thousands of vulnerabilities are discovered,1 and the exposure surface is always increasing. Although ensuring secure code also requires proper scans, especially for third-party libraries and the software we use on our infrastructure, at least the basic security principles should not be an afterthought: they need to be an integral part of any development process.
That being said, before dealing with what to check during a code review process, in this chapter, we will cover the security definitions and fundamentals that will walk you through the understanding of basic aspects to look for during a review.
Security Definitions
An asset is anything that is valuable for the organization, its business operations, and their continuity, including information resources that support the organization’s mission.2
A risk in the general meaning provided in the project management context is an uncertain event or condition that, if it occurs, may have a positive or negative effect on one or more objectives. It may have one or more causes and it may impact one or more assets. In the context of information security, however, it only refers to negative impacts and it expresses the potential and the relative likelihood that something bad might happen.
Software: A security bug in a piece of software is a vulnerability.
Hardware: In the physical security context, not properly enforcing restricted access to a server room is considered a vulnerability.
Human behavior: Plenty of attacks exist to, for example, gain access to, otherwise confidential, information by exploiting psychological vulnerabilities.
Existing vulnerabilities expose the organization to potential damages.
Physical damages to facilities like in the case of natural events (e.g., seismic), information being compromised due to eavesdropping and theft—just to name a few.
Accidental disclosure of confidential information by a personnel member (e.g., social engineering)
The entity that tries to exploit a vulnerability is usually referred to as a threat agent. The way a system gets compromised is referred to as an exploit.
To mitigate the likelihood of a risk becoming a reality, the organization identifies and put in place countermeasures. Back to our case of a natural disaster happening. A commonly adopted countermeasure is to consider redundancy of data and services across several geographically separated regions. Other examples of countermeasures are encryption, firewalls, security fences, access control mechanisms, and so on.
Security Is Such a PAIN
Confidentiality
Confidentiality ensures that only the subjects authorized to access to and use the contents of a message, a transaction, or any other data stored in the system have access to those contents. In other words, confidentiality is closely linked to privacy.
This is, for example, the case of any mailing service you use: the emails you receive need to be visible only to you, the receiver.
Some of the threats to the confidentiality of an information system are due to, for example, intruders, malware, or social engineering attacks.
To keep data and its existence secret is a problem that many organizations face, and they put a significant amount of time and money into it. This is because unauthorized access to confidential data could have a severe impact on the business, not only for critical applications. To this end, it is important to identify and classify data based on the required confidentiality in order to ensure that top priority security assets are adequately protected over time.
Back in the day, people believed that hiding information was enough to consider it confidential. Since the beginning of mathematically sound solutions (given the time period and the technological advances), we finally stepped away from relying on obscurity to keep information secure in most cases. A very well-known practice is given by Kerckhoff’s principle that states that the security of a cryptographic system should not rely on the secrecy of the algorithm, while only keys should remain secret. Since then, security by obscurity is no longer considered a secure enough practice.
Even if different security mechanisms can provide different degrees of confidentiality (availability and integrity), the main prerequisite is to protect confidentiality via cryptography, by encrypting data both at rest and in transit (i.e., moving from one system to another), and access control mechanisms. These activities need to be tracked, audited, and monitored over time.
Integrity
Integrity ensures a message, data, or transaction has not been tampered (i.e., compromised). It encompasses different properties of stored or information including correctness, completeness, consistency, and accuracy. When a security mechanism provides integrity, it prevents improper or unauthorized access and modification of data, or a resource, in a system.
An example of integrity is the case of the balance of your bank account. At any point in time, the balance needs to be correct by means of any transactions (e.g., withdraw and deposit) you legitimately performed.
Maintaining integrity also ensures that any piece of information is internally and externally consistent.
Availability
Availability ensures that information is constantly and readily accessible by authorized users. Protection mechanisms must be in place to protect against both outside and inside threats that could affect the availability of the organization’s assets.
Back to our bank account example. Ensuring availability means that accessing the various banking services should work anytime you need to.
Availability is generally formalized contractually by means of SLA (service-level agreements). Critical services, like those provided in the telecommunication world, generally strive for at least five-nine availability. This means that services should be available 99.999% of the time. In such a case, the maximum allowed downtime of a service is less than 5 minutes and 26 seconds in every given year.
Typical attacks against availability are denial of service (DoS) attacks and its distributed form DDoS. Anyhow, when dealing with system availability, it should also be taken into account possible power outages as well as risks due to natural disasters (i.e., physical security). Between all, properly implementing redundancy is the very first step into achieving availability.
Non-repudiation
The CIA triad is sometimes also referred to as PAIN, which stands for privacy, availability/authentication, integrity, and non-repudiation.
Non-repudiation means that an action cannot be denied. Any action (e.g., access or modification) is bounded to a unique subject. For instance, this property ensures that after a message is sent, it cannot be disputed if it has been sent or not.
Back to our email example; this means that if a friend sends you an email, they cannot argue whether they sent it or not.
Trade-offs
Implementing the right controls, based on security requirements, is fairly intricate. Trade-offs are made all the time between the various triad aspects. Certain aspects might require priority, but a balance should always be considered. Consider, for example, a scenario where high confidentiality is highly enforced but availability is neglected: in case of an attack, even the subject with the right permission would not be able to access anything.
Back to our banking example, high confidentiality means that the account holder is the only person allowed to access banking services. However, a lack of availability implies that not even the account holder can use services even if they should be allowed to.
Not balancing properly security requirements given the criticality of the offered services, the environment they run into, as well as any legal obligation would cost a lot for the industry, both in terms of money and reputation.
Fact or Fiction?
The adage “think first, then act” applies to coding as well as security. The “think first” is also known as security by design, which means that the software is born as secure. It is designed from the ground up to be secure. Secure is how software is meant to be.
Unfortunately, too often new solutions are built and delivered without dealing with security and privacy issues from time zero. Only later, maybe when some attacks and relative money losses took place, people think how to make their product secure and compliant.
- 1.
The business who releases insecure software loses in credibility.
- 2.
The business loses money.
- 3.
It is so much more difficult to add security features later on in the software lifecycle.
- 4.
It is costlier and more time consuming to fix after rather than designing first.
It is like you cooked brownies, but you forgot to add chocolate before baking them, right? It is a huge no, no, no!
Security Principles
Now more than ever, when tons of solutions are deployed into the Cloud and we are (almost) all excited by the Internet of Things wave, using the security by design model is mandatory. And, surely, the underlying principles described as follows can be used to assess code during reviews.3
Least Privilege
A simple example application is often enforced in physical security: you do not have access to an office unless you need to (e.g., you work for the company who owns/rents that specific office).
Defense in Depth
As an an example, think about a simple multilayered software consisting of an API, some backend computation, and a database. Attempting to secure only the API is insufficient: any circumvention of the API would leave backend and data exposed to possible attacks.
Segregation of Duties
This is as simple as thinking about the development tools we use every day, git included. Segregation of duties happens when you are granted read/write permissions to the projects your team owns, but you do not have permissions, by default, to the other projects within the company.
Fail Safe
- 1.
No data actually changed.
- 2.
Error is signaled and handled accordingly.
Back to our banking system. If you attempt to perform a withdrawal, but no money can be released by the ATM, fail safe means that the error is clear and the failure of the withdrawal is reflected by no change into the balance of your checking account.
Complete Mediation
As an example, complete mediation means that you won’t access to any of the data and services (either critical or not) on your laptop unless you authenticated yourself and authorization has been granted.
Least Common Mechanism
Consider, for example, two virtual machines, each running a service with a different set of users. Those two virtual machines need to be properly isolated in order to guarantee no leakage from one machine to another.
Weakest Link
Generally speaking, the commonly known weakest link is ourselves as humans and the usage we do of a system. Phishing and social engineering attacks are at the order of the day, and there is not much security a system can offer if we willingly, yet unconsciously, volunteer information.
Security Principles Caveats
The described principles do not look like rocket science. However, they are a huge boost in securing any solution. Bad things always happen (Murphy’s law), but I think that—really—starting from the foundations of security principles and software development is the way to go. It does not end there for sure, but they are still underrated nowadays.
Worth noting, however, that any of these fundamental principles can be very complex depending on the technologies used and the context they run into.
As an example, let’s go back to the least common mechanism: isolating virtual machines is not the same as isolating containers that, in turn, is not the same as separating flows within a given service on top of them depending on the specific business logic and usage expectations.
Security Code Review
Security implication of the used programming language
Automation and penetration testing
Security architecture reviews
Broad checks of the security of third-party libraries the code depends on.
However, some checks can and need to be performed during the development process, even for not-so-mature software. I can’t stress enough how thinking about security earlier in the process can save you from a lot of headaches, as well as and costs and time, later on. Of course we are not talking about 100% secure and reliable code unicorns, remember?
We are speaking about software and products which are secure enough depending on the environment, context, or domain in which they are deployed. Security is such a broad aspect to consider. Hence, this section has trade-offs in mind and will walk through some of the main aspects to look at during code reviews to ensure that at least major flaws are checked.
Secure Software Development Lifecycle
Security code reviews are only a small piece into the secure software development lifecycle (SSDLC).
Security is a critical piece of any software application and a very complex one. Proper awareness training on the topic is fundamental to the security process and is generally provided by most companies.
As we saw, a good understanding of context and environment in which the application operates is fundamental to understand security requirements, implications, and potential risks from the get-go.
Always consider security by design.
Secure development and testing (reviews performed here as detailed in the following section).
It is very important to have an outlined security/incident response plan in place ahead of releasing the code. In other words, it is fundamental to have and know which processes are in place in case an incident happens and who is supposed to resolve it within which time frame.
Depending whether code needs to be publicly released or not, extra processes (company specific) are generally in place.
Last, but not least, like general code quality, it is important to consider how to maintain security over time.
Companies, especially in heavily regulated environments like healthcare, need to go through auditing processes of their systems to ensure compliance against a multitude of legal regulations that depend on both the sector they work in and their geographical location. We will not provide details about auditing processes in this book since they are generally broader and way more complex than day-to-day software development process for most engineers.
Security Code Reviews
Generally speaking, such reviews focus on finding exposure to potential threats in the areas discussed earlier, that is, availability, confidentiality, and integrity. However, those are pretty broad, since they span across checking for identity and access management, how sessions are managed, data privacy, logging, error handling, confidential information, and encryption, just to mention some.
Automating Security Reviews
Automated code reviews might help a lot in speeding up the review—we love automation, don’t we?
Static application security testing (SAST) tools can be used to support static code analysis and help find security flaws. Generally speaking, they are very useful during the development process, and they are fairly easy to embed during the development phase.
As an example, GitLab CI/CD offers easy integration with SAST (https://docs.gitlab.com/ee/user/application_security/sast/) and can be used to spot both the existence unsafe code that could potentially be used for unintended code execution and XSS attacks.
Plenty of SAST tools exist that can be used depending on the language, your programming environment and preferences. OWASP (https://owasp.org/www-community/Source_Code_Analysis_Tools) provides a fairly comprehensive list of options. Consider adding a SAST tool to your SSDLC.
At the other side of the seesaw, there are manual code reviews that require a good knowledge of the programming language, domain, application, use cases, and—in this case—security as well. They are definitely more complex and time consuming than using automation.
However, some of the aspects like how identities are managed can be better detected via manual reviews. Code scanners can speed up the process, especially if the code base is fairly big. Their use is useful especially as a first pass. Anyway, they may lack context, hence possibly producing both false positives and false negatives. Thus, I encourage you to automatize when possible but still embed manual reviews to ensure as much as possible code quality and security.
Summary
Security code reviews help in finding flaws in the code. But don’t be fooled. You will not spot all of them. Remember that having healthy code is about constant improvements. With security, it is also about constant monitoring. Certain issues are not that easy to detect and code reviews per se surely will not solve them. Monitor data based on the context of your application to discover anomalous behaviors, and if that happens, start to find the root cause.
Especially for security, there is no one size fits them all.
Always consider security by design.
Consider the trade-offs and any consequent risk of the software you write.
Always consider at least the security implications given by the fundamental principles described in this chapter.
Have both manual and automatized reviews and processes in place.
In the next chapter, we will put together the entire code review process and provide guidance on metrics to track and monitor during the review process. Furthermore, we will depict common behavioral issues that can make or break its effectiveness.
Further Reading
Security can’t really be condensed in a couple of readings. To inspect deeper on the foundation of security, the Official (ISC) 2 Guide to the CISSP CBK, 3rd ed. by Steven Hernandez (Auerbach Publications, 2012) is surely not a lightweight read, while an amazing reference. OWASP (https://owasp.org/) also provides a nice guidance on application security.
Review Checklist
- 1.
Is any sensitive/private/confidential information logged?
- 2.
Is any sensitive/private/confidential information disclosed?
- 3.
Are audit trails present?
- 4.
Are authentication and authorization mechanisms consistently enforced? Are they adequate to the intended security degree wanted?
- 5.
Is every access to an object authorized (i.e., complete mediation)?
- 6.
Is the least privilege principle enforced?
- 7.
Is defense in depth applied?
- 8.
Is segregation of duties principle ensured?
- 9.
In case of failures, does the system fail safely?
- 10.
Is encryption performed? Is it adequate to the security needs?
- 11.
Are weak ciphers used?
- 12.
Are security keys too small to provide adequate security?
- 13.
Are certificates valid?
- 14.
Are security keys protected from unauthorized access?
- 15.
Are hashing mechanisms used to check for integrity when needed?
- 16.
Are security tests in place?
- 17.
Which is the weakest link in the security chain? Is it secure enough?
- 18.
Are systems secure enough to expose the least attack surface as possible?
- 19.
Are all entry points to the system secured?
- 20.
Is input validated against well-known attacks (e.g., SQL injection, XSS injection)?
- 21.
Does the system plan for failure?
- 22.
Is security by obscurity in place instead of proper mechanisms?
- 23.
Does the code contain any security bug (also language dependent)?
- 24.
Are privacy-enhanced protocols in place when required?
- 25.
Are the used protocols tamper resistant?
- 26.
Is the code resistant to buffer overflow?
- 27.
Is there any hard-coded password?
- 28.
Is there any backdoor in the code?
- 29.
Is the code compliant with security policies and standards?
- 30.
Are security requirements clear?
- 31.
Is the team well trained on security?
- 32.
Are security response plan and processes in place?