上一篇文章《代码自动化扫描系统的建设(上)》 主要介绍了自动扫描系统的背景和要实现的目标,这篇里我们将会详细介绍各个层与模块的设计。
一、系统设计
1.1 基础与准备
这里我们主要使用 Linux 来搭建我们的自动化扫描系统,按照设计的角色划分,我们这里需要三台 CentOS 7 的服务器,当然服务器可以是物理设备也可以是虚拟机,如果公司内部的扫描项目较多或为以后扩展考虑意见使用物理机。
服务器数量 | 操作系统 | 扫描引擎 | 数据库 | 开发语言 | 角色 |
---|---|---|---|---|---|
3台 | CentOS 7 | SonarQube | MySQL | Python | UI+MySQL+MQ 扫描节点 第三方引擎(SonarQube) |
服务器划分:
这里我们假定一个 codeaudit
域, 三台服务器的主机名称分别为:
(1). ui.codeaudit: 负责后台管理系统的部署,包括数据库、MQ。
(2). task.codeaudit: 负责调度扫描引擎。
(3). sonarqube.codeauit: SonarQube的后台服务端。
1.2 技术说明
这里会讨论到所需的具体技术点,有些技术或方法可能不是最佳的方案,但是已经过我们测试检验是可行的。
以下为实际开发中用到的一些技能:
(1). Python/Django/Jquery/Celery
(2). Gitlab API/Sonar API
(3). Git/Gitlab CI/Jenkins
(4). Centos 7/Shell
(5). NFS/Nginx/uWSGI
(6). MySQL/RabbitMQ/Redis
(7). 安全漏洞知识
CentOS 7
CentOS 7 与 6 的版本会有一些区别,我们需要具有 Linux 的基本操作基础,了解 systemctl
、firewall-cmd
、crontab
等命令;了解SELinux
,修改SELinux
状态;并能编写systemd
的自启动脚本。
Git
了解 Git 的基本操作命令,使用 SSH 密钥的方式提交或拉取代码; 熟悉git clone
、git log
、git pull
、git branch
、git remote
、git fetch
、git for-each-ref
、git ls-files
等基本命令的操作。
例如:
使用git for-each-ref
来得到当前分支的最后一次 commit id;
使用git ls-files
来判断项目中是否存在sonar-project.properties
配置文件;
使用git log -n1 /path/file
来获取文件最后一次 commit 的作者。
CI/CD
不论是集成到 Gitlab CI 或是Jenkins, 我们都需要先了解项目上线的基本流程,如:开发的代码规范、测试环节(单元测试/功能测试)、发布部署环节等。一般我们会将代码扫描环节放在在测试环节后,发布部署前。
Python
这里我们使用Python
进行后台的服务端开发,使用Django
进行前台 UI 的开发,使用django-rest-swagger
来开发 API 接口,使用Celery
作为扫描任务的调度框架。
数据库与中间件
数据库我们选择使用 MySQL 5.7(或MariaDB, 他们在使用上没有太大的区别); Celery
的消息中间件可以使用 Redis
或是 RabbitMQ
,这里你可以在开发的时候使用 Redis
,正式部署时使用 RabbitMQ
。
安全漏洞知识
(1). 了解 OWASP TOP 10 的漏洞类型原理与解决方案;
(2). 了解 CWE 的漏洞信息;
(3). 了解公司主流的开发语言。
1.3 模块设计
下图中我们自上而下按照逻辑大致划分了"四"层:UI层、存储层、服务层、任务调度扫描引擎层(由于任务调度与服务同在后台运行,所以又统称为服务层)。
1.3.1 UI 层
提供扫描系统的后台管理、API接口、漏洞知识库等一系列的交互功能入口,不同的人员或系统可以根据各自的需求通过不同的交互接口来满足自己需求。如:CI/CD系统可通过 API 接口创建扫描任务并获取扫描结果;安全审计人员可通过后台进行规则或插件的添加;开发人员可通过漏洞知识库来获取相关语言或技术的漏洞信息。
1.3.2 存储层
主要包括关系型数据库、消息中间件(指MQ)、NFS(网络文件系统),这里我们使用了 MySQL 5.7 的数据库; RabbitMQ 是作为 Celery 调度框架的消息中间件;NFS担当网络共享存储,用于存储代码与扫描日志。
1.3.3 调度层
扫描任务的执行流程,主要可分为:
(1). 初始化:扫描任务的环境初始化,如:日志目录、日志文件、加载插件、加载漏洞规则等;
(2). 分析项目:项目代码统计、依赖组件统计、漏洞知识库关联等;
(3). 扫描漏洞:调用第三方扫描引擎、统计扫描结果;
(4). 漏报处理:使用黑名单规则和插件进行扫描;
(5). 误报处理:使用白名单规则和插件进行误报处理;
(6). 闭环漏洞:针对高危漏洞在 GitLab 或 Jira 系统中创建一个 Issue。
1.3.4 服务层
后台的服务,其主要包括:GitLab 系统中的项目同步、报表生成、调度进程监控。
二、系统功能
2.1 数据库设计
2.1.1 权限相关
权限控制,这里使用 django 自带的权限表来进行权限控制,我们可以通过auth_group
表来创建用户组,为不同的用户组赋予不同的角色权限auth_group_permissions
,你可以访问官方地址:https://docs.djangoproject.com/en/2.1/topics/auth/default/#topic-authorization来获得更多关于权限的信息。
django 权限表如下:
auth_group
auth_group_permissions
auth_permission
auth_user
auth_user_groups
auth_user_user_permissions
2.1.2 项目相关
项目表主要包括:项目组、项目、分支与TAG、统计信息、依赖组件、插件规则、扫描任务等相关表。
2.1.3 漏洞知识库
漏洞知识库,这里主要存储漏洞类型、漏洞知识等内容。
2.1.4 系统相关
系统表主要包括系统的安全周报、节点监控、系统日志等信息。
2.2 UI系统
扫描系统的后台,方便安全审计人员管理项目和系统。
2.2.1 项目管理
2.2.1.1 项目组
项目组我们通过 GitLab 的 API 同步所有项目组信息到我们的扫描系统,项目组的信息包括:项目组名称
、项目组描述
、创建时间
、URL地址
、项目成员
等。
2.2.1.2 项目
项目是从分组中获取得到,需要注意的是可能会存在项目名相同但分组不同的情况。项目基本信息应包括:项目名称、项目描述、所属分组、默认分支、Git地址、项目成员、代码统计、依赖组件、分支管理、TAG管理等。
2.2.1.3 扫描任务
扫描任务会有四种状态:等待调度、正在扫描、扫描完成、扫描失败。每一次创建扫描任务时,都会查询是否存在等待调度或正在扫描的任务,如果存在则提示创建失败。
2.2.2 规则插件
2.2.2.1 规则
这里使用正则表达式来做特征匹配,并可通过限定文件的后缀来提高精准度。
正则表达式标志位:
(1). 忽略大小写
(2). 支持多行匹配
2.2.2.2 插件
这里使用了 Python 的反射机制,任务初始化时会优先初始化插件,当扫描完成时,扫描引擎会使用插件批量进行检测。插件入口函数为run()
,漏洞详情对象会作为**kwargs
参数的上下文传到该函数中。
2.2.2.3 规则知识库
规则知识库是区别与漏洞知识库的,往往规则知识库的内容要比漏洞知识库的内容简单,但是结构清晰。如:漏洞示例代码、漏洞说明、解决办法、参考链接等信息。
2.2.3 漏洞知识库
2.2.3.1 漏洞类型
这里建议使用 CWE 的漏洞标准,可参考这个文档:https://www.hackerone.com/sites/default/files/2017-03/WeaknessAndLegacyVulnerabilityTypeRelationship.pdf
2.2.3.2 漏洞管理
主要包括添加漏洞和管理漏洞,漏洞的信息应该包括:CVE/CNVD/CNNVD编号、漏洞标题、风险等级、漏洞来源、发现时间、受影响范围、漏洞详情、漏洞类型、解决版本等基本信息。
这里我们要实现漏洞知识库与识别出的组件联动功能,主要通过两个属性:
(1). 组件标签
这里需要为每个漏洞添加一个 Tag 属性,其属性值如:org.springframework、com.alibaba.fastjson,建议标签一律使用小写字母。
(2). 版本规则
使用正则表达式来进行匹配,如:CVE-2018-1270 中受影响的 Spring Framework 版本为:5.0.x-5.0.5 和 4.3.x-4.3.16,那么我们的规则可以写成如下:
5\.0 ### 5.0
(5\.0\.[0-4]{1}) ### 5.0.x -5.0.5
(4\.3\.1[0-5]{1}) ### 4.3.1x.release
(4\.3\.[0-9]{1}\.) ### 4.3.x.release
2.2.4 报表管理
2.2.4.1 语言与项目统计
按照年份进行项目的语言统计。
2.2.4.2 周期性漏洞统计
每季度漏洞数对比
季度统计是为了对比同一段时期的漏洞数。
高危漏洞趋势图
高危漏洞环比,今年实施的安全政策是否合乎预期,可以大概分析出来。
2.3 API接口
2.3.1 接口认证
使用 rest_framework
的 API 来做验证,首先根据登陆的用户 id 生成一个 Token。
from rest_framework.authtoken.models import Token
def create_token(request, user_id=None):
if request.user.id != int(user_id):
return HttpResponseRedirect("/error/403")
try:
user = User.objects.get(id=user_id)
except Token.DoesNotExist:
token = Token.objects.create(user=user)
return HttpResponseRedirect("/users/{0}".format(user_id))
验证接口使用说明,添加 Authorization 的认证 Token。
2.3.2 项目信息接口
信息同步
为什么需要信息同步?这是因为 GitLab 中的项目名称可能不是最终上线的 APP 名称(这里有些绕)。拿一个 Java 的项目举例,该项目的 GitLab 地址为:http://git.companyname.com/A/cloud
,那么这个Java的包名有可能是 com.companyname.cloud
。
我们使用项目的 git 地址来同步信息,建议把 git 地址全部转换为小写。APP 的名称(包名)可以 CI/CD 系统获取或是通过配置文件的硬编码方式来定义。
创建扫描
信息同步完成后,我们就可以根据 APP 名称和 git 地址来创建一个扫描任务,请求参数参考如下:
(1). app_name: APP名称(可选)
(2). module_name: 模块名称(可选)
(3). version: 当前版本(可选)
(4). git_url: git地址 (必选)
(5). branch_name: 分支名称(必选)
2.3.3 任务信息接口
查询扫描任务
根据项目的 git 地址、分支来查询扫描任务,你也可以根据上一步创建扫描任务的 ID 来查询扫描结果。
查询任务漏洞列表
当扫描任务状态为扫描完成/扫描失败
时,就可以根据任务 ID 来查询扫描出的安全漏洞信息。
2.3.4 漏洞规则接口
查询漏洞规则知识
通过漏洞信息中的漏洞规则 ID 或者 Key 来查询相关的规则知识库,该知识库应当包括:漏洞原因、漏洞示例代码、解决修复意见等。
2.4 后台服务
2.4.1 gitlab 的信息同步
使用 crontab 每两个小时遍历一遍 GitLab 上的所有项目,并同步项目信息到扫描系统中。
2.4.2 报表生成服务
使用 crontab 每日凌晨12点生成,季度对比和年度的安全统计数据。
2.4.3 扫描进程监控
使用ps aux| grep codescan
来查看进程是否存活,当然这种暴力方式不能检测到进程的业务健康度的(比如:扫描任务卡死,状态一直为:正在扫描)。
2.5 SonarQube 搭建
2.5.1 服务搭建
下载最新版本https://www.sonarqube.org/downloads/
上传到sonarqube.codeauit
服务器上并解压。进入到bin/linux-x86-64/
目录下,执行 sh ./sonar.sh start
。 SonarQube 启动成功后,使用浏览器打开http://192.168.10.3:9000
, 输入 admin/admin
即可正常访问。
2.5.2 插件管理
SonarQube 6.4 版本登陆的后台管理系统,选择 "配置" -> "系统" -> "更新中心" , 选择对应插件点击 “Install” 进行安装。
SonarQube 7.3 版本, “Administration” -> "Marketplace", 选择对应插件点击 “Install” 进行安装。
SonarQube 6.4 截图
2.6 引擎调度
程序部署在 “task.codeaudit” 服务器上,服务需要安装 cloc 与 sonar-scanner 工具。
2.6.1 代码同步
同步代码分为以下几个步骤:
克隆项目
这里可能会遇到一些坑,比如项目历史比较久远,完整克隆下来可能会达到上百M或G,我们这里可以使用--depth 1
参数进行克隆下载。有的项目可能会存在不规范的情况,比如拿 git 当 svn 使用,每个版本创建一个目录。
切换分支
根据扫描任务中的分支名称 checkout 到指定分支。
更新代码
针对已经克隆的项目进行 pull 操作,来同步 GitLab 上的项目更新代码。
2.6.2 sonar-scanner 扫描
ssh 登录到task.codeaudit
服务器上,执行cd /opt && wget https://sonarsource.bintray.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.2.0.1227-linux.zip && unzip sonar-scanner-cli-3.2.0.1227-linux.zip
来下载并解压,执行成功后使用ln -s /opt/sonar-scanner-3.2.0.1227-linux/bin/sonar-scanner /usr/bin/sonar-scanner
命令创建一个sonar-scanner
的软连接。这里我们会使用sonar-scanner
命令来进行项目的代码扫描。
你也可以通过https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner
来下载不同平台的sonar-scanner-cli-3.2.0.1227-linux.zip
。
2.6.3 代码统计
使用cloc
工具进行文件与代码行数的统计, 这里你可能需要通过--exclude-ext
、--exclude-dir
参数来过滤一些无意义的文件,比如:字体、图片、声音、视频等。举个例子,过滤所有图片后统计:cloc ./目标路径 --exclude-ext=.jpg,.jpeg,.png,.bmp,.gif,ico
。
2.6.4 项目组件分析
组件分析主要是针对如使用 Java 语言开发项目时使用 Maven 管理的 pom.xml 配置文件; Python 中的 requirements.txt 文件;JS 项目中的package.json
文件做解析。这里我写了一个clocwalk
工具可以分析项目的依赖组件,这个项目目前已经开源,你可以通过https://github.com/MyKings/clocwalk
地址来获取这个工具。
2.6.5 漏报处理
关于漏报问题,你可以根据自己企业 SRC 中的漏洞,总结出一套适合自己企业的黑名单规则;或者你可以添加一些 CWE 的漏洞规则,关于 CWE 的信息你可以访问这个地址 https://cwe.mitre.org/data/index.html。
2.6.6 误报处理
关于误报问题可能会较多,比如扫描出单元测试或功能测试的硬编码问题;比如变量参数String PARAM_NAME_PASSWORD="passwd_txt";
问题。以上的问题我们可以通过白名单插件处理,比如插件中对文件路径和方法判断是否存在 test 关键字,如果存在我们就认为这个是误报。另外针对某些特殊类型的误报,比如在 A 项目下才是误报,其他项目就是漏洞的情况,我们可以设置这个项目的白名单漏洞 Case,其匹配规则条件为:项目名称、漏洞文件、漏洞类型、漏洞所在行,当所有条件都同时满足时,那么这个漏洞就会可以判断为误报。
2.6.7 漏洞闭环
当一个高/中危漏洞被发现并确认时,我们应该如何跟踪这个漏洞的生命周期?往往安全人员会将一个漏洞提交到内部的 SOC 系统中,由于 SOC 系统没有和项目开发的流程控制系统(如:Jira)没有直接联系,开发人员可能会忽视或忘记修复这个高危漏洞,如何避免这种情况?我们这里以 GitLab 举例,当扫描系统扫描出高危漏洞时,系统会通过 GitLab 的 POST /projects/:id/issues
API接口来自动创建一条 issue 并指派给当前项目的 master,项目负责人同时会收到一条邮件提醒,那么项目负责人可根据漏洞严重程度来安排项目的迭代计划,这样我们就把审计系统扫描出的漏洞与项目开发流程很好的结合起来了。
2.7 GitLab CI 触发
当然也可以使用 Jenkins 来做 CI/CD 系统。我们这里开发了一个.code-audit.py
触发脚本, Jenkins 你也可以使用 Python 脚本或是开发 Jenkins 插件来达到触发目的。
2.7.1 配置项目
这里需要了解 .gitlab-ci.yml
文件格式的编写,下面是一个 Python 项目的配置。可以看出整个 CI 过程分为 4 个阶段:build
、test
、codeaudit
、deploy
。 其中codeaudit
是我们的代码扫描阶段,这里我们限制了只有 develop 的动作才会触发扫描。
2.7.2 扫描脚本
触发扫描脚本如下图,其大体的执行流程如下:
(1). 获取 GitLab CI 中关于项目的环境变量信息;
(2). 设定要拦截的漏洞级别,默认:中、高危漏洞不通过测试;
(3). 同步项目信息到扫描系统,如果失败扫描代码不通过;
(4). 创建扫描任务,如果失败扫描代码不通过;
(5). 异步查询扫描结果,超时时间10分钟,如果超时扫描代码不通过;
(6). 扫描结果完成,统计是否存在预定义级别的漏洞,如果存在扫描代码不通过。
下图为整个 CI 过程的截图。
下图为代码扫描失败的反馈结果,图中可以看出发现了一个漏洞。
三、总结
关于 “自动代码审计系统的建设” 文章这里就此完结了。下篇中有些章节可能说的比较笼统宽泛,但是要对每一个章节详详细细的说明,恐怕每一个章节都会写下不止一篇文章了,本篇文章只是为大家提供一种思路,具体实施效果还是要靠大家自己来实践总结,就这样吧:)
四、参考链接
https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/crontab.html
https://docs.gitlab.com/ee/api/
https://about.gitlab.com/features/gitlab-ci-cd/
https://docs.gitlab.com/ce/ci/yaml/README.html
https://docs.gitlab.com/ce/ci/examples/README.html
https://docs.gitlab.com/ee/api/issues.html
https://docs.gitlab.com/runner/install/docker.html
https://docs.sonarqube.org/display/DEV/Web+API
https://docs.djangoproject.com/en/2.1/topics/auth/default/#topic-authorization
https://www.sonarqube.org/downloads/
https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner
https://github.com/MyKings/clocwalk
*本文作者:MyKings,转载请注明来自FreeBuf.COM