【翻译】安全编程指南

安全编程指南

这是文章的翻译;
博客园不支持很好的 MarkDown 格式,懒得调整了,更友好地格式见这里

简介

该指南的目的是提供一个简单一致的方法保证 web 应用和 web 服务的安全。指南主要聚焦于 web 应用的安全,但是提到的概念也可以普遍应用于应用程序的安全控制和设计。本文主要聚焦于安全指南,并且会在部分地方提供示例代码。

状态

该指南的内容会根据最新的技术进行更新,指南内容确保是正确的,可以放心使用,如果用任何疑问和建议都可以直接邮件联系 mcoates@mozilla.com。

布局

该指南主要讨论一些逻辑上的安全以及设计安全应用的方法,而不会去讨论如何防范每种类型的攻击。简而言之,本文是创建安全应用的指南,而不是每种类型的攻击的深度分析。

快速开始

以下是几个经常被遗漏但是和绝大部分网站开发相关的要点:

  • 对于所有的 cookie, 设置 HTTPonly 和安全标志;
  • 确保登录授权相关的页面使用的是 HTTPS;
  • 不要信任任何用户数据(用户输入、headers、cookie 等),使用之前先验证。

安全编程指南

1. 认证

1.1 需要关注的攻击:

  • 暴力密码破解;
  • 用户枚举;
  • 大规模账号锁定(帐号 DoS);
  • 离线 hash 攻击(时间权衡);
  • 丢失密码。

1.2 密码复杂度

网站设计都应该有如下的密码设计策略:

  • 密码至少包括 8 个字符;
  • 密码至少要包括字符和数字两种类型;
  • 需要实行密码黑名单(联系安全部门获取)。

1.3 关键网站

关键网站应该增加如下密码策略:

  • 密码需要一个或者多个特殊字符。

1.4 密码轮换

事实证明,密码轮换有点困难,只有在应用内缺少监控或者有一些比较合理的理由时才应该使用,例如密码过短或者缺少密码控制。

  • 授权帐户的密码应该 3-4 个月修改一次;
  • 普通帐号也建议实行密码轮换;
  • 需要对密码修改事件产生应用日志。

1.5 帐号锁定或者登录失败

帐号锁定和登录失败应该根据应用程序进行评估,不管哪种情况,应用程序都应该能够判定是否重复的使用同样的密码,或者一直使用不同的密码进行攻击。这两种情况应该返回相同的错误信息,例如

The username or password you entered is not valid

对于这些异常情况,日志系统将非常关键。这些事件进入了安全系统后,安全系统就可以以此来采取行动。对应用系统来说,也可以采取相应的行动来减缓或停止攻击进程,例如使用图片验证码或者根据 IP 地址进行延迟。只要失败的尝试次数达到限制,就应该使用图片验证码。

1.6 密码重置

进行密码重置的时候将会向用户的邮箱发送一个重置链接,不管用户的用户名和邮箱是否合法,总是会返回如下消息:

邮件已经发送到邮箱,更多信息请邮件内查看。如果您没收到邮件,请确认一下注册邮箱是否正确。

我们不会提供认可以让攻击者判断用户名或者密码是否合法的信息,避免攻击者通过网络钓鱼或暴力破解获取到合法帐号。

1.7 修改和验证邮箱功能

发送给邮箱的连接不应该带有验证的 session;

邮箱验证码第一次使用或者 8 小时没使用应该设置过期。

1.8 密码存储

与密码策略不同,存储密码需要遵循以下标准:

  • 存储在数据库中的密码需要用 hmac+bcrypt 进行加密。

需要加密的原因如下:

  • bcrypt 提供了一种散列机制,可以通过配置来消耗大量的时间,防止多台计算机的暴力破解;
  • bcrypt 能够轻松的随时调整,从而增加防护强度,可以用来保护更大的系统;
  • hmac 值的密钥存储在文件系统中,而不是存在数据库的密码哈希中。即使数据库由于 SQL 注入而被损坏,密钥由于存在文件系统中,仍然是未知的。由于 bcrypt 和 hmac 的复杂性,显著的增加了暴力破解密码的难度;
  • 如果 bcrypt 设计存在缺陷导致密码泄露或者攻击者容易破解,则 hmac 只能作为辅助的防护手段。

这是一个代码示例:https://github.com/fwenzel/django-sha2

时刻记住尽管 bcrypt 是安全的,你仍然需要想一个良好的密码。如果密码是 123,或许通过算法破解会很慢,但是在破解之前别人已经可以猜到了。

