简介:这份Sql Server数据库系统加固规范文档面向数据库管理员、安全运维人员及等保合规实施者,围绕账号管理、认证授权、日志配置、通信协议与设备安全等维度,给出可落地的加固要求与配置依据。资源包内含1个doc文档,压缩包约1.73MB,内容以条目化规范编号组织,便于按模块查阅与对照执行。文档从账号最小权限、密码复杂度与有效期、登录失败锁定、双因素认证,到日志记录、SSL/TLS加密通信、防火墙与入侵检测,再到服务器防毒与定期安全检查,逐项列出实施目的、问题影响、当前状态、实施步骤、回退方案、判断依据、实施风险与重要等级,并附有具体操作命令示例。目前已有245人学习下载,适合需要开展数据库安全加固、合规自查或编写加固方案的技术人员参考使用。
1. 一次基线核查暴露的六个配置缺口
很多团队第一次做 Sql Server 数据库系统加固规范,都是从一次基线核查开始的。某公司内网一台跑了三年多的业务库,平时没人动过配置,直到安全团队用脚本扫了一遍,才发现 sa 账号还开着、远程登录没限制、备份目录权限是 Everyone 完全控制。这类问题不是漏洞,是配置债,平时不疼不痒,一旦被横向利用就是整库沦陷。
Sql Server 数据库系统加固规范,本质是把「装完能用」的默认状态,收敛成「最小权限、最小暴露面、可审计」的受控状态。它解决的不是某个 CVE,而是账号、认证、网络、权限、审计、备份这六条线上的系统性风险。适合谁看:手上管着几台到几十台 Sql Server 实例的 DBA、运维、安全基线负责人,以及需要过等保测评或内部审计的团队。下面按我实际落地的顺序,把每一步的命令、参数和判断标准讲清楚。
2. 账号与认证面收敛:从 sa 到最小权限登录名
加固的第一步永远是账号,因为绝大多数入侵路径的终点都是拿到一个高权限数据库账号。Sql Server 的账号体系分两层:服务器级登录名(Login)和数据库级用户(User),两层都要收。
2.1 先摸清现状:查登录名、角色和认证模式
动手之前先做资产盘点,别上来就改。下面这段查询把服务器级登录名、是否启用、认证类型、所属服务器角色一次性拉出来。
-- 盘点服务器级登录名及其角色归属 SELECT sp.name AS login_name, sp.type_desc AS login_type, sp.is_disabled AS disabled, sp.create_date, sp.modify_date, STRING_AGG(rp.name, ',') AS server_roles FROM sys.server_principals sp LEFT JOIN sys.server_role_members srm ON sp.principal_id = srm.member_principal_id LEFT JOIN sys.server_principals rp ON srm.role_principal_id = rp.principal_id WHERE sp.type IN ('S','U','G') -- S=SQL登录, U=Windows登录, G=Windows组 AND sp.name NOT LIKE '##%' -- 排除系统内部主体 GROUP BY sp.name, sp.type_desc, sp.is_disabled, sp.create_date, sp.modify_date ORDER BY sp.is_disabled, sp.name;逻辑说明:sys.server_principals是服务器级主体的主表,type过滤掉证书、非对称密钥等非登录主体。STRING_AGG把角色拼成一行,方便一眼看出谁挂了 sysadmin。参数上注意sp.name NOT LIKE '##%',##开头的是复制、CDC 等内部主体,误删会出问题。
认证模式用这条查:
-- 查看当前认证模式:1=仅Windows, 0=混合模式 SELECT SERVERPROPERTY('IsIntegratedSecurityOnly') AS windows_only_auth;返回 0 就是混合模式,意味着 SQL 登录名可以密码登录,风险面更大。如果业务不依赖 SQL 登录,应切到仅 Windows 认证;切之前必须确认没有应用在用 SQL 账号,否则直接断业务。
2.2 禁用 sa 与清理多余 sysadmin
sa 是内置最高权限账号,加固规范里默认要求禁用。但禁用前先确认没有作业、链接服务器、应用连接串在用 sa。
-- 禁用 sa(先确认无依赖) ALTER LOGIN [sa] DISABLE; -- 重命名 sa 降低可猜测性(可选,部分合规要求) ALTER LOGIN [sa] WITH NAME = [svc_legacy_admin]; -- 查所有 sysadmin 成员,逐个确认必要性 SELECT p.name AS sysadmin_member FROM sys.server_role_members m JOIN sys.server_principals r ON m.role_principal_id = r.principal_id JOIN sys.server_principals p ON m.member_principal_id = p.principal_id WHERE r.name = 'sysadmin';逻辑说明:ALTER LOGIN ... DISABLE只禁用不删除,保留可回滚的后悔药。重命名 sa 前要评估:某些老应用的连接串硬编码了 sa,改名会直接连不上。sysadmin 成员控制在 2 个以内,多出来的用ALTER SERVER ROLE sysadmin DROP MEMBER [xxx]移除,移除前确认该账号没有正在跑的作业。
2.3 按业务建最小权限账号
应用连接库不应该用 db_owner,更不该用 sysadmin。正确做法是按「只读」「读写」「只执行存储过程」三类建账号。
-- 建一个只读业务账号 CREATE LOGIN [app_readonly] WITH PASSWORD = N'<强密码>', CHECK_POLICY = ON, -- 启用密码策略 CHECK_EXPIRATION = OFF, -- 服务账号不过期 DEFAULT_DATABASE = [BizDB]; USE [BizDB]; CREATE USER [app_readonly] FOR LOGIN [app_readonly]; ALTER ROLE [db_datareader] ADD MEMBER [app_readonly]; -- 显式拒绝写操作,双保险 DENY INSERT, UPDATE, DELETE, ALTER ON SCHEMA::dbo TO [app_readonly];逻辑说明:CHECK_POLICY = ON让密码受 Windows 密码复杂度约束,服务账号用CHECK_EXPIRATION = OFF避免到期断连。DENY是显式拒绝,优先级高于角色授予,即使误加了 db_datawriter 也写不进去。参数上DEFAULT_DATABASE要指向业务库,避免登录后落到 master。
3. 网络与实例暴露面:把 1433 关进该在的边界
账号收完了,下一步是网络。Sql Server 默认监听 1433,很多实例直接暴露在内网大网段甚至公网,这是最容易被扫到的入口。
3.1 确认监听地址与端口
先看实例到底监听在哪。
-- 查看当前实例的监听端点 SELECT ec.name AS endpoint_name, ec.protocol_desc, ec.is_admin_endpoint, ec.port, ec.is_encryption_enabled, ec.encryption_algorithm_desc FROM sys.tcp_endpoints ec WHERE ec.type_desc = 'TSQL';逻辑说明:sys.tcp_endpoints里port是实际监听端口,is_encryption_enabled反映是否强制加密。如果port是 1433 且实例在公网可达,必须处理。改端口不是安全手段(扫描器照样能发现),真正的边界是防火墙和网络隔离。
3.2 用防火墙规则限定来源
Windows 防火墙按来源网段放行,比改端口有效得多。
# 只允许应用服务器网段访问 1433 New-NetFirewallRule -DisplayName "SQL-1433-AppSubnet" ` -Direction Inbound -Protocol TCP -LocalPort 1433 ` -RemoteAddress 10.20.30.0/24 -Action Allow # 显式拒绝其他来源(放在允许规则之后,优先级靠拒绝) New-NetFirewallRule -DisplayName "SQL-1433-DenyAll" ` -Direction Inbound -Protocol TCP -LocalPort 1433 ` -RemoteAddress Any -Action Block逻辑说明:Windows 防火墙对入站规则是「显式拒绝优先于允许」,所以拒绝规则要谨慎,正确顺序是先允许特定网段再拒绝其余。RemoteAddress支持 CIDR,多个网段用逗号分隔。参数上-LocalPort要和实例实际端口一致,命名实例可能是动态端口,得先在配置管理器里固定。
3.3 强制加密与隐藏实例的取舍
传输加密能防内网嗅探,但会带来证书维护成本。
-- 查看是否强制加密 SELECT SERVERPROPERTY('IsIntegratedSecurityOnly') AS win_auth, CASE WHEN EXISTS ( SELECT 1 FROM sys.dm_exec_connections WHERE encrypt_option = 'TRUE' ) THEN '有加密连接' ELSE '无加密连接' END AS enc_status;逻辑说明:强制加密要在配置管理器里导入证书并勾选 Force Encryption,重启实例生效。自签证书客户端要信任,否则连接报证书链错误。隐藏实例(Hide Instance)只对 SQL Browser 广播有效,直连端口照样能连,别把它当安全措施。
4. 权限、审计与备份:把「谁动了什么」留成证据
前两章解决「进不来」,这一章解决「进来了能干什么、干了什么、出事能不能恢复」。
4.1 用服务器审计记录关键操作
Sql Server 自带审计(Server Audit),比触发器轻量,比默认 trace 规范。
-- 创建服务器审计,落到文件 CREATE SERVER AUDIT [Audit_LoginAndDDL] TO FILE ( FILEPATH = N'D:\SQLAudit\', MAXSIZE = 256 MB, MAX_ROLLOVER_FILES = 20, RESERVE_DISK_SPACE = OFF ) WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE); ALTER SERVER AUDIT [Audit_LoginAndDDL] WITH (STATE = ON); -- 审计登录失败与权限变更 CREATE SERVER AUDIT SPECIFICATION [AuditSpec_Login] FOR SERVER AUDIT [Audit_LoginAndDDL] ADD (FAILED_LOGIN_GROUP), ADD (SUCCESSFUL_LOGIN_GROUP), ADD (SERVER_ROLE_MEMBER_CHANGE_GROUP), ADD (DATABASE_OBJECT_PERMISSION_CHANGE_GROUP) WITH (STATE = ON);逻辑说明:QUEUE_DELAY = 1000表示最多延迟 1 秒落盘,兼顾性能与实时性;ON_FAILURE = CONTINUE表示审计写不进去也不停库,生产环境一般选 CONTINUE,合规要求严格的选 SHUTDOWN。FAILED_LOGIN_GROUP是排查暴力破解的关键,SERVER_ROLE_MEMBER_CHANGE_GROUP能抓到谁偷偷加了 sysadmin。审计文件目录要给 SQL 服务账号写权限,否则审计起不来。
4.2 备份加密与权限隔离
备份文件是整库数据的副本,权限必须比数据库本身更严。
-- 创建主密钥和证书,用于备份加密 USE master; CREATE MASTER KEY ENCRYPTION BY PASSWORD = N'<强密码>'; CREATE CERTIFICATE [BackupCert] WITH SUBJECT = N'Backup Encryption Cert'; -- 加密备份 BACKUP DATABASE [BizDB] TO DISK = N'E:\Backup\BizDB_full.bak' WITH INIT, COMPRESSION, ENCRYPTION (ALGORITHM = AES_256, SERVER CERTIFICATE = [BackupCert]), STATS = 10;逻辑说明:ENCRYPTION子句要求先有证书,证书又依赖主密钥,主密钥密码丢了备份就解不开,必须单独保管。COMPRESSION减小体积但增加 CPU,备份窗口紧张时权衡。备份目录的 NTFS 权限只给 SQL 服务账号和备份管理员,去掉 Everyone 和 Users。
4.3 权限回收的检查清单
加固不是一次性动作,下面这张表是我每次复查时逐项过的。
| 检查项 | 期望状态 | 查询入口 |
|---|---|---|
| sa 账号 | 已禁用或已重命名 | sys.server_principals |
| sysadmin 成员 | 不超过 2 个 | sys.server_role_members |
| 混合认证 | 非必要则关闭 | SERVERPROPERTY |
| 1433 暴露 | 仅限应用网段 | 防火墙规则 |
| 审计 | 登录失败+权限变更已开 | sys.server_audit_specifications |
| 备份加密 | 敏感库已加密 | 备份语句带 ENCRYPTION |
| public 角色权限 | 无多余授予 | sys.database_permissions |
5. 加固避坑:五条血泪经验
这一章全是踩过的坑,每条按现象、原因、解决写。
现象:禁用 sa 后某个夜间批处理作业失败。原因:作业的 owner 是 sa,禁用后作业执行上下文失效。解决:先把作业 owner 改成专用服务账号,再禁用 sa。查作业 owner 用SELECT name, SUSER_SNAME(owner_sid) FROM msdb.dbo.sysjobs;。
现象:开了审计后实例 CPU 明显上升。原因:审计规格里加了过多高频事件,比如每一条 SELECT 都记。解决:只审计登录、DDL、权限变更这类低频高价值事件,别审计 DML。QUEUE_DELAY从 0 调到 1000 也能降负载。
现象:强制加密后老应用连不上,报证书链错误。原因:自签证书没导入客户端信任库。解决:要么把证书链分发到客户端,要么对纯内网可信网段暂不强制加密,用防火墙隔离替代。别为了加密把业务搞挂。
现象:备份加密后换服务器恢复失败。原因:证书和主密钥没一起迁移,新实例没有对应私钥。解决:备份证书用BACKUP CERTIFICATE ... TO FILE导出,连同主密钥密码一起归档到独立的密钥管理系统,恢复前先还原证书。
现象:改了端口以为安全了,结果还是被扫到。原因:端口扫描是全端口扫的,改端口只是增加一点噪音,不构成边界。解决:真正的边界是防火墙白名单加网络分区,端口改动只作为辅助。
6. 用脚本把加固做成可重复的基线
手工敲一遍命令只能管一台,几十台实例靠人肉迟早翻车。我的习惯是把上面所有检查项写成一个幂等的 PowerShell 加 T-SQL 脚本包,每次新实例上线跑一遍,定期复查再跑一遍。
核心思路是「检测-修复-验证」三段式。检测段只读,输出当前状态;修复段按参数决定是否执行;验证段重新查一遍确认生效。下面是一个检测段的骨架,用Invoke-Sqlcmd批量跑。
# 批量检测多实例的加固基线状态 $instances = @('SQLNODE01','SQLNODE02','SQLNODE03') $checkSql = @" SELECT SERVERPROPERTY('ServerName') AS instance_name, SERVERPROPERTY('IsIntegratedSecurityOnly') AS win_only_auth, (SELECT COUNT(*) FROM sys.server_principals WHERE is_disabled = 0 AND type = 'S') AS active_sql_logins, (SELECT COUNT(*) FROM sys.server_role_members m JOIN sys.server_principals r ON m.role_principal_id = r.principal_id WHERE r.name = 'sysadmin') AS sysadmin_count; "@ foreach ($inst in $instances) { try { $result = Invoke-Sqlcmd -ServerInstance $inst ` -Query $checkSql -TrustServerCertificate -ErrorAction Stop # 判定:混合认证或 sysadmin 超 2 个即标记不合规 $status = if ($result.win_only_auth -eq 0 -or $result.sysadmin_count -gt 2) { 'FAIL' } else { 'PASS' } [PSCustomObject]@{ Instance = $inst WinOnlyAuth = $result.win_only_auth SysadminCount = $result.sysadmin_count Status = $status } } catch { [PSCustomObject]@{ Instance = $inst Status = "ERROR: $($_.Exception.Message)" } } }逻辑说明:Invoke-Sqlcmd的-TrustServerCertificate在自签证书环境下必须加,否则连不上。try/catch保证单台失败不影响整批。判定逻辑里win_only_auth -eq 0表示混合认证,标记 FAIL。这个骨架可以扩展成读配置文件、输出 CSV 报告、对接工单系统。
参数上要注意:批量跑时用只读账号,别用 sysadmin,避免脚本本身成为风险点。-Query里的查询要加WITH (NOLOCK)吗?系统视图不需要,加了反而可能读到不一致的元数据。
进阶一点的做法是把基线规则外置成 JSON,脚本读规则再执行,这样不同业务线可以用不同基线,不用改脚本。验证方法很简单:故意在一台测试实例上把 sa 启用、加一个 sysadmin,跑脚本应该报 FAIL;改回去应该报 PASS。这个正反测试是脚本可信的前提,别跳过。
我自己的教训是:第一版脚本只做了检测没做验证,结果修复段有个ALTER LOGIN写错了库,跑完看着是 PASS 实际没生效,过了两个月审计才发现。从那以后我坚持每段修复后面必须跟一段验证查询,眼见为实。希望帮到你。
本文还有配套的精品资源,点击获取