☰
SQL Server数据库加固规范落地:账号权限、协议审计与回退验收
2026/10/27 3:03:18 网站建设 项目流程

简介:《Sql Server数据库系统加固规范》面向数据库管理员、安全运维与等保测评人员,围绕SQL Server账号管理、认证授权、日志配置、通信协议及设备安全给出可落地的加固检查与配置参考。压缩包内为1个doc文档,约1.73MB,按SHG-Mssql编号组织条目,逐项列出实施目的、问题影响、系统当前状态、实施步骤、回退方案、判断依据、实施风险与重要等级,便于对照排查和整改。内容涉及最小权限、密码长度与有效期、登录失败锁定、双因素认证、操作日志审计、SSL/TLS传输加密、访问限制以及防毒与定期更新等要点,适合作为安全基线核查和加固实施时的案头资料。已有245人学习,可作为SQL Server安全配置与合规检查的实用参考。

1. 一份规范放在手里,最怕的是照着目录从头改到尾

接手某公司一台跑了六年的业务库时,前任留下的交接文档里,最厚的一份叫《Sql Server 数据库系统加固规范》,账号管理、认证授权、日志配置、通信协议、设备其他安全要求五块,编号从 SHG-Mssql-01-01-01 一路排到 04-01-02,每一条都写了实施目的、问题影响、回退方案和重要等级。我第一次照着改,三天改完,第四天早上报表作业全线失败,备份任务卡在写文件那一步,应用侧报登录失败——规范本身没写错,错在把它当说明书,从头抄到尾。

Sql Server 数据库系统加固规范 这类文档,写的是「应该长成什么样」,没写「你这台机器上该改哪一行、改完怎么退回去」。原文里的syslogins、企业管理器、sp_addlogin是 SQL Server 2000 时代的语法,现在实例上是sys.server_principals、SSMS、CREATE LOGIN,同名条目背后的命令完全不一样。同一句「禁用不必要的协议」,在一台还挂着老客户端的库上执行,就是一次断连事故。这份笔记写给要在一台真实实例上把这套规范落完的人:先盘环境、再逐条实施、最后能验收,中途坏了有得救。

2. 落地前盘环境:版本基线、账号清单与回退包

加固本身不难,难的是「改完之后业务还活着」。所以真正动手之前要做的事,比改配置本身更花时间。我一般会留出半天只做盘点和备份,不碰任何配置项,盘点结果直接落成几张表,后面每条加固动作都对着表改。

2.1 先确认版本与补丁,别拿 2000 的命令打新版本

规范原文给了一张补丁对照表:8.00.194 对应 SQL Server 2000 RTM,8.00.384 是 SP1,8.00.534 是 SP2,8.00.760 是 SP3,8.00.2039 是 SP4。这套对照只在 8.00 这条线里成立,2005 之后的 build 号规则完全不同,所以第一步永远是看实例自己报什么,而不是猜。

-- 一次性取出版本、补丁级别、版本类型和实例名,落到盘点表里 SELECT @@VERSION AS ver_text, -- 完整版本字符串,含操作系统信息 SERVERPROPERTY('ProductVersion') AS build_no, -- 形如 8.00.2039 / 10.50.6000.34 SERVERPROPERTY('ProductLevel') AS patch_level, -- RTM / SP1 / SP2 / SP3 / SP4 SERVERPROPERTY('Edition') AS edition, -- 标准版 / 企业版 / Express SERVERPROPERTY('InstanceName') AS inst_name, -- 命名实例在这里,默认实例返回 NULL SERVERPROPERTY('IsIntegratedSecurityOnly') AS win_auth_only; -- 1 表示只允许 Windows 身份验证

build_no是判断补丁基线的唯一可靠依据,ver_text只是给人看的。IsIntegratedSecurityOnly这一列很关键:如果它返回 1,说明实例当前只接受 Windows 认证,这时候去改「身份验证模式」等于动认证体系,必须排进维护窗口,不能顺手改。命名实例要额外记下来,因为后面协议配置、注册表路径、服务名都跟实例名绑定,默认实例和命名实例的注册表键不是同一个位置。