1.9 旧密码哈希

  • 超过一年的密码哈希值应该从系统中删除;
  • 在密码哈希迁移之后,如果用户在三个月内没有登录进行转换,则旧密码哈希值应该被删除。

1.10 迁移

下面的迁移进程使用了不同于上面提到的标准哈希的哈希算法,用来迁移应用。这种方法的好处是能够在用户不重置密码的情况下,立即更新到强的、推荐的哈希算法。

1.10.1 迁移过程:

迁移数据库全部密码哈希实体的过程如下,这是一次性的离线迁移。存储在数据库中的格式是: {algo}${salt}${migration_hash}

  • {algo} is {sha512+MD5},
  • {salt} is a salt unique per-user,
  • {migration_hash} is SHA512(salt + existingPasswordHash)

新帐号或者密码改变的帐号用了上面提到的标准哈希进程

1.10.2 新的登录过程
  1. 尝试使用新的哈希登录。当然这会涉及到执行旧密码哈希过程,然后加盐,最终执行 sha512;

    Example: Old password hash process is md5
    Migration Hash = sha512(perUserSalt + md5(user supplied password))

  2. 如果迁移后的哈希认证成功,则用用户提供的密码并根据上面定义的算法计算新哈希,并用新哈希重写迁移后的哈希。

  3. 如果认证失败,则用户可能已经用了新哈希,继续用新哈希尝试,还失败的话则提示错误。

2. Session 管理

关注点:Session 劫持,Session 固定,暴力破解合法 Session IDs。

Session ID 长度

Session token 至少需要 128 位。

生成 Session ID

Session token 应该尽可能由服务端处理,或者通过加密的安全随机数生成。

不活跃超时

Session 在授权后,超过一定的时间不活跃应该设置超时,建议时间为 15 分钟。

安全标志位

安全标志位应该在设置 cookie 时产生,这会告诉浏览器不要通过 HTTP 发送 cookie。安全标志位的目的是避免用户点击了一些链接后,cookie 意外泄漏。

一个示例,或者看翻译

HTTP-Only 标志

该标志是为了阻止恶意脚本(XSS)访问用户 Session。

登录

登陆时应该重新设置 Session,避免通过 XSS 在相关域名或者子域名下的 Session 固定。

登出

登出后 Session 在服务端应该失效,在客户端需要重写或者过期 Session 值。

3. 访问控制

关注点:目标站点的网站功能枚举,执行未授权的功能或者修改未授权的数据。

3.1 表达层

只把授权的特征或者功能展示给相应的用户。

不要把链接或者功能展示给未授权的用户,这样可以最少的把访问权限和一些特权信息暴露给未授权用户。

3.2 业务层

执行动作之前先检查权限。

访问之前先验证执行权限,一个用户可能会伪造 GET 或者 POST 请求去执行未授权的功能。

3.3 数据层

对目标数据的访问也要验证权限。

确保授权用户只能在目标数据上进行相应的操作,不要因为用户有了某个数据集的操作,就能在其他数据集上也执行相应的操作。

4. 输入验证

关注点:脏数据。

4.1 输入验证的目标

进行输入验证是为了最小化的减少输入造成的脏数据。输入验证不是阻止 XSS 和 SQL 注入的主要方法,这些包含在下面章节的输出编码中。

输入验证必须要做到以下几点:

  • 适用于任何用户输入的数据;
  • 定义可以接受的字符串类型(主要在 U+0020 到 U+007E 之间,虽然可以删除大多数特殊字符,控制字符几乎是不需要的);
  • 限制输入的的字符长度范围,例如 1-25.

这是几个输入验证的例子,对于每个域都定义了可以接受的字符以及接受的字符的长度:

  • 用户名:包含字符、数字和特殊字符,3-10 个字符长度;
  • 名字:字母、单撇号、下划线,1-30 个字符;
  • 简单的 US 压缩码:数字,5 个字符。

注意:这仅仅是一个例子,具体的输入验证需要根据你的具体情况调整。

4.2 Javascript 验证 VS 服务端验证

需要注意的是攻击者能够通过禁用 js 或者 Web 代理来绕过 js 验证,所以需要 js 验证的同时,服务端也要进行验证。

4.3 使用积极的方法

攻击者的种类是变化多端的,可以用正则表达式定义好接受的输入,并拒绝任何其他的不符要求的输入。换句话说,我们应该 “接受合法的输入” 而不是 “拒绝不合法的输入”。下面是例子:

