代码自动化扫描系统的建设

作者:MyKings

代码审计一直是企业白盒测试的重要一环,面对市场上众多商业与开源的审计工具,你是否想过集众家之所长来搭建一套自动化的扫描系统呢?也许这篇文章会给你一些思路:)

 

一、背景

1. 为什么需要自动化扫描?

互联网的快速发展,程序员是功不可没的。从软件开发的瀑布模型到现在的敏捷开发, 软件的开发周期从数年到数月、从数月到数天,时间不断变换缩减。传统的代码扫描方式已经不能跟进新时代的软件开发流程中,这就需要改变我们的代码扫描方式,它应该在有限的时间内尽量发现足够多的安全问题,并能够结合 CI (持续集成) 来触发代码扫描。

2. 自动化扫描时扫描引擎用什么?

你可以使用任何提供 API 接口的开源或商业的扫描引擎。这里我们把扫描引擎(指第三方的审计工具)与自动化系统剥离(解耦)开来,扫描引擎只负责安全漏洞扫描;自动化系统负责漏洞收集、分析、处理等动作。同时你也可以通过自动化系统的后台来添加一些安全规则。

3. 怎么触发自动化扫描?

两种方式:

4. 如何解决漏报和误报?

两种方式:基于规则和基于插件,这里使用白名单表示误报、使用黑名单表示漏报。

基于规则

这里的规则是指基于文本的处理方式,如:使用正则表达式来匹配文件中的某些特征,使用字符串来判断是否存在敏感信息等。

PS: 这里有一些细节需要注意。

a. 正则表达式:

  • 是否区分大小写
  • 是否支持多行匹配

b. 字符串:

  • 是否首部包含
  • 是否尾部包含
  • 是否在当前字符串中

基于插件

插件的自由度较大,其本质是把要扫描的文件信息作为插件执行入口的上下文,文件信息包括:路径、名称、内容等。你也可以在插件中写一些判断逻辑或调用第三方工具。

5. 漏报率和误报率怎么样?

漏报率暂时无法很好的测量, 漏报率 = 漏报数/(漏洞数+漏报数) × 100% 而实际中很少有人(这里指非安全审计人员)会发现漏报,理论上你写的规则或插件越多越会减少漏报。

误报是可以通过白名单规则或插件处理的,理论上可以百分百消除误报,但是需要手动进行设置。

6. Web 通用型漏洞可以都覆盖么?

Web 通用型漏洞以 OWASP 2017 TOP 10 为例,其大多数都在黑盒测试的范围内。适用白盒测试仅限于:A3:2017-Sensitive Data Exposure、A9:2017-Using Components with Known Vulnerabilities,而这两项也是我们自动化扫描系统的基本要求。

7. 扫描出来的漏洞如何形成闭合?

这需要结合公司自身的软件开发方式而定,大多数公司采用Gitlab + Confluence + Jira + Jenkins的工作方式,那么我们可以通过 Jira 或 Gitlab 的 API 接口来创建问题工单。

 

二、设计要求

1. 目标

系统功能:

  • 尽量发现足够多的安全问题
  • 硬编码问题
  • 敏感信息泄露
  • 使用存在已知漏洞的组件
  • 危险函数识别
  • 可集成第三方扫描引擎
  • 可自动化处理误报
  • 可通过 CI 方式触发扫描
  • 可根据条件自动创建 Issue

扫描目标主要分为两种:一种为线上 Git 扫描;一种为离线扫描。线上 Git 扫描其主要应用场景为企业内部使用如 Gitlab 这种代码托管系统,我们定时同步 Gitlab 上的项目信息,通过 CI 来调用 API 接口进行扫描,自动化扫描就是这种模式,其执行流程为:“后台服务定时同步项目” -> “API接口下发扫描任务” -> “后台调度执行扫描”。离线扫描其形式为审计人员手动上传一个 zip 或 rar 的源码包,扫描系统自动解压后进行代码扫描。

这两种模式的执行流程略有不同,所以后端实现也略有不同,第一种的执行流程要比第二种较为复杂,我们这里会以第一种方式来实现自动化扫描。

2. 要求

系统要求:

  • 单个项目扫描控制在 20 分钟以内
  • 支持调度节点监控
  • 支持漏洞知识库管理
  • 支持 API 接口
  • 支持分布式部署, 方便扩展来提升扫描能力

时间控制是为了保证 CI/CD 过程不会太长,避免影响项目发布。调度节点监控,这里存在两种情况:一种是调度进程的心跳监控;一种为扫描任务的超时监控。漏洞知识库主要为了与组件分析模块分析出的依赖组件进行漏洞匹配。API 接口是为了方便第三方(代指CI/CD)调用来下发扫描任务或查询结果。分布式部署方式,可以水平扩展来提高调度节点和API节点的处理速度与能力。

