4.2

查看英文版

4.2 应用安全

概述与动机

应用安全是抽象威胁与具体代码相遇的地方。大多数登上头条的数据泄露事件,最终都能追溯到一个应用层的缺陷:一次注入攻击、一个破损的身份验证流程、一个暴露的密钥,或者一个被攻陷的依赖项。对于交付众多服务的大型团队来说,难点不在于知道这些缺陷存在,而在于如何在一个由成千上万双手、历经多年编写而成的庞大代码库中,持续一致地防范它们。

对企业而言,应用安全关乎客户信任和监管义务。登录流程或支付路径中的一个缺陷,可能引发欺诈、罚款和强制性的数据泄露披露。政府系统面临同样的技术风险,而数据的风险等级更高:福利资格、税务记录、刑事司法数据和国家基础设施。在这两种场景中,应用都是前门,而攻击者会持续不断、自动化地对其进行试探。

本章涵盖让应用保持韧性的各项实践:了解并防御常见的漏洞类别、验证输入并编码输出、正确实现身份验证和授权、管理密钥,以及保护软件供应链()它日益决定着你真实的攻击面。

另请参见: 第 4.1 章(安全基础、威胁建模与安全开发生命周期)、第 10.3 章(开源供应链与许可)以及第 10.2 章(SBOM、风险与保证)。

关键原则

  • 永远不要信任输入。 把所有跨越信任边界的数据都当作恶意的,直到验证通过为止。
  • 安全的默认设置。 安全的路径应当是省事的路径;不安全的行为应当需要刻意的、显而易见的额外努力。
  • 失败即关闭。 当一项安全检查无法完成时,拒绝访问,而不是放行。
  • 应用层的纵深防御。 结合验证、编码、参数化和框架自带的防护,不要只依赖其中一种。
  • 身份和令牌的最小权限。 严格限定凭证的作用范围,并让它们尽快过期。
  • 你的依赖项就是你的代码。 你要对自己所交付的一切负责,包括第三方和开源组件的安全性。
  • 标准优于临场发挥。 使用经过审核的框架,例如 OWASP(开放式全球应用安全项目)的 ASVS,而不是自行发明安全控制措施。

建议

了解并防御 OWASP 十大风险,用 ASVS 验证

OWASP 十大风险,是业界公认的、关于最关键 Web 应用风险的基线清单:失效的访问控制、加密失败、注入攻击、不安全的设计、安全配置错误、存在漏洞的组件、身份验证失败、数据完整性失败、日志记录失败,以及服务器端请求伪造。把它当作每一位工程师都必须掌握的必修知识,而不仅仅是一份归档备查的合规参考资料。

要获得一套严谨、可测试的标准,请采用 OWASP 应用安全验证标准(ASVS)。ASVS 在三个保证级别上定义了安全要求,为你提供了具体的、可审计的控制措施,用于设计和测试。根据每个应用的风险,选择合适的级别,并据此进行验证。

验证输入,编码输出

注入类缺陷之所以始终名列最具破坏性的漏洞之列,恰恰是因为它太容易被引入了。用分层的控制措施来防御:

  • 依据严格的允许清单验证输入(预期的类型、长度、格式、范围)。能拒绝就拒绝,而不是去做净化处理。
  • 对所有数据库访问使用参数化查询和预编译语句;绝不要通过字符串拼接来构造 SQL。正确使用安全的查询构建器和 ORM(对象关系映射器)。
  • 按上下文编码输出。 HTML、HTML 属性、JavaScript、URL 和 CSS 各自需要不同的编码方式。依赖框架的自动转义功能,并理解它的局限。
  • 防范跨站脚本攻击(XSS),用输出编码作为第一层,再加上一套强健的内容安全策略作为第二层。
  • 防范命令注入和模板注入,避免使用不可信数据来执行 shell 命令,并使用无逻辑或沙箱化的模板引擎。

正确实现身份验证和授权

