MSSQL 事件排查
MSSQL 应急排查部分已经有微步在线应急响应团队、深信服千里目实验室写得非常详细了,本部分基本也就是对其文章的提炼,建议大家阅读 《知攻善防~SQL Server 应急分析(上&下)》、《MSSQL数据库攻击实战指北 | 防守方攻略》
https://mp.weixin.qq.com/s/omDWZ0MK-WRTICXK3K9dZA
https://mp.weixin.qq.com/s/zfUJnAiSzm-gwYVWxGT7Cw
https://mp.weixin.qq.com/s/ug5LmTIbrd_jVG-euNr8aw
0x00 固定证据¶
在发生任何安全事件时,确定安全事件真实存在以后,第一步都建议固定证据,固定证据一般有以下几种类型,受害单位可以根据实际需求选择
- 系统快照 - 一般云环境比较方便这么做
- 磁盘取证
- 针对性取证 - 例如日志文件、网络信息、数据库等
- 内存取证
系统快照¶
这种主要是云环境或虚拟化环境比较方便,目前似乎这类方式取证出来的内容都会丢失内存信息,属于是关机-快照-导出
虚拟机软件似乎支持例如暂停、冻结等功能,具体根据实际情况决定
磁盘取证¶
磁盘取证有很多工具可以考虑
dd
FTK Imager
针对性取证¶
这部分推荐我们自己的 NOPTrace-Collector
https://github.com/Just-Hack-For-Fun/NOPTrace-Collector
我们还推出了一套数字取证和应急响应规范,可以根据此规范自己开发取证程序
https://github.com/Just-Hack-For-Fun/OpenForensicRules
内存取证¶
DumpIt
FTK Imager
取证后,对证据进行分析时,需要先单独复制一份,保持所有安全人员分析的基础是相同的
0x01 简介¶
MSSQL 是微软官方的一款数据库管理程序,常用于 Windows 操作系统中 .net 开发的系统中,也是攻击者常常利用的攻击点,在非常早期的时候,往往是网站与数据库放在同一个操作系统上,而且 MSSQL 的端口(默认 1433)对外暴露,导致很多安全问题,甚至一些早期黑客还为它专门开发了“1433抓鸡”程序
现在安全防护水平提升了,MSSQL 面临的威胁主要是以下这些内容:
- 弱口令
- SQL 注入
- 权限提升
- 数据泄露
- 隐藏后门
这里可以看出,我们只需要对以下几种内容进行关注
- MSSQL 登录日志
- MSSQL 数据库操作日志
- MSSQL 相关的基础组件
- 作业(Job)
- 存储过程
- OLE 对象接口
- 程序集
- 备份与恢复过程
0x02 用户及会话分析¶
我们可以查看的是当前可登录的用户以及会话情况
获取用户信息
SELECT * FROM
sys.server_principals
WHERE
type IN ('S', 'U') -- 'S' for SQL Server authentication, 'U' for Windows authentication
ORDER BY
name;
其中 is_disabled
为 1 的用户被禁止了,其中 helper 是我的测试电脑的用户的名字,实际情况可能不一样,当前身份认证模式是混合模式
也可以通过图形化的方式进行查看
获取当前会话情况
SELECT
session_id,
login_time,
host_name,
login_name,
program_name,
status,
last_request_start_time,
last_request_end_time
FROM
sys.dm_exec_sessions
WHERE
status NOT IN ('sleeping', 'background');
0x03 MSSQL 登录日志分析¶
这部分内容在 暴力破解 -> 0x06 MSSQL 暴力破解 章节已经详细阐述,这里简单介绍一些位置之类的
MSSQL 2016 中,使用 SSMS 进行数据库连接和管理
在 管理 -> SQL Server日志中可以看到具体的日志信息
默认情况下似乎不记录登录成功的日志,除非额外配置审计日志
当然,在这里我们还可以勾选其他日志,一起进行查看
0x04 SQL执行日志分析¶
默认情况下,其实也不会记录执行的 SQL 语句,即使是出错的 SQL 语句也不会执行
这也很好理解,增删改查的量这么大,记录起来会严重影响性能并且额外占用资源
但某些单位自研或者采购的日志记录或者安全设备可能会记录部分或完整的 SQL 日志,比较常见的是记录哪些安全设备认为是可疑或者恶意的请求,这样记录量就会小很多,所以在寻找蛛丝马迹的时候不要忘了安全设备
如果记录了全量的SQL日志,可以关注除了常规的增删改查以外的日志,尤其是包含以下关键字:
-
SQL注入相关
-
字符串拼接:
+
,||
,CONCAT()
,CONCAT_WS()
-
特殊字符和转义序列:
'
,--
,/*
,*/
,;
,#
,\
-
错误触发:
UNION
,SELECT
,FROM
,WHERE
,AND
,OR
,GROUP BY
,HAVING
,ORDER BY
-
数据库元信息泄露:
DATABASE()
,USER()
,VERSION()
,SYSTEM_USER()
,SESSION_USER()
,CURRENT_USER()
,@@VERSION
,@@SERVERNAME
-
存储过程调用:
EXEC
,EXECUTE
,SP_EXECUTESQL
,xp_cmdshell
,sp_OACreate
,xp_regwrite
,xp_regread
,addextendedproc
-
文件系统访问:
LOAD_FILE()
,READTEXT()
,WRITETEXT()
,BULK INSERT
-
内置函数滥用:
ASCII()
,CHAR()
,CHR()
,MID()
,SUBSTR()
,SUBSTRING()
,HEX()
,UNHEX()
- 非常规的执行时间:特别长的查询执行时间可能表明有异常行为
- 频繁的错误尝试:多次失败的登录尝试,或者带有错误信息的查询,可能意味着暴力破解或基于错误的SQL注入攻击尝试
-
-
数据泄漏相关
-
大量数据导出:
SELECT
语句后面跟着大量列名或使用*
选择所有列,尤其是与敏感数据相关的表。 -
文件写入操作:
INTO OUTFILE
,INTO DUMPFILE
,BULK INSERT
,OPENROWSET()
-
-
提权与绕过访问控制
-
权限更改:
GRANT
,REVOKE
,DENY
,ALTER AUTHORIZATION
-
用户管理:
CREATE USER
,ALTER USER
,DROP USER
,LOGIN
,LOGOUT
-
密码重置或修改:
ALTER LOGIN
,ALTER USER SET PASSWORD
-
-
数据库结构修改相关
-
表和索引管理:
CREATE TABLE
,DROP TABLE
,ALTER TABLE
,CREATE INDEX
,DROP INDEX
-
存储过程和函数修改:
CREATE PROCEDURE
,ALTER PROCEDURE
,DROP PROCEDURE
,CREATE FUNCTION
,ALTER FUNCTION
,DROP FUNCTION
-
-
信息收集相关
-
系统信息查询:
INFORMATION_SCHEMA.*
,sys.*
(如sys.databases
,sys.tables
,sys.columns
) -
枚举数据库和表:
SHOW DATABASES
,SHOW TABLES
,SHOW COLUMNS FROM
-
-
配置相关
TRUSTWORTHY
一个数据库级别的配置选项,当设置为ON
时,允许数据库中的代码执行不受限制的操作,包括读取文件系统、注册表和其他数据库。show advanced options
这个命令用于显示SQL Server的一些高级配置选项,其中一些可能影响服务器的安全性。这些选项通常不会直接导致安全问题,但它们的不当配置可能会引入风险。RECONFIGURE
该命令用于在不重启服务的情况下更新SQL Server的运行时配置。这可以立即应用某些配置更改,但如果被攻击者利用,也可以用于迅速改变服务器的安全设置。sp_configure
攻击者常使用该方法开启 xp_cmdshell
-
程序集相关
Unsafe assembly
- 高危 DLL
xplog70.dll
可用于恢复xp_cmdshell等存储过程xpstar.dll
可用于恢复xp_sqlagent_notifyxpsqlbot.dll
可用于恢复xp_qvodsole70.dll
可用于恢复Sp_OACreate
0x05 存储过程排查¶
只要是看过 Web 安全相关书籍的朋友们肯定听过存储过程这个词,也几乎都听到过 xp_cmdshell
这个词,但对于什么是存储过程可能并不完全了解,当然,这可能并不影响排查
简单来说存储过程就像是一个写好的函数,它实现了特定的功能,接收我们传递给它的参数,之后按照预期执行,给我们返回结果。需要注意的是,存储过程本身也是可能存在 SQL 注入的
存储过程分为以下几种
-
系统存储过程(System Stored Procedures):
- 这些存储过程由数据库管理系统提供,以
sp_
为前缀。它们主要用于管理数据库和获取关于数据库的信息,如执行维护任务、查看数据库结构或用户信息。例如,sp_help
可以用来获取对象的帮助信息,sp_helpdb
则可以报告数据库的信息。
- 这些存储过程由数据库管理系统提供,以
-
扩展存储过程(Extended Stored Procedures):
- 这些存储过程通过动态链接库(DLLs)实现,以
xp_
为前缀。它们提供了超出标准SQL语言之外的功能,允许直接调用操作系统级的函数,例如xp_cmdshell
就可以用来执行操作系统命令。扩展存储过程需要特别注意安全,因为它们可以执行潜在危险的操作。
- 这些存储过程通过动态链接库(DLLs)实现,以
-
用户定义的存储过程(User-defined Stored Procedures):
- 这些存储过程由数据库用户创建,用于执行特定的业务逻辑或数据操作任务。它们可以接受输入参数,返回输出参数,以及执行复杂的事务处理和错误处理。
-
临时存储过程(Temporary Stored Procedures):
- 临时存储过程在会话期间存在,可以创建局部或全局的临时存储过程。局部临时存储过程名称以
#
开头,仅在创建它的会话中可见;全局临时存储过程名称以##
开头,对所有会话都可见。
- 临时存储过程在会话期间存在,可以创建局部或全局的临时存储过程。局部临时存储过程名称以
-
远程存储过程(Remote Stored Procedures):
- 这种类型的存储过程在不同的数据库服务器上执行,但可以从本地数据库服务器上调用。这种特性在分布式数据库系统中非常有用。
-
CLR存储过程(Common Language Runtime Stored Procedures):
- 在 SQL Server 2005 及更高版本中,可以使用
.NET Framework
的 CLR 来编写存储过程,这提供了更丰富的编程模型和更高的执行效率。CLR 存储过程可以用 C#、VB.NET 等语言编写。
- 在 SQL Server 2005 及更高版本中,可以使用
MSSQL 不是一个数据库,而是数据库管理系统,也就是它管理着多个数据库,其中系统存储过程 (sp_ 为前缀的) 由 SQL Server 系统提供的,可以在任何数据库上下文中调用,用于执行各种管理任务或获取信息。这些存储过程实际上是存在于master
数据库中的,但由于它们的特殊性质,你可以在任何数据库中直接调用它们,而无需显式地指定数据库名称
扩展存储过程 (主要以 xp_ 为前缀的,也存在 sp_ 为前缀的) 是作为动态链接库(DLL)文件存在于文件系统上的,这些DLL文件是被SQL Server在运行时加载的,它们并不存储在数据库文件中,也不属于任何一个具体的数据库。当在SQL Server中注册一个扩展存储过程时,这个注册信息实际上是保存在master
系统数据库中的sys.dlls
和 sys.server_permissions
表里。这意味着一旦注册,扩展存储过程就可以在SQL Server实例下的所有数据库中被调用,只要相应的用户具有足够的权限。
除了上面两个存储过程以外,其他存储过程均由每个数据库自己维护一份,因此我们排查的时候也是需要挨个数据库排查
1. 排查系统存储过程¶
系统存储过程存储在 master
数据库中
然而比较尴尬的是,默认的系统存储过程有 1000 多个,每一个管理员权限都是可以编辑的,每一个都看一遍的话,时间上来不及,所以我们采取两种方案进行排查
- 排查最新修改的系统存储过程
- 根据系统存储过程执行记录,排查相关的系统存储过程
SELECT name, type_desc, create_date, modify_date
FROM sys.all_objects
WHERE type = 'P' AND name LIKE 'sp_%'
ORDER BY modify_date DESC;
可以通过在 master
数据库中执行上述语句,找出所有系统存储过程,并按照修改时间倒序排序
假设我们想查看最新修改的这个系统存储过程,可以使用以下语句看一下其定义
USE YourDatabaseName; -- 替换为实际的数据库名
SELECT OBJECT_DEFINITION(OBJECT_ID(N'YourProcedureName')) AS ProcedureDefinition;
经过查询发现,其实 sp_MScleanupmergepublisher
这个系统存储过程是sys.sp_MScleanupmergepublisher_internal
系统存储过程的别名,所以我们使用 sys.sp_MScleanupmergepublisher_internal
进行查询
这回就出现了该系统存储过程的定义,我们复制出来格式化一下
-- Name: sp_MScleanupmergepublisher_internal
-- Description: This procedure currently performs the following function(s):
-- 1) Cleans up all the stale dynamic snapshot views
-- in all databases enabled for merge replication. This
-- procedure should normally be called at merge publisher startup.
--
-- Notes: 1)This procedure is enabled as a startup procedure when a database is
-- enabled as a first merge publisher database on the server and it
-- will be unmarked as a startup procedure when the last merge publisher
-- database is disabled.
-- 2)Errors within the SP are mostly ignored.
-- 3)This procedure can also be used by admins/securityadmins to perform
-- manual cleanup of all dynamic snapshot views. Note that cleaning up the
-- dynamic snapshot views can disrupt dynamic snapshots that are being generated.
--
-- Returns: (undefined)
--
-- Security: Only members of the sysadmin fixed server role can execute this
-- procedure successfully. So for this procedure to function properly
-- as a startup procedure, the MSSQLServer service account must be a
-- member of the sysadmin role.
-- Requires Certificate signature for catalog access
CREATE PROCEDURE sys.sp_MScleanupmergepublisher_internal
AS
BEGIN
SET NOCOUNT ON;
DECLARE @status_mask INT;
DECLARE @published_mask INT;
DECLARE @published_database_name SYSNAME;
DECLARE @command NVARCHAR(4000);
-- Security check: sysadmin only
IF (ISNULL(IS_SRVROLEMEMBER('sysadmin'), 0) = 0)
BEGIN
RAISERROR(14260, 16, -1);
RETURN (1);
END;
-- Masks off the databases with status that we don't want to deal with
SELECT @status_mask = 32 | -- loading
64 | -- pre recovery
128 | -- recovering
256 | -- not recovered
512 | -- offline
1024; -- read only
SELECT @published_mask = 4; -- Merge published
DECLARE hPublishedDatabase CURSOR LOCAL FAST_FORWARD FOR
SELECT name FROM sys.databases
WHERE (status & @status_mask) = 0
AND (category & @published_mask) <> 0;
OPEN hPublishedDatabase;
FETCH hPublishedDatabase INTO @published_database_name;
WHILE (@@FETCH_STATUS <> -1)
BEGIN
SELECT @command = QUOTENAME(@published_database_name) + '.sys.sp_MScleanupmergepublisherdb';
EXEC @command;
-- Ignore errors
FETCH hPublishedDatabase INTO @published_database_name;
END;
CLOSE hPublishedDatabase;
DEALLOCATE hPublishedDatabase;
END;
但其实根本没必要,我们是可以直接通过左侧找到该存储过程,右键修改来查看
排查系统存储过程的执行记录
默认 MSSQL 不会记录系统存储过程的执行,我们以 sp_who2
为例
执行成功,我们看一下日志中是否存在记录
并没有相关记录
目前还没有发现能够自动化判断是否存在恶意系统存储过程的工具,有的话,后续会添加进手册
2. 排查扩展存储过程¶
USE YourDatabaseName; -- 替换为实际的数据库名
SELECT name, type_desc, create_date, modify_date
FROM sys.all_objects
WHERE type = 'X'
ORDER BY modify_date DESC;
MSSQL 中默认扩展存储过程不只是 xp_ 开头的存储过程,还有 sp_ 开头的存储过程,它们都是 DLL 起的,所以右键没有修改选项
我们测试一下,执行扩展存储过程是否会留下记录
看来 xp_dirtree
默认还是可以使用的,不需要 EXEC sp_configure 'xp_cmdshell', 1;RECONFIGURE;
我们看一下是否会留下日志
也没有留下日志
直接使用 xp_cmdshell
会留下什么日志?
可以看到,留下了两条日志,内容一样,这也就意味着如果攻击者贸然执行了 xp_cmdshell
是有可能在日志里留下痕迹的
如果以管理员权限配置可以执行 xp_cmdshell
,会留下什么日志
-- 启用 xp_cmdshell
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
EXEC master..xp_cmdshell 'dir C:\';
成功执行,我们去看一下日志情况
产生了四条日志,本质上是两条,就告诉我们 show advanced options
配置项由 0 转为 1 ;xp_cmdshell
配置项由 0 转为 1
所以我们的排查也就分为两个方向
- 根据日志排查
- 根据常见和修改时间排查
根据修改时间排查的语句为
USE master;
GO
SELECT name, create_date, modify_date
FROM sys.all_objects
WHERE type = 'X'
ORDER BY modify_date DESC;
像刚才的配置更改与创建和修改时间没关系,不会影响排查结果
假设 xp_prop_oledb_provider
是近期修改过的,与前面的时间截然不同,我们如何找到对应的 DLL 呢?
可以看到这里只有 dll 文件的名字,没有路径,它所在位置为
路径里包含 MSSQL 版本信息,所以根据实际情况更改,该目录下的 DLL 文件基本都有签名
如果是攻击者制定的 DLL 创建的存储过程,可能会显示出绝对路径
对于扩展存储过程,无法直接修改其内容,直接右键删除就好,或者通过以下命令
删除后,可以通过以下命令确定结果
加载扩展存储过程会留下日志
3. 用户自定义存储过程¶
用户自定义存储过程是每个数据库单独定义的,所以在检查的时候也要检查所有的数据库的用户自定义存储过程
数据库 -> 数据库名字 -> 可编程性 -> 存储过程
默认情况下,只有系统存储过程一个节点,如果还有其他的,例如本案例中的 dbo.usp_GetCustomerInfo
就是用户自定义的存储过程,可以直接编辑并且可以删除
查找每一个数据库的自定义存储过程进行一一确认即可
0x06 程序集排查¶
在 Microsoft SQL Server 2016 中,程序集(Assemblies)是指托管代码(Managed Code)程序集,可以使用 .NET 框架编写并在 SQL Server 中托管和执行。通过使用程序集,开发人员可以编写复杂的逻辑,并将其集成到 SQL Server 中,以扩展其功能。
简单来说就是使用 .Net 语言编写程序,编译成DLL,之后加载进 MSSQL 中,之后 MSSQL 就可以使用该 DLL 中的功能,创建存储过程或者函数。
在 Microsoft SQL Server 中,程序集(Assemblies)是每个数据库单独使用的,而不是所有数据库共用的。每个数据库有自己的程序集空间,程序集在一个数据库中创建后,只能在该数据库内使用。
1. 查找当前数据库中的程序集¶
Microsoft.SqlServer.Types
应该是每个数据库默认的程序集,我们可以通过图形化的方式查看程序集
2. 查找程序集对应的文件¶
该文件所在路径为
3. 添加恶意程序集日志¶
将以下 C# 代码保存为 MyStoredProcedures.cs
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class MyStoredProcedures
{
[SqlProcedure]
public static void HelloWorld()
{
SqlContext.Pipe.Send("Hello, world!");
}
}
使用 csc.exe 编译成 DLL
导入程序集
-- 导入程序集
CREATE ASSEMBLY MyStoredProcedures
FROM 'C:\Path\To\MyStoredProcedures.dll'
WITH PERMISSION_SET = SAFE
刷新一下
在程序集处成功显示出来,观察一下日志
这里的日志显示的是 AppDomain 2 (MyDatabase.dbo[ddl].1)已卸载。
4. 删除程序集¶
直接右键删除即可
或者通过 SQL 命令
刷新页面
删除程序集不会记录日志
0x07 作业 (Job)¶
“作业”(Jobs)是一种自动化任务的机制,允许数据库管理员(DBA)和开发人员调度和执行一系列预定义的操作。这些作业可以在特定的时间点或根据预定的时间表自动运行,也可以由用户手动触发。
一个 SQL Server 作业通常包含以下组成部分:
- 作业名称:用来标识作业的唯一名称。
- 所有者:指定作业的拥有者,通常是 SQL Server 登录账户或 Windows 用户账户。
- 步骤:作业的一个步骤是一组可以执行的操作,比如运行 T-SQL 语句、执行存储过程、导入导出数据、发送电子邮件等。一个作业可以包含多个步骤,步骤之间可以有依赖关系,前一个步骤的执行结果可以决定后续步骤是否执行。
- 计划:定义作业何时运行的时间表,可以是一次性的,也可以是周期性的,比如每天、每周、每月等。
- 警报:可以配置作业在特定条件下触发警报,例如当作业失败时通知管理员。
- 通知:作业可以配置通知选项,以便在作业开始、完成或失败时发送电子邮件或短信给指定的收件人。
- 历史记录:SQL Server 作业保留执行历史记录,包括开始时间、结束时间、持续时间以及作业的状态(如成功、失败或正在运行)。
- 安全性:可以设置作业的安全级别,例如限制哪些用户可以查看或修改作业。
作业是由 SQL Server Agent 统一管理,这意味着作业是跨所有数据库的,或者说是在 SQL Server 实例级别的。SQL Server Agent 是一个服务,它负责调度和执行预先定义的任务,即作业
1. 获取所有的作业(Job)¶
可以看到有一条作业,查看该作业详细信息
通过 job_id
查询更多信息
查询作业步骤
USE msdb;
GO
-- 作业步骤信息
SELECT *
FROM dbo.sysjobsteps
WHERE job_id = '267BA740-1476-49E2-9F5C-B3CDD78B1B9C';
查询作业执行历史
看来该作业从来没有执行过
当然也可以通过图形化获取作业信息
可以看到,默认情况下被禁用了,我们右键启动
可以通过点击直接查看作业列表,并查看作业的详细信息,syspolicy_purge_history
是默认存在的
可以通过作业活动监视器查看作业活动情况
可以通过错误日志查看作业产生的错误
2. 新建作业¶
查看日志
并不会记录新建作业的日志
3. 删除作业¶
0x08 函数排查¶
在 SQL Server 2016 中,用户可以创建两种类型的自定义函数:标量值函数和内聚表值函数(Table-Valued Functions)。标量值函数返回一个单一值,而表值函数可以返回一个结果集,类似于一个表。
每个数据库有自己的函数,MSSQL 2016 默认情况下,默认数据库以及新建的数据库均无以下函数
- 标量值函数
- 表值函数
- 聚合函数
0x09 数据库触发器¶
在 MSSQL 2016 中默认情况下默认数据库以及新建数据库均无数据库触发器
0x10 其他内容¶
其他内容的排查就需要将受害现场与纯净的数据库管理系统进行对比了,对比出现的差异与开发、运维等相关人员确认