3. 模块设计

主要系统:

  • 代码托管子系统
  • 自动化扫描子系统
  • 第三方扫描子引擎

这里从整体角度来观察,大致分为三个子系统,自动化扫描子系统依赖代码托管子系统,但不依赖扫描引擎(泛指第三方商业或开源审计产品)子系统,这是由于自动化扫描子系统内部集成了组件漏洞识别、黑白名单规则、黑白名单插件等功能,最后我们用一张图来说明。

 

三、小结

代码扫描固然重要,但是它不会解决所有项目的安全问题。项目安全应该从多个维度、多角度的来进行。如:项目立项时开始SDL;开发迭代过程中的代码扫描;项目上线前的黑盒测试。

 

四、系统设计

4.1 基础与准备

这里我们主要使用 Linux 来搭建我们的自动化扫描系统,按照设计的角色划分,我们这里需要三台 CentOS 7 的服务器,当然服务器可以是物理设备也可以是虚拟机,如果公司内部的扫描项目较多或为以后扩展考虑意见使用物理机。

服务器划分:

这里我们假定一个 codeaudit 域, 三台服务器的主机名称分别为:

  • ui.codeaudit: 负责后台管理系统的部署,包括数据库、MQ。
  • task.codeaudit: 负责调度扫描引擎。
  • sonarqube.codeauit: SonarQube的后台服务端。

4.2 技术说明

这里会讨论到所需的具体技术点,有些技术或方法可能不是最佳的方案,但是已经过我们测试检验是可行的。

以下为实际开发中用到的一些技能:

  • Python/Django/Jquery/Celery
  • Gitlab API/Sonar API
  • Git/Gitlab CI/Jenkins
  • Centos 7/Shell
  • NFS/Nginx/uWSGI
  • MySQL/RabbitMQ/Redis
  • 安全漏洞知识

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。

安全漏洞知识

  • 了解 OWASP TOP 10 的漏洞类型原理与解决方案;
  • 了解 CWE 的漏洞信息;
  • 了解公司主流的开发语言。

4.3 模块设计

下图中我们自上而下按照逻辑大致划分了”四”层:UI层、存储层、服务层、任务调度扫描引擎层(由于任务调度与服务同在后台运行,所以又统称为服务层)。

4.3.1 UI 层

提供扫描系统的后台管理、API接口、漏洞知识库等一系列的交互功能入口,不同的人员或系统可以根据各自的需求通过不同的交互接口来满足自己需求。如:CI/CD系统可通过 API 接口创建扫描任务并获取扫描结果;安全审计人员可通过后台进行规则或插件的添加;开发人员可通过漏洞知识库来获取相关语言或技术的漏洞信息。

4.3.2 存储层

主要包括关系型数据库、消息中间件(指MQ)、NFS(网络文件系统),这里我们使用了 MySQL 5.7 的数据库; RabbitMQ 是作为 Celery 调度框架的消息中间件;NFS担当网络共享存储,用于存储代码与扫描日志。

4.3.3 调度层

扫描任务的执行流程,主要可分为:

  • 初始化:扫描任务的环境初始化,如:日志目录、日志文件、加载插件、加载漏洞规则等;
  • 分析项目:项目代码统计、依赖组件统计、漏洞知识库关联等;
  • 扫描漏洞:调用第三方扫描引擎、统计扫描结果;
  • 漏报处理:使用黑名单规则和插件进行扫描;
  • 误报处理:使用白名单规则和插件进行误报处理;
  • 闭环漏洞:针对高危漏洞在 GitLab 或 Jira 系统中创建一个 Issue。

4.3.4 服务层

后台的服务,其主要包括:GitLab 系统中的项目同步、报表生成、调度进程监控。

 

五、系统功能

5.1 数据库设计

5.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

5.1.2 项目相关

项目表主要包括:项目组、项目、分支与TAG、统计信息、依赖组件、插件规则、扫描任务等相关表。

5.1.3 漏洞知识库

漏洞知识库,这里主要存储漏洞类型、漏洞知识等内容。

5.1.4 系统相关

系统表主要包括系统的安全周报、节点监控、系统日志等信息。

5.2 UI系统

扫描系统的后台,方便安全审计人员管理项目和系统。

5.2.1 项目管理

5.2.1.1 项目组

项目组我们通过 GitLab 的 API 同步所有项目组信息到我们的扫描系统,项目组的信息包括:项目组名称、项目组描述、创建时间、URL地址、项目成员等。

5.2.1.2 项目

项目是从分组中获取得到,需要注意的是可能会存在项目名相同但分组不同的情况。项目基本信息应包括:项目名称、项目描述、所属分组、默认分支、Git地址、项目成员、代码统计、依赖组件、分支管理、TAG管理等。

5.2.1.3 扫描任务