2.2 把登录名、角色、权限导成一张表

规范第一条要求「为不同的管理员分配不同的账号」,前置动作是先知道现在有哪些账号。原文给的语句是use master加Select name,password from syslogins order by name,这条在新版本上还能跑,但password列返回的是星号,读不出任何信息。换成系统视图更实在:

-- 登录名盘点:账号类型、是否启用、默认库、最近一次登录时间 SELECT sp.name AS login_name, sp.type_desc AS login_type, -- SQL_LOGIN / WINDOWS_LOGIN / WINDOWS_GROUP sp.is_disabled AS disabled, -- 1 表示已禁用 sp.default_database_name AS default_db, sp.create_date, sp.modify_date, ISNULL(CONVERT(varchar(19), sp.last_login_time, 120), '从未登录') AS last_login FROM sys.server_principals AS sp WHERE sp.type IN ('S', 'U', 'G') -- S=SQL 登录,U=Windows 登录,G=Windows 组 ORDER BY sp.name; -- 服务器角色成员:谁挂在了 sysadmin / securityadmin 这类高权限角色上 SELECT r.name AS role_name, m.name AS member_name FROM sys.server_role_members AS rm JOIN sys.server_principals AS r ON r.principal_id = rm.role_principal_id JOIN sys.server_principals AS m ON m.principal_id = rm.member_principal_id ORDER BY r.name, m.name;

第一段查的是「有哪些门」,第二段查的是「谁拿着万能钥匙」。sysadmin成员清单要单独抄一份出来,加固过程中任何账号被误禁用、误降权,都能靠这份清单定位。last_login这一列是判断「无效账号」的直接证据,超过半年没登录、又不是服务账号的,基本可以列进待处理名单。

2.3 回退包:动手之前必须留的四样东西

规范里每条都写了「回退方案」,但回退方案能不能用,取决于你有没有提前留下原值。我一般会固定留四样:

序号留存内容取得方式用途
1master、msdb、model 三个系统库的完整备份完整备份,落地到独立磁盘账号、作业、登录审计配置被改坏时整体回退
2业务库完整备份 + 最近一次日志备份完整 + 日志权限误删导致应用报错时快速比对
3登录名、角色、权限清单2.2 的两段查询结果导出成文本逐个账号还原
4注册表与协议原值3 个 TCP/IP 键值、每个协议的启用状态、服务账号名TCP/IP 加固和协议收敛的回退依据

系统库备份用一条语句就能落:

-- 三个系统库依次备份,路径按实际磁盘改,不要和数据文件放同一块盘 BACKUP DATABASE master TO DISK = 'D:\backup\pre_hardening_master.bak' WITH INIT, COMPRESSION; BACKUP DATABASE msdb TO DISK = 'D:\backup\pre_hardening_msdb.bak' WITH INIT, COMPRESSION; BACKUP DATABASE model TO DISK = 'D:\backup\pre_hardening_model.bak' WITH INIT, COMPRESSION;

WITH INIT会覆盖同名备份文件,所以文件名里必须带日期或「pre_hardening」这类前缀,别直接覆盖上一次的备份。COMPRESSION在标准版及以上可用,能省一半空间,Express 版会报错,去掉这个参数就行。master 库很小,但它是账号信息的载体,一旦sysadmin角色被改空,只能靠它恢复。

2.4 摸清谁在调用危险存储过程

规范最后一章要求停用xp_cmdshell、sp_OACreate、xp_regwrite这一批扩展存储过程。原文给的做法是exec sp_dropextendedproc 'xp_cmdshell'一行行删。问题是,只要有一个老接口或一个作业在调它,删完第二天就会有人来找你。动手前先查有没有对象引用:

-- 在业务库里找引用了危险过程的模块,包括存储过程、函数、触发器 SELECT OBJECT_NAME(m.object_id) AS module_name, m.definition FROM sys.sql_modules AS m WHERE m.definition LIKE '%xp_cmdshell%' OR m.definition LIKE '%sp_OACreate%' OR m.definition LIKE '%xp_regwrite%' OR m.definition LIKE '%xp_dirtree%';