身份验证证明用户是谁。授权决定他们可以做什么。这两者都经常出错,所以务必把它们做对。

  • 优先采用成熟的协议:用 OAuth 2.0 做委托授权,用 OpenID Connect(OIDC) 做身份验证。不要从零开始构建这些机制。
  • 强制启用多因素身份验证(MFA),尤其是对特权和管理性访问。
  • 只以加盐哈希的形式存储密码,使用现代的、计算缓慢且需要大量内存的算法(例如 Argon2 或 bcrypt)。绝不要存储或记录明文凭证。
  • 谨慎管理会话:生成加密强度足够的令牌,设置 secure 和 HttpOnly cookie 标志,在权限变更时轮换令牌,并让闲置会话过期。
  • 对每一个请求都在服务器端强制执行授权检查,核实已通过身份验证的主体拥有或有权访问这个特定资源。破损的对象级授权(通过改变 ID 访问另一个用户的记录)是最常见、也最严重的 API 缺陷之一。
  • 在可行的情况下,将授权逻辑集中管理,让策略保持一致、可审计。

管理密钥并轮换密钥

源代码中硬编码的密钥,是数据泄露长期存在的一个原因。围绕密钥管理建立起一套自律的习惯:

  • 把密钥存储在专用的密钥管理器或密钥库中,绝不要放在源代码、配置文件,或者提交到版本控制中的环境变量里。
  • 自动扫描提交记录和代码仓库,找出泄漏的密钥,并阻止引入这些密钥的合并请求。
  • 定期轮换密钥和凭证,一旦怀疑发生泄漏就立即轮换。优先使用短期有效、自动签发的凭证,而不是长期静态的凭证。
  • 对每一个密钥都应用最小权限原则:将其作用范围严格限定为它实际所需的内容。
  • 对静态和传输中的密钥都进行加密,并审计对它们的访问。

保护软件供应链

现代应用大多是由第三方组件组装而成的,这使得供应链成为一个主要的攻击面。

  • 为每一个应用维护一份软件物料清单(SBOM),这样你才能确切知道自己交付了什么,并在新漏洞出现时能迅速响应。
  • 持续扫描依赖项(软件成分分析,Software Composition Analysis,简称 SCA),及时修复已知存在漏洞的组件。
  • 固定并验证依赖项的版本;使用锁定文件和可信的注册表。
  • 采用 SLSA(软件工件供应链级别)来提升构建完整性,并生成描述工件构建方式的来源证明(provenance)证明文件。
  • 对工件进行签名,并在部署前验证签名,这样你才能确信运行的正是你构建出的东西。
  • 保护构建系统本身;一条被攻陷的 CI 流水线,可能会将恶意代码注入到每一个下游消费者中。

权衡:优点与缺点

决策优点缺点
购买/采用身份提供方(OIDC)久经考验,内置 MFA,需要自己维护安全的代码更少依赖厂商,集成工作量,有成本
自建身份验证完全掌控,无外部依赖极容易出错,维护成本高
严格的允许清单验证能挡住整类漏洞可能破坏合法的边缘用例,前期工作量更大
短期有效的凭证泄露窗口小,可自动吊销需要健壮的签发基础设施
积极的依赖项更新已知漏洞更少变动频繁,可能引入破坏性变更,测试负担重
SBOM + 签名 + 来源证明事件响应速度快,信任可验证需要工具和流程投入,需要文化变革

这里反复出现的权衡是前期的严谨性与持续存在的风险敞口之间的取舍。自建身份验证或跳过依赖项的日常维护,在今天感觉更快,却会在日后付出巨大的代价。采用经过审核的标准和自动化的供应链控制,虽然现在要花费精力,但它把一种无边界、不可预测的风险,转变成了一种可管理、有边界的风险。对大型团队而言,最重要的是自动化的乘数效应:一个在标准化模板中写一次的控制措施,能保护每一个使用它的服务。

