0.2 基本安全性原则

本章的其余部分简要概述了基本的安全性概念和术语,对于开发团队和安全从业人员而言,熟悉这些概念和术语是至关重要的。如果你想了解详细内容,请查看本章和本书中提供的参考资料。

熟悉这些原理和术语是在安全领域中进行学习的基础。

0.2.1 基本概念和术语

图0-2突出显示了系统安全中的关键概念。理解它们是理解为什么威胁建模对于安全系统设计至关重要的关键。

图0-2:安全术语的关系

一个系统包含的资产,有其用户依赖的功能,以及系统接收、存储、操作或传输的数据。系统的功能可能存在瑕疵(即缺陷)。如果这些缺陷是可利用的,容易受到外部影响,则称为漏洞,利用它们可能会使系统的操作和数据面临暴露的风险。行为者(系统外部的个人或进程)可能会恶意利用漏洞。一些熟练的攻击者有能力改变条件,以创造机会利用漏洞进行攻击。行为者在这种情况下会创建威胁事件,并通过该事件对系统产生特定的影响(例如,窃取数据或导致功能异常)。

功能和数据的结合在系统中创造了价值,而造成威胁的敌人则否定了该价值,这构成了风险的基础。风险可以以一定的概率被安全管理措施抵消,安全管理措施涵盖系统的功能能力以及设计和构建系统的团队的运营和组织行为。

每个概念和术语都需要附加的解释才能更有意义:

缺陷

缺陷是一种潜在的瑕疵,会修改行为或功能(从而导致错误的行为),允许未经验证或错误的数据访问。系统设计的缺陷源于未能遵循最佳实践、标准或惯例,给系统带来了某些不良影响。幸运的是,对于威胁建模人员(和开发团队)来说,社区倡议的通用缺陷列表(Common Weakness Enumeration,CWE)创建了安全性缺陷的开放分类法,在研究系统设计时可以参考该分类法。

可利用性

可利用性是对攻击者利用缺陷造成损害的容易程度的度量。换句话说,可利用性是缺陷对外部影响的暴露量[12]

漏洞

当缺陷是可利用的(本地授权上下文之外的可利用性为非零)时,称为漏洞。漏洞为具有恶意意图的攻击者提供了一种对系统造成某种损害的手段。系统中存在但以前未发现的漏洞称为零日(zero-day)漏洞。零日漏洞并不比其他漏洞更危险,但它很特殊,因为它可能没有被修复,因此被利用的可能性较高。与缺陷一样,社区极力创建了漏洞的分类方法,并在CVE数据库中进行了编码。

严重性

缺陷会给系统及其资产(功能和数据)带来不良的影响,此类问题造成的潜在损害和“爆炸半径”被描述为瑕疵的严重程度。工程领域的人可能熟悉这个名词。根据定义,漏洞是可利用的缺陷,至少与潜在缺陷一样严重,而且缺陷的严重性通常会更高,因为它很容易被利用。严重性的计算方法参见0.2.2节。

不幸的是,确定缺陷严重性的过程并不容易。如果发现缺陷时无法度量缺陷造成影响的大小,那么问题的严重性如何计算?如果以后确定缺陷是暴露的,甚至由于系统设计或实现的更改而变得更糟,会发生什么?这些问题很难回答,我们稍后在介绍风险概念时将对此进行介绍。

影响

如果缺陷或漏洞被利用,则会对系统造成某种影响,例如破坏功能或暴露数据。在对问题的严重程度进行评级时,你需要评估影响程度,以度量成功利用后功能和数据的潜在损失。

行为者

在描述系统时,行为者是与系统关联的任何个人,例如用户或攻击者。具有恶意意图的行为者有时被称为敌人。

威胁

威胁是攻击者利用漏洞以特定方式对系统造成负面影响的非零概率结果。

威胁事件

威胁事件是指敌人尝试(不论成功与否)利用具有预期目标或结果的漏洞。

损失

当敌人引发的威胁事件导致一个(或多个)影响作用于系统的功能和数据时,就会发生损失:

·行为者可以破坏系统数据的机密性,造成敏感信息或私人信息泄露。

·行为者可以修改功能的接口、更改功能的行为或更改数据的内容与来源。

·行为者可以临时或永久地阻止授权实体访问功能或数据。

损失是按照资产或价值量来描述的。

风险

