API安全(一)--使用OneAuth进行API加固的Checklist


身份与API安全成熟度

Why 身份是API 安全的核心?

  • 现代应用程序通常是围绕API设计的,API可以让多个不同应用程序能够重用相同业务逻辑,以此加快创新和新业务的上线。

  • 传统的安全架构基于边界思维,假定企业内部是一个“内网”,然后假定存在一个边界对内外网进行隔离,但随着企业的数字化转型,企业大量采用了云计算和大数据技术,大量的业务和数据会通过API的方式对外去进行提供。

  • API安全性的实现范围很广,不同的场景需要用到不同的方式。通常,API的保护仅采用HTTP基本身份验证,API密钥或基于令牌的身份验证。其中最容易忽略一个主要问题就是:身份。

    通常因为身份导致的漏洞占应用漏洞的80%以上,为防止漏洞并获得高效的安全收益,全面的关注身份对于保障API和业务的安全至关重要。

可以说,APIs提供了对外的最有价值的数据或服务的访问,而API又是一个对外开放的服务,因此,API安全将成为一个常见的安全挑战。也需要我们的安全和开发团队给予更多的重视,科学的规划、设计和保护API的安全。

API 安全演进成熟度模型

API提供对有价值的数据或服务的访问,因此API通常需要限制对应用的访问。即,应用程序需要正确的身份和授权才能调用API。除了应用程序自身要调用API,还包括应用程序需要代表用户调用API来访问用户拥有的资源,前提是它需要用户的授权同意。

为了更好的描述身份与API安全之间的关系,我们参考API的成熟度模型,将API安全的模型划分为4个等级。

  • Level 0: API 密钥和基础认证 ,API Keys and Basic Authentication

  • Level 1: 基于令牌的认证,Token-Based Authentication

  • Level 2: 基于令牌的授权,Token-Based Authorization

  • Level 3: 基于声明的集中式信任,Centralized Trust Using Claims

在这个模型中,随着等级升高,安全性和信任度就越高。

Level 0 API密钥和基础认证

级别0的API使用基本身份验证或API密钥来验证API调用。这些值插入在API请求的URL的标头(Header)或正文(Body)中。大多数早期的API都采用这种方式。

例如,一个电子商务商店的应用,它根据用户购买情况对付款API进行API调用。它将身份验证以标头中的API密钥或基本身份验证的形式发送给应用程序,并将其传递给API。将用户的ID放在正文或URL中。在下面的示例中,有两类API:账单类和商品类。由于采用HTTP基本身份验证或API密钥方式,API仅对“商店应用”进行身份验证,因此“商店应用”必须向API传递用户数据。

