审计日志基础:日志格式、字段含义与存储策略
审计日志长什么样?
说白了,就是数据库把谁在什么时间干了什么事,一字不差地记下来。我第一次接触审计日志时,被它那密密麻麻的文本吓了一跳——几百万行日志堆在那里,看都看不过来。但搞懂它的格式和字段之后,你会发现它就是你的“安全监控摄像头”。
常见的日志格式
各家数据库的审计日志格式不太一样。我归纳了一下,主流的有这三种:
| 格式类型 | 典型代表 | 特点 |
|---|---|---|
| 固定宽度文本 | MySQL、PostgreSQL | 字段整齐,用管道符或制表符分隔,解析效率高 |
| XML/JSON 结构化 | Oracle | 字段自带标签,可读性好,但体积大 |
| 二进制/SYSLOG | SQL Server、MongoDB | 需要专用工具解析,压缩率高 |
嗯,这里要注意。固定宽度格式最省空间,但查起来费眼睛。XML/JSON 格式虽然大,却直接能扔进 Elasticsearch 或 Splunk。我个人习惯是在生产环境用固定宽度文本,因为巡检脚本只要解析一行就能拿到所有字段,速度快得多。
核心字段含义
不管什么格式,关键字段就那么几个。我整理了一张表,你平时巡检的时候可以对着看:
| 字段名 | 含义 | 我的建议 |
|---|---|---|
timestamp |
事件发生时间 | 统一转成 GMT+8,否则不同机器对不齐 |
session_id |
会话标识 | 用于关联同一用户的多条操作,我有个客户曾经因为没配这个字段,排查问题时根本串不起来 |
user_name |
执行者 | 区分应用账户还是人工账户,应用账户不该有 DDL |
object_name |
操作对象 | 表名、视图名、存储过程名,异常对象往往是入侵入口 |
sql_text |
执行的 SQL | 注意截断问题——我曾经遇到日志只记了前 1000 字符,正好把恶意部分截掉了 |
status_code |
执行结果 | 0 是成功,非 0 是异常,大量 0 可能是自动化脚本在扫描 |
你想想看,如果字段不全或者含义搞错,那就没法做精准的盲索引巡检。说白了,字段就是你的“眼睛”,必须把每只眼睛都擦亮。
存储策略如何选?
日志存哪里、存多久、怎么删,这仨问题回答不好,审计就是摆设。我自己的经验是这样的:
- 存哪里:单独挂一块高速磁盘,不要跟数据库文件混在一个盘,否则日志暴增会影响业务 IO。我在项目中遇到过因为日志写满磁盘导致数据库直接 hang 住的情况。
- 存多久:生产环境至少 30 天,合规要求高的行业要 180 天。时间太长磁盘扛不住,太短又查不到历史记录。
- 怎么删:别手动删,用轮转策略。MySQL 可以用 mysqlbinlog 的
--expire_logs_days参数,PG 可以用 pg_dump 之后删文件。
审计日志最好做成“冷热分离”。热库放最近 7 天的数据,方便在线查;冷库用压缩归档,只保留索引以备不时之需。我曾经用这个方案帮客户把 200GB 的审计日志压缩到 15GB,查询速度反而快了 3 倍。
字段不完整怎么办?
现实里日志字段常常有缺失。比如某些旧版 MySQL 的 general_log 不记录 session_id,或者 Oracle 的 UNIFIED_AUDIT_TRAIL 里 sql_bind 为空。怎么做?
- 对缺失字段用默认值补全,比如
session_id为空时填unknown - 在采集脚本里增加一个“字段完整性校验”步骤
- 一旦发现某个字段持续缺失超过 30%,就得回查数据库配置
千万不要在日志解析时把缺失字段直接跳过。我曾经因为 status_code 字段缺失就跳过整行,导致漏掉了某个恶意登录尝试。后来补全规则改成“缺失则标记为异常”,才把那家伙揪出来。
巡检时优先看什么?
日志字段那么多,不可能每个都盯着。我建议你抓住这 5 个,就能应付 90% 的巡检任务:
| 优先级 | 字段 | 为什么看这个? | 典型异常特征 |
|---|---|---|---|
| 1 | timestamp |
时间乱了一切都乱 | 大量日志集中在凌晨 2-4 点 |
| 2 | user_name |
人是谁比做了什么更重要 | 同一用户同时从不同 IP 登陆 |
| 3 | sql_text |
直接看到操作内容 | 大量 DROP、ALTER、GRANT |
| 4 | object_name |
知道动的是哪个表 | 敏感表(如 user_info)频繁被查询 |
| 5 | status_code |
成功与否是安全信号 | 连续失败的 LOGON 尝试 |
嗯,有了这些基础,审计日志就不再是黑盒子了。格式、字段、存储策略,三样都弄明白,你才能像看监控录像一样,把数据库里发生的事情一帧一帧复盘出来。