The EQUIFAX USA event of 2017 put into the spotlight an underconsidered aspect of software security: It’s not just our code that we need to secure. The facts of the case are widely known, but, its cause? Not so much. Little is said about the fact that this leak would not have taken place if the developers of the EQUIFAX application had upgraded their Apache Struts web framework to a more secure version.
We developers are not creating applications based only on our code. We rely on servers, programming languages, frameworks, libraries (wheels, gems, JARs…) and we need to secure all that, even that code we copy-paste from Stack Overflow.
When we create applications, we are not just writing code, we are building a house with Lego blocks, and our code is just a part of that house. In fact, our code is on the top of that house. All that other stuff we depend on, is underneath, it’s our software’s foundations, and if it is not secured, our house will fall apart, just like EQUIFAX’s did.
One notable thing to mention in this issue is the fact that this kind of vulnerability is listed in OWASP Top 10 2017.
The good news is that some developers have begun to take notice on the issue. There are projects available that aim to help developers to take care of dependency security even before their code reaches the CI/CD (Continuous Integration/Continuous Delivery) server.
These tools are continuously updated, based on security data obtained from CVE (Common Vulnerabilities and Exposures) data and developer reports, and can be used in CI/CD servers as well.
Here are some of them:
OWASP Dependency Check (Java, multilanguage)
The main reference when it comes to dependency security checking is OWASP’s dependency checking tool, which is mainly thought for Java projects, but has been expanded to other platforms, like .NET, with experimental support for Ruby, Node.js, Python and C/C++.
Bundler Audit (Ruby)
Bundler Audit is made with the popular language Ruby in mind. Its vulnerability database is GitHub repository-based, and it is regularly updated. This allows Ruby developers to keep their gems up-to-date in security terms.
Package manager support
The even better news is that package managers have begun to take notice, and they take security a step further by integrating dependency checks into their features. Here are notable cases:
Pipenv is a packaging tool for Python that is gaining momentum every day, and brings together many aspects of Python development: Management of virtual environments (virtualenv), package definition (pip), and others.
Since version 6, the Node Package Manager includes an audit feature, that allows developers to check for vulnerabilities in their projects’ dependencies.
One notable thing about this functionality is that it also warns about potentially functionality-breaking upgrades, based on the Semantic Versioning standard.
Continuous integration? Yes, but…
Some of these tools already have CI/CD system plugins available for servers like Jenkins, and they work quite well. In the case of the tools that don’t, integration to these systems is not that hard.
Now, we shouldn’t wait until our code reaches our repositories (and our CI servers) to see if our dependencies are secure. Dependency security checks should be done by every developer, before pushing their changes to the repository. Don’t get busted by the CI/CD server.
How to fix
Fixing the issues reported by these tools may seem easy: Just changing the library version and that’s it. But you need to test the changes locally before pushing your code.
Many packaging systems are automatic when syncing versions, and dependencies of your dependencies are upgraded easily. But this may cause functionality breakage. This is the main reason the community created the Semantic Versioning standard.
Usually, security vulnerability mitigations are put in the Patch versions, but sometimes you will need to upgrade to a Minor or Major version, and if that happens, you must do feature testing, because these kinds of upgrades could cause problems with functionality.
Not just libraries
The tools we saw earlier allow us to check vulnerabilities in libraries/frameworks. But we don’t just depend on those. Servers and our (every day more) virtualized infrastructure should be checked for security vulnerabilities. Automatically.
Developers must check, not just our code for vulnerabilities, but also our dependencies to make our applications more secure.
This process should be automated, but not dependent on automation: We need to be more proactive, and run our available tools before commiting changes. This way, we will be one step ahead from malicious actors who intend to harm our software and its users.