风险将潜在被利用目标的价值与造成影响的可能程度相结合。价值与系统或信息所有者以及攻击者有关。你应该使用风险来告知问题的优先级,并决定是否解决该问题。容易被利用的严重漏洞以及可能导致重大损害的漏洞应该优先解决。

0.2.2 计算严重性或风险

严重性(成功利用漏洞可能造成的损害程度)和风险(威胁事件发起的可能性和由于利用漏洞而成功产生负面影响的可能性的组合)可以通过公式确定。这些公式并不完美,但使用它们可以提供一致性。今天,存在许多用于确定严重性或风险的方法,并且某些威胁建模方法使用其他的风险评分方法(在本书中未介绍)。本章介绍了三种常用的方法(一种用于测量严重性,两种用于测量风险)。

CVSS(测量严重性)

通用漏洞评分系统(Common Vulnerability Scoring System,CVSS)现在是3.1版本,属于事件响应与安全团队论坛(Forum of Incident Response and Security Teams, FIRST)的产品。

CVSS的取值为0.0~10.0,它使你可以识别严重性的组成部分。该方法基于成功利用漏洞的可能性以及潜在影响(或破坏)的度量进行计算。如图0-3所示,在计算方法中设置了8个指标,用以确定严重等级。

图0-3:CVSS的指标、向量和分数

成功利用漏洞的可能性是根据给定数字评级的特定指标来度量的,这将得出一个称为可利用性子分数的值。漏洞影响的评估方法类似(使用不同的指标),称为影响子分数。将两个子分数加在一起得出总的基本分数。

请记住,CVSS并不是度量风险,而是度量严重性。CVSS可以告诉你攻击者成功利用受影响的系统的漏洞的可能性以及可能造成的损害。但是它无法指出攻击者何时或是否将尝试利用此漏洞,也无法告诉你受影响资源的价值或解决漏洞的成本。发起攻击的可能性、系统或功能的价值以及缓解漏洞的成本驱动了漏洞风险的计算。依靠原始严重性是传达缺陷信息的好方法,但它在管理风险方面非常不完善。

DREAD(测量风险)

DREAD是一种较旧[13],但非常重要的一种方法,用于理解安全隐患的风险。DREAD是STRIDE(详见第3章)威胁建模方法的合作伙伴。

DREAD是以下各项的首字母缩写:

损害Damage

如果敌人发动攻击,他们能造成多大的破坏?

复现性Reproducibility

潜在的攻击是否容易复现(在方法和效果上)?

可利用性Exploitability

成功进行一次攻击有多容易?

受影响的用户Affected users

可能会影响多少比例的用户?

可发现性Discoverability

如果敌人还不知道潜在的攻击机会,那么他们发现它的可能性是多少?

DREAD是一个过程,用于(通过敌人的攻击向量)记录对系统的潜在攻击的特征,并得出可以与其他攻击场景或威胁向量的值进行比较的值。通过考虑攻击者利用漏洞的特征并在各个维度(例如,D,R,E,A,D)上分别针对低影响力、中影响力和高影响力问题分配一个分数,可以计算出任何给定攻击场景(安全漏洞和敌人的组合)的风险值。

每个维度的总分决定了总体风险值。例如,特定系统中的任意安全问题可能具有[D=3,R=1,E=1,A=3,D=2]分数,总风险值为10。你可以将此风险值与针对该特定系统确定的其他风险进行比较。但是,尝试将此值与其他系统中的值进行比较不太有用。

风险量化的FAIR方法(测量风险)

信息风险因素分析(Factor Analysis of Information Risk,FAIR)方法在执行类型中越来越受欢迎,因为它提供了正确的粒度级别和更多的特异性以实现更有效的决策。FAIR由Open Group发布,并包含在ISO/IEC 27005:2018中。

DREAD是定性风险计算的一个示例。FAIR是一项国际标准,用于量化风险建模,并通过使用行为者对威胁的价值(硬货币成本和软货币成本)和威胁实现概率(或发生威胁事件)的度量来理解威胁对资产的影响。使用这些量化值可以向你的管理层和业务负责人描述系统中识别出的风险对业务产生的财务影响,并将它们与防御威胁事件的成本进行比较。适当的风险管理实践表明,防御成本不应超过资产的价值或资产的潜在损失,这也称为“5美元笔上了50美元锁”范式。