与团队讨论的问题

  1. 你会把哪些 ASVS 控制措施内建到你的标准化模板中,让工程师免费获得它们? 对大型团队而言,杠杆效应最高的举措,就是让安全的路径成为默认路径,这样一个在共享框架中写过一次的控制措施,就能保护每一个采用它的服务。决定哪些 ASVS 要求(参数化查询、输出编码、安全的会话标志、服务器端授权检查)应当写进模板,而不是留在每位工程师的记忆里。对企业和政府的应用组合而言,还要决定哪些应用需要 ASVS 二级,哪些需要三级,并把这个决定与每个应用所接触数据的敏感程度挂钩。带上你的服务清单,标记出哪些已经继承了这些默认设置,哪些还在手工重新实现安全机制,因为那些手工实现的地方,正是注入攻击和破损访问控制潜藏的地方。如果安全的默认设置只存在于一个维基页面上,它们就会在交付压力下被跳过,所以要把它们写进代码里。

  2. 你将如何在每一个 API 中()而不只是新的 API()找出并修复破损的对象级授权问题? 通过改变 ID 来访问另一个用户的记录,是最常见、也最严重的 API 缺陷之一,它往往隐藏在早于你现行标准的老旧端点中。对每一个请求和每一个对象都在服务器端执行授权检查是规则,但难点在于验证这条规则在一个由许多人、历经数年编写而成的庞大代码库中是否真的得到了遵守。决定你是要集中管理授权逻辑,还是增加尝试跨租户访问的自动化测试,或者先针对你风险最高的 API 进行有针对性的测试。带上你那些暴露对象标识符的端点清单,并按它们返回内容的敏感程度进行排序。如果不进行有计划的排查,你会持续交付这个缺陷,直到某个研究人员或攻击者发现它为止。

  3. 面对下一次大范围的依赖项漏洞,你的应对计划是什么:你能多快找到并修复每一个受影响的服务? 当一个严重缺陷出现在一个热门库中时,拥有精确 SBOM 的公司能在几小时内识别出受影响的服务,而其他公司则要花上数周去排查,这个速度差距决定了你会承受多大的损失。现在就决定你是否为每一个工件生成软件物料清单,依赖项扫描是否在每一条流水线中运行,以及谁负责紧急补丁的决策。对受监管的和政府买家而言,SBOM 和已签名的来源证明正日益成为做生意的先决条件,因此这种就绪状态也在保护你的营收。带上一次演练的诚实答案:选一个你广泛使用的库,计时看看要花多久才能列出每一个交付了它的服务。如果答案是以天为单位计算,那就在下一次事件逼迫你行动之前,投入到清单管理和签名机制中。

  4. 你将如何从长期有效的静态密钥,转向短期有效、自动签发的凭证,今天有哪些系统在阻碍这一转变? 硬编码和长期有效的密钥,是数据泄露长期存在的原因,而解决方案()按需签发短期有效凭证()依赖于签发基础设施,而许多老旧系统往往无法使用这类基础设施。对大型团队而言,危险在于采用程度参差不齐:一个现代平台每小时轮换一次密钥,而一个遗留服务却仍在配置文件中携带一个静态数据库密码。决定哪些工作负载现在就能使用密钥管理器或工作负载身份系统,哪些需要先行投入,以及一旦怀疑某个密钥泄漏,谁来负责执行轮换操作手册。带上一份正在使用的每一个凭证的清单,包括它的有效期、一旦暴露的影响范围,以及提交扫描是否能在合并前捕获到它。在企业和政府场景中,把这一点与审计挂钩:审查者越来越期望看到关于轮换、限定范围的访问,以及每个密钥访问日志的证据,而一个无法在不停机的情况下轮换的静态凭证,正是一个等着被写进审计发现的问题。

  5. 你在哪些地方仍在运行自研或不一致的身份验证机制,整合到经过审核的协议上的计划是什么? 自建身份验证是最容易引入细微、可被利用的缺陷的方式之一,然而大多数大型系统资产中,都至少存在一条早于「统一采用 OAuth 2.0 和 OIDC」这一决策的遗留登录流程。相互竞争的压力是真实存在的:迁移一条旧流程有破坏现有用户和集成的风险,而保留它不变,则让一个高价值目标持续处于防御不足的状态。决定你是要整合到单一身份提供方上、统一强制执行 MFA,并为每一条定制流程设定退役期限,还是接受带有补偿性控制措施的、有据可查的例外情况。带上一份你整个体系中每一条身份验证路径的地图,标明哪些强制执行 MFA,哪些用现代的、内存密集型哈希算法存储密码,哪些是自定义实现。对企业和政府的应用组合而言,还要加上合规角度:像 NIST SP 800-63 这样的标准,对身份保证设定了具体的期望,而一条无法证明达到这些期望的自研流程,将无法通过审计或运营授权评审。

  6. 你如何验证这些控制措施在生产环境中确实生效,你能用证据而不是断言来证明这一点吗? 写出一个安全的默认设置,并不等于知道每一个服务是否仍然遵循它,而控制措施会随着代码变化、例外情况的累积和新端点的上线而悄悄腐化。对大型团队而言,问题在于覆盖率:哪些服务运行了静态分析、依赖项扫描,以及动态测试或渗透测试,你又如何知道那些跳过了这些环节的服务,不正是你风险最高的应用?决定流水线中哪些验证是强制性的,哪些是周期性的,谁来分诊这些发现,以及你保留什么证据来证明某个控制措施在某一天经过了测试且通过了。带上你当前的覆盖率地图、按严重程度划分的平均修复时间,以及最近没有经过测试的应用清单。在受监管和政府的场景中,这份证据不是可有可无的:审计员、授权官员和数据泄露调查人员都会要求提供控制措施经过验证的证明,而一份没有测试记录的政策,很少能让他们满意。

