认证和授权
身份验证(AuthN)和授权(AuthZ)是一个非常重要的主题,因为它们经常出现漏洞。由于它们似乎经常出现,这通常意味着它们是什么,甚至是什么导致了它们的出现,都存在一定的不确定性。
在此提醒大家,每个学期都包括以下内容:
- 身份验证:用户是谁?
- 授权:用户可以访问哪些内容?
下面我们将分别介绍它们。
验证(识别和验证失败)
不正确的身份验证可覆盖大量漏洞,例如
- 特定页面/端点未进行身份验证
- 缺乏对暴力破解攻击(凭据填充)的保护
- 不安全的账户/密码恢复流程
- 不安全的身份验证令牌生成、验证、过期、传输或存储
- 用户已使用 2FA 进行身份验证的验证不当或缺失(如适用)
列表中的第一项(缺乏身份验证)是迄今为止最常见的问题。很多时候,开发人员必须明确注释/配置页面或端点所需的身份验证级别,而这一步很容易被遗漏。
一个好的做法是确保系统关闭而不是开放。因此,与其在每个端点上注释它们需要经过身份验证的用户会话的信息,不如默认所有路由都需要经过身份验证的用户会话,除非被特别覆盖。这样做可以大大减少出错的可能性。
授权(中断的访问控制)
授权问题可能以多种不同的方式出现,而这些方式都很常见:
- 不安全的直接对象引用(IDOR)
- 功能级访问控制缺失(缺失 AuthZ)
- 权限升级(横向或纵向)
不安全的直接宾语引用
对象往往有唯一的标识符(ID),作为键来引用它们。当用户发送请求查看订单、账户或类似内容时,通常会包含该 ID。当应用程序无法验证用户(或无用户)是否应该能够访问该特定对象时,就会出现 "不安全的直接对象引用"。
缺少功能级访问控制
另一个非常常见的漏洞是没有对页面或端点(而不是对象)进行授权检查。
根据所使用的框架,开发人员通常需要在处理程序中检查授权,或对端点进行注释,并指定调用端点所需的要求。
不幸的是,这些额外的步骤也很容易被遗忘,这往往是一些授权漏洞最终发生的原因。
建议
默认为关闭而不是打开
就身份验证和授权而言,默认为封闭式而非开放式的原则非常重要。
根据语言/框架的不同,好的做法是确保进入应用程序的所有路由的默认设置都要求经过身份验证的会话具有尽可能高的角色或权限。 这样做会迫使开发人员覆盖路由的要求。
cs
// Ensure the default behaviour is to authenticate requests, and check if they are admin
[Authenticate]
[Authorize("Admin")]
public class SecureController : Controller
{
}
public class MyController : SecureController
{
// Overrides the Authorize attribute inherited to allow any user to access the page
[Authorize("User")]
public Page ShowUserProfile() {
}
// Can only be accessed by an Admin user
public Page ShowAdminPage() {
}
// Overrides the Authenticate and Authorize attribute to allow ME
[AllowAnonymous]
public Page ShowLoginPage() {
}
}
在服务中执行授权检查
在访问数据时,确保所有数据访问以统一的方式执行相关的访问和授权检查极为重要。这一般通过使用域服务来实现。
更多实例
下面,我们收集了一些简短的示例,让大家了解安全与不安全的身份验证和授权之间的区别。
C# - 不安全
认证缺失
public class AdminController : Controller
{
// INSECURE: Does not check whether the user is logged in before showing an Admin page
public Page ShowAdminPage() {
}
}
授权缺失