扫描任务会有四种状态:等待调度、正在扫描、扫描完成、扫描失败。每一次创建扫描任务时,都会查询是否存在等待调度或正在扫描的任务,如果存在则提示创建失败。

5.2.2 规则插件

5.2.2.1 规则

这里使用正则表达式来做特征匹配,并可通过限定文件的后缀来提高精准度。

正则表达式标志位:

  • 忽略大小写
  • 支持多行匹配

5.2.2.2 插件

这里使用了 Python 的反射机制,任务初始化时会优先初始化插件,当扫描完成时,扫描引擎会使用插件批量进行检测。插件入口函数为run(),漏洞详情对象会作为**kwargs参数的上下文传到该函数中。

5.2.2.3 规则知识库

规则知识库是区别与漏洞知识库的,往往规则知识库的内容要比漏洞知识库的内容简单,但是结构清晰。如:漏洞示例代码、漏洞说明、解决办法、参考链接等信息。

5.2.3 漏洞知识库

5.2.3.1 漏洞类型

这里建议使用 CWE 的漏洞标准,可参考这个文档:https://www.hackerone.com/sites/default/files/2017-03/WeaknessAndLegacyVulnerabilityTypeRelationship.pdf

5.2.3.2 漏洞管理

主要包括添加漏洞和管理漏洞,漏洞的信息应该包括:CVE/CNVD/CNNVD编号、漏洞标题、风险等级、漏洞来源、发现时间、受影响范围、漏洞详情、漏洞类型、解决版本等基本信息。

这里我们要实现漏洞知识库与识别出的组件联动功能,主要通过两个属性:

  • 组件标签

这里需要为每个漏洞添加一个 Tag 属性,其属性值如:org.springframework、com.alibaba.fastjson,建议标签一律使用小写字母。

  • 版本规则

使用正则表达式来进行匹配,如: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

5.2.4 报表管理

5.2.4.1 语言与项目统计

按照年份进行项目的语言统计。

5.2.4.2 周期性漏洞统计

每季度漏洞数对比

季度统计是为了对比同一段时期的漏洞数。

高危漏洞趋势图

高危漏洞环比,今天实施的安全政策是否合乎预期,可以大概分析出来。

5.3 API接口

5.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。

5.3.2 项目信息接口

信息同步

为什么需要信息同步?这是因为 GitLab 中的项目名称可能不是最终上线的 APP 名称(这里有些绕)。拿一个 Java 的项目举例,该项目的 GitLab 地址为:http://git.companyname.com/A/cloud,那么这个Java的包名有可能是 com.companyname.cloud。

我们使用项目的 git 地址来同步信息,建议把 git 地址全部转换为小写。APP 的名称(包名)可以 CI/CD 系统获取或是通过配置文件的硬编码方式来定义。

创建扫描

信息同步完成后,我们就可以根据 APP 名称和 git 地址来创建一个扫描任务,请求参数参考如下:

  • app_name: APP名称(可选)
  • module_name: 模块名称(可选)
  • version: 当前版本(可选)
  • git_url: git地址 (必选)
  • branch_name: 分支名称(必选)

5.3.3 任务信息接口

查询扫描任务

根据项目的 git 地址、分支来查询扫描任务,你也可以根据上一步创建扫描任务的 ID 来查询扫描结果。

查询任务漏洞列表

当扫描任务状态为扫描完成/扫描失败时,就可以根据任务 ID 来查询扫描出的安全漏洞信息。

5.3.4 漏洞规则接口

查询漏洞规则知识

通过漏洞信息中的漏洞规则 ID 或者 Key 来查询相关的规则知识库,该知识库应当包括:漏洞原因、漏洞示例代码、解决修复意见等。

5.4 后台服务

5.4.1 gitlab 的信息同步

使用 crontab 每两个小时遍历一遍 GitLab 上的所有项目,并同步项目信息到扫描系统中。

5.4.2 报表生成服务

使用 crontab 每日凌晨12点生成,季度对比和年度的安全统计数据。

5.4.3 扫描进程监控

使用ps aux| grep codescan来查看进程是否存活,当然这种暴力方式不能检测到进程的业务健康度的(比如:扫描任务卡死,状态一直为:正在扫描)。

5.5 SonarQube 搭建

5.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 即可正常访问。

5.5.2 插件管理

SonarQube 6.4 版本登陆的后台管理系统,选择 “配置” -> “系统” -> “更新中心” , 选择对应插件点击 “Install” 进行安装。

SonarQube 7.3 版本, “Administration” -> “Marketplace”, 选择对应插件点击 “Install” 进行安装。

SonarQube 6.4 截图

5.6 引擎调度

程序部署在 “task.codeaudit” 服务器上,服务需要安装 cloc 与 sonar-scanner 工具。

5.6.1 代码同步