用户名域接受的合法的输入是如下正则表达式:[0-9a-zA-Z]{3,10},任何不匹配的输入都要拒绝;
拒绝不合法输入的例子是维护一个不合法输入的列表,用户输入的时候进行判断是否在不合法的列表中,但这会带来一个问题,我们很难列举出所有的不合法输入。

4.4 输入验证的鲁棒性

用户输入的任何数据都需要验证,这包括如下:

  • 表单数据;
  • URL 参数;
  • 隐藏域;
  • Cookie;
  • HTTP 头;
  • 任何其他在 HTTP 请求中的数据。

4.5 验证富文本

验证用户输入的富文本非常困难,可以考虑更合适的方法,例如 HTML Purifier(PHP),AntiSamybleach(Python)

5. 输出编码

输出编码是组织 XSS 和注入攻击的主要方法,输入验证虽然帮助减少了脏数据,但只是一种二次验证。

关注点:XSS 以及各种(SQL/OS/LDAP/XML)注入。

5.1 阻止 XSS

  • 当返回 HTML 页面的时候,用户输入的任何数据都需要被编码,以防止恶意数据(如 XSS)的执行。例如 <script> 将会返回 &lt;script&gt;
  • 只对页面中插入用户输入的数据的地方进行具体编码。例如对于 HTML body 中的数据进行 HTML 实体编码就比较合适,然而对于脚本中的用户数据则需要使用 js 进行编码。

这是一个阻止 XSS 的详细信息。

5.2 阻止 OS 注入

  • 尽可能阻止用户数据进入操作系统;
  • 确保强大的字符转义,防止用户添加额外的可以被操作系统执行的字符(例如在数据后面链接上 |,这样就可以执行另一个命令了)。记住在进行转义的时候,要使用积极的方式,这是一个例子

点此了解更多

5.3 阻止 XML 注入

  • 和 OS 注入类似,除了输入验证之外,也要用一些合适的方法转义能被解释为 XML 的字符,例如:< > " ' &;
  • 如果收到原始的 XML 文件需要验证,可以联系 infrasec@mozilla.com 进行讨论。

6. 跨域

关注点:CSRF、Clickjacking、第三方脚本、和第三方进行的不安全交互。

6.1 阻止 CSRF

攻击者伪造一个 POST 表单或者图片标签来代替授权用户执行一些操作,对于跨站请求伪造可以点击这里了解更多。

  • 任何改变状态的操作都需要一个安全的随机 Token(例如 CSRF token) 来阻止 CSRF;
  • CSRF Token 的一些特征
    • 每个用户或者每个 session 都是独一无二的;
    • 绑定到单一用户;
    • 巨大的随机值;
    • 通过加密的安全随机数产生
  • CSRF Token 作为一个隐藏的域和表单一起提交,如果是改变状态的 GET 方法,则在 URL 内一起提交;
  • 如果 token 验证失败,服务端则会拒绝请求。

注意:一些框架自带防止 CSRF 的能力(例如 Django)。建议使用这种成熟的框架,而不是自己造一个。

6.2 阻止点击劫持

点击劫持通过在页面上的浮层或者框架内诱导用户点击或者输入数据。用户的这些行为会发送的攻击者的网站上,然后会执行一些用户不知道的操作。点击这里了解更多点击劫持的信息。

为包含 HTML 内容的所有响应设置 x-frame-options 头。可能的值为 “DENY” 或 “SAMEORIGIN”。

  • DENY 将会阻塞任何的网站构建内容;
  • SAMEORIGIN 将会阻塞除本站之外的任何网站构建内容。

除非已经确定了特殊的需求,都建议设置为 "DENY".

这是一个代码示例

6.3 第三方脚本

  • 使用第三方脚本要小心,虽然可以确认每个人初次使用的时候会进行检查,但是当脚本更新后,也需要进行检查;
  • 确保任何第三方脚本只在本地使用,不要动态的链接到第三方网站。

6.4 链接 Twitter, Facebook 等网站

  • 如果使用 Oauth,需要确保整个通信过程中使用 HTTPS,这包括了初始化 Oauth 请求和作为参数传递的任何 URL。
  • 如果重定向到应用内的登陆页,也要确保 URL 是 HTTPS,不要用 HTTP;
  • 确保按钮不会通过所在的网页向第三方网站发送请求,例如用户点击按钮的时候,没有明确的意图,就不要请求第三方网站。

7. 安全传输

关注点:中间人、密码被盗、session 被盗。

