65.9K
CodeProject 正在变化。 阅读更多。
Home

理解 SQL Server 集成安全的 服务主体名称

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (8投票s)

2023年5月15日

CPOL

14分钟阅读

viewsIcon

17555

服务主体名称基础知识及其对SQL Server安全的重要性

引言

说实话,我写这篇文章时犹豫了。本网站主要面向开发者。而这篇文章实际上与编码关系不大。它主要讲Kerberos、服务主体名称(SPN)、SQL Server以及一点点AD。所以,这不太像一篇典型的CodeProject文章。

然而,几乎所有编写过连接SQL Server客户端的开发者,都接触过SQL Server的安全方面。而在现代应用程序中,这意味着集成安全。如果某个地方配置不当,连接安全就会失败。发生这种情况时,诊断起来可能非常困难,尤其是当你对底层原理了解不深时。

本文旨在解释客户端和服务器之间**如何**建立连接安全的基础知识,因为这对您作为开发人员来说是一个重要的考量。这样一来,如果您开发并部署了一个带有SQL后端的客户端应用程序,并且遇到了令人头疼的“无法生成SSPI上下文”错误,您将拥有足够的知识来结构化地调查问题,而不是疯狂地在Google上搜索错误信息,却得到大量无效的解决方案。

需要明确的是,实现数据库安全、授予表权限、执行权限等的流程**不在**本文的讨论范围之内。这属于系统管理范畴。

背景

在现代客户端-服务器系统中,例如SQL Server,主要有两种实现安全的方式。第一种是使用应用程序特定的登录凭据,让应用程序基于此进行身份验证。对于SQL Server来说,这些是创建和维护在数据库实例本身中的旧式SQL登录。在现代安装中,默认情况下是禁用的,因为这类帐户存在许多问题。

  1. 它们不是集中管理的。每个实例都有自己的帐户和密码集合。此外,当用户离开公司时,这些帐户不会自动失效。
  2. 它们不能用于需要外部资源的 कोड。也就是说,如果SQL查询使用了存储过程,而该存储过程例如与文件或其他资源交互,则没有好的方法来管理它,因为SQL登录不是操作系统登录。
  3. 由于它们的性质,它们经常存储在连接字符串、配置文件或其他类似的东西中,这意味着它们比操作系统登录或Active Directory登录更容易受到攻击。

除了支持遗留应用程序外,最好保持这些登录禁用状态。如果您确实需要它们,那么就称为“混合模式安全”。

Windows集成安全要好得多,因为它遵循现有的用户管理模式,即用户属于组,而组被授予资源权限。个人用户将受到标准的 корпоративного 访问控制程序的约束。

Windows使用两种协议来保护客户端和服务器之间的通道:NTLM和Kerberos。

NTLM 与 Kerberos

NTLM是领域环境中用于安全的老式协议。它很方便,但安全性不如Kerberos。而Kerberos安全性要高得多,但配置起来可能很麻烦,因为它需要您非常精确地定义谁可以代表谁做什么。

打个比方,NTLM的安全等同于CEO的私人秘书可以代表CEO致电人力资源部,为自己大幅加薪,而人力资源部则满足于“老板让我告诉你……”的说法。

使用Kerberos时,秘书必须证明她最近收到了CEO的命令,并且人力资源部能够验证命令的真实性以及秘书是否被正式授权委托处理此类命令。

使用哪种协议取决于系统配置。NTLM可以通过GPO、本地安全策略或注册表操作在领域的各个级别启用/禁用。如果您查看安全选项GPO的参考,可以看到所有可能的NTLM相关设置。

现在,假设NTLM和Kerberos都允许。SQL Server在集成安全连接时,如果可能,仍会使用Kerberos。如果不行,它将回退到NTLM。为了使SQL Server能够使用Kerberos,需要在Active Directory中配置正确的服务主体名称。

检查使用的身份验证机制

验证使用哪种方法很简单,因为在您连接时,可以查询到该信息。为了测试,我使用了SqlServer PowerShell模块,该模块可在Github上找到,这是连接到SQL Server的标准方法。

用于确定连接类型的查询如下:

SELECT auth_scheme FROM sys.dm_exec_connections WHERE session_id=@@SPID

因此,在PowerShell中,使用SqlServer模块,您的脚本将如下所示:

ipmo SqlServer
Invoke-Sqlcmd -Query "SELECT auth_scheme _
FROM sys.dm_exec_connections WHERE session_id=@@SPID" -ServerInstance <INSTANCE_NAME>

其结果将类似于:

服务主体名称

详细解释Kerberos超出了本文的范围。如果您想了解更多关于Kerberos的信息,我推荐这本书

抛开所有细节,Kerberos的核心概念是通信双方都有一种方式可以安全地验证对方的身份。这意味着客户端需要能够知道它正在连接的服务的身份。服务本身可能以“本地系统”(使用计算机对象的身份)运行,或者以通用用户帐户或托管服务帐户运行。

服务主体名称(SPN)用于将该服务与Active Directory对象关联起来,以便客户端知道它正在与谁通信。没有SPN,客户端将无法弄清楚这一点,因为它基本上只是连接到一个套接字,而不知道谁在监听该套接字。通过查询Active Directory,客户端可以了解它应该与谁通信,并相应地设置安全。

这些SPN的配置是在使用该服务的Active Directory对象标识的servicePrincipalName属性中完成的。在这种情况下,您可以看到Microsoft SQL Server MSSQLSvc在服务器SE-WINDEV021上使用了计算机本身在网络上的身份。

您也可以使用SetSPN命令进行验证:

结果是,通信通道的安全将在客户端的身份和计算机对象的身份之间建立。如果SQL Server被配置为使用常规用户帐户的身份运行,例如(假设)SQLSVC01,那么SPN MSSQLSv/SE-WINDEV01...将列在SQLSVC01用户帐户的servicePrincipalName属性下。

SPN的确切格式如下(来自Microsoft):

MSSQLSvc/fqdn 当使用非TCP协议时,提供程序生成的默认SPN,用于默认实例。

fqdn是完全限定域名。
MSSQLSvc/fqdn:port 当使用TCP时,提供程序生成的默认SPN。

port是TCP端口号。
MSSQLSvc/fqdn:InstanceName 当使用非TCP协议时,提供程序生成的默认SPN,用于命名实例。

InstanceName是SQL Server实例的名称。

现在您知道了SPN应该在哪里以及它们应该是什么样子,下一个问题是:谁来创建它们?

SPN的自动创建

在某些情况下,它们可以由服务本身自动创建。例如,当SQL Server启动时,作为启动序列的一部分,它会尝试为您注册SPN。文档提到,如果服务帐户是LocalSystemNetworkService,或者具有明确授予的正确权限,就会这样做。这是不正确的,而且相当令人困惑。

注册SPN需要修改servicePrincipalName属性的权限。由于它涉及到服务使用的对象标识的属性,因此对象安全需要为SELF主体授予特殊权限。在标准目录中,此设置默认配置。

需要注意的是,授予的访问权限是针对servicePrincipalName属性的**已验证**写入。根据Microsoft的说法,在此上下文中**已验证**意味着“在将值写入DS对象的属性之前,要求系统执行值检查或验证,超出了模式所要求的范围。这确保了为属性输入的值符合必需的语义,在合法的值范围内,或经过了在对属性进行简单低级写入时不会执行的任何其他特殊检查。

这有一定的道理,因为一个对象只能在SPN的语法有效时添加SPN。然而,问题在于语法被定义为“与要设置的计算机的DNS主机名**兼容**的SPN属性。”。请记住,与其他服务不同,SQL支持多个实例,这在SPN的语法中有所体现。

SQL的默认实例的SPN格式为MSSQLSv/SE-WINDEV01.contoso.com。实例名为SQLSANDBOX的命名实例的SPN格式为MSSQLSv/SE-WINDEV01.contoso.com:SQLSANDBOX

此格式违反了已验证写入的DNS主机名要求。因此,与文档相反,SPN记录仅为默认实例自动创建,因为它没有附加实例名称。结果,即使使用应该根据文档自行注册的服务帐户,命名实例的安全也会回退到NTLM。

您可以通过允许SELF拥有Write servicePrincipalName权限来解决此问题。

如果您这样做,SQL Server将启动并成功注册SPN。

然后,如果您进行连接测试,将会得到这样的结果:

手动配置SPN

在不允许为启用SELF注册的安全配置,或者其他方式不可行的情况下,任何对服务标识的AD对象拥有管理权限的人都可以通过SetSPN实用程序手动完成。

有一条警告是关于不要注册重复的SPN,所以我手动将其从Active Directory中删除,然后使用SetSPN添加。

选择自动或手动配置的考虑因素

决定哪种方法最好取决于公司政策和基础设施的管理方式。

