身份验证与授权
身份验证 (AuthN) 和授权 (AuthZ) 是极其重要的课题,因为涉及这两者的漏洞频率极高。由于它们频繁出现,通常意味着人们对它们的定义,甚至是对其成因存在一定的认知模糊。
提醒一下,这两个术语分别涵盖以下内容:
- 身份验证:用户是谁?
- 授权:用户应该拥有哪些访问权限?
我们将在下文中分别探讨。
身份验证(识别与身份验证失败)
身份验证不当可能涵盖多种漏洞,例如:
- 特定页面/端点缺乏身份验证
- 缺乏针对暴力破解攻击(撞库)的防护
- 账户/密码恢复流程不安全
- 身份验证令牌的生成、验证、过期、传输或存储不安全
- 未正确验证或遗漏验证用户是否已通过双重身份验证 (2FA)(如适用)
列表中的第一项(缺乏身份验证)是目前实际应用中最常见的问题。很多时候,开发人员需要显式地标注/配置页面或端点所需的身份验证级别,而这一步很容易被遗漏。
确保系统采用默认拒绝策略而非默认允许策略是一种良好的实践。因此,与其为每个端点标注其需要已验证的用户会话,不如将默认设置设为所有路由都需要已验证的用户会话,除非已明确覆盖该设置。这样做可以大幅减少出错空间。
授权(失效的访问控制)
授权问题通常以多种常见方式出现:
- 不安全的对象直接引用 (IDOR)
- 功能级访问控制缺失(授权缺失)
- 权限提升(水平或垂直)
不安全的对象直接引用
对象通常拥有唯一的标识符 (ID),作为引用它们的键。当用户发送请求以查看订单、账户或类似内容时,请求中通常包含此 ID。“不安全的对象直接引用”是指应用程序未能验证用户(或未登录用户)是否有权访问该特定对象。
功能级访问控制缺失
另一个非常常见的漏洞是页面或端点(相对于对象而言)缺乏授权检查。
根据所使用的框架,开发人员通常需要在处理程序中检查授权,或者对端点进行标注并指定调用该端点所需的权限要求。
遗憾的是,这些额外的步骤也非常容易被遗忘,这往往解释了某些授权漏洞产生的原因。
建议
默认关闭而非开放
在身份 验证和授权方面,遵循“默认关闭而非开放”的原则至关重要。
根据您使用的语言或框架,最佳实践是确保应用程序的所有入口默认都需要经过身份验证的会话,并具备尽可能高的角色或权限。这样可以强制开发人员在需要时显式覆盖这些限制。
cs
// 确保默认行为是验证请求,并检查其是否为管理员
[Authenticate]
[Authorize("Admin")]
public class SecureController : Controller
{
}
public class MyController : SecureController
{
// 覆盖继承的 Authorize 属性,允许任何用户访问该页面
[Authorize("User")]
public Page ShowUserProfile() {
}
// 仅限管理员用户访问
public Page ShowAdminPage() {
}
// 覆盖 Authenticate 和 Authorize 属性,允许我访问
[AllowAnonymous]
public Page ShowLoginPage() {
}
}
在服务层强制执行授权检查
在访问数据时,确保所有数据访问都以统一方式强制执行相关的访问和授权检查至关重要。这通常通过使用领域服务来实现。
更多示例
以下我们整理了一些简短的示例,直观展示了安全与不安全的身份验证及授权之间的区别。
C# - 不安全
缺少身份验证
public class AdminController : Controller
{
// 不安全:在显示管理页面前未检查用户是否已登录
public Page ShowAdminPage() {
}
}
缺少授权
[Authenticate]
public class AdminController : Controller
{
// 不安全:在显示管理页面前未检查用户的授权情况 public Page ShowAdminPage() {
}
}
C# - 安全
[Authenticate]
[Authorize("Admin")]
public class AdminController : Controller
{
// 安全:同时检查用户是否已登录以及是否具备管理员角色
public Page ShowAdminPage() {
}
}
实用安全编码
探索安全编码指南,了解并缓解 OWASP Top 10 等软件漏洞,并通过引导式训练任务在真实的应用程序模拟中进行实践。