第二章 审计日志的构成与格式

做数据库审计这么多年,我最深的感受是:看不懂日志,就甭想做好检测。很多新手上来就堆算法,结果连日志里某个字段是啥意思都没搞清,闹出乌龙是常事。

审计日志说白了就是数据库的“黑匣子”。谁、在什么时间、通过哪个IP、干了什么事、对哪些对象动了手——全记下来。咱们这一章就掰开这些字段,看看它们到底长什么样,有什么坑。

2.1 核心字段一览

不同的数据库(MySQL、PostgreSQL、Oracle、SQL Server)审计日志格式略有差异,但核心字段几乎一致。我列了张表,方便你对照。

字段 含义 常见示例
时间戳 事件发生的精确时间 2025-03-18 14:32:15.732
用户名 发起操作的数据库账号 app_user, root
操作类型 具体执行的SQL命令类别 SELECT, DELETE, CREATE
对象 被操作的库、表、视图、存储过程等 test_db.orders
来源IP 客户端IP地址 10.0.1.45, 192.168.1.100
会话ID 数据库连接的唯一标识 1234567
返回码 操作执行结果(成功/失败/错误代码) 0, 1146 (表不存在)
执行耗时 SQL执行所花时间 0.023s, 12.5s
提示: 个人习惯是拿到日志后先把这8个字段过一遍。少了任何一个,后续分析都可能漏掉关键线索。比如返回码为0但耗时巨长,就值得怀疑是不是在跑全表扫描。

2.2 时间戳:时区问题是第一大坑

时间戳看似简单,但我在项目里被它坑过不止一次。有一次一个告警系统凌晨三点狂报警,查了半天才发现是数据库服务器用UTC,而日志采集系统用CST,两边的时区差8小时。那个凌晨,我对着屏幕把头发抓掉不少。

需要注意的点:

  • 确认时区:日志中是否明确标注时区?比如2025-03-18T06:32:15Z中的Z表示UTC。
  • 精度:毫秒还是微秒?有些审计日志只精确到秒,这种在短时间内大量操作时根本分不清先后顺序。
  • 时钟同步:多节点日志如果存在时钟偏差,关联分析会得出错误结论。建议NTP统一对时。

还有一点很容易被忽略——时间戳的格式。有的数据库用UNIX时间戳(长整数),有的用ISO8601字符串。如果你用正则或者字符串函数解析,一定要注意格式统一。

2.3 用户与身份映射

用户字段记录的是数据库账号名。但你想想看,很多时候应用层用的是同一个数据库连接池,日志里看到的全是webapp这个账号。这时候你就需要额外的信息来映射到具体操作人。

怎么办呢?我建议关注这些:

  1. 客户端程序名称:有些数据库审计能记录application_name(PostgreSQL)或program_name(SQL Server)。
  2. 会话绑定变量:如果应用层在连接时注入了用户ID,可以从SET session user_id = 'xxx'这类语句中提取。
  3. 代理用户:部分数据库支持代理认证(如Oracle),这时记录的是代理用户而非最终用户。
警告: 千万不要认为“审计日志里有user字段就能追溯到人”。我曾经历过一个事故:某员工用公司通用账号删了表,审计日志里只有“dba”这个账号名,根本锁定不了具体是谁。后来强制要求每个人使用独立账号,才解决了这个问题。

2.4 操作类型:别只看“增删改查”

操作类型一般分几大类:DML(SELECT、INSERT、UPDATE、DELETE)、DDL(CREATE、ALTER、DROP)、DCL(GRANT、REVOKE)、TCL(BEGIN、COMMIT、ROLLBACK),还有管理操作(KILL、SHUTDOWN、BACKUP)等。

但这里有个细节:同样的操作在不同的数据库里分类可能不同。比如TRUNCATE在MySQL里是DDL,在Oracle里算DML?反正我们做异常检测时,最好把操作类型归一化成自己的分类体系。

我通常会这样分类:

类别 包含操作 异常关注度
高危险 DROP, ALTER, GRANT, REVOKE, KILL, SHUTDOWN 极高
中危险 DELETE, UPDATE without WHERE, TRUNCATE 中
低危险 SELECT, INSERT, BEGIN, COMMIT 按频率与上下文判断

2.5 对象:表和字段的完整路径

对象字段通常记录数据库.表或数据库.模式.表的完整路径。有时候还包括字段名、索引名甚至分区名。我见过最长的对象名包含四级:prod.sales_db.dbo.order_items.PK_orders。

做异常检测时,对象的层次信息非常有用。比如你发现某个用户突然访问了一个从没访问过的敏感表,哪怕操作类型只是SELECT,也值得标记。

另外,注意对象别名的解析。有些日志直接记录FROM t这种SQL文本,而没有解析出真实的表名t到底是什么。这时候需要SQL解析器来还原。

2.6 来源IP:网络层溯源

来源IP记录的是客户端的IP地址。看起来直白,但实际场景里常遇到:

  • NAT/代理:应用服务器通过中间件连接数据库,日志里全是中间件的IP,看不到真实客户端。
  • 容器/Pod:K8s环境下Pod IP会频繁变化,需要结合Pod名称或Service名称做映射。
  • VPN:跨地域访问时来源IP可能来自VPN网关,而不是实际用户所在城市。

所以我建议,在采集阶段就做IP富化——把IP转换成内网段标识、地理位置、主机名等信息。这样检测时才能更快定位问题。

2.7 一条完整的审计日志长啥样

下面给一个MySQL审计日志的示例(实际格式可能因插件而异):

2025-03-18T14:32:15.732000Z, 
connect, 
root@10.0.1.45, 
1234567, 
0, 
0, 
0, 
0, 
0, 
0, 
'CONNECT', 
'root', 
'10.0.1.45', 
'test_db', 
'', 
'', 
'', 
'', 
'', 
'', 
'',
'',
'',
'',
'',
'',
'',
0

嗯,字段确实很多,而且顺序固定。我们做异常检测时一般只取前几个关键字段,剩下的可以按需解析。

重点: 日志解析的第一步永远是字段对齐。拿到日志后,先写一个简单的解析器把每个字段提取出来,存入结构化表(比如ClickHouse或Elasticsearch)。这一步做扎实了,后面检测就稳了。

2.8 知识体系图

下面的SVG图总结了审计日志的核心字段及它们之间的关系,你可以把它当作“日志结构记忆卡”。

审计日志核心字段结构 审计日志记录 时间戳 (Timestamp) 用户 (User) 操作类型 (OpType) 对象 (Object) 来源IP (SrcIP) 会话ID (SessID) 返回码 (RetCode) 执行耗时 (Cost) 📌 一条审计日志 = 上述字段的组合 异常检测通常聚焦:时间戳 + 用户 + 操作类型 + 对象 + 来源IP