
消除软件中已知漏洞的务实方法
升级软件的依赖关系以消除已知的漏洞是很重要的,但它可能是痛苦的。没有工程师喜欢这样做,而且要花费原本可以用来提供新功能的时间。许多组织忽视了这个问题,或者甚至没有意识到他们有一个问题,使他们自己暴露在安全漏洞之下。我们需要一种务实的方法。
DevSecOps方法、健康的CI/CD实践,以及诸如dependabot等工具,使生活变得更加容易,但最终并不能解决所有问题。大量的破坏性变化和没有修复的软件包仍然存在。
我们如何评估剩下的东西?风险暴露是什么?我们怎样才能在不花费太多时间的情况下将风险降低到可接受的水平?评估和修复所有的东西可能是一个巨大的消耗。特别是对于拥有众多长期项目的成熟团队。
下面是我们在Shine成功使用的解决这个问题的方法。这是一种基于风险的方法,确保最重要的项目被及时补救,而不在其他项目上浪费时间。这种方法可以根据你的组织的风险状况和胃口进行调整。
什么是已知的漏洞?如果我们什么都不做会怎么样?
已知漏洞或 "常见漏洞和暴露(CVE)"是指已经检测到并在MITRE监督下的CVE列表中公开注册的漏洞。
OWASP将 "使用有已知漏洞的软件组件"列入他们的OWASP前十名。的确,在许多情况下,CVE是无害的,然而,有无数有据可查的安全漏洞,涉及攻击者利用依赖关系中的已知漏洞。
不能保证你的自动化(假设你有的话)会修复所有的问题,甚至是最糟糕的问题。事实上,恰恰相反。你的自动化不能修复的漏洞是那些需要破坏性改变才能升级的漏洞,甚至是重大的重写。它们往往是具有最严重漏洞的最古老的软件包。
即使你从一开始就用良好的自动化来处理事情,但最终还是会有需要人工干预以消除漏洞的时候。
如果你什么都不做,你会让自己处于安全漏洞的巨大风险中。正是由于这个原因,组织应该有一个关于漏洞的合理政策。然而,正如我们将看到的,魔鬼就在细节中。
首先,将你能做的事情自动化(只是不要被愚弄)
尽管你不能完全消除与修复漏洞有关的开销,但你可以通过自动化来减少它。
下面的自动化方法将意味着在许多情况下,你会被自动通知一个软件包有漏洞,自动提供一个拉动请求来批准,然后能够自动测试和发布修复到生产。我们把这个方法分为三个阶段。
检测
检测的关键是确保你的漏洞扫描在代码库本身或作为CI/CD管道的一部分自动运行,而不是依赖不太频繁的手动触发的外部扫描。你可以更早地发现问题,甚至可以通过让开发人员知道他们引入的组件在发布到生产之前就含有漏洞来防止一些问题。
根据你当前的生态环境,有很多选择。Dependabot、snyk、Sonatype Nexus、OWASP Dependency Check都是你可以用来实现这一任务的工具的例子。
外部扫描仍然可以作为一种治理机制,确保你的自动化流程配置和工作正常。
补救
下一个阶段是补救。在许多情况下,自动化可以用来解决这些问题。renovate,dependabot,snyk, 是一些可以用于此目的的例子。然而,如前所述,不要被愚弄,以为这本身就足够了。我们将在下文中更详细地探讨这个问题。
发布
最后,我们要发布我们的修复后的软件。健康的CI/CD实践,如足够的自动化测试覆盖率、生产监控和自动化发布流程,减少了将这些修复程序投入生产的摩擦。反正这些东西应该已经在你的雷达上了。
剩下的是什么?恐怕是很多
不幸的是,即使有最成熟的自动化,在生产中一直保持零漏洞也是不可行的,甚至是不可能的。
如前所述,自动化方法在以下情况下没有帮助。
- 软件包有漏洞,但还没有安全的版本存在
- 有一个安全的版本,但它涉及到一个重大的升级和破坏性的变化
- 没有安全版本,也不可能有安全版本。
我们的经验是,根据不同情况,非破坏性的修复措施只有50%到90%的时间可以使用。使用遵守semver的技术和依赖关系的较新项目,能够更经常地自动修复。而使用不遵守semver的依赖关系的老项目,则较少。
发现CVE的速度,以及某个软件项目所使用的依赖关系的平均数量,都随着时间的推移而增加。在项目中,新的漏洞往往在被修复后几天就被发现。相关的工作也在不断增加。我们在一个客户那里发现,每个应用程序每月平均有20-40个新的漏洞被发现。这个客户有100多个源代码库需要维护。事情很快就变得不堪重负了。
即使在可以使用自动化的情况下,修复也不是立即可用的。通常情况下,修复程序的开发需要时间。在此期间,我们必须问自己一些重要的问题。我们面临的风险是什么?等待修复程序的发布是可以接受的,还是我们应该采取其他措施,如更换软件包?我们应该等待多长时间?
CVE有不同程度的风险,许多人根本没有风险
确定一个特定的漏洞所带来的风险的一个好的起点是CVSS评分。使用通用漏洞评分系统(CVSS)对CVEs进行严重程度评分。
然而,仅凭CVSS评分并不足以确定某一特定漏洞所带来的风险,因为它没有考虑到你的特定情况。谁可以访问包含该漏洞的应用程序?这个应用程序处理的是什么样的数据?一个不包含敏感数据的私人应用程序中的关键漏洞比一个处理大量敏感数据的公开应用程序所带来的风险要小。
此外,在很多情况下,无论何种应用,漏洞都不会带来任何风险。这些情况包括。
"开发 "依赖
这些情况是指组件包含一个漏洞,但只在构建过程中使用,实际上并没有部署到生产中。这方面的例子有:编译器、铸币机和测试框架。偶尔,如果漏洞利用了构建管道本身(例如通过挖掘加密货币),这些会构成真正的风险。然而,在绝大多数情况下,开发依赖中的漏洞几乎不构成风险。
误报
扫描仪通常会错误地检测到漏洞。例如,当漏洞存在于一个库的.NET版本,但我们使用的是Java版本。
与不使用的功能有关的问题,或者在其他地方已经被缓解的问题
通常,只有在你使用某个特定依赖的特定功能时,漏洞才会适用。例如,如果你的记录软件在其远程记录器中包含一个漏洞,但你只使用本地记录,那么该漏洞对你不构成风险。
你可能会说,"我们现在不使用这个,并不意味着我们将来不会使用它"。虽然这可能是一个合理的论点,但要求你的工程师和产品拥有者花费宝贵的资源来升级一个依赖性,以防它在未来可能会带来风险,是不太明智的。如果你担心这个问题,可以考虑定期审查你的豁免权,以确保它们仍然有效,或者让它们在规定的时间内失效。
使用基于风险的方法来集中你的努力
鉴于 "零容忍 "的方法既不现实,也不是对你宝贵的工程资源的合理利用,考虑采用一种基于风险水平的方法来定义目标。或者换一种说法,给你的团队更长的时间来修复低优先级的问题,但要确保高优先级的问题得到快速修复。
在一个新的团队或一个较小的项目中,你也许可以采用 "立即修复一切 "的方法,但当事情变得越来越老,越来越复杂时,这将变得越来越困难。开发人员会开始抱怨,事情会开始花更长时间。
下面展示的是一种更细致的方法。具体的分类和目标可以根据你的组织进行调整。
1.对你的应用程序的风险状况进行分类。
首先对你的应用程序的风险状况进行分类。具体来说, 考虑它们如何被访问,以及它们处理什么样的数据。
2.根据CVSS评分和应用程序的风险分类来定义目标。
目标是专注于构成最大风险的问题,并在其余问题上节省时间和精力。通过扩展低优先级问题的目标,我们可以通过规模经济来节约,并让开源社区有更多时间来开发修复程序。一次性升级、测试和发布一个应用程序,同时修复多个漏洞,要比在每个漏洞出现时都这样做更具成本效益。
3.自动报告
手动跟踪上述一切是不可能的。值得庆幸的是,大多数扫描工具都提供API,可以用来生成自动报告。
4.4. 授权工程师 "放弃 "不构成风险的漏洞。
工程师应该被赋予自由裁量权,以放弃那些由于上述原因之一而不构成风险的漏洞。
重点不一定是无限期地推迟升级,而是要等到适合团队的时间,就像其他的依赖性一样。在这期间,这些漏洞不应该出现在报告中。
这看起来可能会被滥用,或者你可能觉得不能信任开发人员来做这些判断。现实情况是,你每天已经把许多安全问题委托给你的工程师了。可以说,这项任务比你的工程师已经承担的许多其他安全相关的责任更不容易出错。
你还可以确保豁免权得到同行的审查,甚至与你的安全团队一起增加一个定期的 "豁免权审查 "会议,以帮助减轻这种风险。
5.评估那些剩余项目的努力和风险
对于那些剩余的项目,需要决定是否值得花时间做必要的改变,或者组织是否可以接受这个风险。
这个决定应该由工程师来评估风险和补救的努力,并由产品负责人或同等人员来做出优先决定。你可能希望这个决定也由你的安全团队批准,这取决于你的组织。
例如,如果一个不处理敏感数据的内部应用有一个低严重性的CVE,需要花费数周的开发工作来补救,也许这不是对组织资源的最佳利用?
6.为其他团队提供文件
一旦你决定在特定的应用程序上进行特定的升级,重要的是要有一个商定和沟通的机制来记录任何学习成果。
把它放在一起
现在你有了所有的拼图,你可以把一个流程放在一起,让你的团队遵循。
我们发现这个过程大大降低了团队的开销,同时保留了一个可接受的风险暴露水平。 这个流程可以与你的团队使用的现有交付流程整合。一旦一个应用程序有一个接近目标的漏洞,就可以提出一个票据,而团队在其他时间不需要担心任何问题。
结论
为你的组织解决这个问题不是小事,但它是可以实现的。今天的现实是,许多团队和组织在涉及到漏洞时,根本不在状态,而且/或者完全不知所措。通过采取更务实的方法,你可以将所需的努力减少到可管理的水平,并重新回到提供价值上。