行业视角

初创企业。 只有两三名工程师,也没有安全专家,你的杠杆是继承安全能力,而不是自己构建:采用一个托管的 OIDC 身份提供方,依托一个默认就对查询进行参数化处理的框架 ORM,把密钥放在平台的密钥管理器中,而不是一个队友可能不小心提交上去的 .env 文件里。启用自动化依赖项扫描,让它自动开出补丁合并请求,眼下这样做就足够了。不要自建身份验证或加密机制,因为一次注入查询或一个泄漏的密钥,就可能在公司还没有客户之前,就让它彻底完蛋。

小型企业。 你大概没有应用安全专家,预算也很紧张,所以要购买那些已经内嵌在你正在付费使用的工具和平台中的控制措施,而不是自己组建一个专门的职能团队。选择一个内置 MFA 的托管身份提供方、一个能引导你走向参数化访问的托管数据库,以及一个开箱即用就能扫描提交记录中泄漏密钥的代码仓库托管服务。把你稀缺的注意力集中在 OWASP 十大风险这些基础事项上,因为它们导致了大多数真实世界的数据泄露,并优先选择那些默认交付安全设置、且你无法随意关闭它们的供应商。

企业。 在众多团队之间,挑战在于一致性:把 ASVS 控制措施内建到标准化模板中,让每一个新服务都能免费继承参数化查询、输出编码、安全会话和服务器端授权。运行精确的 SBOM 和全系统范围的依赖项扫描,这样下一次大范围的库漏洞爆发,只需要几小时,而不是几周就能应对,并集中管理授权策略,让跨租户访问变得可测试。统一采用一个身份提供方并强制执行 MFA,把应用安全作为一个受治理的组合来管理,配以按风险分级的 ASVS 级别和经过审计的证据。

