OWASP #6 ASP.NET 中防止敏感数据泄露 – 第一部分





5.00/5 (9投票s)
OWASP 排名的第六个最漏洞百出的安全风险与保守秘密有关。
敏感数据泄露
2007 年,Albert Gonzalez 进行了一系列黑客攻击,窃取了企业的信用卡信息。马萨诸塞州助理美国检察官 Steve Heymann 指出, Gonzales 和他的同伙之所以能够成功,是因为大量数据(超过 9000 万笔信用卡交易)未受保护且未加密。
保护秘密的概念体现在开放式 Web 应用程序安全项目 (OWASP) 的 Web 应用程序最关键的第六项风险——敏感数据泄露。虽然这三个简单的词可以应用于许多领域,但我们将重点关注正确的密码哈希、静态信息的加密以及通过 HTTPS 保护传输中的通信。这些主题有一个共同点,那就是保守秘密。
哈希与加密
大多数开发人员都明白,加密是将敏感数据转换为不可读数据的过程,需要使用密钥,从而生成我们称之为密文的数据。它可以被逆转。这意味着可以使用相同的要素——正确的密钥、加密算法(密码)和密文——来解密密文,从而恢复原始数据。
另一方面,哈希也是我们将数据转换为不可读值(称为哈希值,也称为摘要、消息摘要或简称为哈希)的过程。但是,哈希在重要方面与加密不同。
- 这是一个单向过程。您无法从哈希值返回到原始输入。
- 生成哈希摘要不需要共享密钥(与加密需要接收者解密加密数据不同)。
- 哈希值是确定性的,也就是说,相同的数据输入将始终计算出相同的哈希值。
哈希摘要示例
A3EKhEs+xYnYLx5GriGT7/oy80n/lUMwUhTZyshcJxZ5DIBnZiP97GFG6vxVAGv34C4AAA==
用户密码基本上标识了系统的有效用户。鉴于传统加密密码的缺点,Roger Needham 看到了一个机会,并在 1966 年率先使用单向哈希值来替代存储明文或加密密码。
由于特定输入每次都会生成相同的哈希值,因此您可以通过存储密码的加密哈希值而不是在系统中保留用户密码(即使已加密)来验证系统用户。我们将更多地讨论加密,当涉及到加密敏感数据和讨论不安全的传输层(HTTP)时。
存储用户凭证
您可能认为每个人都知道以明文形式存储用户密码是一个巨大的禁忌,但您就错了。用户密码是敏感数据,需要适当的程序来保护(例如,不要以明文形式存储)。无论这些适当的程序是什么,都可以将其视为最后一道防线。已经获得信息访问权限的攻击者很可能已经利用了我们系统的其他漏洞并绕过了其他防御措施。因此,未受保护的密码为进一步利用受损系统提供了手段。它们还会危及用户使用相同密码访问的其他系统(但没有人会重复使用相同的密码,对吧?)。
我只想说,没有密码是绝对安全的。无论您采取何种措施来保护用户的密码,都无法做到万无一失。句号。您要做的就是设法使其不可行,并且不值得攻击者花费时间。
我们已经确定,加密哈希可能是用户密码等敏感数据的良好保护。然而,就目前而言,哈希函数并不完美。使用相同的哈希函数和输入,哈希值将始终相同。例如,取“Password1”并使用 SHA-256 哈希函数对其进行处理以生成 32 位哈希值。
19513fdc9da4fb72a4a05eb66917548d3c90ff94d5419e1f2363eea89dfee1dd
无论我们对“Password1”进行多少次哈希处理,它都会生成相同的哈希值。这构成了一个严重的困境。如果我们的数据被泄露,攻击者试图破解我们的用户密码列表,他们只需要找到一个值,该值可以计算出存储的哈希值,他们就能知道原始密码。因此,如果攻击者有一个预先计算好的哈希列表(直到给定的密码长度),并简单地将存储的每个密码哈希与该列表进行比较,他们就可以轻松破解任何密码哈希。事实上,这已经存在,即彩虹表。
彩虹表提供预生成的哈希,可以与(例如)哈希密码进行比较。彩虹表提供了成本/时间权衡(假设其他人已经花费了时间和金钱来生成哈希);对于彩虹表用户来说,只需比较哈希值即可找到匹配项。但这并不像听起来那么好。彩虹表本身也有成本,主要是存储成本。为了了解这些成本,这里有一些统计数据:
是的,您没有看错。仅仅覆盖大小写字母和数字就需要 864 GB 的空间才能达到 100% 的成功率(不包括特殊字符)。但并非所有希望都渺茫,有一个解药。
增添点“佐料”
那么,为什么我们要考虑使用加密哈希函数来哈希用户密码,如果攻击者可以预先计算值进行比较直到找到匹配项并破解底层密码呢?因为我们可以为用户密码添加一点“佐料”,然后在将其通过哈希算法进行处理之前,以随机性的形式添加,以生成唯一的哈希。我们通过添加技术上称为**“盐”(salt)**的值来实现这一点。
**“盐”(salt)**是一个唯一的值,可以添加到原始密码中,以确保哈希值是唯一的。盐的有效性取决于其唯一性。随机性的值越高,盐越有可能唯一。通过向密码添加唯一的盐值,即使不同的用户使用相同的密码,输出的哈希值也将始终是唯一的哈希值。
**但关键在于:哈希的有效性取决于盐的唯一性。如果盐被重复使用,您就破坏了盐的全部目的,即为生成的哈希值提供唯一性。**
那么,这对彩虹表有什么影响呢?这意味着它们的终结。所需彩虹表存储的必要性使其过时。但是,这并不意味着我们安全了。从云提供商等来源获得的廉价计算能力或 GPU 提供的并行计算能力,使任何人都可以做得更多。 Coda Hale 进行了说明:
彩虹表,尽管最近作为博文主题而广受欢迎,但并没有很好地保值。密码破解程序的 CUDA/OpenCL 实现可以利用 GPU 中大量的并行处理能力,每秒可处理数十亿个候选密码。您可以在不到 2 秒的时间内测试所有 ≤7 个字符的纯小写字母密码。现在,您可以以每小时不到 3 美元的价格租用使之成为可能的硬件。大约每小时 300 美元,您可以每秒破解约 5000 亿个候选密码。
是的,您没有看错,是**B**,即每秒**数十亿**次。如果我们一个人可以轻易获得必要的且价格合理的硬件来生成每秒数十亿次的哈希进行比较,我们能实施什么样的对策?一个词:**时间**。
时间在我们这边
到目前为止,我们已经看到攻击者掌握了所有筹码。他们可以以可负担的成本获取硬件,以克服彩虹表的限制并暴力破解我们经过适当盐值处理的哈希密码。这就是基于密码的密钥派生函数发挥作用的地方。
除了对密码进行盐值处理和哈希处理之外,像bcrypt、scrypt 和PBKDF2 这样的基于密码的密钥派生函数,还在正确的密码哈希中添加了一个额外的要素,那就是**时间**。它们通过一种称为密钥拉伸(key stretching)的方法来实现这一点(这与密钥增强 slightly different)。如果您正在寻找完整的实现,我之前在 .NET 中演示过 PBKDF2 的实现。
密钥拉伸会故意减慢哈希过程,迫使数据(在此例中为密码)经过多次哈希处理。因此,如果攻击者想要生成匹配的哈希,他们也必须将密码猜测值经过相同数量的哈希轮数,才能获得匹配。如果生成一个哈希需要 1 毫秒,那么在 1000 轮哈希处理后,它现在需要 1 秒来生成由密钥派生函数产生的哈希。
整合起来
如果我建议我们应该考虑自己构建身份验证框架并在我们的 Web 应用程序中实现基于密码的密钥派生函数,那将是不负责任的。您听过多少次这句话——**Web 安全很难,敏感数据的正确加密更难。因此,自己构建身份验证系统就是个无知的人。**相反,我们需要依赖经过广泛测试和使用的、高度经过测试的框架/库。因此,让我们以 Visual Studio MVC 模板创建的 ASP.NET MVC 应用程序中的 ASP.NET 团队的实现为例。
例如,创建一个 ASP.NET MVC 应用程序并选择一个身份验证方案,这将部署最新的 ASP.NET Identity 2.0 系统。
ASP.NET Identity 2.0 安全库是他们用于取代 .NET 2.0 中早期成员资格系统的第二代产品。您可以在此处阅读更多关于入门 Identity 2.0 系统。尽管这不是一篇关于身份验证系统的帖子,但 ASP.NET MVC 模板提供了所有必要的机制,允许用户注册帐户并进行身份验证。
虽然已经有很多变化,但在新的身份验证系统中,密码的处理方式与我们上面概述的一致,即使用 Rfc2898DeriveBytes 类中的基于密码的密钥派生函数 (PBKDF2)。在这里,我们可以在 Crypto 类中看到 Identity 2.0 实现对新用户密码的哈希处理。
虽然已经有很多变化,但在新的身份验证系统中,密码的处理方式与我们上面概述的基于密码的密钥派生函数(PBKDF2)在 Rfc2898DeriveBytes 类中一致。在这里,我们可以在 Crypto 类中看到 Identity 2.0 实现对新用户密码的哈希处理。
namespace Microsoft.AspNet.Identity { Internal static class Crypto { private const int PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes private const int PBKDF2SubkeyLength = 256/8; // 256 bits private const int SaltSize = 128/8; // 128 bits /* ======================= * HASHED PASSWORD FORMATS * ======================= * * Version 0: * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. * (See also: SDL crypto guidelines v5.1, Part III) * Format: { 0x00, salt, subkey } */ public static string HashPassword(string password) { if (password == null) { throw new ArgumentNullException("password"); } // Produce a version 0 (see comment above) text hash. byte[] salt; byte[] subkey; using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount)) { salt = deriveBytes.Salt; subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength); } var outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength]; Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize); Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength); return Convert.ToBase64String(outputBytes); }
如代码注释中所述,PBKDF2 使用 SHA-1 哈希算法,进行 1000 轮哈希处理。.NET 类提供了一个 Salt 属性,该属性提供了要应用于密码的唯一盐。最后,他们将密码哈希与盐一起存储,这将允许将相同的盐和迭代次数应用于任何密码猜测,以验证是否提供了正确的密码。
Identity 2.0 系统生成的用于用户帐户的数据库表占用空间要小得多。
下面我们可以看到,当用户的密码通过 Identity 2.0 中的上述 Crypto.cs
类进行哈希处理后的最终结果,是由 ApplicationUserManager
生成的记录。
你也看到了吗?
现在,如果您阅读了PBKDF2文档,您可能会注意到,当它在 2000 年问世时,建议的迭代次数是 1000 次。如果您应用摩尔定律,您可能会像许多人一样得出结论,自最初草案以来,硬件已经显著增加了 8 倍以上。不幸的是,我们仍然部署原始的 1000 次迭代计数,而没有任何覆盖此值的能力(至少对于更高的轮数来说)。
还有其他身份验证框架使用其他基于密码的密钥派生函数,例如 bcrypt 和 scrypt,它们实现了 blowfish 密码,在某些情况下允许指定哈希轮数。但无论您使用哪个框架,**请确保它经过充分测试,并且已经经过了反复的验证。**
所以我们没事了吧?
那么,我们是否利用了一个运转良好的框架,一个痛苦而刻意缓慢的身份验证系统,比如 ASP.NET Identity 2.0,我们的密码现在是安全的,对吧?还没完全(下起了坏天气!)。为什么?因为在这个密码存储的整个马戏团中还有最后一个元素——那就是密码本身。你在说什么 Willis?
简单的事实是,密码的强度对攻击者的能力有显著影响。直接摘录Jeff Atwood的精彩演示,您自己看看。
- 随便抓一个随机密码,例如 ML9Y6#pM。
- 将其输入GRC 密码破解检查器。
- 勾选“Massive Cracking Array”(大规模破解阵列),您会发现它只需要 1.12 分钟。
是的,没错,破解一个包含大小写字母、数字和特殊字符的 8 位密码只需要略多于 1 分钟。有趣的是,如果将其增加到 10 位,则需要 1 周。如果您将其增加到 12 位(比 10 位多 2 位),则需要 1.74 世纪。
关于好密码的话题本身就可以写一篇帖子,而且已经被写了很多次了,但当我们看到轻松获得的 GPU 集群设备,它们每秒能够吐出数百万甚至数十亿个密码猜测,以及我前面提到的云计算能力时,我们就需要强制执行更严格的密码要求。
为了更详细地说明这一点,您认为以下哪一个会更强?
- 一枚戒指,统御所有!
- _B3n!dle#1
我甚至懒得问您哪个更容易记住。但毫无疑问,第二个只需要大约 2 小时,而第一个则不可行。
结论
密码存储是一项棘手的业务。它始于您的应用程序设计,确定您愿意强制用户遵守的密码强度限制。然后,确保您使用的是经过充分测试、更新和维护的框架,该框架包含一个众所周知的、刻意缓慢的哈希算法(最好允许调整工作负载)。
在 OWASP #6 敏感数据泄露的第二部分中,我们将讨论加密静态数据和传输中的数据。
发人深省
这里有一些数据供您思考,这些数据是由 Jeremi Gosney 的25 GPU 集群设备整理出来的,该设备是前段时间建造的。
OWASP #6 防止敏感数据泄露 - 第一部分 最先出现在 LockMeDown.com。
是否曾想过一个故事叙述与技术播客的结合会是什么样子?那么,别再犹豫了,Lock Me Down 播客与您听过的任何其他技术播客都不同。神秘、引人入胜的故事?有。面向开发人员的信息性和指导性 Web 安全信息?有。公司疯狂的安全失误?也有。