sys.sql_modules只覆盖 T-SQL 模块,查不到应用层拼出来的动态 SQL,也查不到作业步骤里的命令行。作业那部分要去 msdb 里翻sysjobsteps,看command列有没有cmdshell字样。这两处都干净,才说明可以安全删除;只要有一处命中,就先跟业务方确认替代方案,把「删除」改成「先禁用」。

3. 账号管理与认证授权:六条规范逐条落到命令

这一章是规范里条目最多的部分,六条编号 01-01-01 到 01-01-06,重要等级从三星到一星不等。按我的经验,顺序不能反着来:先建新账号、再禁旧账号、然后调权限、最后改口令,中途任何一步走错,都能靠前一步留下的东西还原。

3.1 一人一账号:从 sp_addlogin 到 CREATE LOGIN

规范 01-01-01 要求「按照用户分配账号,避免不同用户间共享账号」,原文示例是sp_addlogin 'user_name_1','password1'。这条语法在旧版本上还能用,但密码以明文出现在脚本和日志里,本身就是一个新隐患。

-- 新版本写法:单独建登录名,强制密码策略,指定默认库 CREATE LOGIN [app_report] WITH PASSWORD = '换成实际强口令', -- 至少 10 位,含大小写、数字、符号 DEFAULT_DATABASE = [BizDB], -- 登录后默认落到业务库,减少误操作 CHECK_POLICY = ON, -- 继承 Windows 密码复杂度与锁定策略 CHECK_EXPIRATION = ON; -- 启用口令有效期 -- 建库内用户并只给只读权限,报表类账号通常只需要这些 USE [BizDB]; CREATE USER [app_report] FOR LOGIN [app_report]; ALTER ROLE [db_datareader] ADD MEMBER [app_report];

CHECK_POLICY = ON是这条命令里最值得保留的参数,它把密码复杂度、锁定阈值交给操作系统策略管,省得自己写规则。代价是:如果这台机器的本地安全策略要求 90 天改密,应用连接串里的密码到期就会失效,所以业务账号一般设CHECK_EXPIRATION = OFF,运维账号设ON,这两类账号必须分开建,不要图省事共用一个。旧语法sp_addlogin不是不能用,是在新版本上已经不推荐,而且它绕过了密码策略。

建角色、把角色赋给用户,是规范里「把角色赋给不同的用户」这句话的具体落法:

-- 建一个只读角色,把对象权限挂在角色上,人员变动时只改成员关系 USE [BizDB]; CREATE ROLE [role_report_read]; GRANT SELECT ON SCHEMA::[dbo] TO [role_report_read]; GRANT EXECUTE ON SCHEMA::[dbo] TO [role_report_read]; ALTER ROLE [role_report_read] ADD MEMBER [app_report];

权限挂在角色上而不是人身上,这是后面「权限最小化」能落地的前提。人走了只删成员关系,不用逐条回收对象权限,也不会出现「这个人权限收干净了,那个人还留着」的情况。

3.2 无效账号与默认账号的处置顺序

规范 01-01-02 要求「删除或锁定无效账号」,原文的操作路径是企业管理器里右键删除。删除是不可逆的,所以我的顺序永远是「先禁用、观察一个周期、确认没人用、再删」。

-- 第一步:禁用,不删除,保留回退可能 ALTER LOGIN [old_temp_user] DISABLE; -- 第二步:观察期内确认没有连接再来,返回 0 行才可以走第三步 SELECT session_id, login_name, status, program_name, host_name FROM sys.dm_exec_sessions WHERE login_name = 'old_temp_user'; -- 第三步:确认无用后删除(用户先从库里移除,再删登录名) USE [BizDB]; DROP USER IF EXISTS [old_temp_user]; USE [master]; DROP LOGIN [old_temp_user];

删登录名的顺序必须是「先库内用户、后服务器登录名」,反过来会留下没有对应登录名的孤儿用户,sys.database_principals里能看到但谁也登不上,排查时很费时间。

