零
基础概念
在软件安全的语境中,漏洞是指软件中的具体缺陷或疏忽,能够被攻击者利用并执行一些恶意行为,例如泄露或修改敏感信息、干扰或销毁系统、接管计算机系统或程序权限等等。
安全漏洞(Vulnerability)与大众熟悉的软件缺陷(Bug)有所不同。软件缺陷是程序中的错误、失误或疏忽,导致意外或者不希望发生的情况(即应当发生却没发生,结婚十年一直没怀上)。笼统的说,安全漏洞可以看作是软件缺陷的一个子集,是一种特殊的软件缺陷:恶意用户可以利用它发动攻击,影响软件及其支持系统。几乎所有的安全漏洞都可以被看作软件缺陷,但只有一些软件缺陷最终成为严格意义上的安全漏洞,意味着这类缺陷必须具有安全属性相关的影响,允许攻击者执行通常无法进行的操作(即不应当发生却发生了,外出三年老婆怀上了)。
在谈论安全性与可靠性时,有的人会说安全性是可靠性的一个重要组成部分。虽然这种说法不太普遍,但一个可靠性高的软件基本上是缺陷较少的软件:
它在用户使用过程中很少出现故障,能够妥善处理各种异常情况,并且采用“防御性编程(Defensive Programming)”策略(如减少缺陷、增加代码可读性、设置异常处理)来应对不稳定的执行环境和错误的输入数据。
而一个安全性高的软件则更像是一个健壮的软件:
它能够抵御那些企图通过篡改环境和输入数据来利用软件漏洞以达到恶意目的的攻击。
因此,软件的安全性和可靠性追求的是共同的目标,这个目标需要通过采用专注于消除各种软件缺陷的开发策略来达成。但安全漏洞与软件缺陷并非总是可以清晰界定。比如:一个允许用户编辑其不应有权访问的关键系统文件的程序,可能在规范和设计层面上没有问题,但这无疑是一个严重的安全漏洞。
壹
安全策略
系统的安全性本质上是由安全策略(policy)、安全模型(model)、安全控制(control)构成,其中最重要的是安全策略,策略定义了系统应当具备的安全标准以及允许接受的安全损害。因此,任何对软件系统安全性的侵害都可以被视为是对其安全策略的破坏。
对于一个由软件、用户和资源构成的系统,安全策略本质上是一系列关于允许和禁止行为的规则。例如,“未经验证的用户不得访问系统的A服务。”如果未经验证的用户能够访问该服务,则违反了既定的安全策略。
每个软件系统都应该具备一定的安全策略。这些策略可能是正式的文件集合,也可能是用户对系统合理行为非正式的期望。对于许多软件系统,即便没有明确的书面规定或描述,人们通常也能认识到哪些行为可能构成安全风险(比如用户可以看到其他用户的购物记录)。因此,“安全策略”通常指的是用户对于软件系统行为的允许和禁止方面的共识。
贰
安全期望
为了深入理解软件安全,我们需要考虑用户对软件安全可能抱有的期望。这有助于我们识别哪些行为可能被视为安全违规,或破坏了安全属性。安全属性至少涵盖三个基础部分:机密性、完整性和可用性。
机密性:机密性关注的是信息的保护,确保信息能够持续保持私密状态,包括从国家级别的情报秘密、企业商业机密到个人敏感信息等各种敏感数据。
完整性:完整性关乎数据的可信度和准确性。人们期望软件能够防止数据被不当篡改,确保数据的来源和内容都是可信的。
可用性:可用性涉及访问和使用信息和资源的能力,通常指的是用户对系统能够持续的、正常的访问的期望。导致软件崩溃或其他造成用户不可访问的问题,包括利用特定输入或环境导致的程序中断,以及耗尽软件系统资源(如CPU、磁盘空间或网络带宽)的攻击都属于破坏可用性。
叁
代码审计的必要性
用户的默认期望是软件供应商能够保证其产品的安全性,比如购房、购车的时候不会特别注意和强调房子的抗震能力或车辆的防侧翻能力,但实际使用中会默认其产品具备符合用户普遍预期的安全性。然而,现实中供应商往往没有提供足够的质量保证,查看大多数商业软件附带的最终用户许可协议(EULA)便可知。尽管如此,为了保证公司的长远利益,供应商通常还是会实施一定的质量保证措施,这些措施通常集中在市场直接关注的部分,如功能、用户体验和系统稳定性,但这种做法往往导致安全问题被忽视或边缘化。
现在,安全意识的提升促使许多供应商采纳了更严格的业务和开发流程进行安全测试,比如自动代码扫描、安全漏洞扫描、渗透测试、手动代码审计(Code Review)等安全测试方法已经成为行业标准。
除了公司开发内部软件的明显情况外,代码安全审计在其他多种情况下也具有重要意义。
如上所见,代码审核在多种情境下都显得至关重要。尽管对于具备这些技能的专业人员存在显著需求,但很少有人接受过相应的专业训练或培训,并且拥有高标准执行代码审计的实践、实战经验。
肆
代码审计与黑盒测试
黑盒测试是一种通过操作其暴露的接口来评估软件系统的方法。通常,此过程涉及构造特定的输入,这些输入可能会导致应用程序执行某些意外行为,例如软件崩溃或暴露敏感信息。模糊测试是黑盒测试的一种常见方法,使用模糊测试工具时无需手动测试每种情况,只需运行工具(可能需要对配置进行一些修改)并记录结果即可。
对应用程序进行黑盒测试的优势在于测试速度快,几乎可以立即获取结果。然而,黑盒测试也存在一些重大缺点。黑盒测试是将大量数据输入应用程序,希望它执行一些不应该执行的操作。由于不知道应用程序如何处理数据,可能存在代码路径无法被扫描到,存在未触发的路径。例如,假设请求的查询字符串中存在特定关键字,则它可能具有某些内部功能。如下图中的示例代码:
上述代码在查询字符串时具有特殊的行为:如果查询字符串包含”mode=”序列,服务器则将设置环境变量MODE的值为xxx。然而,这段代码对sprintf()函数的不慎使用会导致缓冲区溢出。
上述示例可以通过检查代码立即发现问题,而黑盒测试或模糊测试工具可能会漏掉这个漏洞。这就是为什么代码审核很重要。
伍
代码审计和SDLC
鉴于应用程序可能遭受恶意利用的风险,安全评估显得至关重要,确定评估的最佳时机也同样关键。通常,在开发生命周期(SDLC)的任何阶段,都应该开展安全评估和测试。
评估和测试的成本取决于选择进行审核的时间和方法,以及发现和修复漏洞可能带来的成本差异:
可行性研究:此阶段确定项目应满足的需求,确定需求在技术和财务上的可行性。
需求:在此阶段,将对项目需求进行更深入的研究,并建立项目目标。
设计:设计解决方案并就系统如何在技术上实现做出决策。
编码:此阶段时根据前一阶段的设计进行具体的代码开发。
测试:通过质量测试方法尽可能捕获软件中的任何错误。
运维:软件投入使用后根据用户反馈进行修订、更新和更正。
所有软件的开发过程和开发方法都在某种程度上借鉴了软件开发生命周期(SDLC)模型。传统的瀑布模型倾向于严格遵循这一模型,只经历一次生命周期迭代,相较之下,敏捷开发则倾向于通过重复迭代SDLC的各个阶段来逐步完善应用程序。
陆
软件安全漏洞类别
漏洞类别是对具有相似特征或共同模式的软件缺陷进行分组的一种方法,也是一种帮助理解和交流软件缺陷的心理工具。没有完美无缺的分类体系能够将漏洞精确地划分到互不重叠的类别中,一个漏洞可能同时属于多个类别,具体的类别归属取决于安全人员的主观意识。
在界定漏洞的类别时,可以从软件开发生命周期(SDLC)各阶段的看出关键差异。通常漏洞可以分为设计漏洞和实现漏洞,设计漏洞主要出现在SDLC的前三个阶段,涉及软件设计、系统架构和规范中的缺陷,实现漏洞则发生在SDLC的后两个阶段,通常指软件实际开发和构建过程中的纯粹的技术缺陷。此外在软件运行过程中,还存在环境漏洞或操作漏洞,这类漏洞与软件在特定环境中的部署和配置有关。
设计漏洞
设计漏洞是由于软件设计的基本错误或疏忽导致的问题。这类漏洞使得软件即使按照设计目的正常运行也存在安全风险,因为其根源在于错误的设计逻辑或设计方法。本质上,设计漏洞源于设计人员在设计过程中对程序运行流程、运行环境、使用方法的错误假设,或者是未充分考虑程序引入的组件存在的安全漏洞和风险。设计漏洞也常被称为架构缺陷,因此此类漏洞的修复成本和代价在所有类型漏洞中最高,甚至会面临无法修复或者没办法修复的情况。
实现漏洞
在实现漏洞中,代码通常会按照预期执行其功能,但执行的方式可能存在安全风险。这些问题主要出现在软件开发生命周期(SDLC)的开发阶段,但往往也会延续到集成和测试阶段。当开发过程与设计阶段设定的技术规范出现偏差时,就可能出现这类漏洞。在大多数情况下,可利用的安全漏洞是由于技术工具、构建软件的平台以及编程语言环境的细微差异所引起的。
环境(操作)漏洞
环境(操作)漏洞是指软件在特定环境下的操作过程和一般使用中出现的安全问题。这类漏洞的关键特征是它们并不直接存在于软件的源代码中,相反,它们源于软件与其运行环境的交互方式或者人员操作的方式。具体来说,环境漏洞可能涉及软件配置问题、支持软件和硬件以及系统周围的自动化和手动流程。此外,环境漏洞也包括对用户的社会工程学攻击和物理攻击。这些问题主要在软件开发生命周期(SDLC)的运行和维护阶段出现。
柒
漏洞类别区分
就SDLC而言,设计漏洞和实现漏洞之间的区别看似简单,但区分起来并不那么容易。许多实施漏洞也可以被解释为设计阶段没有充分预见或解决问题的情况。程序员在实现过程中会涉及大量的组件,包括类、函数、网络协议、虚拟机,或者可能是一系列巧妙、复杂的代码结构。
同样,环境漏洞与实现或设计漏洞之间的界限也不是非常清晰。例如,如果一个程序由于环境问题而以不安全的方式运行,这也可以被视为设计或实现的问题。理想情况下,应用程序的开发应该使其不易受到这些环境因素的影响。
因此,设计、实现和环境漏洞的定义并非是严格划分的,它们之间存在很大的解释空间和重叠区域。
捌
软件漏洞的成因
控制流与数据流是软件安全的两大关键要素。众多软件漏洞往往是程序处理恶意数据时出现的意外行为所触发。
大多数漏洞在利用时,攻击者通过各种方式将恶意数据注入系统,以触发漏洞的利用。然而,这些数据并不总是直接来源于用户的输入,它们可能采取更为复杂的路径进入系统。这些恶意数据可能源自众多不同的来源,并通过多种接口进入系统。在到达能够触发可利用条件的关键位置之前,它们可能会穿越系统的多个模块,并在这个过程中经历多次转换。
因此,在分析和评审一个软件系统时,必须关注的核心属性是整个系统中各个模块、组件间的数据流,掌握数据流的全貌对于确保系统的安全性和防御潜在的攻击至关重要。
在软件系统中,不同组件之间的信任关系是安全分析的关键方面。这些信任关系是数据流的基础,因为组件间的信任级别往往决定了它们在交换数据时所需的验证程度。
设计和开发人员通常假定某些组件或接口是可信的,这意味着他们认为这些组件不会受到恶意影响,从而在处理这些组件的数据和行为时按照默认其安全。然而,一旦这种信任被错误赋予,攻击者就可能利用受信任的实体,导致系统安全性的连锁反应。
在评估信任关系时,信任的传递性同样不容忽视。例如,如果软件系统信任某个外部组件,而该组件又信任某个网络,那么您的系统实际上也间接信任该网络。如果这种信任链中的任何一环信任度不足,整个系统都可能成为攻击的牺牲品,从而将您的软件置于风险之中。
探索软件缺陷的另一种有效途径是将其视为开发者和设计者在构建软件时做出的未经证实的假设。这些假设可能涉及软件的多个层面,包括输入数据的有效性和结构、辅助程序的安全性、操作环境的安全性、攻击者和用户的技能,以及特定API调用或编程语言特性的行为和细微差别。
不恰当的假设与错误信任的概念紧密相连。实际上,对某个组件的过度信任可以被视为对其做出了不切实际的假设。
以下为这种假设和过度信任的具体问题类型:
– 输入 –
如前所述,软件漏洞很大程度上源于攻击者向系统注入的恶意数据。这些数据之所以能够造成麻烦,部分原因在于软件往往对其通信伙伴持有过度的信任,并对数据的来源及其内容做出了不切实际的假设。
在编写处理数据的代码时,开发人员常常对数据提供者(无论是用户还是其他软件组件)持有一定的预期。例如,在处理用户输入时,开发人员可能会假设用户不会输入包含5,000个字符及不可打印符号的字符。同样,当编写两个软件组件之间的接口代码时,他们可能默认输入数据格式将是正确的,而没有考虑到程序可能会处理负长度的二进制记录或接收高达数十亿字节的网络请求。
然而,攻击者在审视输入处理代码时,会尝试探索每一个可能的输入,特别是那些可能导致程序状态不一致或意外的输入。他们致力于测试软件的每一个接口,寻找开发人员所做的任何潜在假设。对于攻击者来说,任何能够提供意外输入的机会都是极具价值的,因为这些输入可能会在后续处理中引发开发人员未曾预料的微妙错误。
通常,如果攻击者能够对软件的运行时属性进行意外的修改,他们就可能找到方法来放大这种影响,从而利用漏洞对程序造成更严重的后果。
– 接口 –
接口是软件组件之间以及与外部世界交互的关键机制。众多漏洞的根源在于开发人员对这些接口的安全特性理解不足,进而错误地假设只有可信节点会与之交互。当程序组件可以通过网络或本地计算机上的多种途径被访问时,攻击者就可能直接与这些组件交互,并注入恶意输入。如果组件在编写时假设其通信对方是可信的,那么应用程序可能会以易受攻击的方式处理这些输入。
加剧这一漏洞的是开发人员常常对攻击者访问接口的难度做出误判,因此对那些缺乏足够安全措施的接口赋予了过多的信任。例如,开发人员可能认为他们的应用程序非常安全,因为他们使用了包含自定义加密的复杂且专有的网络协议。他们可能错误地假设攻击者不太可能独立构建客户端和加密层,并以非预期的方式操纵协议。然而,这种假设往往是站不住脚的,因为许多攻击者乐于对专有协议进行逆向工程,寻找可以利用的安全漏洞。
总的来说,开发人员可能会因为以下几个原因而对接口的安全性产生疑虑:
1、不充分的保护措施:开发人员选择的接口暴露方式可能无法提供足够的安全防护,从而无法有效抵御外部攻击。
2、错误的使用或配置:虽然开发人员可能选择了可靠的接口公开方法,比如操作系统服务,但他们可能在接口的使用或配置上出现了错误。此外,攻击者还可能利用基础平台中的漏洞来非预期地控制该接口。
3、对访问难度的误判:开发人员可能认为某个接口对于攻击者来说访问难度过高,这种假设往往存在风险。攻击者往往会找到方法来克服看似难以访问的接口。
– 环境攻击 –
软件系统并非孤立运行,而是依赖于一个由多种组件构成的计算环境,这些组件通常包括操作系统、硬件架构、网络、文件系统、数据库以及用户等。虽然许多软件漏洞源于对恶意数据的处理不当,但还有一部分缺陷是由于攻击者操控软件的底层环境而触发的。
这些缺陷可被视为开发人员对软件运行所依赖的环境做出的不切实际的假设所导致。每种支持技术,如操作系统、网络或数据库,都有其最佳实践和复杂细节。如果应用开发人员对这些技术可能存在的安全问题缺乏全面了解,他们就可能犯下导致安全漏洞的错误。
一个典型的漏洞类型是竞争条件,这类漏洞的触发并非源自攻击者提供的数据,而是由于攻击者针对程序运行时环境的操作,导致程序与操作系统之间的交互出现了非预期的且不安全的方式。
– 特殊条件 –
特殊条件漏洞通常与异常情况的处理紧密相关,并交织着数据和环境漏洞。这类漏洞的基本特征是攻击者通过外部手段引发程序正常控制流的非预期变化。这种变化可能需要程序的非同步中断,例如信号传递,或者通过耗尽全局系统资源来故意触发程序中的故障点。
作者:
2024年3月19日
洞源实验室