政府。 采购规则、透明度和公共问责,塑造的是你必须证明、而不仅仅是实现的控制措施。依据与数据敏感度相匹配的级别,对面向公民的服务进行 OWASP ASVS 验证,对每一个部署的工件进行签名,并依据 SLSA 证明其来源,以满足供应链方面的强制要求,并从一个具备完整访问日志记录的中央密钥库中签发短期有效的凭证。做好准备,向审计员和授权官员展示一条从源代码到生产环境、有据可查的监管链,并让身份保证与 NIST SP 800-63 等已发布标准保持一致。

示例

初创企业。 一个三人组成的 SaaS 团队,从第一天起就跳过了自建登录功能,转而采用一个托管的 OIDC 提供方,从而获得了 MFA 和安全的密码重置功能,而不必编写他们承担不起出错代价的安全关键代码。它依托框架的 ORM,让查询默认就是参数化的,把密钥放在平台的密钥管理器中,而不是放在一个队友可能不小心提交上去的 .env 文件里,并启用自动化依赖项扫描,一旦某个库需要打补丁,就自动开出一个合并请求。这一切都没有拖慢团队的速度,也意味着一个泄漏的密钥或一次注入查询,不会在公司还没有客户之前就让它彻底完蛋。

企业。 一个服务数千万购物者的零售平台,通过单一身份提供方将身份验证统一到 OIDC 上,对员工强制执行 MFA,对高价值的账户变更强制执行二次验证。所有的数据库访问都经过一个配置为参数化查询的 ORM,一套内容安全策略为输出编码提供了后盾。在一个热门日志库中被广泛报道的漏洞出现后,公司的 SBOM 让它能在数小时内识别出每一个受影响的服务,并在两天内完成修复,而没有清单的竞争对手则花了数周时间去排查。

政府。 一家联邦福利机构构建的面向公民的服务,经过 OWASP ASVS 二级验证,而处理最敏感记录的组件则达到三级。密钥存放在一个中央密钥库中,由它签发短期有效的凭证;提交扫描会阻止任何泄漏密钥的提交。每一个部署的工件都经过签名,其来源都依据 SLSA 得到证明,这满足了联邦对可验证软件供应链的强制要求,也为审计员提供了一条从源代码到生产环境的清晰监管链。

商业案例:动机、投资回报率与总拥有成本

应用安全方面的支出,买下的是最有可能发生、也代价最高昂的那一类数据泄露。总拥有成本包括工具(扫描器、密钥管理器、身份提供方)、工程师修复问题所花的时间,以及安全默认设置带来的轻微摩擦。与此相对,要权衡跳过它的代价:注入攻击和破损访问控制导致的数据泄露,常常一次就暴露数百万条记录,引发监管罚款、强制通报、欺诈损失、修复冲刺,以及会压制营收长达数年的声誉损害。

当控制措施被自动化并被复用时,投资回报率最为可观。一次配置良好的身份集成、共享框架中一个加固过的查询层,以及一条能阻止存在漏洞依赖项的流水线,能以每个服务边际成本的方式,保护整个系统。供应链方面的控制措施尤其已经从可选项变成了必需项:一个被攻陷的依赖项,可能把你的每一位客户都变成受害者,而监管者和企业买家也越来越多地把 SBOM 和已签名的来源证明,当作做生意的先决条件。向领导层说明这一点时,要把这项投资与具体、有名有姓的风险,以及一旦达不到就会阻碍营收的采购和合规要求联系起来。

反模式与陷阱

  • 自建加密或身份验证机制。 几乎总是会产生细微的、可被利用的缺陷。
  • 仅在客户端进行验证。 极易被绕过;服务器必须重新验证一切。
  • 黑名单式净化处理。 试图剔除「坏」字符,而不是列出好字符的允许清单;攻击者总能找到漏洞。
  • 密钥放在源代码或环境文件中。 凭证泄漏最常见的单一原因。
  • 忽视对象访问的授权检查。 假设一个已通过身份验证的用户,可以访问任何它能猜到 ID 的对象。
  • 设置后就不再管的依赖项。 直到数据泄露倒逼才更新第三方组件。
  • 把十大风险清单当作终点。 它是一条底线,而不是一套全面的标准;要用 ASVS 来深挖。
  • 记录敏感数据。 密码、令牌和 PII(个人身份信息)一旦出现在日志中,就是一场等待发生的数据泄露。