关于默认账号,sa不要直接删,规范也没要求删——它要求的是「减少系统安全隐患」。合理做法是先确认实例是否启用了 SQL 认证,如果业务全部走 Windows 认证,就把sa禁用:

-- 先看 sa 当前状态,is_disabled 返回 1 说明已经禁用 SELECT name, is_disabled, last_login_time FROM sys.sql_logins WHERE name = 'sa'; -- 业务不依赖 SQL 认证时,禁用而不是删除 ALTER LOGIN [sa] DISABLE;

禁用sa之后如果还有应用用它连库,会立刻报「登录失败」,所以这一步必须和业务方确认过,或者先把应用改成独立账号再禁。

3.3 启动账号降权:最容易把服务搞起不来的地方

规范 01-01-03 要求「限制启动账号权限」,原文写的是新建 SQL Server 服务账号后,从 Users 组中删除,不要把账号提升为 Administrators 组成员。这条是所有条目里风险最高的,因为它直接决定服务能不能起来。

正确的做法是分步走,每一步都验证服务状态:

# 用 PowerShell 查看当前各服务的登录身份,确认改之前是什么账号 Get-CimInstance Win32_Service | Where-Object { $_.Name -like 'MSSQL*' -or $_.Name -like 'SQLAgent*' } | Select-Object Name, StartName, State # 改服务账号(示例服务名 MSSQLSERVER,命名实例是 MSSQL$实例名) $svc = Get-CimInstance Win32_Service -Filter "Name='MSSQLSERVER'" $svc.Change($null, $null, $null, $null, $null, $null, "DOMAIN\svc_sqlserver", "口令", $null, $null, $null)

StartName就是服务启动身份,改之前必须记下来。Change方法的第七、第八个参数分别对应账号和密码,账号格式是「域\用户」或「.\本地用户」,写错格式服务会直接起不来。

改完账号之后有三件事必须马上做,否则服务大概率启动失败:给新账号授予「作为服务登录」权限;给新账号授予数据文件目录、日志目录、备份目录的读写权限;如果开了 SQL Server 代理,代理服务账号也要单独配。规范里提到的 SQLRunAs 账户,本质就是「一个只跑数据库、不进管理员组、不登录系统」的专用账号,它不需要交互式登录权限,给了反而是多余攻击面。回退路径很直接:把服务账号改回原值,重启服务即可,所以这一步的原值记录不能省。

3.4 权限最小化与角色化管理

规范 01-01-04 和 01-01-05 其实是同一件事的两面:前者要求「取消业务账号不需要的服务器角色和数据库角色」,后者要求「用角色来管理对象权限」。操作上就是三步——列出多余角色、移除、改由自定义角色承载。

-- 查某个登录名当前挂了哪些服务器角色,重点看 securityadmin / sysadmin / bulkadmin SELECT r.name AS role_name FROM sys.server_role_members AS rm JOIN sys.server_principals AS r ON r.principal_id = rm.role_principal_id JOIN sys.server_principals AS m ON m.principal_id = rm.member_principal_id WHERE m.name = 'app_report'; -- 移除不需要的服务器角色 ALTER SERVER ROLE [securityadmin] DROP MEMBER [app_report]; -- 库内同理:先看库角色成员,再移除多余的写权限 USE [BizDB]; SELECT r.name AS role_name FROM sys.database_role_members AS rm JOIN sys.database_principals AS r ON r.principal_id = rm.role_principal_id JOIN sys.database_principals AS m ON m.principal_id = rm.member_principal_id WHERE m.name = 'app_report'; ALTER ROLE [db_datawriter] DROP MEMBER [app_report];

判断「多余」的标准是业务测试是否正常,而不是「看起来不需要」。规范原文里判断依据写的就是「业务测试正常」四个字,这四字背后意味着:收紧权限只做减法、一次只收一档、每收一次跑一遍业务回归。最忌讳一次性把db_datareader、db_datawriter、db_executor全撤掉再慢慢加,出了问题根本不知道是哪一档导致的。

3.5 空密码与强口令整改

