现在很多公司都会面临,内部敏感信息,比如代码,内部系统服务器地址,账号,密码等等泄露到GitHub上的风险,有恶意的也有非恶意的。这个问题有时很难完全规避掉,为了降低可能的恶劣影响,一般都是会内部搭建一个GitHub敏感信息泄露的监控系统。
一个典型的泄露敏感信息的配置文件(只是为了说明问题,该文件内容是随机生成的)
在负责这方面工作几个月之后,我遇到了两个问题: 1) 人工指定关键字,必然是不全面的,一般前期是依靠经验来指定,后期根据实际情况慢慢添加。 2) 告警日志数量非常巨大,其中绝大部分是误报,而真正的危险内容就深藏在其中。
人工审核,需要长期耗费大量时间,并且人在长期面对大量误报的情况下,因疲劳产生的思维敏感度下降,可能会漏掉真正的敏感内容,造成漏报。之所以对于敏感信息泄露的审核,一直由人工来进行,我感觉就是因为这个的识别没有一个很有效的模式,很难实现自动化,需要人工去判断。
后来,我在互联网上看到有关机器学习技术的文章,就想尝试用机器学习的方式去解决下工作痛点。
因为在做技术分享的同时,要保证绝对不能够泄露公司的敏感信息,所以下文中涉及到的运行演示,重要敏感信息都进行了脱敏(瞎编。。。)处理。大佬们如果有兴趣,可以使用自己在这方面工作中积累的样本来测试效果。
程序解析
用到的第三方库:hmmlearn, joblib, nltk, numpy, pymongo, scikit-learn, tldextract
实现的功能主要是两个:
1) 找出更多人工没有想到或注意到的敏感关键字。
首先,遍历现有的20个敏感文件样本,读取扩展名,行数,文件大小等信息,作为识别特征。解析文件内容,从中提取出域名和IP地址等主机资产识别信息。对这部分,做判断,如果是主机名,就提取出"domain"和"suffix"部分,如果是IP地址,则计算出相应B段网络。然后将目标文本内容Token化,剥除自定义标点符号和停止词等噪声元素,提取出单词列表。
这部分涉及的几个重要函数实现如下:
对于样本文件的解析,有两种方式,一种如上所示使用正则表达式,另一种是用 https://github.com/skskevin/UrlDetect/blob/master/tool/domainExtract/domainExtract.py 所介绍的"domainExtract.py"
这时,需要把上述"get_affect_assets()"函数的实现改为如下形式:
经测试表明,当样本文件普遍较小的时候,使用"domainExtract.py"效率更高,具体可根据实际情况选择使用哪种解析方法。 接下来,根据域名和单词的IDF值(IDF逆向文件频率是一个词语在文档中普遍重要性的度量),计算出主机名和敏感关键字列表。
这部分涉及的几个函数实现如下:
将上述计算结果和样本最大、最小字节数及最大行数等保存到JSON配置文件中,供后续使用。
运行效果演示:
2) 识别告警信息,排除误报,找出真正的敏感泄露信息。
首先从JSON配置文件读取配置。也就是通过上一步程序获取的重要信息。然后建立几个后续要用到的临时变量。
遍历敏感信息样本目录,针对其中的每一个样本文件,收集如上一个脚本中收集的元信息数据,与配置列表进行对比。 将命中主机和IP地主数量,未命中主机数量,命中敏感关键字数量,文件字节数,文本行数和扩展名等统计信息结合在一起,组成矩阵。 再给予一个识别标志数据,表示这个类别是敏感文件类别。
对于正常信息样本目录,也执行基本相同的操作,然后给予一个相反的识别标志数据,表示这个类别是正常文件类别。
使用隐马尔可夫(HMM)模型对敏感信息样本文件逐个进行评分,然后将阈值保存到JSON配置文件中。
运行效果演示:
到这里,就为后续最重要的识别检测工作做好了准备。
程序原本是连接到Hawkeye系统的MongoDB数据库,直接审核在线数据。 出于演示效果和大佬们测试方便的目的,改写了一个以本地目录为数据源的版本,检测逻辑是完全相同的。 稍后我会介绍如何对接Hawkeye系统。
先从配置文件读取配置信息,建立一些临时变量。然后读取并反序列化HMM和SVM机器学习模型。
从数据源处一个接一个的获取待审核告警信息,针对每一个具体的告警文件,首先判断文本行数和字节数是否超出了阈值,接着检查资产信息和敏感关键字的命中情况。 满足条件的话,进入下一步检测,使用HMM模型对目标进行评分,如果评分结果处于敏感文件HMM模型分数阈值范围之内,再使用SVM模型进行二次判断,最终输出一个结果。
运行效果演示:
关于对接Hawkeye系统:
假设MongoDB数据库连接信息如下:
在Hawkeye系统中,每一条数据记录,体现为"result"集合中的一条"document"数据。 在前端Web管理页面中,显示为待审核的数据,在对应"document"中,其"desc"域是为空,或者说不存在的,在Python里体现为"None" 利用这个属性,将待审核条目中需要用到的数据查询出来进行处理。
PS: 这里有一点要注意,因为"no_cursor_timeout"参数置为"True"了,所以在遍历完成后切记应该执行"cursor.close()"显式关闭链接。
这时候,变量"cursor"就成为一个迭代器,从里面获取数据就好了。从"link"域提取文件名,从"code"域提取BASE64编码表示的文件实际内容,解码一下就行。
总结
我们用来作为检测依据的各种特征,都来源于对安全和非安全样本的学习,通过对样本集的各种特征的收集和计算,得出阈值,再做适当扩展。也就是说,只有处在阈值范围内的文件,程序才能够进行有效识别,超出了认知盲区的部分,是不能随便给出结论的。 结合数据源特性,程序形成的效果就是,将确定一定不是敏感信息的告警置为安全忽略状态。其他的则留在待审标记下,由人工来做判断。
想象一下,当人工在审核告警信息的时候,思维模式大概分为两个部分: 一部分是,一眼看上去就知道一定是误报的告警,果断忽略掉。另一部分是,有的告警文件,内容上比较难以辨别,需要仔细检查一小会儿才能确定。
程序的运行其实也是相同的过程,机器学习可以在极短的时间内将绝大部分一眼看上去就是误报的告警排除掉,剩下就是那些占比极小的,需要仔细检查一会儿才能确定的文件,将它们留在原地,由人工来进行判断,实现在节省巨量不必要的时间投入的同时,准确识别出那些可能会带来巨大损失的信息泄露隐患。
我们的样本文件集,数量越多,质量越好,产出的机器学习模型也就越高效和精准。就好像人读书越多越聪明一样。 在前期,当我们的样本集具备基本的可用性的时候,实际上就已经能够很好的运行了,随着工作的进行,收集到的样本会越来越多,对机器学习模型的训练也就越来越容易。
结束
因为我自身的工作主要是渗透测试方面。程序开发经验和对机器学习算法的理解都十分匮乏,所以历时很久,参考了网上各路大神的资料之后,反复测试迭代,才搞到现在这个效果,能够很好的解决自身的这个工作需求。 但程序本身还存在很多不足,后面打算再做优化,尝试添加其他的算法和思路,以期打磨的更加完善。