Web 应用程序安全检查表 (2021)
原始帖子可以在这里阅读。
获取电子表格!
立即订阅AppSec Monkey 并免费获得 2021 年 Web 应用程序安全检查表电子表格作为欢迎礼物!
概述
开发人员真是太害怕了!代码中只要有一个错误,依赖项中有一个漏洞,开发者工作站一旦被入侵,你的数据库就会被存入 Pastebin,你就会上新闻。
那么,该去哪里寻求指导呢?OWASP 的十大漏洞列表太短,而且更侧重于列出漏洞而非防御措施。而ASVS 的列表则比较隐晦,不便于实际应用。
本文尝试采用黄金法则。我们将介绍一些实用步骤,帮助您从各个角度保障 Web 应用程序的安全。现在就开始吧!
- 防御浏览器端的威胁
- 使用 HTTPS 且仅使用 HTTPS 来保护您的用户免受网络攻击
- 使用 HSTS 和预加载保护您的用户免受 SSL 剥离攻击
- 使用“安全”属性提供 Cookie,以保护用户免受网络攻击
- 安全生成 HTML 以避免 XSS 漏洞
- 安全使用 JavaScript 避免 XSS 漏洞
- 清理和沙盒化不受信任的内容以避免 XSS 漏洞
- 实施有效的内容安全策略,保护用户免受 XSS 和其他漏洞的侵害
- 使用 HttpOnly 属性提供 cookie,以保护它们免受 XSS 攻击
- 使用适当的 Content-Disposition 标头提供下载以避免 XSS 漏洞
- 使用适当的 Content-Disposition 标头来提供 API 响应,以避免反射下载漏洞
- 使用平台的反 CSRF 机制来避免 CSRF 漏洞
- 验证 OAuth/OIDC 状态参数以避免 CSRF 漏洞
- 正确使用 HTTP 动词以避免 CSRF 漏洞
- 使用 SameSite 属性提供 cookie,以保护您的用户免受 CSRF 漏洞的侵害,并可选择防御 XSS
- 登录时创建新的会话 ID,以防止会话固定攻击
- 正确命名您的 Cookie 以防止会话固定攻击
- 提供适当的 Cache-Control 标头,以保护用户数据不被后续计算机用户访问
- 在注销时提供 Clear-Site-Data 标头,以保护用户数据不被后续计算机用户窃取
- 正确注销用户,以保护他们的数据不被后续计算机用户获取
- 使用 SessionStorage 存储 JavaScript 机密信息,保护用户数据不被后续计算机用户窃取
- 不要在 URL 中传输敏感数据,因为 URL 并非设计为保密的
- 使用引荐来源策略防止 URL 地址泄露到其他网站
- 为您的应用程序使用唯一的域名,以保护它免受同一来源下的其他应用程序的侵害(反之亦然)
- 除非必须,否则不要使用 CORS;如果必须使用,请谨慎使用
- 正确使用 WebSocket 以避免 CSRF 和其他漏洞
- 使用 U2F 令牌或客户端证书保护您的关键用户免受网络钓鱼攻击
- 防御服务器端威胁 - 应用程序
- 正确验证输入以保护您的应用程序免受...如此多的漏洞
- 优雅地捕获异常以避免泄露技术细节
- 不要自己进行身份验证
- 验证所有内容以减少攻击面
- 在应用程序中使用 MFA 来打破与身份提供者的信任关系
- 使用严格的访问控制来防止未经授权访问数据或功能
- 使用适当的工具和技术来避免注入漏洞
- 安全地构建数据库查询以避免 SQL 注入漏洞
- 如果必须运行操作系统命令,请正确执行以避免命令注入和相关漏洞
- 通过正确配置解析器来避免 XML 漏洞
- 通过使用适当的 URL 构造类来避免 URL 注入漏洞
- 通过使用适当的类来构造路径,避免路径遍历漏洞
- 如果可以避免,请不要将文件系统用于不受信任的内容(例如上传)
- 不要执行动态代码以避免远程代码执行漏洞
- 谨慎使用序列化以避免反序列化漏洞
- 防御服务器端威胁 - 基础设施
- 防御服务器端威胁 - 架构
- 防御服务器端威胁 - 监控
- 防御服务器端威胁 - 事件响应
- 安全开发考虑
- 结论
防御浏览器端的威胁
最终用户方面存在一些威胁,作为开发人员,您可以帮助缓解这些威胁。这些威胁包括:
- 通过用户浏览器中的恶意网站/链接进行攻击。
- 对用户本地网络的攻击。
- 有人在用户之前或之后访问共享 Web 浏览器的攻击(例如,如果敏感信息留在浏览器缓存中,那么即使前一个用户已注销,后续计算机用户也将能够检索它)。
使用 HTTPS 且仅使用 HTTPS 来保护您的用户免受网络攻击
你可能已经知道这一点了。加密用户浏览器和你的 Web 服务器之间的所有连接。禁用一些较旧的加密套件和协议也无妨。
仅仅加密网站的“敏感”部分是不够的。域名下任何位置的单个未加密 HTTP 请求都可能被网络上的攻击者拦截,然后攻击者可以伪造来自服务器的包含恶意内容的响应。
幸运的是,如今 HTTPS 很容易,您可以免费获得证书(LetsEncrypt)和自动证书创建/管理工具(CertBot)。
进一步阅读
使用 HSTS 和预加载保护您的用户免受 SSL 剥离攻击
HSTS 或者Strict-Transport-Security
是一个标头,可用于告诉 Web 浏览器从现在开始连接到此域时始终使用加密连接(HTTPS)。
这将防止所谓的SSL 剥离攻击,即网络上的攻击者拦截浏览器发出的第一个 HTTP 请求(通常未加密),并立即伪造对该未加密 HTTP 请求的回复,假装是服务器并将连接降级为拦截的纯文本 HTTP。
需要注意的是,HSTS 仅在用户之前已成功访问过该应用程序时才会保护该应用程序。为了克服此限制,您应该将您的网站提交到https://hstspreload.org,以便浏览器供应商可以将您的域名硬编码到 HSTS 列表中。
例子
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
警告
实施 HSTS 时务必谨慎。它会强制加密流量进入你的网站,如果网站仍然保留纯文本,可能会崩溃。因此,请先从小规模开始
max-age
,等你确信一切正常后再逐步增加。将预加载留到最后一步,因为取消它会很麻烦。
进一步阅读
使用“安全”属性提供 Cookie,以保护用户免受网络攻击
使用属性配置您的 Cookie Secure
。这将防止它们通过(意外或强制的)未加密连接泄露。
Set-Cookie: foo=bar; ...other options... Secure
进一步阅读
安全生成 HTML 以避免 XSS 漏洞
为了避免 XSS(跨站点脚本)漏洞,请使用以下方法之一:
- 完全静态的网站(例如 JavaScript SPA + 后端 API)。避免生成 HTML 问题的最有效方法是根本不生成 HTML。
- 模板引擎。如果您有一个传统的 Web 应用程序,其中 HTML 在后端服务器上生成和参数化,请不要通过字符串连接来编写 HTML。而是使用模板引擎,例如
Twig
适用于 PHP、Thymeleaf
Java、Jinja2
Python 等的模板引擎。
如果您使用模板引擎,请确保正确配置它以自动正确编码参数,并且不要使用任何绕过自动编码的“不安全”功能。
进一步阅读
安全使用 JavaScript 避免 XSS 漏洞
为了避免 JavaScript 端的 XSS(跨站脚本)漏洞,请勿将任何不受信任的数据传递给可能最终执行代码的函数或属性。这里需要运用常识,但一些常见的可疑数据包括:
eval
,setTimeout
,setInterval
, ETC。innerHTML
、React 的dangerouslySetInnerHTML
等等onClick
,onMouseEnter
,onError
, ETC。href
,src
, ETC。location
,location.href
, ETC。
进一步阅读
清理和沙盒化不受信任的内容以避免 XSS 漏洞
最好避免使用不受信任的内容。但有时您需要从远程来源获取原始 HTML,然后将其渲染到您的网站上。或者,您可能需要允许用户使用所见即所得的编辑器撰写帖子。有很多用例。
为了避免这些情况下的 XSS(跨站点脚本)漏洞,请先清理内容DOMPurify
,然后在沙盒框架内呈现它。
即使你的所见即所得库声称可以清除 HTML 中的恶意内容,你仍然可以通过重新净化和沙盒化内容来破坏这种“我相信我的所见即所得库能够清理内容”的信任关系。你破坏的信任关系越多,你的应用程序就越安全。
进一步阅读
实施有效的内容安全策略,保护用户免受 XSS 和其他漏洞的侵害
内容安全策略 (CSP) 可以有效防御 XSS(跨站脚本)攻击。此外,它还可以防御点击劫持等攻击。
所以一定要使用它!默认情况下,CSP 几乎可以阻止所有事情,所以你设置的内容越少越好。例如,以下是一个不错的入门策略:
Content-Security-Policy: default-src 'self'; form-action 'self'; object-src 'none'
它允许从 Web 应用程序的源加载脚本、样式、图像、字体等,但其他内容则无法加载。最值得注意的是,它会阻止内联脚本 ( <script>...</script>
),这使得利用 XSS 漏洞变得更加困难。
此外,该form-action: 'self'
指令还可以防止在网站上创建恶意 HTML 表单(例如“您的会话已过期,请在此处输入您的密码”)并将其提交给攻击者的服务器。
无论您做什么,都不要指定script-src: unsafe-inline,因为这样您的 CSP 将失去其魔力。
最后,如果您担心 CSP 会在生产中破坏某些功能,您可以首先以Report-Only
以下模式进行部署:
Content-Security-Policy-Report-Only: default-src 'self'; form-action 'self'
要为您的网站生成 CSP 政策,请使用CSP 工具
进一步阅读
使用 HttpOnly 属性提供 cookie,以保护它们免受 XSS 攻击
使用属性配置您的 Cookie HttpOnly
。这将阻止 JavaScript 代码访问它们,从而使攻击者在遭受 XSS(跨站脚本)攻击时更难窃取它们。当然,对于需要 JavaScript 访问的 Cookie,您无法执行此操作。
Set-Cookie: foo=bar; ...other options... HttpOnly
进一步阅读
使用适当的 Content-Disposition 标头提供下载以避免 XSS 漏洞
为了避免在向用户提供下载时出现 XSS(跨站脚本)漏洞,请在发送下载时添加Content-Disposition标头,以指示文件是否为附件。这样,文件就不会直接在最终用户的浏览器中呈现,否则,例如 HTML 或 SVG 文件,就可能导致 XSS 漏洞。
Content-Disposition: attachment; filename="document.pdf"
如果您希望在浏览器中打开某些特定文件(例如出于可用性原因的 PDF 文档),并且您知道这样做是安全的,则可以省略标题或更改attachment
为该inline
特定文件扩展名。
进一步阅读
使用适当的 Content-Disposition 标头来提供 API 响应,以避免反射下载漏洞
有一种称为反射文件下载 (RFD) 的攻击,其工作原理是制作一个 URL,该 URL 从您的 API 下载为恶意文件扩展名,反映其中的恶意负载。
您可以通过在 API HTTP 响应中返回Content-Disposition
带有安全的标头来防止此类攻击。filename
Content-Disposition: attachment; filename="api.json"
进一步阅读
使用平台的反 CSRF 机制来避免 CSRF 漏洞
为了防止跨站点请求伪造(CSRF) 漏洞,请确保您的平台的反 CSRF 机制已启用并按预期运行。
进一步阅读
验证 OAuth/OIDC 状态参数以避免 CSRF 漏洞
有一种与 OAuth/OIDC 相关的 CSRF 攻击,攻击者会在不知情的情况下使用攻击者的帐户登录用户。如果您正在使用 OAuth/OIDC,请确保使用正确配置且可靠的软件库来处理身份验证流程,以确保state
参数得到验证,从而避免这种情况。
进一步阅读
正确使用 HTTP 动词以避免 CSRF 漏洞
切勿使用除POST
、PUT
或PATCH
之外的任何东西DELETE
进行任何更改。GET
例如,请求通常不受反 CSRF 机制的保护。
进一步阅读
使用 SameSite 属性提供 cookie,以保护您的用户免受 CSRF 漏洞的侵害,并可选择防御 XSS
使用 属性配置您的 Cookie SameSite
。这将防止大多数 CSRF(跨站请求伪造)漏洞(例如,恶意网站会以您不知情的用户的名义提交表单)被攻击者成功利用。有两种模式:Lax
和Strict
。
该Lax
模式(Lax+Post 缓解机制除外,见下方链接)可以有效防御大多数 CSRF 攻击,但基于 GET 的 CSRF 漏洞除外,因为这类漏洞会在 GET 请求处理程序中错误地进行更改(例如修改某些数据库记录)。该Strict
模式也能防止此类错误被利用。
然而该Strict
模式还有另一个强大的副作用,它使得反射型 XSS(跨站点脚本)漏洞实际上也不可能被利用。
话虽如此,该Strict
模式并不适用于大多数应用程序,因为它会破坏经过身份验证的链接,也就是说,如果您的用户已登录并在另一个网站上打开指向该应用程序的链接,那么在打开的选项卡/窗口中,用户将不会登录(因为由于严格模式,会话 cookie 未随请求一起发送)。
但至少在模式SameSite
下实现Lax
,这样做没有坏处,并且当 CSRF 漏洞潜入您的代码库时,它可以作为一种极好的保护措施。
Set-Cookie: foo=bar; ...other options... SameSite=Lax
...或者:
Set-Cookie: foo=bar; ...other options... SameSite=Strict
进一步阅读
登录时创建新的会话 ID,以防止会话固定攻击
- 比如说,攻击者会偷偷将 Cookie
JSESSIONID=ABC123
植入用户的浏览器。攻击者可以通过多种方式实现这一目标。 - 您的用户使用他们的凭证登录,并
JSESSIONID=ABC123
在登录请求中提交攻击者选择的 cookie。 - 您的应用程序对 cookie 进行身份验证,并且用户将从此时起登录。
- 攻击者也拥有 cookie,从那时起也以用户身份登录。
因此,为了防止这种情况,请创建一个新的、经过身份验证的会话 ID 并将其返回给用户,而不是对可能已被泄露的现有 cookie 进行身份验证。
进一步阅读
正确命名您的 Cookie 以防止会话固定攻击
这一点可能不太为人所知,但说到 Cookie,名称至关重要!命名您的 Cookie __Host-Something
,网络浏览器就会……
- 不允许通过未加密的连接设置 cookie,以防止会话固定攻击以及与攻击者强制将 cookie 放入用户浏览器相关的其他威胁。
- 不允许子域名覆盖 cookie,这可以防止来自受损/恶意子域名的类似攻击。
Set-Cookie: __Host-foo=bar ...options...
进一步阅读
提供适当的 Cache-Control 标头,以保护用户数据不被后续计算机用户访问
默认情况下,网络浏览器会缓存看到的所有内容,以加快页面加载速度并节省网络带宽。
缓存是将访问过的网站和下载的文件以未加密的形式存储在磁盘上,直到有人手动删除它们。
您的应用程序用户应该能够相信,一旦他们点击“注销”,后续的计算机用户将不再能够访问他们的信息(例如共享库计算机、朋友的电脑等)。
因此,Cache-Control
您应该在所有包含非公开/非静态内容的 HTTP 响应中适当地返回一个名为的标头。
Cache-Control: no-store, max-age=0
进一步阅读
Cache-Control
在注销时提供 Clear-Site-Data 标头,以保护用户数据不被后续计算机用户窃取
另一个用于确保用户数据在注销时被清除的有用标头是 newClear-Site-Data
标头。您可以在用户注销时将其发送至 HTTP 响应中,浏览器将清除该域的缓存、Cookie、存储和执行上下文(JavaScript 变量等,可能通过刷新所有相关选项卡来实现,但在撰写本文时此功能尚未实现)。大多数浏览器都支持该标头,但 Safari 仍然不支持。
您可以按照如下方式发送:
Clear-Site-Data: "*"
进一步阅读
Clear-Site-Data
正确注销用户,以保护他们的数据不被后续计算机用户获取
确保注销会使访问令牌/会话标识符无效,以便当它稍后从浏览历史记录/缓存/内存/等泄露给攻击者时,它将不再可用。
此外,如果有 SSO,那么不要忘记正确调用单点注销端点,否则注销将是徒劳的,因为只要单击“登录”按钮就会自动将用户重新登录,因为 SSO 会话仍然处于活动状态。
最后清除您可能存储了用户信息的任何 cookie、HTML5 存储等。Clear-Site-Data
例如,Safari 尚不支持上述功能,因此您也必须手动清除数据。
使用 SessionStorage 存储 JavaScript 机密信息,保护用户数据不被后续计算机用户窃取
它类似于 LocalStorage,但每个标签页都是唯一的,并且在浏览器/标签页关闭后会被清除。因此,用户数据有可能泄露给下一个电脑用户。
注意
如果您希望您的用户在应用程序的多个选项卡中进行身份验证而无需再次登录,则必须使用事件在选项卡之间同步 sessionStorage。
进一步阅读
会话存储
不要在 URL 中传输敏感数据,因为 URL 并非设计为保密的
URL 地址并非设计用来保密的。例如,它们会显示在屏幕上、保存到浏览历史记录中、通过 referrer 标头泄露,以及保存在服务器日志中。所以,不要把机密信息放在那里。
使用引荐来源策略防止 URL 地址泄露到其他网站
默认情况下,当你从应用程序链接到一个网站,并且用户点击该链接时,Web 浏览器会Referrer
随请求发送一个标头,告知网站链接到了哪个网站。这个标头包含完整的 URL,这至少可能会引发隐私问题。
您可以通过Referrer-Policy
在 HTTP 响应中指定标头来禁用此行为:
Referrer-Policy: no-referrer
进一步阅读
为您的应用程序使用唯一的域名,以保护它免受同一来源下的其他应用程序的侵害(反之亦然)
托管这样的应用程序是危险的:https://www.example.com/app1/
和https://www.example.com/app2/
。这是因为浏览器认为它们属于同一类origin
(相同的主机、端口和方案),这意味着它们将彼此拥有完全访问权限,因此影响 app1 的任何漏洞/恶意内容也会使 app2 陷入危险。
因此,为每个应用程序指定一个自己的起源。因此,解决方案可以是https://app1.example.com/
和https://app2.example.com/
。
注意:
共享父域名的子域名仍然可以为整个域名设置Cookie。例如,app1.example.com
可以设置一个 Cookie,example.com
该 Cookie 随后也会发送到app2.example.com
。这可能会导致某些会话固定漏洞。
如果你现在想知道.herokuapp.com 下的所有应用程序是否都存在漏洞,答案是否定的,因为有公开的后缀列表。此外,通过将 Cookie 命名为“__Host-”,可以防止其被子域名覆盖。本文稍后将介绍此机制。
进一步阅读
除非必须,否则不要使用 CORS;如果必须使用,请谨慎使用
Web 浏览器的安全模型主要基于同源策略,该策略阻止evil.example.com
读取您的电子邮件,但仍允许您使用 jQuery。CORScode.jquery.com
或跨源资源共享是一种允许其他网站违反该策略的手段。
因此,如果您决定需要它,请确保您知道自己在做什么。
验证来源
如果您api.example.com
需要通过 GET 请求进行访问,那么www.example.com
您可以在 上指定以下标头api.example.com
:
Access-Control-Allow-Origin: https://www.example.com
如果您有一个公共 API(比如说您希望整个互联网从客户端 JavaScript 使用的计算器),那么您可以指定一个通配符来源:
Access-Control-Allow-Origin: *
如果您希望允许多个域名访问您的 API(例如,您只想允许 Google 和 Facebook 访问),那么您需要读取Origin
请求的标头,将其与允许的域名列表进行比较,然后返回相应的标头。建议您使用经过严格审查的库来执行此操作,而不是手动修改标头,因为这样做可能会出错。
注意“允许凭据”选项
默认情况下,CORS 不允许带有凭证的请求,即携带用户(会话)Cookie 的请求。但 Web 服务器可以通过指定如下 header 来允许这种请求:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
这很危险,因为它会允许https://www.example.com
像登录用户一样完全访问指定标头的网站。所以如果你必须使用它,请务必小心。
验证方法
尽量减少攻击面并仅允许所需的 HTTP 方法是一种很好的做法。
Access-Control-Allow-Methods: GET
笔记
如果您不需要 CORS,那么就不要使用它,默认情况下它是未启用的。
进一步阅读
正确使用 WebSocket 以避免 CSRF 和其他漏洞
WebSocket 仍然比较新,文档很少,使用时也存在一些风险。因此,请仔细阅读以下内容。
1.加密连接
就像您应该使用https://
而不是一样http://
,使用wss://
而不是ws:///
。
HSTS 也会影响 WebSockets,并会自动将未加密的 WebSocket 连接升级到
wss://
!HSTS 好样的。
2. 验证连接
如果您使用基于 Cookie 的身份验证,并且 WebSocket 服务器与应用程序位于同一域,则您也可以继续使用现有会话进行 WebSocket 连接。请务必注意下一节关于来源验证的内容,否则您将面临麻烦。
如果没有,那么您可以在应用程序中创建一个票证,即绑定到用户 IP 地址的一次性、有时间限制的身份验证令牌,可用于验证 WebSocket 连接。
3. 验证连接的来源
关于 WebSocket,需要理解的关键一点是它们不受同源策略的约束。这意味着任何网站都可以打开到您应用程序的 WebSocket 连接,并且如果您使用基于 Cookie 的身份验证,还可以访问已登录用户的信息。
因此,您必须在 WebSocket 握手中验证连接的来源。您可以通过验证Origin
请求标头来实现。
如果您想要双重安全,请将 CSRF 令牌作为 URL 参数,但为该作业创建一次性使用的唯一令牌,不要使用用于保护应用程序其余部分的 CSRF 令牌(因为在 URL 中发送某些内容可能会在很多地方泄漏)。
进一步阅读
使用 U2F 令牌或客户端证书保护您的关键用户免受网络钓鱼攻击
如果您的威胁模型包括网络钓鱼攻击,即“如果攻击者创建一个虚假网站,窃取我们的管理员/CEO/等的用户名、密码和 MFA 代码” ,那么您应该使用U2F 令牌或客户端证书来防范此类攻击,即使攻击者拥有用户名、密码和 MFA 代码,也无法伪造它们。
注意:对于普通用户来说,强制执行网络钓鱼防护通常有些过头,但为最终用户提供使用 YubiKey 等工具的可能性
并无不妥。不过,您可以做的是向用户提示有关网络钓鱼攻击的一般信息。
进一步阅读
创建不可钓鱼的安全密钥
防御服务器端威胁 - 应用程序
正确验证输入以保护您的应用程序免受...如此多的漏洞
尽可能严格地验证所有输入。这将使攻击者难以发现和利用许多漏洞。拒绝无效输入,不要对其进行过滤。
- 使用限制性数据类型。例如,日期类型使用 DateTime,数字类型使用 Integer,等等。枚举类型用于可能值的列表。尽量避免使用 String 类型。
- 当您确实必须使用字符串时,请尽可能为其设置长度限制。
- 当您确实必须使用字符串时,请将字符集限制为最小值。
- 如果您处理 JSON,请使用 JSON 模式。
- 如果您处理 XML,请使用 XML 模式。
优雅地捕获异常以避免泄露技术细节
切勿向最终用户显示堆栈跟踪或类似的调试信息。应准备一个全局异常处理程序,用于捕获未处理的异常并向浏览器显示通用错误消息。这将使攻击者更难以发现和利用应用程序中的漏洞。
不要自己进行身份验证
在用户身份验证过程中,有很多事情可能出错。防御各种密码猜测和用户枚举攻击、管理密码重置、存储凭证等等,都并非易事。这几乎就像密码学一样:普通人不应该独自完成这些工作。
请使用身份提供商(例如 )来auth0
验证用户身份,并OpenID connect
在应用程序中使用广泛使用且安全的软件组件实现协议(通常为 )。如果您不想使用像 auth0 这样的第三方身份提供商,那么您可以自行托管类似 的服务KeyCloak
。
进一步阅读
验证所有内容以减少攻击面
配置您的应用程序,使所有内容默认经过身份验证。然后为静态资产以及某些端点(例如登录页面或“退出”页面)创建必要的例外情况。
在应用程序中使用 MFA 来打破与身份提供者的信任关系
如果您想将“如果有人完全攻破 IDP(身份提供者)会怎样?”纳入您的威胁模型,请在应用程序中使用某种形式的 MFA(多重身份验证)。这样,即使 IDP 遭到黑客攻击,攻击者可以以任何人的身份进行身份验证,攻击者仍然无法知道用户在应用程序本身的 MFA 密钥。
使用严格的访问控制来防止未经授权访问数据或功能
访问控制并非易事,但可以做到正确。只需集中管理,就不会因为忘记在某个控制器函数中检查用户访问权限而导致 IDOR(不安全直接对象引用)漏洞。
- 默认情况下阻止访问所有控制器方法(或等效方法)。
- 允许按角色访问单个控制器。
- 使用方法级别的安全性来限制对服务功能等的访问。
- 使用集中权限评估器来防止未经授权访问单个记录。
- 使用集中权限评估器来过滤返回给客户端的对象。
- 使用具有前端 Web 应用程序和后端 API 的架构,然后在每个应用程序/API 中实现相同的访问控制,而不仅仅是面向 Internet 的部分。
为了稍微澄清一下权限评估器方法,其关键在于:
- 您的数据记录扩展了一个类,该类具有一些用于访问控制的属性。例如
int ownerId
。 - 您的经过身份验证的用户有一个 ID。
ownerId
您有一个权限评估器类,它知道如果对象的权限等于用户的权限,则用户可以访问该对象id
。- 然后将该权限评估器插入应用程序平台的访问控制系统,例如 Spring Security 的 PreAuthorize、PostAuthorize、PreFilter、PostFilter 等。
- 如果您需要比或类似更复杂的访问控制
ownerId
,那么您可以设置(例如)完整的 ACL 系统。
进一步阅读
使用适当的工具和技术来避免注入漏洞
很多漏洞都属于“注入”类别,而且它们都大同小异。这些漏洞包括 SQL 注入、HTML 注入(XSS 的一种形式)、XML 注入、XPath 注入、LDAP 注入、命令注入、模板注入、SMTP 注入、响应头注入……这些“不同”的漏洞实际上都是同一个问题,需要使用相同的解决方案:
- 问题:使用字符串连接/格式化来构造协议 X 的参数化消息。
- 解决方案:使用适合该工作的、经过良好(安全性)测试的软件库并正确使用它。
由于注入漏洞列表无穷无尽,本文不会一一列举,所以无论您构建的是哪种协议,只需记住这条规则即可。不过,我们将介绍一些比较常见/有趣的漏洞,例如我们接下来要介绍的 SQL 注入。
安全地构建数据库查询以避免 SQL 注入漏洞
为了避免 SQL 注入漏洞,切勿通过字符串连接来构造 SQL 查询。如果可以,请使用 ORM(对象关系映射器),这将加快开发速度,并使应用程序更安全。
如果您想要对查询进行精细控制,请使用低级 ORM(通常称为查询构建器)。
如果您由于任何原因不能使用 ORM,那么请使用准备好的语句,但要小心,因为它们比 ORM 更容易出现人为错误。
警告
从两个意义上来说,ORM 框架并不是灵丹妙药。
首先,它们仍然支持原始 SQL 查询/部分查询。只要不使用这些功能,就没问题。
第二,ORM 框架有时会存在漏洞,就像任何其他软件包一样。因此,请遵循其他良好实践:验证所有输入、使用 WAF 并保持软件包更新,这样就万事大吉了。
进一步阅读
如果必须运行操作系统命令,请正确执行以避免命令注入和相关漏洞
如果可以避免,就不要执行任何操作系统命令。这总是有点危险。
如果必须这样做,您可以按照以下准则避免命令注入漏洞和相关问题:
- 使用合适的库/函数来构造和参数化命令。参数应符合
list
数据类型。切勿将命令创建为单个字符串。 - 不要使用 shell 来调用该命令。
- 预先确定输入命令的参数。
curl
例如,如果允许用户指定-o
参数,就意味着允许攻击者写入本地文件系统。 - 了解程序的功能并适当地验证参数。再次以……
curl
为例,你可能希望允许用户检索诸如……之类的网站,https://www.appsecmonkey.com/
但如果攻击者检索了……该怎么办file:///etc/passwd
? - 仔细想想。即使你验证了参数以
http://
或开头https://
,你愿意让攻击者访问http://192.168.0.1/internal_sensitive_service/admin
或对内部网络进行端口扫描吗? - 仔细想想。即使你验证了该参数是一个有效的 DNS 主机名,并且不包含例如,有什么能阻止攻击者创建指向 的
yourcompany.local
公共 DNS 记录吗?答案是……不能。攻击者可以做到。www.example.com
192.168.0.1
进一步阅读
通过正确配置解析器来避免 XML 漏洞
XML 是一种危险的标记语言,它包含访问系统资源的功能,XSLT 的某些实现甚至支持嵌入代码。因此,在处理 XML 时必须格外小心。
- 如果可以,请避免接受来自不受信任来源的 XML/XSLT。
- 如果您参数化 XML、XSLT 或 XPath 表达式,请使用合适的软件组件。这是为了避免注入漏洞。请勿使用字符串连接/格式化等操作。
- 使用知名且经过全面(安全)测试的软件组件来解析 XML/XSLT。这一点至关重要。不要使用劣质库或代码来处理 XML。此外,在任何情况下都不要尝试创建自定义实现来处理 XML 签名(例如 SAML),因为有很多地方可能出错。
- 正确配置您的解析器。禁用
document
XSLT。禁用xinclude
。禁用文档类型定义。禁用外部实体。启用 DOS 保护。具体选项会因具体实现而异,请对您选择的解析器进行一些研究。
进一步阅读
通过使用适当的 URL 构造类来避免 URL 注入漏洞
当你遇到如下情况时就会发生 URL 注入:
python
flavour = request.getParam("flavour");
url = "https:/api.local/pizzas/" + flavour + "/";
return get(url).json();
有人输入了这样的值:
../admin/all-the-sensitive-things/
这会导致 API 调用返回一个响应,https://api.local/admin/all-the-sensitive-things/
而不是像开发人员期望的那样返回披萨端点。
与往常一样,解决方案是使用适当的 URL 构造库来参数化 URL,以便正确编码值。
通过使用适当的类来构造路径,避免路径遍历漏洞
与 URL 地址一样,如果攻击者设法在路径中的某个位置潜入一个序列,文件路径最终也可能指向不需要的位置../../../
。为了避免这种情况,请创建一个类来安全地构建路径,并验证最终路径是否位于目标目录中。避免在文件路径中使用不受信任的数据,或者更好的是,完全避免使用文件系统,而应选择云存储等方式。
进一步阅读
如果可以避免,请不要将文件系统用于不受信任的内容(例如上传)
允许用户写入服务器文件系统时,可能会出现无数问题。请使用云存储,如果云存储不适合您,请在数据库中使用二进制 blob。
如果您确实必须访问磁盘,这些准则可以帮助您确保安全:
- 要非常小心,不要让任何不受信任的数据影响内部文件路径的任何部分。
- 将文件保存在远离 webroot 的独立目录中。
- 在写入磁盘之前验证文件内容是否符合预期格式。
- 正确设置文件系统权限以防止写入不需要的位置。
- 不要提取压缩(例如 ZIP)档案,因为它们可以包含任何文件,包括符号链接和系统上任何地方的路径。
不要执行动态代码以避免远程代码执行漏洞
不要使用eval
或等效函数。找到一种不使用它们也能实现目标的方法。否则,存在不受信任的数据到达函数调用的风险,从而导致有人在你的服务器上执行任意代码。
谨慎使用序列化以避免反序列化漏洞
对不受信任的数据进行反序列化是一种危险的操作,很容易导致远程代码执行。
- 如果可以避免,请不要使用序列化。
- 如果您可以在服务器端序列化对象,请对其进行数字签名。当需要再次反序列化时,请在继续反序列化之前验证签名。
- 使用知名的软件组件来完成这项工作,并严格保持其更新。许多反序列化库中经常会发现漏洞。GSon是一个不错的选择。
- 使用简单的文本格式(例如 JSON),而不是二进制格式。此外,应避免使用 XML 等存在问题的格式,因为除了反序列化漏洞之外,还需要考虑 XML 漏洞。
- 在处理序列化对象之前,请先对其进行验证。例如,对于 JSON,在进行反序列化之前,请先根据严格的 JSON 架构验证 JSON 文档。
进一步阅读
防御服务器端威胁 - 基础设施
使用 WAF
在你的应用程序前安装一个 Web 应用防火墙产品。这将使许多漏洞更难被发现和利用。ModSecurity 是一个不错的开源选择。
进一步阅读
仔细配置您的 Web 服务器以避免 HTTP 异步攻击
有一种称为“HTTP Desync”或“请求走私”的攻击,如果满足以下条件,攻击者可以进行各种恶意操作,例如窃取随机用户收集到的 Web 应用程序的 HTTP 请求:
- 有一个前端 Web 服务器,例如负载平衡器/任何反向代理,它接受同时具有、和
Content-Length
标Transfer-Encoding
头的请求,并在不规范化请求的情况下传递它们。 - 线路上的下一个 Web 服务器(例如应用程序 Web 服务器)使用(或者可能被欺骗使用)与前端 Web 服务器不同的机制来确定 HTTP 请求的开始位置和结束位置,例如,前端将使用
Content-Length
而应用程序服务器将使用Transfer-Encoding
。 - 前端 Web 服务器重新使用与后端 Web 服务器的连接。
- 前端 Web 服务器在后端服务器连接中使用 HTTP/1(而不是 HTTP/2)。
那么该如何保护自己呢?具体方法取决于产品,但一般来说:
- 查阅您正在使用的反向代理产品的文档/供应商,并确保他们正在积极防御攻击。
- 配置前端 Web 服务器以在后端连接中使用 HTTP/2。
- 配置前端 Web 服务器以防止来自单独的客户端 TCP 流的 HTTP 请求聚合到同一个服务器端连接中。
- 使用 WAF(Web 应用程序防火墙)并确保其具有阻止请求走私尝试的模块
进一步阅读
使用容器
隔离运行您的应用程序,以便在发生漏洞时,攻击者无法访问不必要的文件、系统或网络资源。因此,最好使用 Kubernetes 或云无服务器堆栈之类的工具来部署您的应用程序。如果您出于任何原因被迫使用裸服务器,请手动运行 Docker 等工具来限制应用程序。
进一步阅读
使用 SELinux/AppArmor
即使你的应用程序在容器中运行,也值得使用 SELinux 或 AppArmor 策略对其进行进一步约束。这将使利用容器逃逸漏洞变得非常困难,并带来其他好处。
进一步阅读
使用具有最低权限的服务帐户
这通常可以在出现问题时限制损失。同样,不可能列出详尽的清单,但以下是一些示例,可以帮助您理解:
- 即使您使用 Docker,即使您使用了 SELinux/AppArmor,也不要以 root 身份运行应用程序。这将使容器逃逸/内核漏洞以及其他恶意攻击更难被攻击者利用。请为应用程序创建一个具有最低权限的特定用户。
- 如果您有数据库,请确保应用程序的数据库用户对表、列和 dbms 功能具有最低限度的访问权限。
- 如果您与 API 集成,请确保应用程序具有访问 API 的最低权限。
限制出口网络连接
攻击者通常需要某种反向通信通道来建立命令和控制通道并/或窃取数据。此外,一些漏洞需要出口网络连接才能被发现和利用。
因此,您不应允许应用程序与外部世界建立任意连接,包括 DNS。如果您nslookup www.example.com
的服务器能够成功运行,则说明您尚未正确限制出口。
如何进行此操作取决于您的基础设施。
通常可以使用以下一项或多项措施禁用出口 TCP/UDP/ICMP:
- 如果有的话,使用网关级防火墙。
- 如果您有老式服务器,请使用本地防火墙(例如 iptables 或 Windows 防火墙)。
- 如果您在服务器上运行 Docker,则需要 iptables。
- 如果您使用 Kubernetes,则需要 NetworkPolicy 定义。
DNS 有点棘手,因为很多时候需要允许某些主机使用它。
- 如果您可以使用本地 hosts 文件,那就太好了,这是一个简单的解决方案,您可以完全禁用 DNS(使用前面列表中的任何技术)。
- 如果没有,则必须在上游 DNS 中配置一个私有区域,并将网络级别的访问限制为仅对该 DNS 服务器进行访问。该区域应仅解析预先确定的主机名列表。
跟踪您的 DNS 记录以防止子域名接管
子域名劫持如下发生:
- 您有一个域名
example.com
。 - 您为某项活动购买了另一个域名
www.my-cool-campaign.com
,并创建了CNAME
从campaign.example.com
到www.my-campaign.com
。 - 您的活动结束并最终
www.my-cool-campaign.com
到期。 - 您仍然指向
CNAME
已campaign.example.com
过期的域名。 - 攻击者购买了过期域名,现在您的域名下有一个 DNS 记录(
campaign.example.com
),该记录指向攻击者控制的域名。 - 攻击者托管恶意内容,
www.my-cool-campaign.com
可以通过https://campaign.example.com
所以一定要留意你的 DNS 记录。如果你需要处理大量类似的域名,强烈建议使用自动化监控解决方案。
进一步阅读
防御服务器端威胁 - 架构
创建用于访问数据源的内部 API,以摆脱危险的信任边界
你不应该对面向互联网的 Web 应用程序抱有过多的信任。例如,它不应该直接访问数据库。否则,如果有人入侵了面向互联网的应用程序,你的整个数据库就会丢失。
而是将您的架构分成多个组件,例如:
- 您的 Web 应用程序
www.example.com
将在 上验证您的用户auth0
。 - 您的 Web 应用程序
www.example.com
可以连接到内部 API,api.example.local
并使用经过身份验证的用户access-token
(从 获得)在调用内部 API 时auth0
将其作为标头传递。Authorization
- 您的 API
api.example.local
将根据(最终用户的)访问令牌强制执行访问控制并适当地读取/写入数据库。
现在,如果攻击者完全破坏了您的www.example.com
应用程序,攻击者将无法完全访问整个数据库,而只能访问当时访问令牌恰好位于内存中的单个用户的数据。
加密并验证所有连接
不要相信你的内部网络是安全的,它有很多可能被入侵的方式。使用 TLS(即使用 HTTPS)加密所有系统到系统的连接,并最好在网络和应用程序级别对连接进行身份验证:
- Web 应用 -> API:这是我的客户端证书。它由我们信任的 CA 签名,证书内容为“CN=WebApp”。
- Web 应用<- API:这是我的服务器证书。它由我们信任的 CA 签名,上面写着“CN=API”。
- Web 应用程序 -> API:这是我的访问令牌,由我们信任的 IDP 签名,我使用 OAuth2 客户端凭据授予流程获取它。
- Web 应用程序 -> API:...这是登录用户“John Doe”的访问令牌,我代表其发出此请求,该令牌也由我们信任的 IDP 签名。
- Web 应用程序 -> API:...那么,您能给我 John Doe 的信息吗?
- Web 应用<- API:很高兴。因为这是一个加密且相互认证的网络层连接,而且您在应用层看起来像是“Web 应用”,而且您似乎在以“John Doe”的权限进行操作。
集中管理机密
如果没有合适的机密管理解决方案,就很难保证凭证的短期有效性、审计记录以及不被人为泄露。出于这个原因(以及许多其他原因),建议使用 HashiCorp vault 等工具来集中管理集成机密、加密密钥等。
进一步阅读
防御服务器端威胁 - 监控
收集、分析、警报
将日志集中收集到系统,例如 SIEM(安全信息和事件监控),您可以在其中针对指示漏洞或攻击的特定事件触发警报。配置警报渠道,以便相关人员在发生重大威胁时立即知晓。
收集应用程序安全事件
最重要的日志来源可能是你的应用程序本身。当可疑行为发生时,你应该抛出异常,记录事件,甚至自动锁定那些可能造成麻烦的用户/IP 地址。
此类事件可能是(这些只是示例,具体情况很大程度上取决于您的应用程序):
- 输入验证错误(例如,尝试为不应该通过 UI 实现的参数提供值)。
- 访问控制错误(例如,尝试通过 UI 访问不可能实现的记录)。
- 数据库语法错误表明有人发现了 SQL 注入漏洞,您需要快速采取行动。
- XML 错误表明有人发现了 XML 注入漏洞,或者可能正在尝试查找/利用 XXE(XML 外部实体)漏洞。
- 错误请求错误,表示最终用户发送的内容被应用程序拒绝。Spring 框架的 RequestRejectedException 就是一个例子。
- CSRF 令牌验证错误通常意味着有人正在寻找您的应用程序中的漏洞。
收集运行时安全日志
使用运行时安全监控工具(例如 Falco)来检测异常的系统调用。如果您恰好使用 Kubernetes,Falco 会特别有用。此外,还可以远程收集和监控这些日志。
进一步阅读
收集 SELinux/AppArmor 日志
如果您的 SELinux 策略阻止了传出连接,而您的应用程序突然尝试向 (例如 )发出 HTTP 请求burpcollaborator.net
,那么立即了解情况将非常有用。或者,您的应用程序尝试访问/etc/passwd
。这两种情况都表明有人已经在您的应用程序中发现了一个严重的漏洞。
收集网络服务器事件
至少从你的 Web 服务器软件收集访问日志和错误日志,并将它们发送到中央日志服务器。这将有助于绘制事件响应的时间线。
收集WAF日志
如果您使用上述推荐的 WAF,也请收集这些日志。但不必触发警报,因为 WAF 产品通常会受到来自互联网的各种垃圾信息的轰炸,大多数时候您无需担心。
防御服务器端威胁 - 事件响应
制定计划
一旦你部署了监控和强化措施,漏洞就很难被攻击者发现,漏洞被成功利用的速度也会变慢,你也能很快发现这些攻击行为。这真是个好主意。
但仅仅了解攻击并减缓攻击者的速度还不够,您仍然需要采取一些措施。因此,请准备好人员、工具和流程,以应对以下情况:
- 快速分析日志并了解正在发生的事情以及需要做什么
- 快速限制应用程序防火墙产品中的单个 URL 地址或参数
- 如果需要,快速关闭应用程序
安全开发考虑
威胁模型
思考“哪里可能出错”,然后采取措施解决。最好在开始设计系统时就这样做,但任何时候开始都不晚,而且无论如何,在对系统进行更改时,都应该重新审视这个过程。
例如:
吉姆:如果攻击者攻破了面向互联网的网络服务器怎么办?
鲍勃:那么我们就彻底倒霉了。
Jim:好的,所以我们建立了信任关系,我们相信面向互联网的 Web 服务器不会被攻破。我们真的能相信吗?
鲍勃:嗯,不,有无数的事情可能导致该东西被黑客入侵,例如我们自己的代码中的漏洞,或者我们使用的依赖项中的漏洞,或者我们的网络服务器软件中的漏洞。
吉姆:好的。那我们就打破这种信任关系吧。但是怎么做呢?
Bob:让我们打破单体应用,创建一个内部 API 来实际访问数据库。这样,前端 Web 服务器就不用一次性访问所有数据了。
吉姆:好主意。那还有什么问题呢?
鲍勃:那么,如果攻击者侵入我们的内部网络怎么办?
吉姆:一切都会丢失,服务器到服务器的连接都是未加密的。
鲍勃:……
这就是威胁建模,它并不一定复杂或可怕。用它来发现危险的信任关系,然后打破这些关系。
在源代码控制中强制进行同行评审
实施技术控制,防止代码未经至少一两名其他开发人员批准就进入存储库。这是安全开发生命周期的基础,因为现在会发生两件事:
- 如果攻击者破坏了开发人员的工作站,或者开发人员变得叛逆,就不可能直接将恶意代码推送到存储库中。
- 如果开发人员犯了错误并试图将易受攻击的代码引入存储库,则很有可能其他开发人员在合并之前审查代码并发现错误。
进一步阅读
自动化 CI 管道并限制普通用户访问
个人开发者应该能够触发例如 Jenkins 构建等操作,但 Jenkins 应该配置为允许该操作,而不允许其他操作。个人开发者不应在构建阶段引入任意代码。不过,只要同行评审流程按照上述建议在技术上强制执行,您就可以将 Jenkinsfile 保留在源代码管理中。
对构建工件进行签名
对构件进行签名。例如,如果您正在构建容器镜像,请将镜像签名作为构建过程的一部分。请妥善保存签名密钥。构建阶段需要访问密钥,但不应将其存储在 Jenkinsfile 的版本控制中。最好将密钥保存在 HashiCorp 等存储库中,并在构建时提取它们。
进一步阅读
作为 CI 管道的一部分运行静态应用程序安全扫描程序
在 CI 管道中运行 SpotBugs + FindSecBugs 之类的工具(或适用于你所选技术的类似工具)。这将帮助你在部署代码之前发现一些已知的漏洞。
您还可以在开发人员的工作站上运行这些工具(例如作为 IDE 插件),以便在将问题检查到版本控制之前捕获问题。
进一步阅读
验证构建的依赖关系并将其保持在最低限度
你依赖的每个软件包都存在风险。你正在从别人的仓库拉取代码,并在你的应用服务器上执行。所以,务必谨慎选择你依赖的内容以及如何依赖。
- 保持最低限度的依赖性。
- 仅使用您信任的依赖项。它们都应该被广泛使用并且信誉良好。
- 使用支持依赖项验证的构建框架,并确保已启用验证。
作为额外的强化措施,限制来自应用程序服务器的出口连接(本文前面已描述)以防止任何后门“呼叫回家”。
进一步阅读
作为 CI 管道的一部分运行依赖项安全扫描器
运行诸如 OWASP DependencyCheck 之类的工具作为 CI 管道的一部分,以捕获您可能正在使用的一些已知安全问题的依赖项。
您也可以在开发人员的工作站上运行这些工具(但也可以在 CI 管道中运行它们,这是最重要的)。
进一步阅读
作为 CI 管道的一部分运行容器镜像安全扫描器
如果您使用容器,请使用 Trivy 等工具扫描创建的容器映像中是否存在已知漏洞。
进一步阅读
自动部署并验证签名
个人开发者完全可以拥有部署到生产环境的权限,但只有前几个阶段构建并签名的特定镜像才可以部署。访问生产机密或直接访问服务器应该是不可能的。请验证部署镜像的签名,例如,如果您使用 Kubernetes,则可以通过 Notary 和 Open Policy Agent 等方式验证容器签名。
进一步阅读
拥有安全冠军
一个人的执着程度是有限的。你不可能指望每个开发人员都成为精通渗透测试的工程师或安全工程师。正如你不可能指望所有安全专业人员都成为杰出的开发人员一样。
因此,将关注安全的人员介绍到您的团队中是一个好主意,以便与开发人员、架构师等进行交流,并帮助保护您的应用程序并在团队中传播安全意识。
进一步阅读
结论
保护应用程序安全远不止避免漏洞那么简单。以下是一些主要思路:
- 使用最新、现代且知名的软件组件来执行高风险操作,例如身份验证、访问控制、加密、访问数据库或解析 XML。并确保已正确配置这些组件,例如在 XML 解析器中禁用外部实体。
- 使用平台提供的安全控制,例如 CSRF 保护。
- 使用 Web 浏览器提供的安全控制,例如 HSTS、SameSite cookie 和内容安全策略。
- 集中您的安全控制,特别是身份验证和访问控制,以避免“忘记为某些控制器功能添加安全性”等漏洞。
- 使用 Web 应用程序防火墙会使查找和利用应用程序中的许多类别的漏洞变得困难。
- 通过限制应用程序对文件、网络和系统资源的访问来控制应用程序。
- 威胁模型可以发现架构中任何危险的信任关系,然后打破它们。例如,这可能包括源代码控制策略,它可以打破对每个开发人员工作站完整性的信任关系,以及一个巧妙的架构,它可以打破对前端 Web 服务器不被入侵的完全信任。
- 密切监控,当出现问题时制定计划。
- 在开发环境和 CI 管道中使用代码/图像/依赖项漏洞扫描程序。
- 对开发人员、架构师等进行安全方面的教育,并在团队中任命一名安全专家。
获取网络安全检查表电子表格!
☝️订阅AppSec Monkey 的电子邮件列表,将我们的最佳内容直接发送到您的收件箱,并免费获得我们的 2021 年 Web 应用程序安全检查表电子表格作为迎宾礼物!
不要停在这里
如果您喜欢这篇文章,请查看我们在AppSec Monkey上的其他应用程序安全指南。
谢谢阅读。
文章来源:https://dev.to/appsecmonkey/web-application-security-checklist-2021-5266