规范 01-01-06 要求检查空密码,特别是sa需要至少 10 位强口令。原文给的查询是Select name,Password from syslogins where password is null,在旧版本上确实能查到。新版本password列不可读,要用PWDCOMPARE来判断:

-- 找出所有空密码的 SQL 登录名:PWDCOMPARE 第一个参数为空串,返回 1 即为空密码 SELECT name, is_disabled, is_policy_checked, is_expiration_checked FROM sys.sql_logins WHERE PWDCOMPARE('', password_hash) = 1; -- 改口令,同时打开密码策略 ALTER LOGIN [app_report] WITH PASSWORD = '新的强口令' , CHECK_POLICY = ON;

sys.sql_logins只包含 SQL 认证登录名,Windows 认证账号不需要这条检查,因为口令由域控管。is_policy_checked和is_expiration_checked两列能直接看出哪些账号还在裸奔——没开策略的账号,口令可以设成一位字符,这条检查是必须补上的。改sa口令时注意:如果有作业或链接服务器用sa连本机,改完会一起失效,这类依赖要在 2.4 那一步一并查出来。

4. 日志配置与通信协议:审计开关、协议收敛和加密

账号那章做完,实例的「谁能进来」就定了;这一章解决的是「进来之后有没有记录」和「从哪条路进来」。规范里这部分的条目少,但可操作面很窄,配置项大多在图形界面或注册表里,没有 SQL 语句那么直观,也更容易配错。

4.1 登录审计:审计级别到底改的是哪个开关

规范 02-01-01 要求启用日志记录,记录账号、登录是否成功、登录时间和来源 IP。原文的操作方式是「打开数据库属性,选择安全性,把审计级别调整为全部,身份验证调整为 SQL Server 和 Windows」。在 SSMS 里对应的是实例属性 → 安全性 → 登录审核,四个选项分别对应「无」「仅失败」「仅成功」「成功和失败」。

图形界面之外,也可以走注册表写入的方式,适合批量脚本:

-- 开启高级选项后才能改注册表扩展过程 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; -- LoginAuditLevel:0=无,1=仅成功,2=仅失败,3=成功和失败 EXEC master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer', N'LoginAuditLevel', REG_DWORD, 3; -- 改完复查当前值 EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer', N'LoginAuditLevel';

xp_instance_regwrite的第三个参数是注册表值名,第四个REG_DWORD是数据类型,第五个才是要写进去的值。命名实例的注册表路径不是MSSQLServer这一层,而是Microsoft SQL Server\<实例名>,路径写错会静默失败,所以最后那次xp_instance_regread不能省,它是唯一能确认写入成功的动作。

审计级别设成 3 之后,登录成功和失败都会写进错误日志,代价是日志增长变快,高并发短连接场景下 ERRORLOG 会涨得很快,这一点在 4.2 里单独处理。

4.2 日志落盘与保留,别让审计记录自己滚没了

登录审计记录在错误日志里,而错误日志默认只保留 7 个文件(6 个归档 + 1 个当前),滚动一次最老的就没了。规范要求「记录内容包括用户登录使用的账号、登录是否成功、登录时间以及远程登录时用户使用的 IP 地址」,这些字段确实都在,但前提是日志还在。

-- 查询最近的登录失败记录,参数依次是:日志序号、日志类型(1=错误日志)、检索串 EXEC sp_readerrorlog 0, 1, N'登录失败'; EXEC sp_readerrorlog 0, 1, N'Login failed'; -- 查看当前错误日志文件数量,默认 6,可按合规要求调大 EXEC xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer', N'NumErrorLogs';

sp_readerrorlog的第一个参数是日志序号,0 表示当前日志,1 表示上一个归档。巡检脚本一般会从 0 扫到 6,把失败记录汇总出来。NumErrorLogs默认值是 6,按日切的话只覆盖一周,如果合规要求保留 90 天,要么把它调到 90,要么在错误日志之外单独做归档——后者的常见做法是用 SQL Server 代理作业定时把 ERRORLOG 复制到归档目录,再按日期命名。

