文章目录
- 要解决什么
- 背景
- Jsqlparser
- 安全系统
- 难点
- 目标
- 方法
- 语法树遍历
- 实现
- 初始化
- 第一轮
- 第二轮
- 第三轮
- 总结
要解决什么
企业进行软件开发、数据分析、数据查询时,不可避免的涉及到数据安全的问题,不同人员能看到的数据不同,如何识别哪些敏感数据被查询了,或者被通过一些处理后隐性的被查询了是非常重要的。如手机号,直接判断是否查询手机号是十分容易的,但是如果查询时通过两次SQL查询,一次查询前手机号前3位,一次查询后8位,则不易被发现,数据也已泄露。本文介绍如何解决“在SQL使用中发现使用敏感数据泄露的行为”。
背景
Jsqlparser
Jsqlparser是一个用Java编写的SQL解析器,可以将SQL语句解析成语法树,方便开发人员进行SQL语句的分析和操作。目前有很多类似的SQL解析器,此类解析器只能将语句解析为语法树,但是不能使上下层结构的业务逻辑关联在一起,因而它只是一个工具,如何判断最终结果中是否涉及敏感数据的逻辑还需要我们自己编写。
安全系统
目前有一些商用的数据安全系统,包含数据脱敏功能,开源的如archery。此类系统可以识别一定类型的SQL语句,但是只包含简单结构,像包含多层嵌套查询、关联查询、函数处理、casewhen语法时,系统不能检测出。
难点
难点:无法支持复杂的嵌套查询、无法支持函数处理后的数据、无法支持casewhen语法、无法支持union关键字等。
目标
搭建一种易于扩展的SQL分析架构,能够方便的分析SQL语法树,建立上下层嵌套语句间的关系,使得SQL查询过程中能够便捷的支持各种语法,识别出可能的敏感数据泄露行为。
方法
语法树遍历
使用Jsqlparser工具将SQL解析为语法树,选择深度遍历优先算法。有两个重要概念:
一是,敏感数据的传染。
即在深度优先遍历时,先找到最底层的子句,根据from后的数据源找出本子句使用的敏感数据,如果上层父语句使用此数据,则对映数据被传染为敏感数据。
二是,敏感数据表。
即维护一个敏感数据<数据名称,来源表,来源字段>的三元组集合。一开始集合中只包含数据库中的原始字段,我们把敏感数据做一个事前的基础维护。然后在分析语法树的每层结构时,不断增加这个集合的内容,一旦某层的中间数据被传染,则加入到集合中,并记录来源。最后自下而上一层层追溯到顶端,则可以发现全部被传染的敏感数据。
实现
// 数据三元组如下
public class Col {public String srcTable;// 真实来源表public String srcCol;// 真实来源列public String name;// 别名
}
// 数据集合如下
private HashMap<String, Set<Col>> tableMaps = new HashMap<>();
以select a.pho,b.money, (select f.name from user_friend f where f.user_id = a.id limit 1) friendName from (select pho from (select phone pho from user t1 where t1.create_time < ‘2023-05-01’) t where t.create_time > ‘2023-01-01’) a join bank b on a.id = b.user_id;
为例
初始化
首先初始化中间数据集合,这些初始信息由其他系统认为设置或自动扫描识别得到,如我们设置user表的phone是敏感信息,则初始集合为Col<name:phone,srcCol:phone,srcTable:user>
。
使用Jsqlparse等工具解析sql为语法树,对语法树进行深度优先遍历,伪代码如下:
public void visit(PlainSelect plainSelect) {//accept(this)递归本方法实现深度遍历PlainSelect sub = findSub(plainSelect.getFromItem());sub.accept(this);List<Join> joins = plainSelect.getJoins();for (Join join : joins) {PlainSelect s = findSub(join.getRightItem());s.accept(this);}for (SelectItem selectItem : plainSelect.getSelectItems()) {PlainSelect sub = findSub(selectItem);sub.accept(this);}
}
第一轮
第一轮处理如下子句,根据集合得知phone为敏感数据,pho虽然重新定义了别名,但其被传染为敏感数据,则集合中增加一条Col<name:t.pho,srcCol:phone,srcTable:user>
(select phone pho from user t1 where t1.create_time < ‘2023-05-01’) t
第二轮
第二轮处理如下子句,根据上一轮集合结果,t.pho为敏感数据,则a.pho也被传染为敏感数据,则集合中增加一条Col<name:a.pho,srcCol:phone,srcTable:user>
(select pho from (select phone pho from user t1 where t1.create_time < ‘2023-05-01’) t where t.create_time > ‘2023-01-01’) a
第三轮
第三轮处理如下子句,根据上一轮集合结果,a.pho为敏感数据,则最终发现a.pho是敏感数据,依此类推。
select a.pho,b.money, (select f.name from user_friend f where f.user_id = a.id limit 1) friendName from (select pho from (select phone pho from user t1 where t1.create_time < ‘2023-05-01’) t where t.create_time > ‘2023-01-01’) a join bank b on a.id = b.user_id;
总结
1、便于逻辑分析,每次只需要解决上下相邻层间的逻辑问题,而不必跨层,大大降低了难度。
2、逻辑结构清晰,多层嵌套问题被切割成了两层语句的问题,便于后期更复杂逻辑的扩展。