前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >技术分享 | 某二手交易平台数据安全实践(建议对照自检)

技术分享 | 某二手交易平台数据安全实践(建议对照自检)

作者头像
爱可生开源社区
发布2025-02-28 15:07:44
发布2025-02-28 15:07:44
8300
代码可运行
举报
运行总次数:0
代码可运行
作者:莫善,某互联网公司高级 DBA。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

本文约 4000 字,预计阅读需要 15 分钟

1背景介绍

在当今数字化的时代,数据泄露问题层出不穷,往小了说会威胁到个人隐私,往大的说可以威胁到国家安全。每年因数据泄露造成的事故一直还在上升,我们不禁要反思一下,这是为什么?

数据工单导出 是业务人员与 DBA 很常见的工作内容,但也是一个很容易出现数据泄露的行为。本文将分享 某知名二手交易平台数据导出工单改造 过程中的实践经验,希望能够提升大家对于数据安全的认识,共同探索更加完善的数据保护策略。文末有奖留言。

内容大纲

  1. 数据导出工单功能的价值
  2. 数据导出工单功能 1.0 到 2.0 的改造
  3. 数据分类分级判定功能

🎁 文末有奖留言

2为什么需要数据导出工单?

在开始讲解实践经验之前,让我们先思考一下为什么需要数据导出工单这个功能?还是需要先交代一下需求背景。

需求背景

业务人员没有直接通过 MySQL 客户端访问线上数据库的权限,想查看线上的数据只能找 DBA 协助。为此,DBA 提供两个通道:一是数据查询平台,数据查询平台每次只能查询少量数据且不可导出(复制);二是数据导出工单,数据导出工单允许业务拿到数据。

需求分析

数据导出工单 这个功能主要是面向研发同学,即数据的操作者。我们可以从下面几个角度分析:

  • 问题调试:有时线上服务出现故障或异常行为,研发同学需要分析实际运行中的数据来定位问题根源,这样可以帮助他们更准确地重现和诊断问题。
  • 测试验证:一般情况下,新功能上线前需要在沙箱环境进行验证,这时候往往需要使用真实的线上数据来进行全面测试。
  • 数据分析:良好的数据分析可以有助于产品的迭代和发展。
  • 数据备份:备份是数据保护的一个措施,这也是一个很好的习惯。

基于上述的合理需求,作为 DBA 应该予以协助完成。有经验的 DBA 都应该知道,这种工作重复性很高又比较繁琐,耗时可能还很长。如果偶尔处理一两个需求尚且能接受,但是随着业务的不断壮大、管理的数据库集群越来越多、数据量越来越大,一个月几十个上百个这种需求也很正常。

我相信,任何一个正常的 DBA 面对这种情况都能疯掉,那么 数据导出工单 功能需求就应运而生了。

当然,这个功能还有一个重要的作用,它可以明确记录每个操作的时间、执行人及数据内容,以便于事后审查与责任划分。

至此,我们把业务的需求明确了,那就可以开始干活了。

3数据导出工单 1.0 - 2.0

1.0 时代

下面是一张 DBA 为该功能设计的工单审批流程。

1.0 工单处理流程

这里只是简单的介绍一下 1.0 时代的工单处理流程,2.0 的部分会详细介绍。

读完请读者思考一下,有没有发现这个处理流程存在的安全问题?

  1. 业务按需编写查询(SELECT)语句并提交工单。
  2. 生成审批流(固定审批流)。
  3. 目标集群的负责人审批。
  4. 执行数据导出操作,并返回下载的 URL。
  5. 下载数据文件。
1.0 安全问题清单
1. 集群负责人权限过大

固定审批流存在重大安全问题,不管被导出的数据是否敏感,只要集群负责人的审批即可。其次,业务人员通过这个工单可以将线上的数据全部导出来。

可见当集群负责人权限过大,与其能承担的责任相当不匹配,风险不可控。

2. 审批人无法看到导出数据的具体情况

形成工单流后,审批人没法看到目标数据的具体情况。比如,该导出的数据量条数,数据是否包含敏感数据等。

这些信息只能通过跟提交人沟通获取。假如工单提交者对实际情况进行隐瞒,审批人是没法发现的。

3. URL 未做鉴权

生成的 URL 未进行鉴权,即只要有查看工单详情的人员都可以通过点击该 URL 进行下载。

4. 数据文件未加密

数据文件未加密,即下载以后可直接查阅。

以上,梳理分析才发现,这个工单存在的问题真不少。

1.0 时代结束

其实这些问题不是 DBA 发现的,或者说我们在开发之初就没考虑那么全面,默认使用者都是好人,要不然当初也不能设计这样的处理流程。这个 1.0 功能在上线四年后,安全组的同学找到 DBA,希望严格把控线上数据的流出,这才有了上述几点问题的总结。

随后,我们开始了长达一年多的整改,进入 2.0 时代。

2.0 时代

下面是一张 2.0 版本的工单审批流程。