另外规范提到的「数据库操作记录」和「登录记录」不是一回事。登录审计只覆盖认证环节,增删改这类操作要靠业务库自身的审计功能或触发器,这部分原文没有展开,实施时按各自的合规要求单独设计,别以为开了登录审计就万事大吉。

4.3 网络协议收敛:只留 TCP/IP 之前先查客户端

规范 03-01-01 要求「除去不必要的服务,建议只使用 TCP/IP 协议,禁用其他协议」。SQL Server 的网络协议包括 Shared Memory、Named Pipes、TCP/IP 和 VIA,其中 Shared Memory 只在本机有效,禁用它会让本机 SSMS 的连接行为发生变化,所以我的做法是:本机保留 Shared Memory,跨机只留 TCP/IP,Named Pipes 和 VIA 一律禁用。

操作在 SQL Server 配置管理器里:SQL Server 网络配置 → 实例名的协议 → 右键每个协议选启用或禁用。禁用后必须重启 SQL Server 服务才生效,重启就是一次业务中断,所以这一步必须排进窗口。

动手前先确认没有客户端依赖命名管道:

-- 看当前连接用的是哪个协议:net_transport 列直接告诉你 SELECT session_id, net_transport, protocol_type, client_net_address, program_name FROM sys.dm_exec_connections WHERE net_transport IS NOT NULL;

net_transport返回TCP、Named Pipes、Shared memory三种常见值。如果查出有远程客户端正在走Named Pipes,说明某个连接串或别名里写死了管道名,禁之前必须把那些客户端改到 TCP,否则重启那一刻它们全部掉线且连不回来。这是我在真实环境里踩过的最典型的坑,排在 5.1 条里详细说。

4.4 TCP/IP 栈加固:三个注册表键的原值与回退

规范 03-01-02 列了三个注册表键,都是 Windows 层面的网络栈加固,不是 SQL Server 自己的配置:

注册表键(均在HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters下)建议值作用
DisableIPSourceRouting2禁用源路由,防源路由欺骗
EnableICMPRedirect0关闭 ICMP 重定向,防路由表被改写
SynAttackProtect2加强 SYN FLOOD 攻击防护
# 改之前先把原值导出,回退时逐条写回 $path = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' Get-ItemProperty -Path $path | Select-Object DisableIPSourceRouting, EnableICMPRedirect, SynAttackProtect # 写入加固值,类型统一为 DWord Set-ItemProperty -Path $path -Name DisableIPSourceRouting -Value 2 -Type DWord Set-ItemProperty -Path $path -Name EnableICMPRedirect -Value 0 -Type DWord Set-ItemProperty -Path $path -Name SynAttackProtect -Value 2 -Type DWord

第一条命令输出的是原值,必须抄到 2.3 的回退包里。Set-ItemProperty的-Type DWord不能省,写成字符串类型某些系统上不生效。这三个键属于系统级网络参数,改完不需要重启 SQL Server,但部分系统要重启网络适配器或整机才彻底生效,所以要和维护窗口一起安排。

注意一点:在较新的 Windows 版本上,这三个键的默认值和生效方式已有变化,有的键即使写进去也不再产生旧版本那样的效果。规范给的是一套通用基线,落到具体机器上以「改完能验证、出问题能回退」为准,别把它当成必须逐字命中的硬指标。

4.5 强制协议加密:证书不对就是全线连不上

规范 03-01-04 要求「使用通讯协议加密」,原文的操作是启动服务器网络配置工具,把常规设置改成「强制协议加密」。这个开关打开之后,客户端和服务器之间的所有流量都走 TLS,但前提是实例上装了一张能被客户端信任的证书。

操作路径是:SQL Server 配置管理器 → SQL Server 网络配置 → 实例的协议 → 右键 TCP/IP 属性 → 标志页 → 强制加密设为「是」,然后重启服务。证书本身要提前导入到「本地计算机 → 个人」证书存储,并且主题名称(CN)必须和客户端连接时使用的主机名一致。

