在讨论如何在应用程序中应用安全性之前,您应该首先了解如何保护应用程序。为了进行恶意操作,攻击者会识别并利用应用程序的漏洞。我们经常将漏洞描述为一种弱点,它可以允许执行不需要的操作,通常带有恶意意图。
了解漏洞的一个很好的起点就是了解开放Web应用程序安全项目,也称为 OWASP(https://www.owasp.org)。 在 OWASP上,您将找到应该在应用程序中避免的最常见漏洞的描述。 在进入下一章之前,让我们花几分钟时间进行理论上的讨论,然后从下一章开始应用 Spring Security 的概念。 在应注意的常见漏洞中,您会发现以下这些:
这些项与应用程序级安全性相关,而且其中大多数也与使用 Spring Security 直接相关。我们将在本书中详细讨论它们与 Spring Security 的关系,以及如何保护您的应用程序不受这些问题的影响,但首先是一个概述。
我们将深入讨论身份验证和授权,并且您将学习使用Spring Security实施身份验证和授权的几种方法。 身份验证表示应用程序识别尝试使用它的人的过程。 当某人或某物使用该应用程序时,我们希望找到其身份,以便授予或不授予进一步的访问权限。 在现实世界中的应用中,您还会发现匿名访问的情况,但在大多数情况下,只有在被识别后,用户才能使用数据或执行特定操作。 获得用户身份后,我们就可以处理授权。
授权是确定经过身份验证的调用方是否具有使用特定功能和数据的特权的过程。 例如,在移动银行应用程序中,大多数经过身份验证的用户都可以转账,但只能从其帐户中转账。
我们可以说,如果一个有不良意图的人以某种方式获得对不属于他们的功能或数据的访问权,我们的授权将被破坏。 诸如 Spring Security 之类的框架有助于降低此漏洞的可能性,但如果使用不当,仍然有可能发生这种情况。 例如,您将使用 Spring Security 为具有特定角色的经过身份验证的个人定义对特定端点的访问。 如果数据级别没有限制,那么有人可能会找到一种使用属于另一个用户的数据的方法。
请看图1.5。经过身份验证的用户可以访问 /products/{name} 端点。在浏览器中,web应用程序调用这个端点来从数据库中检索并显示用户的产品。但是,如果应用程序在返回这些产品时没有验证这些产品属于谁,会发生什么呢?一些用户可以找到一种方法来获取另一个用户的详细信息。这种情况只是应用程序设计一开始就应该考虑的示例之一,这样您就可以避免这种情况。
图 1.5
登录的用户可以看到他们的产品。如果应用服务器只检查用户是否登录,那么用户可以调用相同的端点来检索其他用户的产品。这样,John就能够看到属于Bill的数据。导致此问题的问题是应用程序没有为数据检索对用户进行身份验证。
在整个系列中,我们都会提到漏洞。我们将在后面从身份验证和授权的基本配置开始讨论漏洞。然后,我们将讨论漏洞如何与 Spring Security 和 Spring Data 的集成相关,以及如何设计一个应用程序来避免这些漏洞,使用 OAuth 2。
会话固定漏洞是 web 应用程序的一个更具体、更严重的弱点。如果存在,则允许攻击者通过重用以前生成的会话 ID 来模拟有效用户。如果在身份验证过程中,web 应用程序没有分配唯一的会话 ID,就会发生此漏洞。这可能会导致重用现有的会话 id。利用这个漏洞包括获取一个有效的会话 ID,并让目标受害者的浏览器使用它。
根据您实施Web应用程序的方式,个人可以使用多种方法来使用此漏洞。 例如,如果应用程序在URL中提供了会话ID,则可能会诱骗受害者单击恶意链接。 如果应用程序使用隐藏属性,则攻击者可以使受害者欺骗并使用外部表单,然后将操作发布到服务器。 如果应用程序将会话的值存储在cookie中,则攻击者可以注入脚本并强制受害者的浏览器执行该脚本。
跨站点脚本(也称为XSS)允许将客户端脚本注入到服务器公开的Web服务中,从而允许其他用户运行这些脚本。 在使用甚至存储之前,您应该正确地“清理”请求,以避免意外执行外来脚本。 潜在影响可能与帐户模拟(与会话固定结合)或参与分布式攻击(如DDoS)有关。
让我们举个例子。用户在web应用程序中发布消息或评论。发布消息后,该站点将显示它,以便访问该页面的每个人都可以看到它。每天可能有数百人访问这个页面,这取决于该网站的受欢迎程度。对于我们的示例,我们将认为它是一个已知的站点,并且有大量的个人访问它的页面。如果该用户发布了一个脚本,当在web页面上找到该脚本时,浏览器会执行该脚本(图1.6和1.7),该怎么办?
图 1.6
一个用户在网络论坛上发布了一个包含脚本的评论。用户定义脚本,以便发出请求,试图发布或从另一个应用程序(App X)获取大量数据,该应用程序代表攻击的受害者。如果web论坛应用程序允许跨站脚本(XSS),那么显示带有恶意评论的页面的所有用户都会收到它。
图 1.7
用户访问显示恶意脚本的页面。 他们的浏览器执行脚本,然后尝试发布或从App X获取大量数据。
跨站请求伪造 (CSRF) 漏洞在web应用程序中也很常见。CSRF 攻击假设可以从应用程序外部提取并重用调用特定服务器上的操作的 URL (图1.8)。如果服务器信任执行而不检查请求的来源,则可以从任何其他地方执行。通过 CSRF,攻击者可以通过隐藏操作,让用户在服务器上执行不需要的操作。通常,通过此漏洞,攻击者的目标是更改系统中的数据的操作。
图 1.8
跨站点请求伪造(CSRF)的步骤。 登录他们的帐户后,用户访问包含伪造代码的页面。 然后,恶意代码代表毫无戒心的用户执行操作。
缓解此漏洞的方法之一是使用令牌来标识请求或使用跨域资源共享(CORS)限制。 换句话说,请验证请求的来源。 在后面章节中,我们将详细介绍 Spring Security 如何处理 CSRF 和CORS 漏洞。
对系统的注入攻击非常普遍。 在注入攻击中,利用漏洞的攻击者会将特定数据引入系统。 目的是损害系统,以不必要的方式更改数据或检索攻击者不希望访问的数据。
注入攻击有很多类型。 甚至我们在前面章节中提到的 XSS 都可以视为注入漏洞。 最后,注入攻击会注入客户端脚本,从而以某种方式损害系统。 其他示例可能是 SQL 注入,XPath 注入,OS 命令注入,LDAP 注入等等。
漏洞的注入类型很重要,利用这些漏洞的结果可能是更改、删除或访问系统中的数据遭到破坏。例如,如果您的应用程序在某种程度上容易受到 LDAP 注入的攻击,那么攻击者可以通过绕过身份验证,从而控制系统的基本部分而获益。XPath 或 OS 命令注入也会发生同样的情况。
SQL 注入是最古老的、可能也是众所周知的一种注入漏洞。如果您的应用程序有 SQL 注入漏洞,攻击者可以尝试更改或运行不同的 SQL 查询,以更改、删除或从您的系统中提取数据。在最高级的 SQL 注入攻击中,个人可以在系统上运行 OS 命令,从而导致整个系统的危害。
即使就复杂性而言,机密数据的披露似乎是最容易理解、最不复杂的漏洞,但它仍然是最常见的错误之一。之所以会出现这种情况,可能是因为在网上找到的大多数教程和示例,以及说明不同概念的书籍,为了简单起见,都直接在配置文件中定义了凭据。如果一个假设的例子最终集中在其他事情上,这就说得通了。
注意:
大多数时候,开发人员会不断地从理论例子中学习。一般来说,为了让读者专注于一个特定的主题,例子会被简化。但是这种简化的缺点是开发人员习惯了错误的方法。开发人员可能会错误地认为他们读到的所有东西都是好的实践。
这个方面与 Spring Secyrity 有什么关系?我们将在本书的示例中处理凭证和私钥。我们可能会在配置文件中使用 secrets,但是我们会为这些情况做一个说明,以提醒您应该将敏感数据存储在金库中。当然,对于一个已开发的系统,开发人员不允许在所有环境中看到这些敏感键的值。通常,至少在生产中,只有一小部分人应该被允许访问私人数据。
通过在配置文件中设置此类值,例如 Spring Boot 项目中的 application.properties 或 application.yml 文件,您可以使那些可以看到源代码的人访问这些私有值。 此外,您可能还会发现自己将这些值更改的所有历史记录存储在源代码的版本管理系统中。
与敏感数据暴露相关的还有应用程序写入控制台或存储在 Splunk 或 Elasticsearch 等数据库中的日志信息。我经常看到一些日志,这些日志披露了被开发人员遗忘的敏感数据。
切勿记录非公开信息。 公开,我的意思是任何人都可以查看或访问该信息。 诸如私钥或证书之类的东西不是公开的,不应与您的错误,警告或信息消息一起记录。
下次您从应用程序中记录某些内容时,请确保所记录的内容与以下消息之一不同:
[error] The signature of the request is not correct. The correct key to be used should have been X.
[warning] Login failed for username X and password Y. User with username X as password Z.
[info] A login was performed with success by user X with password Y.
请注意服务器返回给客户端的内容,尤其是但不限于应用程序遇到异常的情况。 通常由于缺乏时间或经验,开发人员忘记实施所有此类情况。 这样(通常在错误的请求之后发生),应用程序将返回过多的细节以暴露实现。
此应用程序行为也是由于数据泄露而引起的漏洞。 如果您的应用由于请求错误(例如缺少一部分)而遇到NullPointerException,则该异常不应出现在响应的正文中。 同时,HTTP状态应该是400,而不是500。类型为4XX的HTTP状态代码旨在表示客户端的问题。 最终,错误的请求是客户端问题,因此应用程序应相应地代表它。 类型为5XX的HTTP状态代码旨在通知您服务器上存在问题。 您是否在下一个代码段的响应中看到了问题?
{
"status": 500,
"error": "Internal Server Error",
"message": "Connection not found for IP Address 10.2.5.8/8080",
"path": "/product/add"
}
异常消息似乎正在公开一个IP地址。 攻击者可以使用此地址来了解网络配置,并最终找到一种方法来控制基础结构中的VM。 当然,仅凭这些数据,就不会造成任何伤害。 但是,收集不同的公开信息并将它们汇总在一起可以提供对系统产生不利影响所需的一切。 在响应中具有异常堆栈也不是一个好选择,例如:
at java.base/java.util.concurrent.ThreadPoolExecutor
➥ .runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker
➥ .run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable
➥ .run(TaskThread.java:61) ~[tomcat-embed-core-9.0.26.jar:9.0.26]
at java.base/java.lang.Thread.run(Thread.java:830) ~[na:na]
这种方法还公开了应用程序的内部结构。 从异常堆栈中,您可以看到命名符号以及用于特定操作的对象及其之间的关系。 但更糟糕的是,日志有时会泄露您的应用程序使用的依赖项版本。 (您是否在前面的异常堆栈中发现了 Tomcat 核心版本?)
我们应该避免使用易受攻击的依赖关系。 但是,如果我们发现自己错误地使用了易受攻击的依赖关系,则至少我们不想指出这个错误。 即使该依赖关系不是易受攻击的,也可能是因为还没有人发现该漏洞。 上一小节中的暴露可以激发攻击者寻找该特定版本中的漏洞,因为他们现在知道这就是您的系统使用的漏洞。 邀请他们破坏您的系统。 攻击者经常针对系统使用最小的细节,例如:
Response A:
{
"status": 401,
"error": "Unauthorized",
"message": "Username is not correct",
"path": "/login "
}
Response B:
{
"status": 401,
"error": " Unauthorized",
"message": "Password is not correct",
"path": "/login "
}
在此示例中,响应A和B是调用同一身份验证终结点的不同结果。 他们似乎没有透露任何与类设计或系统基础结构有关的信息,但这些隐藏了另一个问题。 如果消息公开了上下文信息,那么这些信息也可以隐藏漏洞。 基于提供给端点的不同输入的不同消息可用于理解执行的上下文。 在这种情况下,这些可以用来知道用户名何时正确但密码错误。 这会使系统更容易受到暴力攻击。 提供给客户的回复无助于确定对特定输入的可能猜测。 在这种情况下,它应该在两种情况下都提供相同的消息:
{
"status": 401,
"error": " Unauthorized",
"message": "Username or password is not correct",
"path": "/login "
}
这种预防措施看起来很小,但如果不采取措施,在某些上下文中,暴露敏感数据可能成为针对您的系统使用的优秀工具。
即使在应用程序级别,也不能只对其中一个层应用授权。有时,必须确保完全不能调用特定用例(例如,如果当前经过身份验证的用户的特权不允许调用)。
假设您有一个设计简单的web应用程序。该应用程序有一个公开端点的控制器。控制器直接调用一个服务,该服务实现了一些逻辑,并使用了通过存储库管理的持久化数据(图1.9)。设想这样一种情况,授权仅在端点级别完成(假设您可以通过REST端点访问该方法)。开发人员可能倾向于只在控制器层应用授权规则,如图1.9所示。
图 1.9
开发人员在控制器层应用授权规则。但是存储库不知道用户,也不限制数据的检索。如果服务请求的帐户不属于当前经过身份验证的用户,则存储库将返回这些帐户。
虽然图 1.9 所示的情况工作正常,但仅在控制器层应用授权规则可能会留下出错的空间。在这种情况下,一些未来的实现可能不测试就公开该用例,或者不测试所有授权需求。在图 1.10 中,您可以看到如果开发人员添加另一个依赖于同一存储库的功能会发生什么。
可能会出现这些情况,您可能需要在应用程序的任何层处理这些情况,而不仅仅是在存储库中。我们将在后面讨论更多与这个主题相关的事情。在那里,您还将了解如何在需要时对每个应用程序层应用限制,以及应该避免这样做的情况。
图 1.10
新添加的 TransactionController 在其依赖关系链中使用 AccountRepository。开发人员还必须在此控制器中重新应用授权规则。但是,如果存储库本身确保不公开不属于经过身份验证的用户的数据,情况就会好得多。
尽管不一定与 Spring Security 直接相关,但仍然是应用程序级安全的重要方面,但我们需要注意的依赖项。 有时,并非具有漏洞的开发应用程序,而是用于构建功能的依赖项,例如库或框架。 请始终注意您使用的依赖项,并消除任何已知包含漏洞的版本。
幸运的是,我们可以通过向您的 Maven 或 Gradle 配置中添加插件来快速完成静态分析的多种可能性。 如今,大多数应用程序都是基于开源技术开发的。 甚至 Spring Security 都是一个开源框架。 这种开发方法很棒,并且可以快速发展,但这也使我们更容易出错。
开发任何软件时,我们都必须采取所有必要的措施,以避免使用任何具有已知漏洞的依赖项。 如果发现使用了这种依赖关系,则不仅必须快速纠正此问题,还必须调查应用程序中是否已经利用了该漏洞,然后采取必要的措施。
from:https://www.toutiao.com/i6953251158106423839/