成熟度模型

第 1 级:启动。 应用安全依赖于个别开发者的知识,只在事件发生之后才有所反应。没有标准的控制措施。密钥存放在源代码中。依赖项很少更新。身份验证是自定义且临时拼凑的,注入攻击或破损访问控制缺陷是靠运气、而不是靠流程被发现的。

第 2 级:发展。 基本实践开始出现,但因团队而异。对 OWASP 十大风险的认识开始普及,一些框架层面的防护措施已经就位,也存在一个密钥管理器,但使用并不统一。依赖项扫描偶尔运行。新系统采用标准身份提供方,而老旧服务则保留着未经触碰的自研登录流程。

第 3 级:标准化。 控制措施在整个组织内被成文记录并强制执行。基于 ASVS 的要求按风险等级设定,参数化查询和输出编码成为常态,并要求使用带有 MFA 的中央身份提供方。密钥被自动管理和扫描,SBOM 得以生成,依赖项扫描在每一条流水线中运行。

第 4 级:管理。 该实践被以数据的方式度量,并对照基线进行控制。扫描和测试覆盖率、按严重程度划分的平均修复时间、继承了标准化默认设置的服务占比、凭证和密钥的轮换周期,以及 ASVS 符合度,都在仪表盘上被追踪。例外情况被记录并附有到期日期,偏离基线会触发行动,发布则依据明确定义的安全阈值来设关卡,而不是凭主观判断。

第 5 级:协同。 安全被持续改进,并在整个组织内深度整合。安全的默认设置被内建到标准化模板中,使安全的路径成为自动的;短期有效凭证被用于一切场景;具备签名和来源证明(SLSA)的完整供应链保证成为标准做法。验证是持续进行的,对新漏洞的响应迅速且可度量,每一次事件都会反馈到共享模板中,让一次修复能加固整个系统。

供讨论的思路

  1. 授权逻辑应当放在哪里,才能在众多服务之间既保持一致,又易于维护?
  2. 考虑到风险敞口与变动频率之间的权衡,你应该多积极地更新依赖项?
  3. 对你应用组合中的每一类应用而言,哪个 ASVS 级别是合适的?
  4. 你如何在不制造脆弱签发基础设施的前提下,消除长期有效的密钥?
  5. 要让你的组织为每一个工件都生成并使用 SBOM 和来源证明,需要具备什么条件?
  6. 你如何防止安全的默认设置在交付压力下被禁用?

关键要点

  • OWASP 十大风险是必备知识;ASVS 提供了可测试的标准。
  • 分层运用输入验证、参数化和输出编码,来击败注入攻击和 XSS。
  • 使用经过审核的协议(OAuth 2.0、OIDC)并强制执行 MFA;绝不要从零开始构建身份验证。
  • 对每一个请求和每一个对象,都在服务器端强制执行授权检查。
  • 让密钥远离源代码,集中管理它们,并轮换为短期有效的凭证。
  • 供应链是一个主要的攻击面;使用 SBOM、SCA、签名和来源证明(SLSA)。
  • 自动化的、可复用的控制措施,能以每个服务边际成本的方式保护整个系统。

参考资料与延伸阅读

  • OWASP, Top 10 Web Application Security Risks
  • OWASP, Application Security Verification Standard (ASVS)
  • OWASP, Cheat Sheet Series(输入验证、身份验证、授权、密钥管理)
  • Dafydd Stuttard and Marcus Pinto, The Web Application Hacker’s Handbook
  • Aaron Parecki, OAuth 2.0 Simplified
  • National Institute of Standards and Technology, SP 800-63: Digital Identity Guidelines
  • Cloud Native Computing Foundation and OpenSSF, SLSA framework 与 Supply-chain Security guidance