-- 查看实例当前使用的加密证书信息,encrypt_option 为 TRUE 表示连接已加密 SELECT session_id, encrypt_option, net_transport, client_net_address FROM sys.dm_exec_connections; -- 确认加密用的是哪张证书 SELECT name, issuer_name, expiry_date, thumbprint FROM sys.certificates WHERE name = '所选证书的名称';

encrypt_option返回TRUE才说明连接真正加密了,只开开关不装证书,SQL Server 会自己生成一张自签证书,客户端不信任它就报证书链错误。这条在所有加固条目里回退成本最高:一旦客户端全量报错,只能把「强制加密」改回「否」并重启服务,等于又一次中断。所以我的做法是先在一台测试实例上把证书链跑通,再推到生产。

5. 常见问题与排查:加固后连不上、作业失败怎么救

加固过程中出事,绝大多数不是命令写错,而是「改的东西影响了没在清单上的依赖」。下面五条是我在不同环境里真实遇到过的,按「现象 → 原因 → 解决」记下来,碰到类似症状可以直接对号。

5.1 现象:应用突然连不上,报登录失败或超时

原因:协议收敛时禁用了 Named Pipes,而某个客户端连接串或 SQL Server 别名里写死了np:前缀,走的是命名管道。服务重启后这条路径直接消失,客户端表现是连接超时,日志里未必有明确报错。

解决:先查客户端侧配置,把别名和连接串统一改成tcp:主机名,端口。如果改不了(第三方老系统),临时把 Named Pipes 重新启用并重启服务,恢复到可用状态后再推改造。事后补一条前置检查:用sys.dm_exec_connections按net_transport分组统计一遍,确认没有远程的命名管道连接,再动协议开关。

5.2 现象:备份作业和代理作业在服务账号降权后集体失败

原因:服务账号从 Administrators 或 Users 组移除后,对数据目录、备份目录、日志目录的 NTFS 权限一并丢失。SQL Server 服务本身能起来(它对实例目录有权限),但备份作业要往另一个盘写文件,那里没有新账号的写权限,作业报「拒绝访问」。

解决:给新服务账号单独授予三个目录的修改权限,不要整组加回 Users 组,那就白降权了。目录清单是:数据文件目录、日志文件目录、备份目录,加上sp_readerrorlog用到的错误日志目录。授权后用一条BACKUP DATABASE ... TO DISK手工验证一次,比看作业历史快。

5.3 现象:删掉 xp_cmdshell 之后,第二天某个老接口挂了

原因:应用侧依赖xp_cmdshell做文件搬运或调外部程序,调用点不在数据库模块里,sys.sql_modules查不到,2.4 那一步漏掉了。

解决:先恢复可用,再谈替代。xp_cmdshell属于内置扩展过程,新版不能再靠sp_addextendedproc加回来,而是用配置项控制:

-- 恢复 xp_cmdshell:先开高级选项,再打开开关 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;

恢复之后按规范原意处理:能改造成作业或外部程序调度的,改造成本一次付清;确实改不了的,至少限制调用账号只有sysadmin,并把开关默认关掉、只在执行窗口打开。sp_OACreate这一组同理,sp_configure 'Ole Automation Procedures'才是总开关,单删扩展过程在新版本上不一定有效。

5.4 现象:打开强制加密后,客户端报证书链错误或直接超时

原因:三种可能——实例上没有导入证书,用的是自签证书;导入的证书 CN 和客户端连接的主机名不一致;客户端机器不信任签发该证书的根 CA。

解决:按顺序排查。先在服务端查sys.dm_exec_connections的encrypt_option,确认加密是否真的生效;再核对证书 CN 和连接串里的主机名是否逐字相同,用 IP 连而证书是域名签发的,一样会失败;最后在客户端用工具验证证书链,把根 CA 导入到「受信任的根证书颁发机构」。实在来不及,先关掉强制加密保业务,证书改造单独立项。

5.5 现象:审计级别设成「全部」,ERRORLOG 里却查不到登录记录

原因:三种情况——改的是注册表但没重启服务,值没生效;sp_readerrorlog查的是当前日志,记录已经滚动到归档文件里;查询用的检索串和实际日志语言不匹配,中文环境下写Login failed是搜不到的。