2.0 工单处理流程
  1. 业务按需编写查询(SELECT)语句并提交工单。
  2. 数据分类分级判定。新增
  3. 生成审批流(动态审批流)。固定变动态
  4. 目标集群的负责人审批。根据数据判定添加相应审批人,甚至到 CEO
  5. 执行数据导出操作,并返回下载的 URL。设置失效时间
  6. 下载数据文件。

可以看到 2.0 新增了数据分类分级判定,这个主要做的事情就是将业务 SQL 语句进行解析,将表名、字段名等信息都拿到,然后判断是否是敏感数据。另外会通过执行计划预估导出的数据行数(预估行)。最终会将这些信息返回到工单详情页,审批人可以看到,可以作为审批参考。

预估行:通过执行计划得到,不准确但可以作为参考。如果 SQL 是全表扫描,这个值跟实际情况可能相差很大。

数据分类分级判定:公司内部系统,会定期扫描线上的表和字段,抽样检查样本数据,最后对表及字段形成敏感等级,如果导出工单涉及到最敏感级别的数据将可能需要 CEO 审批。

要实现这个数据分类分级判定功能,我们还需要解决三个大问题。

4数据分类分级判定

终于来到了文章最核心的部分。

要实现 实现数据分类分级判定 功能需要解决三个大问题:

  1. 如何获取导出语句的表名字段名?
  2. 如何获取导出语句的执行计划?
  3. 获取表和字段的敏感等级?

下面详细说明。

1. 如何获取导出语句的表名字段名?

大家先来思考一下这个需求好做吗?我的回答是非常难实现,这无异于是实现了 SQL 解析的功能。

MySQL 所有 SQL 语句类别里面,SELECT 一定是最复杂的,所以想实现获取表名、字段名是十分有难度的。

有些同学可能就会说了,有很多开源工具都支持 SQL 解析,直接拿来用就好了。这么说也没问题,但是即便使用了开源工具,还是会存在很多问题,归根结底还是因为 SELECT 语句太复杂。下面就以 Python 代码为例。

Python 的 sqlparse 模块是 SQL 的解析库,但是在使用过程中遇到了很多问题,下面列举一些我遇到的问题:

  1. 连接查询:使用连接查询的场景,包含不限于 inner joinleft joinright joinjoin, 等,这些会对结果产生影响,可能无法获取预期内的表名列表(没错, FROM 后面的逗号也是一种特殊的连接查询语法)。
  2. UNION 查询:取并集操作也会对结果产生影响,可能无法获取预期内的表名列表。
  3. 子查询问题:不管是 SELECT 后面使用子查询,还是 FROM 后面使用子查询,还是 WHERE 后面使用子查询,都会对获取表名列表结果产生影响,可能无法获取预期内的表名列表。而多层子查询又是更加复杂的问题。
  4. 四则运算问题:SELECT 后面使用了运算符,如常见的加减乘除会对获取字段列表结果产生影响,甚至大部分情况下都是结果未达到预期。
  5. 函数问题:SELECT 后面使用了函数,如常见的聚合函数,字符串处理的函数等都会对获取字段列表结果产生影响,甚至大部分情况下都是结果未达到预期。
  6. CASE WHEN:SELECT 后面使用了 CASE WHEN,会对获取字段列表结果产生影响,甚至大部分情况下都是结果未达到预期。
  7. 使用别名问题:SELECT 后面的字段使用了别名,会对获取字段列表结果产生影响,甚至大部分情况下都是结果未达到预期。
  8. SQL 语句长解析慢:如果业务的 SQL 语句很长,好几万个字符( IN 里面成千上万个值),这种场景解析超级慢,使用体验很不友好。
  9. 奇怪的语法:如果 SELECT 语句使用奇怪的语法,如正常语法是 db.table,然后奇怪的语法是 db. table,即 db. 后面多一个空格,在 MySQL 里面是不会报错,能正常执行。但是解析的时候会有问题,最终会对获取表名列表结果产生影响,甚至大部分情况下都是结果未达到预期。

对上述提到的场景,直接使用 sqlparse 是没法获取到表名字段名等信息,需要对sqlparse 的解析结果做场景适配,具体的场景测试这里就不一一展示了,直接上最终的代码。

有兴趣想了解更加详细的介绍/解释可以查阅代码的注释。

代码语言:javascript
代码运行次数:0
复制
git clone https://gitee.com/mo-shan/get-tab-clo.git

该项目依赖 sqlparse,需要自行安装该模块,Python 2.7.5/3.7.4 版本都测试通过。

代码语言:javascript
代码运行次数:0
复制
python GetResult.py 

{"col": ["sql", "id", "db_name", "db_port", "db_type"], "tab": ["tb_export_data_new"], "sql": "select `sql`,id,db_name,db_port,db_type from tb_export_data_new where workorder_id = 1"}
{"col": ["name", "order_id"], "tab": ["tb_export_data_new", "t2"], "sql": "select t1.name,t2.order_id from tb_export_data_new t1 join t2 on t1.id = t2.id where workorder_id = 1"}
{"col": ["addr", "name", "order_id"], "tab": ["t3", "tb_export_data_new", "t2"], "sql": "select t1.name,t2.order_id,(select t3.addr from t3 limit 1) from tb_export_data_new t1 join t2 on t1.id = t2.id where workorder_id = 1"}
{"col": ["sql", "id", "db_name_old", "db_port", "db_type", "sql", "db_name"], "tab": ["tb_export_data_old", "tb_export_data_new"], "sql": "select `sql`,id,db_name_old,db_port,db_type from tb_export_data_old where workorder_id = 1 union  select `sql`,id,db_name,db_port,db_type from tb_export_data_new where workorder_id = 2"}

