In this 13 page cheat sheet we'll cover best practices in the following areas of the CI/CD pipeline: Infrastructure security, code security, secrets management, access and authentication, monitoring and response.
Source code security refers to the practice of protecting and securing the source code of an application from vulnerabilities, threats, and unauthorized access.
Source code security refers to the practice of protecting and securing the source code of an application from vulnerabilities, threats, and unauthorized access. The basic idea is that you should follow secure coding best practices when developing your apps and use tools to notify you when you might forget to apply them. And because anyone with access to source code can accidentally introduce vulnerabilities, everyone on your teams should be up-to-date on the latest security standards.
What’s the developer’s role in source code security?
Because developers create the code and know what it’s supposed to do, they are in the perfect position to apply secure coding practices from the beginning of the software development lifecycle. Of course, this means developers must be up-to-date about security best practices and have security tools that support them. A developer who knows secure coding best practices and is equipped with the right tools can prevent vulnerabilities before they enter production. The more time between implementation and fixing a security issue, the less context the developer now has, and the harder it is to fix it.
The key tools for source code security are code linters and security scanners that identify vulnerabilities. They can run in an IDE when a developer writes their code, before it gets committed to a version control system, or in a CI/CD pipeline.
Common source code security vulnerabilities
As a first line of defense, a developer should know about potential security risks in the development process, which depend on the type of software you’re implementing, its intended use, and the audience. The OWASP Top Ten list of web app vulnerabilities is always a good source to check against. Let’s look at the most prevalent vulnerability types from OWASP’s list and where they occur.
Injection attacks
Injection attacks are a technique threat actors use to execute commands in your system. Every time you allow free-text inputs from your users without sanitizing them, there’s the chance of an injection.
SQL injections are a common example. They usually happen when you concatenate an input string to a SQL command, allowing an attacker to add other SQL commands to that input string. Other injection types are shell commands or XML. Again, in these attacks, an input string is added to a command or XML string without being sanitized first, letting an attacker execute them in your system.
A code example for an SQL injection in NodeJS looks like this:
server.get('/users', (request, response) => {
const query = `
SELECT*FROM users
WHERE name ='${request.query.username}' `
database.query(query, (error, rows) => {
if (error) throw error
response.json(rows)
})
})
In the example, username could contain malicious code like Jim'; DROP TABLE 'users which would be executed after SELECT.
Cross-site scripting
Cross-site scripting (XSS) vulnerabilities are similar to injection attacks in that they try to add malicious code via a text input to your system. But in contrast to injection attacks, cross-site scripting doesn’t target your system directly and instead aims to execute injected code on your users’ machines. Simply put, an XSS attack uses your system to pretend the injected code originates from a trusted source (you) and is therefore safe for your users.
Cross-site scripting occurs when user inputs are displayed unsanitized to other users. Usually, when HTML strings are concatenated with user inputs, an attacker can add JavaScript code or a modified HTML button that coerces a user to perform a malicious action.
Buffer overflows
A buffer overflow is a low-level vulnerability, usually prevalent in C/C++ code bases, as these languages allow access to invalid memory locations. With a buffer overflow, an attacker can circumvent access control, execute arbitrary code, or even crash the whole system so it can’t serve its users anymore.
The following code example illustrates a buffer overflow in C:
The gets function will read from stdio and write the input into the buffer array, even if it’s longer than 10 characters. This can allow an attacker to override memory outside of the function’s stack.
Insecure authentication and authorization
Authentication is how a system checks that a user is really who they claim to be, and authorization is how a system checks what a user can access. Both can be weak spots that allow attackers in.
Authentication vulnerabilities are often related to insecure cryptographic methods. These vulnerabilities can relate to insecure storage, for example (when passwords are stored in plaintext or with a broken hash algorithm, attackers who gain access to the database containing them can extract those passwords). Authentication vulnerabilities can also relate to insecure connections, where attackers can extract the password from a transmission if authentication is facilitated via an insecure channel (i.e., HTTP without TLS).
Authorization vulnerabilities give users permissions they shouldn’t have, including access to data and permission to execute specific functionality. This usually happens when access to the resource has an indirection but only this indirection is checked for permission.
For example, a service hosts multiple image galleries, but each user can only access their own gallery. However, the links to the actual images aren’t protected. If a user can guess image names, they can access pictures from galleries they cannot view.
Hardcoded secrets
Applications that access protected resources can be vulnerable because of hardcoded secrets. Instead of reading a password, token, or key from a secrets management solution, they are hardcoded into the application. This often happens for development or testing purposes so that the developer doesn’t have to set up a secure storage mechanism or type the password manually. The downside? Hardcoded secrets often end up in code repositories that are already public or will become public at some point in the future, allowing an attacker to extract the secrets.
You can take several security measures to ensure your source code stays free of the vulnerabilities discussed above:
1. Use encryption at rest and in transit
Never transfer sensitive information via insecure channels or store it in plaintext. Use TLS/SSL for end-to-end encryption to ensure no third party can listen to your communication, and use encrypted storage for sensitive data.
2. Validate inputs and encode outputs
Validate and sanitize all user inputs. Validating means checking that each input is in the right format (e.g., JSON, email, URL, XML, etc.), and sanitation means transforming inputs into safe representations (i.e., removing unsafe characters). Also, make sure that you encode outputs before presenting them to users, especially when the outputs depend on user inputs.
3. Apply secure authentication and access control methods
Store secrets like passwords or tokens with secure algorithms and only transfer them via secure channels (i.e., TLS/SSL). Always use two-factor authentication so a malicious party can't use a password without controlling a specific user device, even if a password is leaked.
Don’t use guessable resource IDs (i.e., incrementing counters); if you must, ensure that you check access permissions at the resource level to prevent unauthorized access.
4. Handle and log your errors
Try to catch and handle all exceptions and log every error. This way, you fix bugs more easily and have a history you can check for attempted attacks. It’s also a good idea to keep an access log to see if someone accesses data when they shouldn’t have permission.
5. Automate security tests within your CI/CD pipeline
While security tests in an IDE are much closer to the developer who writes the code, you can’t be sure they run the scanner before pushing updates to a code repository. Developers could have deactivated the scans on their machine for performance reasons, or attackers could pretend to be a developer that pushes a benign change to your repository. Even if developers do run the scanners before pushing changes, the repository can contain code from before you adopted source code security best practices. With static application security testing (SAST) as part of your CI/CD pipeline, you can be sure that nothing slips through the cracks.
Summary
Source code security plays a vital role in the software development lifecycle. Because development teams can implement source code security measures as they write the code, it forms the first line of defense—turning fixing vulnerabilities into preventing them.
Use SAST scanners with up-to-date vulnerability lists and educate your developers about the most common threats and best practices to ensure the safety of your systems and the sensitive data they contain.
Checkmarx SAST gives you these scans in the IDE and the CI/CD pipeline, and with its recent Wiz integration, you can correlate the results with your runtime environments. This way, you get fast responses and limit the number of false positives.
Want to see for yourself? Schedule a Wiz demo today to learn how easy it is to protect everything you build and run in the cloud.
Secure your cloud from code to production
Learn why CISOs at the fastest growing companies trust Wiz to accelerate secure cloud development.
Data access governance (DAG) is a structured approach to creating and enforcing policies that control access to data. It’s an essential component of an enterprise’s overall data governance strategy.
Cloud data security is the practice of safeguarding sensitive data, intellectual property, and secrets from unauthorized access, tampering, and data breaches. It involves implementing security policies, applying controls, and adopting technologies to secure all data in cloud environments.
SaaS security posture management (SSPM) is a toolset designed to secure SaaS apps by identifying misconfigurations, managing permissions, and ensuring regulatory compliance across your organization’s digital estate.
Data risk management involves detecting, assessing, and remediating critical risks associated with data. We're talking about risks like exposure, misconfigurations, leakage, and a general lack of visibility.
Cloud governance best practices are guidelines and strategies designed to effectively manage and optimize cloud resources, ensure security, and align cloud operations with business objectives. In this post, we'll the discuss the essential best practices that every organization should consider.