允许SELF更新servicePrincipalName属性无疑是最简单的方法,可以确保注册的正确性。然而,这需要修改AD对象安全,并且许多管理员出于各种原因对此类事情持谨慎态度(AD配置本身可能受到繁琐的变更控制,可能需要冗长的测试过程或文档更新,管理员可能不被公司允许进行更改,或者他们可能只是过于谨慎)。

而且,老实说,添加SELF权限需要直接应用于对象本身,或者应用于它所在的容器OU。考虑到计算机和用户不会在同一个OU中,而且您不希望所有主体都能注册SPN,这可能会很繁琐。遗憾的是,创建组来设置此权限是不可能的,因为如果这样做,组中的所有对象都可以互相设置SPN。

允许一个主体设置自己的SPN可能是一件危险的事情,因为该主体就有能力通过注册大量错误SPN来对Active Directory执行拒绝服务(DoS)攻击。

出于这些原因,大多数环境都选择手动注册。但是,如果您这样做,建议固定SQL Server服务的端口号,以便注册的SPN的端口号与实际端口号匹配。

默认情况下,任何SQL Server的命名实例都使用动态端口。文档指出,要使用动态端口,应将动态端口设置为0。但如果您检查,您会看到:

情况是这样的,当实例创建时,SQL会选择一个随机的高端口号作为动态端口。您会注意到它会在服务重启和服务器重启后重用这个端口。那么,为什么它被称为动态端口呢?狡猾之处在于,它会尽量一直重用这个端口,但如果碰巧被占用,它就会选择一个新的端口并开始使用。这是那些很少发生的事情之一,以至于您会信任它总是相同的。

在SQL Server服务主体没有权限自动更新SPN的情况下,如果发生这种情况,后果是SPN记录将不再匹配实际运行的服务。发生这种情况时,SQL客户端将无法设置Kerberos会话,并会回退到NTLM,或者如果NTLM被禁用,则会直接失败。

因此,如果您遇到无法进行自动SPN注册的所需配置的情况,请确保**不要**使用动态端口,而是使用固定端口。这很简单,只需获取当前的动态端口号,并将其保存在TCP端口字段中,而不是动态端口字段。务必将动态端口字段留空,以确保服务不会也使用动态端口。

请注意,如果其他进程碰巧占用了该高端口,SQL服务将无法启动。但这更可取,因为它易于观察和诊断。有时会使用的一种技巧是使用在tcp协议中分配给不再存在或已知不在您的网络或服务器上的公司或软件的端口号

孤立SPN的陷阱

如果SPN的自动注册不被允许,就存在一个严重问题的可能性。顺便说一句,正是这个问题促使我研究SPN并撰写本文。

假设您有一个正在运行的SQL Server,由于某种原因(迁移到生产环境、安全审计等),您必须更改SQL服务的用户帐户。例如,从LocalSystem更改为托管服务帐户,并且禁用了自动注册。如果原始帐户已经注册了SPN,那么切换到不同的服务主体将不会更新服务主体名称。SPN将成为孤立的。

发生这种情况时,事情会迅速停止工作。这比根本没有SPN还要糟糕。当没有SPN时,相互身份验证将回退到NTLM。但如果存在错误的SPN,这种情况就不会发生。客户端将尝试为与其认为运行SQL Server的用户主体建立安全通道,并失败。

故障排除技巧

如果您遇到相互身份验证和/或Kerberos问题,可以按以下方式进行故障排除:

  • 从SQL Server日志开始,查看SPN是否自动生成。如果没有,请根据实例名称和端口号,检查应该存在于实例的两个SPN。
  • 使用PowerShell的Test-Connection cmdlet验证网络连接。
  • 检查端口号是动态还是固定。
  • 使用SetSPN查询SPN,并验证它们是否已注册到正确的Active Directory主体。
  • 如果您对客户端本身不确定,可以使用SSPIClient测试应用程序来测试客户端是否能够建立安全连接。
  • 如果您想对SQL Server本身进行诊断,可以使用SQL Server的Kerberos配置管理器

结论

通过本文,我希望已经足够地解释了基础知识,让您有一个基本的理解。总结来说:

  • SPN用于将用户主体与计算机上运行的服务相关联,以便能够使用Kerberos在客户端和服务之间建立安全通道。
  • SPN可以自动或手动注册。如果不能自动创建,每次更改服务主体都需要手动更新SPN注册。
  • 当SPN不存在时,Kerberos将无法使用,相互身份验证将回退到NTLM,或者完全失败,具体取决于通过GPO、本地安全策略或注册表进行的安全性配置。
  • 错误的SPN记录将导致身份验证失败。

历史

  • 2023年5月15日:初版
© . All rights reserved.