解决:先用xp_instance_regread复查LoginAuditLevel的实际值,确认是 3;再用sp_readerrorlog从 0 扫到 6,把归档日志一起搜;检索串中英文各试一次。这三步走完还查不到,就手工制造一次失败登录(用错误口令连一个测试账号),立刻查当前日志,能查到说明审计是好的,之前只是日志滚掉了。

6. 验收:把加固结果逐条对回规范编号

改完不等于生效,生效不等于业务正常。我习惯在收尾时跑一遍固定脚本,把规范编号和查询结果一一对上,输出一张带结论的表,谁来看都能判断这次加固到底做没做。

规范编号验收动作期望结果
01-01-01查sys.server_principals与业务账号对应关系每个使用者有独立登录名,无共享账号
01-01-02查is_disabled与last_login_time无效账号已禁用,观察期后已删除
01-01-03PowerShell 查服务StartName服务账号不在 Administrators 组,非系统内置账号
01-01-04查服务器角色与库角色成员业务账号只保留必需角色
01-01-05查自定义角色与成员对象权限挂在角色上,成员关系可追溯
01-01-06PWDCOMPARE('', password_hash)返回 0 行,sa口令 10 位以上
02-01-01制造一次失败登录后查错误日志账号、时间、来源 IP 均被记录
03-01-01查sys.dm_exec_connections的net_transport远程连接全部为 TCP
03-01-02读三个 TCP/IP 注册表键值为 2 / 0 / 2,原值已归档
03-01-04查encrypt_option返回 TRUE,证书 CN 与主机名一致
04-01-01查sys.extended_procedures与sys.configurations危险过程已删除或对应开关已关闭
04-01-02SELECT @@VERSION比对补丁级别补丁为当前可用的最新级别

验收脚本可以写成一段可直接执行的 T-SQL,输出 PASS/FAIL 两列:

-- 验收:空密码账号数量,期望 0 SELECT '01-01-06 空密码检查' AS item, CASE WHEN COUNT(*) = 0 THEN 'PASS' ELSE 'FAIL' END AS result, COUNT(*) AS detail FROM sys.sql_logins WHERE PWDCOMPARE('', password_hash) = 1; -- 验收:远程命名管道连接数,期望 0 SELECT '03-01-01 协议收敛' AS item, CASE WHEN COUNT(*) = 0 THEN 'PASS' ELSE 'FAIL' END AS result, COUNT(*) AS detail FROM sys.dm_exec_connections WHERE net_transport = 'Named Pipes' AND client_net_address <> 'local machine'; -- 验收:高危扩展过程是否存在,期望存在的数量为 0 SELECT '04-01-01 危险过程' AS item, CASE WHEN COUNT(*) = 0 THEN 'PASS' ELSE 'FAIL' END AS result, COUNT(*) AS detail FROM sys.extended_procedures WHERE name IN ('xp_cmdshell', 'sp_OACreate', 'xp_regwrite', 'xp_dirtree');

三段查询分别对应账号、协议、存储过程三条线,detail列给出具体数量,result列直接给结论。这套脚本我一般会存成一个 .sql 文件放在运维仓库里,每次加固前后各跑一次,前后对比就是一份完整的变更记录。

登录审计的验收要单独做,因为它是唯一需要「主动制造一次失败」的项目:用一个不存在的账号连一次库,然后立刻执行EXEC sp_readerrorlog 0, 1, N'登录失败',能查到刚发生的那条记录才算通过。这一步别嫌麻烦,审计开关是最容易出现「以为开了其实没开」的配置。

最后说个习惯:那次报表作业全线失败之后,我每次做加固都会先把 2.3 的四样东西备齐,再按 2.4 查一遍依赖,然后才动第一条配置。规范里的编号和回退方案是给人看的,真正能救命的是你手上那份原值清单和系统库备份。这份资源的价值不在条目多全,而在于每条都写了实施风险和回退路径——照着做的时候,把回退那一栏当成必读项而不是可选项,能少走很多弯路。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询