7.1 什么时候使用 SSL/TLS

  • 登陆登出页面需要使用 HTTPS;
  • 访问用户登陆表单的界面需要使用 HTTPS,这是一个使用 HTTPS POST 传递表单的补充;
  • 包括 CSS、脚本和图片在内的任何授权页面需要通过 HTTPS 访问,如果不这样做,会在浏览器中显示一个 SSL 警告。

7.2 不要允许 HTTP 访问安全页面

  • 任何授权页面都要通过 HTTPS 访问;
  • 当用户使用 HTTP 请求的时候,最安全的方法是显示一个警告,并引导用户收藏或则使用 HTTPS 访问界面,作为将来使用。当然最好的方式是重定向到和 HTTP 界面等价的 HTTPS 界面。

点击这里了解更多关于 SSL/TLS 的内容。

7.3 使用 STS

只要有可能,就要使用 STS

8. 内容安全策略(CSP)

开发没有内连 js 的网站更容易适应 CSP。链接

8.1 日志

看这里

8.2 管理登陆界面

下面是对任何网站都通用的登陆界面管理内容。

  1. 采取控制措施防止暴力攻击(下面的方法都是合适的);
  • 在 ssl vpn 后面管理界面;
  • 帐号登出;
  • 5 次失败之后进行图像识别;
  • 管理界面进行 IP 限制。
  1. 登陆页和管理页面只能通过 HTTPS 访问,任何 HTTP 访问都被重定向到 HTTPS;
  2. Session ID 使用安全标志位;
  3. Session ID 使用 HTTPOnly;

查看配置

9. 上传

关注点:恶意用户可能会上传包含 js、HTML 或者其他可执行代码的内容,任意的文件覆盖;

9.1 通用上传

  1. 上传验证
    • 用数输入的任何文件都要进行验证,确保使用的是预期的文件名;
    • 确保上传的文件大小不超过限制。
  2. 上传存储
  • 重新命名文件进行存储,不要在文件中或临时文件中使用用户输入的任何字符;
  • 把用户上传的文件进行分别存储(例如 .net 或者 .org),并且需要分析文档中的恶意内容(例如反恶意分析,静态分析等)。
  1. 对上传内容提供通用服务
  • 确保上传的图片使用了正确的类型(例如 image/jpeg、application/x-xpinstall)。
  1. 注意特殊的文件
  • 对特定的文件类型和拓展名进行上传的白名单设置。虽然如此,也需要意识到下面的文件类型可能会造成安全问题;
  • “crossdomain.xml” 允许跨域数据在 Flash, Java and Silverlight 中下载。如果网站中对这种脚本进行了授权,可能会导致跨域数据泄漏或者 CSRF 攻击。这种脚本可能会对不同版本的插件有各种复杂的依赖问题,最好的方法就是直接禁止文件被命名为 "crossdomain.xml" 或者 "clientaccesspolicy.xml"。
  • ".htaccess" 和 ".htpasswd" 提供给服务器个人路径基础的配置选项,也需要被禁止。点击 这里 了解更多。

9.2 图片上传

上传验证

  • 使用图片重写库去验证图片是合法的,并且过滤掉图片中任何无关的内容;
  • 依赖于监测到的上传的图片的内容类型去设置被存储图片的拓展类型是合法的(例如不要仅仅信任来自于上传图片的 header);
  • 确保上传的图片类型在可信的图片类型之内,例如 jpg、png 等。

9.3 压缩上传

上传验证

  • 确保上传的压缩文件的大小不超过限制;
  • 确保上传的文件类型合法,例如 zip、rar、gzip 等;
  • 对于结构化上传,确保压缩文件中包含需要的文件。

10. 错误处理

关注点:敏感信息泄漏、系统信息泄露、其他漏洞的利用。

10.1 用户可见的错误消息

用户可见的错误提示不应该包含系统、诊断调试信息。

10.2 Debug 方式

Debug 方式是许多应用或者框架支持的,并且在网站应用中是可以接受的,然而仅仅应该在调试阶段使用。

10.3 格式化错误信息

错误信息经常被记录成文本文件或者其他在浏览器中可见的文件

  • 文本文件:确保任何换行符(%0A%0C)能够被正确处理,避免日志伪造;
  • Web 日志文件:查看日志的时候,确保任何 HTML 字符能被正确编码,防止 XSS.

10.4 推荐的错误处理方式

  • 只在系统日志中存储必须的错误信息;
  • 原始的错误信息对用户不可见;
  • 如果需要,把错误日志的映射码提供给用户,然后根据用户提供的错误码诊断问题。

11. 更多阅读

12. 联系人

posted @ 2018-07-22 22:58  潇湘旧友  阅读(252)  评论(0编辑  收藏  举报