同步代码分为以下几个步骤:

克隆项目

这里可能会遇到一些坑,比如项目历史比较久远,完整克隆下来可能会达到上百M或G,我们这里可以使用–depth 1参数进行克隆下载。有的项目可能会存在不规范的情况,比如拿 git 当 svn 使用,每个版本创建一个目录。

切换分支

根据扫描任务中的分支名称 checkout 到指定分支。

更新代码

针对已经克隆的项目进行 pull 操作,来同步 GitLab 上的项目更新代码。

5.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。

5.6.3 代码统计

使用cloc工具进行文件与代码行数的统计, 这里你可能需要通过–exclude-ext、–exclude-dir参数来过滤一些无意义的文件,比如:字体、图片、声音、视频等。举个例子,过滤所有图片后统计:cloc ./目标路径 –exclude-ext=.jpg,.jpeg,.png,.bmp,.gif,ico。

5.6.4 项目组件分析

组件分析主要是针对如使用 Java 语言开发项目时使用 Maven 管理的 pom.xml 配置文件; Python 中的 requirements.txt 文件;JS 项目中的package.json文件做解析。这里我写了一个clocwalk 工具可以分析项目的依赖组件,这个项目目前已经开源,你可以通过https://github.com/MyKings/clocwalk地址来获取这个工具。

5.6.5 漏报处理

关于漏报问题,你可以根据自己企业 SRC 中的漏洞,总结出一套适合自己企业的黑名单规则;或者你可以添加一些 CWE 的漏洞规则,关于 CWE 的信息你可以访问这个地址 https://cwe.mitre.org/data/index.html

5.6.6 误报处理

关于误报问题可能会较多,比如扫描出单元测试或功能测试的硬编码问题;比如变量参数String PARAM_NAME_PASSWORD=”passwd_txt”;问题。

以上的问题我们可以通过白名单插件处理,比如插件中对文件路径和方法判断是否存在 test 关键字,如果存在我们就认为这个是误报。另外针对某些特殊类型的误报,比如在 A 项目下才是误报,其他项目就是漏洞的情况,我们可以设置这个项目的白名单漏洞 Case,其匹配规则条件为:项目名称、漏洞文件、漏洞类型、漏洞所在行,当所有条件都同时满足时,那么这个漏洞就会可以判断为误报。

5.6.7 漏洞闭环

当一个高/中危漏洞被发现并确认时,我们应该如何跟踪这个漏洞的生命周期?往往安全人员会将一个漏洞提交到内部的 SOC 系统中,由于 SOC 系统没有和项目开发的流程控制系统(如:Jira)没有直接联系,开发人员可能会忽视或忘记修复这个高危漏洞,如何避免这种情况?我们这里以 GitLab 举例,当扫描系统扫描出高危漏洞时,系统会通过 GitLab 的 POST /projects/:id/issues API接口来自动创建一条 issue 并指派给当前项目的 master,项目负责人同时会收到一条邮件提醒,那么项目负责人可根据漏洞严重程度来安排项目的迭代计划,这样我们就把审计系统扫描出的漏洞与项目开发流程很好的结合起来了。

5.7 GitLab CI 触发

当然也可以使用 Jenkins 来做 CI/CD 系统。我们这里开发了一个.code-audit.py触发脚本, Jenkins 你也可以使用 Python 脚本或是开发 Jenkins 插件来达到触发目的。

5.7.1 配置项目

这里需要了解 .gitlab-ci.yml 文件格式的编写,下面是一个 Python 项目的配置。可以看出整个 CI 过程分为 4 个阶段:build、test、codeaudit、deploy。 其中codeaudit是我们的代码扫描阶段,这里我们限制了只有 develop 的动作才会触发扫描。

5.7.2 扫描脚本

触发扫描脚本如下图,其大体的执行流程如下:

  • 获取 GitLab CI 中关于项目的环境变量信息;
  • 设定要拦截的漏洞级别,默认:中、高危漏洞不通过测试;
  • 同步项目信息到扫描系统,如果失败扫描代码不通过;
  • 创建扫描任务,如果失败扫描代码不通过;
  • 异步查询扫描结果,超时时间10分钟,如果超时扫描代码不通过;
  • 扫描结果完成,统计是否存在预定义级别的漏洞,如果存在扫描代码不通过。

下图为整个 CI 过程的截图。

下图为代码扫描失败的反馈结果,图中可以看出发现了一个漏洞。

 

六、总结

关于 “自动代码审计系统的建设” 文章这里就此完结了。下篇中有些章节可能说的比较笼统宽泛,但是要对每一个章节详详细细的说明,恐怕每一个章节都会写下不止一篇文章了,本篇文章只是为大家提供一种思路,具体实施效果还是要靠大家自己来实践总结,就这样吧:)

 

参考链接

(完)