How to decide what to fix when you can't fix everything
Contributing to a legacy software development project, as a security-aware developer, is a bit like inheriting an old house. In my old house, the roof is missing tiles, the bathroom taps are dripping, the front door doesn't lock properly, the hallway needs redecorating and there are worrying cracks in the foundations. I don't know where to start.
The security problems with the application I've recently (hypothetically) joined are similarly vexing and diverse. It has deprecated dependencies to older versions of software libraries. It could be misconfigured using insecure protocols. It could also be using open source components with known vulnerabilities, like the recently discovered CVE-2021-44228 (log4j).
Taking the comparison further, deprecated dependencies might be compared to the broken roof. If attention is delayed, then a small problem can become much more significant before long. The use of insecure protocols is akin to the broken door lock - both are easy entry ways for intruders. And the cracked foundations are similar to vulnerable open source libraries - complete disaster could happen at any moment, whether that's through a bad actor attacking my application or a storm knocking down my house.
Understanding how to prioritise long lists of problems is difficult in both cases. The sheer volume of issues can lead to paralysis, or equally, make developers (of both the code and house varieties) inclined to tear down the whole thing and start again.
Adopting a simple system to prioritise actions can be a powerful aid. You might begin by assigning a score to the amount of impact a fix can make, closely related to the severity of a vulnerability. Severe vulnerabilities that are actively being exploited would score high, whereas those that are more a matter of hygiene would score low.
Effort vs. impact
Alongside this set of scores, also estimate a score for how much effort is entailed in fixing the problem. Replacing a large piece of functionality would obviously have a high score, whereas swapping out one dependency for another, updated version would be fairly trivial, and have a low score.
At this point, your list of tasks will fall into four broad categories. Tasks with a high impact but which won't require much effort should be your first priority: these are the low-hanging fruit that will make a big difference to your application's security posture without consuming much time.
Then come those tasks that will have a high impact, but also require a considerable amount of effort: these will be your next major projects, to be tackled steadily once those easy-wins are completed.
Tasks with low impact and low effort should be towards the bottom of your to-do list: complete them between larger tasks or when you're waiting for something else. Those tasks that require considerable effort but will have little impact will definitely be at the bottom of the list. You may be able to rethink these tasks to find an alternative approach to reducing the effort required to make the necessary improvements.
These two dimensions - impact and effort - might be sufficient to triage your list of outstanding items to be fixed sufficiently. However, with longer lists or perhaps an initial security pass on an existing application, this may still yield very long lists. Here, a third dimension will assist. This extends your triage map into a cubic representation of prioritisation. Though rather than thinking in 3-D space, a simple scoring algorithm will typically help more.
Into the third dimension
The third dimension might be exploitability, for example. Your application may have outstanding vulnerabilities - there are some long-standing issues with almost every operating system and programming language, for example, and edge-case examples with many common open source libraries. But if the full conditions for exploiting these vulnerabilities are not met, then it lessens the urgency of fixing the vulnerability.
For example, if a vulnerability is not, in any way, visible to an attacker, or if a vulnerable command is not employed within a project, then the urgency of a fix is decreased. They still need to be fixed at some point — circumstances can change and a vulnerability that is not exploitable today may become so in the future — but this added lens provides clarity over priority tasks.
This third dimension should help to triage vulnerabilities, no matter how ragged the incumbent project may be. It provides a solid approach to placing every vulnerability to its proper place in the lengthy list of tasks that may need to be performed.
You will, however, need tools to assist you, especially when the list of potential areas to fix is especially long. Automation is key here. Every element of automation systematically arms organisations to more effectively combat threats. Aim to automate as much of this triaging task as possible. Assigning the severity and risk of a vulnerability is a key part of this and may be accomplished using Snyk Vulnerability DB and Snyk Advisor, alongside other tools and other free alternatives.
Triage, establishing prioritisation between the fixes and changes required to improve the security posture of your application is an essential first and ongoing step. Applying methodologies alongside dev-first tools will accelerate triage and every subsequent step towards initial hardening and ongoing application security.