如何审计MySQL 8.0中的分类数据查询?
作者:Mike Frank 译:徐轶韬
面临的挑战
通常,涉及到敏感信息时用户需要使用审计日志。不仅仅是在表上运行Select,还包括访问表中的特定单元格。通常,这类数据将包含一个分类级别作为行的一部分,定义如何处理、审计等策略。
诸如此类的敏感数据可能被标记为–
- 高度敏感
- 最高机密
- 分类
- 受限制的
- 需要清除
- 高度机密
- 受保护的
以某种方式分类或标记的数据通常会被合规要求所涵盖。合规性要求审计数据库中发生在这类数据上的事件。特别是对于可能具有数据访问权限,但不应查看某些数据的管理员。
敏感数据可以与带有标签的数据穿插在一起,例如
- 公开
- 未分类
- 其他
当然,您可以在MySQL Audit中打开常规的选择/读取审计。但是,您无法判断是否选择了敏感数据,仅可以看到在表上运行了一个Select,以及Select的SQL文。
一个解决方案
虽然不是很明显,但是有多种方法可以根据所选择的数据来完成数据审计。下面是一个例子。
我们的示例表非常简单,id,name,desc,以及sec_level列。我们要审计Select语句里sec_level为H的行。
CREATE SCHEMA test_sel_audit;
CREATE TABLE `test_sel_audit`.`info_cat_test` (
`id` INT NOT NULL,
`name` VARCHAR(20) NULL,
`desc` VARCHAR(20) NULL,
`sec_level` CHAR(1) NULL,
PRIMARY KEY (`id`));
让我们添加几行数据。
INSERT INTO `test_sel_audit`.`info_cat_test` (`id`, `name`, `desc`, `sec_level`) VALUES ('1', 'fred', 'engineer', 'H');
INSERT INTO `test_sel_audit`.`info_cat_test` (`id`, `name`, `desc`, `sec_level`) VALUES ('2', 'jill', 'program manager', 'M');
INSERT INTO `test_sel_audit`.`info_cat_test` (`id`, `name`, `desc`, `sec_level`) VALUES ('3', 'joe', 'maintenance', 'L');
启用EE审计(需要使用MySQL企业版 –使用shell连接显示MySQL的版本。
> mysqlsh
mysqlsh> connect newuser@localhost
Server version: 8.0.21-commercial MySQL Enterprise Server – Commercial
或者执行
mysql> select @@version;
如何安装审计插件的详细“操作方法”
https://dev.mysql.com/doc/refman/8.0/en/audit-log-installation.html
如何安装审计发行组件的详细信息
https://dev.mysql.com/doc/refman/8.0/en/audit-api-message-emit.html
> bin/mysql -u root -p
INSTALL COMPONENT "file://component_audit_api_message_emit";
在[mysqld]中设置启动时开启审计功能并设置选项。例如:
> vi /etc/my.cnf
plugin-load-add=audit_log.so
audit-log=FORCE_PLUS_PERMANENT
audit-log-format=JSON
audit-log-strategy=SYNCHRONOUS
有关审计选项和变量的更多详细信息,请参考审计日志手册。
https://dev.mysql.com/doc/mysql-security-excerpt/8.0/en/audit-log-reference.html
重新启动MySQL服务器。
注意:有多种方法可以启用审计而无需重新启动。上面是强制执行审计的操作方式。
首先,我将编写一个简单的函数,其中包含我想在审计跟踪中拥有的审计元数据。
我将创建一个简单的封装函数
DELIMITER $$
CREATE FUNCTION audit_api_message_emit_FN(name CHAR(20))
RETURNS VARCHAR(2)
DETERMINISTIC
BEGIN
DECLARE aud_msg VARCHAR(255);
select audit_api_message_emit_udf('sec_level_selected',
'audit_selected',
'Sensitive Data Selected',
'FOR ', name
) into aud_msg;
RETURN('OK');
END$$
DELIMITER ;
让我们运行一次select来演示如何构建select审计。
SELECT
`info_cat_test`.`id`,
`info_cat_test`.`name`,
`info_cat_test`.`desc`,
`info_cat_test`.`sec_level`,
IF(`info_cat_test`.`sec_level` = 'H',
AUDIT_API_MESSAGE_EMIT_FN(name),
CAST('NA' AS CHAR))
FROM
`test_trigger`.`info_cat_test`;
如您所见,当sec_level为'H'时,有一个IF调用udit_api_message_emit_ud。OK显示了选择了H级别。
现在,我们可以在审计日志中看到它。
>sudo cat /usr/local/mysql/data/audit.log | egrep "H level sec data retrieved" | egrep fred
{ "timestamp": "2020-08-24 18:42:46", "id": 10, "class":
"message", "event": "user", "connection_id": 10, "account": {
"user": "root", "host": "localhost" }, "login": { "user": "root",
"os": "", "ip": "::1", "proxy": "" }, "message_data": {
"component": "sec_level_selected", "producer": "SELECT Audit",
"message": " H level sec data retrieved", "map": { "FOR ": "fred"
} } },
如果我运行
select * from FROM `test_sel_audit`.`info_cat_test`;
如果我为这种类型的SQL事件(读)设置了一个审计过滤器,我可以看到表数据被访问了——但是正如您所看到的,我不知道fred是否被选中了。
{ "timestamp": "2020-08-24 18:47:07", "id": 2, "class": "general",
"event": "status", "connection_id": 10, "account": { "user":
"root", "host": "localhost" }, "login": { "user": "root", "os":
"", "ip": "::1", "proxy": "" }, "general_data": { "command":
"Query", "sql_command": "select", "query": "select * FROM
`test_sel_audit`.`info_cat_test`nLIMIT 0, 1000", "status": 0 } },
当然,我并不想让用户看到审计调用。因此,让我们创建一个简单的视图,并将emit审计函数移动到select的where部分,从而使其透明。
create view audit_cat_test as SELECT `info_cat_test`.`id`,
`info_cat_test`.`name`,
`info_cat_test`.`desc`,
`info_cat_test`.`sec_level`
FROM `test_trigger`.`info_cat_test`
where
length(IF(`info_cat_test`.`sec_level`= 'H',
audit_api_message_emit_FN(name),
CAST('NA' as CHAR))) = 2;
保持视图简单,以确保WHERE子句执行audit_api_message_emit_FN函数。
现在我可以运行视图
select * from audit_cat_test;
快速查看审计日志,我发现可以访问Fred。
{ "timestamp": "2020-08-25 13:58:11", "id": 2, "class": "message",
"event": "user", "connection_id": 10, "account": { "user": "root",
"host": "localhost" }, "login": { "user": "root", "os": "", "ip":
"localhost", "proxy": "" }, "message_data": { "component":
"sec_level", "producer": "audit_selected", "message": "Sensitive
Data Selected", "map": { "FOR ": "fred" } } },
结论
还有其他方法可以将audit_api_message_emit_udf()中的选定数据推送到MySQL审计流中。这只是一种可能且简单的方法。
- Django 相关
- 装饰器进阶
- P2894 [USACO08FEB]酒店Hotel
- 深入理解计算机系统读书笔记之第一章:漫游
- 【实战】工控网络协议模糊测试:用peach对modbus协议进行模糊测试
- 【Django错误】OSError: raw write() returned invalid length 14 (should have been between 0 and 7)
- P2234 [HNOI2002]营业额统计
- Python 中格式化字符串 % 和 format 两种方法之间的区别
- [实战]如何在Kali Linux中进行WIFI钓鱼?
- jQuery
- P3369 【模板】普通平衡树(Treap/SBT)
- Django之ORM基础
- P3381 【模板】最小费用最大流
- Django基本命令
- MySQL 教程
- MySQL 安装
- MySQL 管理与配置
- MySQL PHP 语法
- MySQL 连接
- MySQL 创建数据库
- MySQL 删除数据库
- MySQL 选择数据库
- MySQL 数据类型
- MySQL 创建数据表
- MySQL 删除数据表
- MySQL 插入数据
- MySQL 查询数据
- MySQL where 子句
- MySQL UPDATE 查询
- MySQL DELETE 语句
- MySQL LIKE 子句
- mysql order by
- Mysql Join的使用
- MySQL NULL 值处理
- MySQL 正则表达式
- MySQL 事务
- MySQL ALTER命令
- MySQL 索引
- MySQL 临时表
- MySQL 复制表
- 查看MySQL 元数据
- MySQL 序列 AUTO_INCREMENT
- MySQL 处理重复数据
- MySQL 及 SQL 注入
- MySQL 导出数据
- MySQL 导入数据
- MYSQL 函数大全
- MySQL Group By 实例讲解
- MySQL Max()函数实例讲解
- mysql count函数实例
- MYSQL UNION和UNION ALL实例
- MySQL IN 用法
- MySQL between and 实例讲解
- 3分钟短文:Laravel路子真野啊!路由昵称前缀中间件
- CSS中重要的BFC概念
- Redis哨兵集群中哨兵挂了,主从库还能切换吗?
- 你的 Redis 为什么变慢了?
- 解决Maven依赖冲突的好帮手,这款IDEA插件了解一下?
- Python爬虫实现HTTP网络请求多种实现方式
- 在tensorflow以及keras安装目录查询操作(windows下)
- Python调用OpenCV实现图像平滑代码实例
- php微信公众号开发之音乐信息
- Laravel关联模型中过滤结果为空的结果集(has和with区别)
- php微信公众号开发之二级菜单
- django中的ajax组件教程详解
- php微信公众号开发之校园图书馆
- 查看keras的默认backend实现方式
- Python包和模块的分发详细介绍