FAIR既彻底准确又复杂,并且需要专业知识才能正确地执行计算和模拟。这不是你想在威胁建模审查会议中进行的工作,也不是想与安全主题专家(SME)联系的事情。安全专家擅长发现缺陷和威胁,而不是对财务影响评估进行建模。如果你打算采用FAIR,则雇用具有计算方法和财务建模能力的人员,或者找到一种可以为你进行艰深数学运算的工具,将是更好的做法。

0.2.3 核心属性

机密性、完整性和可用性这三个核心属性是安全性的基础。某人想知道某物是否安全时,通过这些属性以及判断它们是否完整就可以确定结果。这些属性支持一个关键目标:可信度。此外,第4个和第5个属性(隐私性和安全性)与前三个属性有关,但侧重点稍有不同。

机密性

一个系统只有在保证只有那些拥有适当权限的用户才能访问相应权限的数据时,才具有机密性。不阻止未经授权的访问的系统无法保护机密性[14]

完整性

当数据或操作的真实性可被验证,未经授权的活动未修改数据或功能,或没有使数据或功能不真实时,可以说具备完整性[15]

可用性

可用性意味着经过授权的行为者能够在需要或希望这样做时访问系统功能或数据。在某些情况下,由于用户与系统运营商之间的合同或协议(例如,网站因定期维护而关闭),系统的数据可能无法使用。如果系统由于敌人的恶意操作而无法使用,则可用性将受到损害[16]

隐私性

机密性是指对与他人共享的私人信息的控制访问,而隐私性是指不将该信息暴露给未经授权的第三方的权利。很多时候人们谈论机密性时,确实希望获得隐私,尽管这些术语经常互换使用,但它们并不是同一个概念。你可能会说机密性是隐私性的前提条件,例如,如果系统无法保证其存储的数据的机密性,则该系统将永远无法为用户提供隐私性。

安全性

安全性是“免于因财产或环境的损害而直接或间接造成人身伤害或人身健康损害的不可接受风险”。[17]当然,为了满足安全性要求,它必须以可预测的方式运行。这意味着它必须至少保证完整性和可用性。

0.2.4 基本控制

以下控制或功能行为和能力支持高安全度系统的开发。

身份鉴别

必须为系统中的行为者分配一个对系统有意义的唯一标识符。标识符也应对将使用该身份的个人或进程有意义(例如,身份验证子系统)。

行为者是系统中影响系统及其功能或希望获得对系统数据访问权限的任何对象(包括用户、系统账户和流程)。为了支持不同的安全目标,必须授予行为者一个身份,然后才能在该系统上进行操作。该身份必须带有信息,允许系统积极识别行为者,换句话说,允许行为者向系统展示身份证明。在一些公共系统中,还标识了未命名的行为者或用户,这表明他们的具体身份并不重要,但仍会在系统中得到体现。

在许多系统中,访客作为共享账户都是可接受的身份。可能存在其他共享账户,但是应仔细考虑使用共享账户,因为它们缺乏基于个体单独跟踪和控制行为者行为的能力。

身份验证

具有身份的行为者需要向系统证明其身份。通常使用凭证(例如,密码或安全令牌)来证明身份。

所有希望使用该系统的行为者都必须能够令人满意地提供其身份证明,以便目标系统可以验证其是否与正确的行为者通信。身份验证是附加安全功能的前提条件。

授权

一旦行为者通过了身份验证,就可以在系统内为该行为者授予权限,以执行操作或访问功能和数据。授权是上下文相关的,可能是传递性的,双向的或对等的。

身份验证带来了系统的能力,系统能够根据行为者提供的身份证明来指定该行为者的权利。例如,一旦用户通过了系统身份验证并被允许在数据库中执行操作,则仅基于行为者的权限授予对该数据库的访问权限。通常根据读取写入执行等原始操作授予访问权限。控制系统中行为者行为的访问控制方案包括以下内容:

强制访问控制MAC

系统限制了行为者的授权。

自由访问控制DAC

行为者可以定义操作权限。

基于角色的访问控制RBAC

行为者按有意义的“角色”分组,角色在其中定义权限分配。

基于能力的访问控制

授权子系统通过行为者必须请求(并被授予)才能执行操作的令牌来分配权限。

访客账户通常不经过身份验证(没有身份证明),但是可以使用最低级别的功能明确授权这些账户。

日志记录

当行为者(人或进程)执行系统操作时,应记录该事件的日志。这支持可追溯性。在尝试调试系统时,可追溯性很重要。当记录的事件被认为与安全相关时,可追溯性还支持关键任务的能力,例如,入侵检测和预防、取证和证据收集(对于恶意行为者入侵系统的情况)。