![5-2 Level0.jpg](https://i.loli.net/2021/07/16/rngvp1BwQjbMKqY.jpg)5-2 Level0.jpg

Level0 缺点

这个方式实际上是非常基本的方法,没有对于安全的考量。首先,密钥在每次API的访问过程中都携带,这样极为容易泄漏。另外,API密钥验证只是对于(商店应用)机器验证,对用户的身份没有是否篡改的校验,容易被利用。最后,这种方式仅提供了应用的身份验证,即证明断言的行为,而没有包含授权的相关信息。

Level 1: 基于令牌的认证 Token-based Authentication

级别1的API在基于令牌的体系结构中利用访问令牌(Access Token)进行身份验证。访问令牌中的信息可以包含用户的类型(机器,应用程序,用户等)。通过启用特权访问,能够有效的对内部和外部用户的安全级别进行分离,有效的帮助提升安全性。同时,因为用户身份是请求的一部分,便于更好的审计。

例如,继续上面的用例,考虑在电子商店中使用基于访问令牌的身份验证。在此场景下,如果API同时需要支持2个应用的调用,即,除了商店应用以外,还有内部的管理后台时,就会非常的明显。即需要使用自定义逻辑来了解该请求是属于具有特权的后台请求,还是来自外部网络用户对于该网上商店的请求。

5-2 Level1.jpg

Level 1的缺点:

在等级1,拥有令牌的任何人都可以修改API,提供了便捷的同时,也意味着特权访问的令牌的风险更高,如果被黑将带来巨大的风险。此外,级别1仅仅涵盖身份验证,而不涉及授权。换句话说,没有策略去限制应用允许做什么不允许做什么?此外,对令牌进行身份验证时,所有授权访问,需要通过自定义代码实现,诸如if语句之类的自定义编码,这无异将权限和业务耦合在了一起。

在第2级和第3级中对此种情况进行了否定,改进的方案可以使用令牌的数据直接进行授权,从而生成授权的逻辑,而无需再业务中去耦合授权控制的逻辑。

Level 2: 基于令牌的授权 Token-based Authorization

为了解决以上级别1的缺陷,在令牌的设计上,级别2在基于令牌认证的体系结构上,引入了授权机制。即,通过描述请求方的特权,询问请求方将被允许做什么?这一思想被应用到Oauth2.0中,作为一种广泛采用的授权标准。

OAuth最大的亮点是在token中包含了作用范围(标准中定义为Scope),范围Scope可以在授权令牌中包含“指定的权限”,即作用范围可以指定用户权限。 这一优点在后续的协议中又得以进一步的规范,如后续的OpenID Connect定义了一组标准的[作用范围],即可以用来生成标准的身份参数。当然,开发者除了标准范围以外,也可以创建[自定义范围]用于自己开发的API。范围可以根据开发者需求包含更多和授权相关的数据,以用于后续的API的授权,这比“ if”语句的编码方式要更为友好和优雅。

这时,让我们再次回顾我们的电子商店的案例。现在,我们引入了通过范围进行授权,因此可以更加容易的实现公共网络的商店和后台可以具有不同的特权。比如,只有管理后台应用才能获取添加商品(AddProduct)的权限。这就有效的区分了不同应用的权限。

但是,此种方案仍然没有尽善尽美,在这个场景下,依然存在些问题。比如,商店应用和管理后台的的某些操作会出现重叠。比如,使用Scope为LIST,用于在账单API中列出发票。 列出发票的ID被包含在URL或作为传递的参数,作为该API的请求参数。因此,可能会出现恶意越权的情况,即一个用户可以操纵篡改传递的ID参数以列出另一个用户的发票。因此,仅仅使用范围是不够的,范围Scope锁定了客户端应用程序被允许做什么操作,但对于对特定用户被允许操作哪些数据没有帮助,因为它们只包含了参数的“名称”,而没有限定“值”。而是应使用Claims,以便将参数“铸造”到令牌中。然后程序就可以很容易地将后台应用特权与公共网络商店应用的特权分开。

5-2 Level2.jpg

Level 2的缺点:

级别2的问题:级别2中的一个问题是系统面临着反编译的威胁。如果将身份的信息直接放在在API参数中,通过对于API的访问进行逆向,其中的逻辑错误很容易被发现和利用。 另外,级别2中引入了更高的系统复杂度,因为某些API请求参数可能会依赖其他API响应或其他条件。如果,一个API调用另一个API时失败了,或者,如果数据请求参数中包含了错误,怎么办?我们不能假设数据从一个API传递到下一个API总是正确的。这些现实问题可能会导致一系列的级联信任问题,这种“意大利面条式的信任”,有可能让系统变成一团糟。

Level 3: 基于声明的集中式信任,Centralized Trust Using Claims

通过对以上各级的问题的总结,最终API的安全计划到第3级,可以认为是目前最完善的API安全思想和实践。这种实践引入了使用Claims和签名的JSON Web令牌(JWT)进行集中式信任。

同时,授权的服务作为一项独立的服务,客户端向授权服务器进行请求授权,将授权与业务进行分离,它定义了不同的流来获取令牌,客户端从而能够在不需要知道用户凭证的情况下授予对资源的访问权。通过这样做,我们解决上述所有问题。

什么是JWT?澄清下常见的对于JWT的误解,JWT不是一种协议。JWT是一种使用了签名的数据。 OAuth协议中使用JWT验证交易。 JWT可以用来共享范围和声明?声明本质上是一种断言。例如,如下声明:“特拉维斯(Travis)说雅各布(Jacob)是一位身份专家。”此声明具有声明方(Travis),被声明的主体(Jacob),和主体的属性(身份专家)。如果您信任Travis,那么您相信此声明。主体的身份可以由许多属性可以组成,例如“主体”属性名称,年龄,身高,体重等。对于这些属性,声明方可能为一个官方的机构。当然,也可以有上下文属性,例如情况,时间,位置,天气,等等。与其信任属性本身,不如信任一个可信的声明方。

如果你相信颁发密钥的OAuth授权服务器,那么您可以信任由它签发提出的声明。

至于,验证声明的是未经篡改的真实性,一般流程如下:

1.请求方向声明颁发方发起请求

2.声明颁发方签发返回的声明数据,并用私钥签名

3.请求方携带签名后的声明数据向第三方去请求服务

4.第三方服务使用公钥验证签名合法性

这种方法通过信任令牌的发行者,而不是信任参数本身的方式,有效的解决了各级信任问题。从而达成集中式的授权机制。

对于API安全开发的一些建议Checklist

以下是当你在设计,测试以及发布你的 API 的时候所需要核对的重要安全措施。


身份和授权

认证

  • 不要使用 Basic Auth ,请使用标准的认证协议(如 JWTOAuth)。
  • 不要重新实现 Authenticationtoken generatingpassword storing,请使用标准库。
  • 使用 UUID 代替自增长的 id。
  • 限制密码错误尝试次数,并且增加账号冻结功能。
  • 加密所有的敏感数据。

JWT(JSON Web Token)

  • 使用随机复杂的密钥(JWT Secret)以增加暴力破解的难度。
  • 不要在请求体中直接提取数据,要对数据进行加密(HS256RS256)。
  • 使 token 的过期时间尽量的短(TTLRTTL)。
  • 不要在 JWT 的请求体中存放敏感数据,因为它是可解码的

OAuth 授权或认证协议

  • 始终在后台验证 redirect_uri,只允许白名单的 URL。
  • 始终在授权时使用有效期较短的授权码(code)而不是令牌(access_token)(不允许 response_type=token)。
  • 使用随机哈希数的 state 参数来防止跨站请求伪造(CSRF)。
  • 对不同的应用分别定义默认的作用域和各自有效的作用域参数。

访问控制

  • 限制流量来防止 DDoS 攻击和暴力攻击。
  • 在服务端使用 HTTPS 协议来防止 MITM (中间人攻击)。
  • 使用 HSTS 协议防止 SSL Strip 攻击。

输入限制

  • 使用与操作相符的 HTTP 操作函数,GET(读取)POST(创建)PUT(替换/更新) 以及 DELETE(删除记录),如果请求的方法不适用于请求的资源则返回 405 Method Not Allowed
  • 在请求头中的 content-type 字段使用内容验证来只允许支持的格式(如 application/xmlapplication/json 等等)并在不满足条件的时候返回 406 Not Acceptable
  • 验证 content-type 中申明的编码和你收到正文编码一致(如 application/x-www-form-urlencodedmultipart/form-dataapplication/json 等等)。
  • 验证用户输入来避免一些普通的易受攻击缺陷(如 XSSSQL-注入远程代码执行 等等)。
  • 不要在 URL 中使用任何敏感的数据(credentialsPasswordssecurity tokens,or API keys),而是使用标准的认证请求头。
  • 使用一个 API Gateway 服务来启用缓存、限制访问速率(如 QuotaSpike ArrestConcurrent Rate Limit)以及动态地部署 APIs resources。

处理

  • 检查是否所有的接口都包含必要都身份认证,以避免被破坏了的认证体系。
  • 避免使用特有的资源 id。使用 /me/orders 替代 /user/654321/orders
  • 如果需要解析 XML 文件,确保实体解析(entity parsing)是关闭的以避免 XXE 攻击。
  • 如果需要解析 XML 文件,确保实体扩展(entity expansion)是关闭的以避免通过指数实体扩展攻击实现的 Billion Laughs/XML bomb
  • 在文件上传中使用 CDN。
  • 如果数据处理量很大,尽可能使用队列或者 Workers 在后台处理来避免阻塞请求,从而快速响应客户端。
  • 不要忘了把 DEBUG 模式关掉。

输出

  • 增加请求返回头 X-Content-Type-Options: nosniff
  • 增加请求返回头 X-Frame-Options: deny
  • 增加请求返回头 Content-Security-Policy: default-src 'none'
  • 删除请求返回中的指纹头 - X-Powered-ByServerX-AspNet-Version 等等。
  • 在响应中遵循请求的 content-type,如果你的请求类型是 application/json 那么你返回的 content-type 就是 application/json
  • 不要返回敏感的数据,如 credentialsPasswordssecurity tokens
  • 给请求返回使用合理的 HTTP 响应代码。(如 200 OK400 Bad Request401 Unauthorized405 Method Not Allowed 等等)。

持续集成和持续部署

  • 使用单元测试以及集成测试的覆盖率来保障你的设计和实现。
  • 引入代码审查流程,禁止私自合并代码。
  • 在推送到生产环境之前确保服务的所有组件都通过代码安全工具静态地扫描过,包括第三方库和其它依赖。
  • 为部署设计一个回滚方案。

小结

信任是主观的东西。在设计基于API的安全系统时,我们应该信任密钥、令牌,密码,机器或用户本身吗?答案要比大多数的API设计人员认为的要复杂,但这对于保护您的平台整体至关重要。

本质上,API安全的顶峰是信任声明,而不是属性。如果GitHub存储库中存在重要的密钥,API很容易受到攻击。因此,API提供者必须做出更明智的安全决策,以保护整个平台的完整性。

在网络安全领域,很少鼓励你发明自己的授权规则。为了使集中信任发挥作用,授权系统需要使用稳定的协议。就像街道交通规则一样,身份系统也遵循通用协议。需要它们自己的共享开放标准,正像我们API成熟度逐渐演进过程中定义的一些开发协议或引用的协议,如果JWT、OAuth2.0和OpenID Connect等等。利用这些标准,应用程序可以在JWTs中共享安全的断言数据以进行验证。

正如API安全成熟度模型逐步演进所显示的那样,高度成熟的API只信任很少的源,这些演化的API被信任的令牌的发行者逐渐成为独立的服务——身份服务和授权服务,这不能保证100%的真实性,但最接近于验证请求方的身份。通过标准逐渐固化信任标准过程,可以消除系统开发者在身份授权的自定义代码上浪费的精力。

更多API安全可以扫码添加微信,我们致力于提供身份的标准,通过身份驱动API安全。