至此获取表名,字段名列表的工作就算完成了。

2. 如何获取导出语句的执行计划?

这部分工作倒是没什么特别复杂的,就说几个注意事项:

  1. MySQL 版本问题:5.6、5.7、8.0 的的执行计划结果不一样,需要注意适配。
  2. 预估行问题:对于使用子查询或者连接查询的场景,预估行需要求乘积。
代码语言:javascript
代码运行次数:0
复制
(dba:4202)@[dbzz_new_dbversion1]>desc select * from t t1 join t t2 on t1.id = t2.id where t1.id = 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 849643 |    10.00 | Using where                                |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 849643 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.01 sec)

多表连接需要多次乘积,比如这里的案例需要 849643 * 849643,另外,如果这里解析异常可以给一个默认值,比如 10000。

  1. TiDB 的执行计划:TiDB 和 MySQL 的执行计划结果不一样,需要注意适配。另外,TiDB 的 ROWS 可能是 FLOAT 型,需要做类型转换。

3. 获取表和字段的敏感等级?

这部分工作每个公司的情况都不一样,且涉及到安全问题就不展开细说了,大致介绍一下实现的逻辑。

敏感等级的判定分两个维度:命名实际存储的数据

命名规则判定

需要定期去扫描业务表,获取到表名、字段名。需要注意的是,跑完全量样本后,后续每次只需要扫描增量即可。

实现增量扫描,需要做到将新建表、删除表、新增字段、删除字段等操作告知扫描系统即可,即:需要有建表/删表/改表工单。比如:

  • 业务人员命名了一个名为 user 的表,会被视为敏感类型。
  • 业务人员建表时设置了 usernamepasswd 等字段,也会视为敏感类型。
数据样本抽样判定

这里也需要定期去扫描业务表,获取到样本数据。需要注意的是,跑完全量样本后,后续每次只需要扫描增量即可。比如:

  • 业务人员创建了字段存储的 18 位或者 11 位数字类型的数据,就有理由怀疑存储的了身份证号或者手机号,所以也会被视为敏感类型。

以上就是《敏感数据分级判定规范》制定的原则和思路,具体的匹配规则需要根据各自公司的实际情况进行设定。

执行判定

解决上面三个问题后,终于可以完成 2.0 流程中的数据库分类判定步骤了。

数据导出工单 会根据执行计划结果的预估行及数据敏感等级综合评估,然后给一个最终等级。依赖导出数据条数的判断是参考了相关法律法规。

(一)致使泄露行踪轨迹信息、通信内容、征信信息、财产信息五百条以上的;

(二)致使泄露住宿信息、通信记录、健康生理信息、交易信息等其他可能影响人身、财产安全的用户信息五千条以上的;

(三)致使泄露第一项、第二项规定以外的用户信息五万条以上的;

(四)数量虽未达到第一项至第三项规定标准,但是按相应比例折算合计达到有关数量标准的;

数据敏感等级越高,就需要更高级别的领导审批,最高到 CEO。

申请人直属领导 --> 集群负责人 --> 集群的部门领导 --> 安全组 --> 集群的部门领导的直属领导 / CEO

这里需要注意,有可能会出现同一个人出现在不同的审批节点上,针对这种情况需要自动通过。

比如,A 申请了本部门的一个数据导出工单,A 的直属领导是 B,同时 B 又是部门领导,这时候一级审批和三级审批都是 B,B 只需要在一级审批的时候审批,等二级审批后到三级审批需要自动通过(如果判定失败可以给一个默认数据等级并发通知给 DBA)。

5总结

数据导出工单 应该说是一把双刃剑,既方便业务也方便 DBA,但是也带来了很多安全问题,我们现在所做的事情就是对其进行收口,尽可能的做到防止未经授权的数据泄露出去,也可以起到责任追溯的作用。

希望该功能在设计和实施中,可以牵引业务人员在使用数据导出功能的同时,符合安全管控规范,有效提升数据防泄漏能力。

改造后,数据导出工单数量在同周期内下降了 57% 且工单撤回率提升 25%。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱可生开源社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1背景介绍
    • 内容大纲
  • 2为什么需要数据导出工单?
    • 需求背景
    • 需求分析
  • 3数据导出工单 1.0 - 2.0
    • 1.0 时代
      • 1.0 工单处理流程
      • 1.0 安全问题清单
      • 1.0 时代结束
    • 2.0 时代
      • 2.0 工单处理流程
  • 4数据分类分级判定
    • 1. 如何获取导出语句的表名字段名?
    • 2. 如何获取导出语句的执行计划?
    • 3. 获取表和字段的敏感等级?
      • 命名规则判定
      • 数据样本抽样判定
    • 执行判定
  • 5总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档