审计

行为日志创建记录。审计记录是明确定义的(格式和内容),按时间排序,并且通常是防篡改的(或至少防篡改)。“及时回顾”和了解事件发生的顺序、谁执行了哪些操作、何时执行,以及确定操作是否正确和得到授权,对于安全操作和事件响应活动至关重要。

0.2.5 安全系统的基本设计模式

在设计系统时,应牢记某些安全原则和方法。并非所有原理都适用于你的系统。对你而言,重要的是要考虑它们以确保它们适用于你。

1975年,Jerome Saltzer和Michael Schroeder发表了一篇开创性的文章“The Protection of Information in Computer Systems”[18],尽管自发布以来发生了很大变化,但基本原则仍然适用。我们在本书中讨论的一些基本原理基于Saltzer和Schroeder提出的原理。我们还将向你展示其中一些原则如何以与最初预期不同的方式变得相关。

零信任

系统设计和安全合规性的一种常见方法是“信任,但要验证”或零信任,即为操作(例如设备加入网络,或客户端调用API)承担最佳结果,然后再验证信任关系。在零信任环境中,系统会忽略(或从不建立)任何先前的信任关系,而是在决定建立信任关系之前验证所有内容[19]

零信任也称为完全中介,这个概念听起来非常简单:确保每次访问对象时都会检查对操作的访问权限,并且事先检查该访问操作的权限。换句话说,每次请求访问时,你必须验证行为者是否具有访问对象的正确权限。

John Kindervag于2010年提出了零信任的概念[20],这个概念已普遍应用于网络外围架构的讨论中。作者决定将该概念引入安全性原则中,并认为它也适用于无须修改的应用程序级别的安全决策。

契约式设计

契约式设计与零信任有关,并假定每当客户端调用服务器时,来自该客户端的输入将采用某种固定格式,并且不会偏离该契约。

它类似于锁和钥匙的范例。你的锁仅接受正确的钥匙,而不信任其他任何钥匙。Christoph Kern在“Securing the Tangled Web”[21]一文中解释了Google如何通过设计使用一个本质上安全的API调用库来显著减少应用程序中的跨站点脚本(XSS)漏洞。契约式设计通过确保每次交互都遵循固定的协议来解决零信任问题。

最小权限

这个原则意味着一个操作应该只使用最严格的权限级别来运行,这仍然能够使操作成功。换句话说,在所有层和机制中,请确保你的设计将操作员限制在完成单个操作所需的最低访问级别,仅此而已。

如果不遵循最小权限,应用程序中的漏洞可能会提供对底层操作系统的完全访问权限,并且随之而来的后果是特权用户可以不受限制地访问你的系统和资产。该原则适用于维护授权上下文(例如,操作系统、应用程序、数据库等)的每个系统。

纵深防御

纵深防御使用多方面的分层方法来防御系统及其资产。

在考虑防御系统时,请考虑要保护资产的内容以及攻击者如何尝试访问资产。考虑你可以采取哪些控制措施来限制或阻止敌人的访问(但允许经过适当授权的行为者访问)。你可能会考虑并行或重叠的控制层来减慢攻击者的速度。或者,你可以考虑实施会混淆或积极阻止敌人的功能。

应用于计算机系统的纵深防御示例包括:

·使用锁、防护装置、摄像头和气隙保护特定的工作站。

·在系统和公共互联网之间引入堡垒主机(或防火墙),然后在系统本身中设置端点代理。

·使用多因素身份验证来补充用于身份验证的密码系统,时间间隔在失败的尝试之间成倍增加。

·部署一个蜜罐和假数据库层,有意使用限制优先级的身份验证功能。

任何其他充当“道路障碍”并在复杂性、成本或时间方面使攻击成本更高的因素,都是你进行纵深防御的成功之道。这种评估纵深防御措施的方式与风险管理有关——纵深防御并不意味着不惜一切代价进行防御。在决定花费多少来保护资产与这些资产的感知价值之间进行平衡,这属于风险管理的范围。

保持简单

保持简单就是避免过度地设计系统。随着复杂度的增加,系统运行的不稳定性、维护和其他方面的挑战以及安全控制无效的可能性都会增加[22]

还必须注意避免过度简化(例如,遗漏或忽略重要细节)。这通常发生在输入验证中,因为我们假设上游数据生成器将始终提供有效和安全的数据,并避免(错误地)进行我们的输入验证以简化操作。有关这些期望的更广泛讨论请参阅Brook S. E. Schoenfield关于安全契约的工作[23]。归根结底,干净、简单的设计优于过度设计,简单的设计通常会随着时间的推移提供安全优势,因此应该优先考虑。

没有秘密武器

不要依靠不透明、不公开作为安全手段。即使系统实现的每个细节都已知并已发布,你的系统设计也应具有抵抗攻击的能力。请注意,这并不意味着你需要发布它[24],并且实施操作所依据的数据必须受到保护——这只是意味着你应该假设每个细节都是已知的,而不是依赖于任何保密的方式保护你的资产。如果要保护资产,请使用正确的控制方式——加密或散列。不要指望行为者无法识别或发现你的秘密!

权限分离

它也称为职责分离,这一原则意味着将把系统内部功能或数据的访问分开,这样行为者就不会拥有所有权限。相关概念包括制造者/检查者,其中一个用户(或进程)可以请求进行操作并设置参数,但是需要另一个用户或进程授权后才能进行操作。这意味着单个实体不能不受阻碍或没有监督地执行恶意活动,提高了发生恶意行为的门槛。

考虑人为因素

人类用户被认为是任何系统中最薄弱的环节[25],因此,心理可接受性的概念必须是基本的设计约束。对强大的安全措施感到沮丧的用户将不可避免地想方设法绕过它们。

在开发安全系统时,决定用户可接受多少安全性至关重要。我们采用双因素验证而不是十六因素验证是有原因的。在用户和系统之间设置过多的障碍将发生以下情况之一:

·用户停止使用系统。

·用户找到解决方法来绕过安全措施。

·停止支持安全决策的权力,因为它会影响生产力。

有效的日志记录

安全性不仅可以防止不良事件的发生,而且可以使你知道已发生的事情,并在可能的情况下知道发生了什么。查看发生了什么的能力来自能够有效地记录事件。

但是什么构成有效的日志记录呢?从安全的角度来看,安全分析师需要能够回答三个问题:

·什么人执行了导致事件被记录的特定操作?

·什么时间记录该动作或事件?

·什么功能或数据被进程或用户访问了?

不可否认性与完整性密切相关,它意味着具有一组操作记录来表明谁做了什么,并且每项操作的记录都将完整性作为一个属性来维护。有了这个概念,行为者就不可能声称自己没有执行特定的动作。

知道要记录什么以及如何保护是很重要的,知道不记录什么也至关重要。特别是:

·绝对不应以纯文本形式记录个人身份信息(PII),以保护用户数据的隐私。

·绝对不应记录API或函数调用中包含的敏感内容。

·绝对不应记录加密内容的明文版本。

·绝对不应记录密钥,例如,系统密码或用于解密数据的密钥。

在这里使用常识很重要,但是请注意,要避免将这些日志集成到代码中,这是对开发(主要是调试)需求的持续斗争。必须向开发团队明确:在代码中设置开关来控制是否应记录敏感内容以进行调试是不可接受的。在可部署的生产就绪代码中,绝不应该包含敏感信息的日志记录功能。

故障安全

当系统遇到故障情况时,这一原则意味着不要向潜在的敌人透露太多信息(例如,在日志或用户错误消息中),也不能简单地授予访问权限。

重要的是要理解,故障安全(fail secure)和故障保护(fail safe)之间存在显著差异。故障保护可能会与故障安全的要求相矛盾,因此需要在系统设计中进行协调。当然,在给定情况下,哪一种方法更合适需要具体情况具体分析。归根结底,故障安全意味着即使系统中某个组件或逻辑出现故障,结果也是安全的。

内置而不是借助硬件

安全性、隐私性和可靠性应该是系统的基本属性,任何安全特性都应该从一开始就包含在系统中[26]

这三个属性不应被认为是事后的想法,也不应完全或主要依赖于使用的外部系统组件实现。这种模式的一个很好的例子是安全通信的实现。系统必须在本地支持这一点,即应该设计为支持传输层安全性(Transport Layer Security, TLS)或用于保护传输中数据机密性的类似方法。依靠用户安装专用的硬件系统来实现端到端通信安全性意味着,如果用户不这样做,则通信将不受保护,并且可能被恶意行为者访问。在系统安全方面,不要假设用户会代表你采取行动。