DevSecOps工具与平台交互的桥梁 -- SARIF入门
1. 引言
从2012年Gartner的研究报告“DevOpsSec: Creating the Agile Triangle”提出了DevSecOps的概念, 到目前DevSecOps已经成为构建企业级研发安全的重要模式。 DevSecOps模式中有些重要的原则:安全左移、默认安全、运行时安全、安全服务自动化/自助化、基础设施即代码(IaC)、持续集成和交付,以及需要组织和文化建设。
在DevSecOps的过程中,最重要的一环就是安全工具能够快速集成到自动化平台,保证平台的持续集成和交付,从而提高产品的整体的安全水平。 在整个研发过程中静态安全检查工具是保证代码安全的重要手段,每个安全检查工具因为技术实现上的差异,对不同的安全缺陷有着不同的检查能力,所以稍微大型一些的开发团队, 在开发的时候会引入多个安全扫描工具,以获取安全检查能力覆盖的最大化。 但多工具的集成, 就会对程序员或平台的集成会带来以下问题:
- 对于开发人员
- 在使用IDE的时候, 缺少一个通用的检查工具结果接口和交互接口;
- 需要查看多个检查工具的结果;
- 对于集成平台
- 需要学习每个工具的输出扫描格式;
- 在扫描报告转入到缺陷跟踪系统的时候, 缺少一个通用的缺陷转换程序;
- 缺少一个通用扫描结果度量方式。
为了解决这些的问题,最初由微软、Micro focus等几家静态分析软件公司提出了:静态分析结果交换格式(Static Analysis Results Interchange Format (SARIF))。
2. 结构化信息标准促进组织(OASIS)
结构化信息标准促进组织(Organization for the Advancement of Structured Information Standards (OASIS))是一个非营利性的国际财团,推动全球信息社会制定、融合和采用开放标准。OASIS 促进行业共识,并制定网络安全、隐私、云计算、物网、智能电网和其他领域的全球标准。OASIS 开放标准具有降低成本、刺激创新、开拓全球市场和保护自由选择技术的权利的潜力。
OASIS 成员广泛代表公共和私营部门技术领导者、用户和影响者的市场。自1993年成立开始,OASIS已经发展成为了由来自100多个国家的600多家组织、企业,参与人数超过5000人的国际化组织。相比其他组织,OASIS形成了更多的Web服务标准的同时也提出了面向安全、电子商务的标准,同时在针对公众领域和特定应用市场的标准化方面也付出很多的努力。
OASIS以其管理透明化及工作流程化而著称。OASIS成员自己设置技术议程,并通过简单的工作流程促进产业达成一致以及统一不同观点。OASIS的全部工作将是通过公开投票的方式认可,管理层具有责任心并且不受其他因素制约。OASIS理事会和技术顾问委员会的成员都由民主选举产生,任期2年。OASIS的领导层是由于个人能力而非资金资助、企业背景或特别任命而产生的。
OASIS现在拥有最受人们广泛接受的XML以及Web服务标准2个信息入口,Cover Pages和XML.org。OASIS的成员分会包括CGM Open、DCML、LegalXML、PKI和UDDI。
OASIS于1993年最初以SGML开放组织名义成立。它最初是代表用户和产品提供商致力于推动产品互操作性架构的开发以及支持标准广义标记语言(Standard Generalized Markup Language)。1998年,组织的名称正式更换为OASIS开放组织,以表示其在技术领域的工作已经扩展到可扩展标记语言 (XML)及相关标准以外的范围。
-
OASIS 主要技术成员
-
OASIS成立SARIF技术委员会
因为静态分析工具在安全保障中的重要地位,越来越多的静态分析工具的主要厂商CA Technologies, Cryptsoft, FireEye, GrammaTech, Hewlett Packard Enterprise (HPE), Micro Focus, Microsoft, New Context, Phantom, RIPS, SWAMP, Synopsys, U.S. DHS, U.S. NIST等等都在推进静态分析工具的通用数据格式的标准。 2017年10月12日, OASIS成立了SARIF技术委员会为静态分析制定国际互操作性标准。SARIF技术委员会汇集了主要软件公司、网络安全提供商、政府、安全协调专家、程序员和顾问,就一种数据格式达成一致,该格式将由整个行业的工具解析。目标是通过聚合来自多个工具的数据,使软件开发人员更容易评估其程序的质量和安全性。
在接下来的2018年,SARIF就获得了OASIS的年度Open Standards Cup的杰出新计划(Outstanding New Initiative)。
3. SARIF规范简介
-
SARIF的目标:
- 全面捕获常用静态分析工具生成的数据范围;
- 分析工具直接输出的一种有用的格式,并且是可以将任何分析工具的输出转换成的有效交换格式;
- 适用于与分析结果管理相关的各种方案,并且可扩展用于新方案;
- 降低将各种分析工具的结果汇总到通用工作流程中的成本和复杂性;
- 捕获有助于评估项目是否符合公司政策或符合认证标准的信息;
- 采用广泛使用的序列化格式,可以使用现成的工具进行解析;
- 表示各种编程工件的分析结果,包括源代码和目标代码。
-
规范描述的范围:
- 单个日志文件中包含多个不同分析工具的运行结果;
- 执行每次运行的分析工具,包括:
- 工具名称
- 工具版本
- 分析工具的调用,包括:
- 命令行
- 开始时间和结束时间
- 被分析的文件,包括:
- Uniform Resource Identifier(URI)
- Multipurpose Internet Mail Extensions(MIME 类型)
- 嵌套文件,例如压缩存档中包含的文件,例如ZIP文件。
- 执行的分析规则
- 有关产生的每个分析结果的信息,包括:
- 结果的位置
- 违反的规则
- 违规的严重性
- 代码中与结果相关的执行路径
- 相对于结果的调用堆栈
- 该问题的可能修补程序
- 分析工具产生的通知,包括:
- 进度消息
- 配置信息
-
不包含在规范范围
- 用于访问、操纵或管理SARIF文件中包含的信息的任何应用程序编程接口(API)的定义或实现;
- 用于查看或以其他方式与SARIF文件中包含的信息进行交互的任何体验的定义或实现。
目前这个版本有200多页。按照由浅入深的方式,介绍将分为基础和进阶两个部分。此篇为基础部分,将完成规范中的基本概念和初步使用的介绍。 后续的进阶部分,将完成深层次用户的需求实现。
4. SARIF基础
SARIF文件是以一种以面向对象(Object Model)的方式构成json文件。 这样的设计便于工具通过对象的方式对应到json的各个组成部分。 SARIF 的json文件格式通过格式文件sarif-2.1.0-rtm.4.json定义, 当前版本是2.1.0。
我们先来看一个ESLint扫描结果的一个SARIF的例子。
-
代码(simple-example.js):
var x = 42
-
ESLint 检查命令:
eslint --format sarif simple-example.js --output-file simple-example.sarif
-
SARIF格式输出的检查结果
{
"version": "2.1.0",
"$schema": "http://json.schemastore.org/sarif-2.1.0-rtm.4",
"runs": [
{
"tool": {
"driver": {
"name": "ESLint",
"informationUri": "https://eslint.org",
"rules": [
{
"id": "no-unused-vars",
"shortDescription": {
"text": "disallow unused variables"
},
"helpUri": "https://eslint.org/docs/rules/no-unused-vars",
"properties": {
"category": "Variables"
}
}
]
}
},
"artifacts": [
{
"location": {
"uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js"
}
}
],
"results": [
{
"level": "error",
"message": {
"text": "'x' is assigned a value but never used."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js",
"index": 0
},
"region": {
"startLine": 1,
"startColumn": 5
}
}
}
],
"ruleId": "no-unused-vars",
"ruleIndex": 0
}
]
}
]
}
直接看这个报告,还是很容易直接读懂的。
4.1. SARIF schema的基本组成
下图为SARIF的schema定义文件的视图:
从SARIF的schema的结构。 我们可以看到:
- 第7行(“type”: “object”,), 定义了一个节点名为类型的对象;
- 第8-45行,定义了这个对象的属性包(“properties”)中有的5个子节点:$schema,version,runs,inlineExternalProperties,properties;
- 第47行, "required"节点,定义了这个对象的属性节点中必须有的节点:version, runs;
- 第16-19行,给出了节点version的定义,其中17行节点description给出了这个节点version的描述;18行告诉了version的取值是个枚举值,并且只有一个可能的取值:2.1.0;
- 第21-29行,给出了节点runs的定义:
- 22行,description给出了这个节点的描述:一组运行结果;
- 23行,给出了这个节点的类型是数组(array),每一个运行结果就是这个数组中的一个结果;
- 24行,数组最小可以为0;也就是说可以没有运行结果;
- 25行,结果的内容可以不唯一;
- 26行,每个数组元素的组成定义;
- 27行,数组元素的定义参考定义组节点definitions下的run的定义;
- 第41-44行,定义了这个对象的扩展属性通过key,value的方式定义,具体的定义参考定义组节点definitions下的propertyBag的定义;
- 第49-3369(到结束)行,通过定义组节点definitions定义了52个基础对象:address, artifact, artifactChange等,包括前面提到的run和propertyBag;
- SARIF就是通过这52个基础对象,以及这些基础对象的组合,并通过属性包的key,value方式完成各种扩展信息的定义, 从而形成了了一个适应各种静态扫描分析工具报告的规范化;
4.2. SARIF对象定义(definitions)的基本规则
- 通过定义组节点definitions定义了52个基础对象, 完成对象的定义的描述;
- 通过基本类型(type)定义每个属性的类型;类型有:object, string,number, integer, boolean, array;对于array类型,后面需要跟items,来说明数组元素的信息;
- 通过enum来枚举属性的值;例如: “enum”: [ “none”, “note”, “warning”, “error” ]
- 通过参考引用的方式完成相似类型的定义描述,避免了反复定义和定义的统一性,归一性;例如:"$ref": “#/definitions/propertyBag”,表明该对象是参考定义组definitions下的propertyBag;
- 通过anyOf,required属性定义对象中必须存在的属性, 默认情况下,属性不是必须的;
- 每一个对象都有一个properties属性,通过属性包的方式中key,value的方式完成对象扩展信息定义的需要;
4.3. 定义举例:消息(message)
- message在definitions中的定义:
"message": {
"description": "Encapsulates a message intended to be read by the end user.",
"type": "object",
"additionalProperties": false,
"properties": {
"text": {
"description": "A plain text message string.",
"type": "string"
},
"markdown": {
"description": "A Markdown message string.",
"type": "string"
},
"id": {
"description": "The identifier for this message.",
"type": "string"
},
"arguments": {
"description": "An array of strings to substitute into the message string.",
"type": "array",
"minItems": 0,
"uniqueItems": false,
"default": [],
"items": {
"type": "string"
}
},
"properties": {
"description": "Key/value pairs that provide additional information about the message.",
"$ref": "#/definitions/propertyBag"
}
},
"anyOf": [
{ "required": [ "text" ] },
{ "required": [ "id" ] }
]
},
-
description:对象的描述,用于封装提供给用户读取的信息;
-
type: 对象;
-
additionalProperties: 没有辅助属性信息;
-
properties:属性集
- text:字符型,纯文本字符串;
- markdown:字符型,markdown格式的字符串;
- id: 字符型,信息的编号;
- arguments:字符串数组,用于替代message中的参数;
- properties:属性集,通过key,value的方式扩充,参考定义组节点definitions下的propertyBag的定义;
-
anyOf: 至少text 或 id 必须;其他属性可选;
-
例子:
{
"message": {
"text": "'x' is assigned a value but never used."
}
}
5. SARIF报告的基本结构
5.1. 工具的定义(runs.tool.driver)
在扫描结果(runs)的定义中,tool节点是必须的,tool中driver是必须的,而driver中也只有name是必须的,也就是说除了工具的名字是必须的,其他的属性都是可选的。这也可以看出来,这个报告也可以做的非常的简单,只强制定义了一些非常必要的信息。
-
运行结果组runs下的数组元素是参考定义组节点definitions下的run的定义;
-
run属性中的必须节点tool的定义是参考定义组节点definitions下的tool的定义;
-
tool属性中的必须节点driver的定义是参考定义组节点definitions下的toolComponent的定义;工具可以通过tool.driver 完成工具的各种基本信息的定义;
-
对与工具的其他扩展组件(例如各种插件),可以通过tool.extensions完成定义,extensions是个数组类型,每个数组元素也是参考定义组节点definitions下的toolComponent的定义;
-
通过toolComponent,可以定义:
- 组件的基本信息:
- 工具的名字(name),全名(fullName),版本(version),交付时间(releaseDateUtc);
- 组件所属组织(organization),产品(product)和产品组(productSuite)信息;
- 下载地址(downloadUri),组件的参考信息地址(informationUri);
- 组件的语言信息:
- 使用语言(language);
- 多语言支持(globalMessageStrings);
- 本地化的信息版本(localizedDataSemanticVersion);
- 关联组件(associatedComponent);
- 组件的扩展信息通过属性包(properties)实现;
- 组件的基本信息:
-
举例:
"tool": {
"driver": {
"name": "ESLint",
"informationUri": "https://eslint.org",
}
},
5.2. 规则的定义(run.tool.driver.rules)
定义组节点definitions下的toolComponent中定义了rules节点。规则(rules)的定义是一个数组类型,每个规则的定义参考定义组节点definitions下的reportingDescriptor的定义,reportingDescriptor定义中:
-
id 是必须的;
-
名字(name),guid(guid),长、短描述(shortDescription,fullDescription);
-
以前版本的相关信息(deprecatedIds,deprecatedGuids,deprecatedNames);
-
规则的信息(messageStrings),通过参考定义组节点definitions下的multiformatMessageString实现多语言支持;
-
规则的默认配置(defaultConfiguration);
-
规则的帮助信息(helpUri,help), help通过参考定义组节点definitions下的multiformatMessageString实现多语言支持;
-
规则的扩展信息通过属性包(properties)实现;
-
举例:
"rules": [
{
"id": "no-unused-vars",
"shortDescription": {
"text": "disallow unused variables"
},
"helpUri": "https://eslint.org/docs/rules/no-unused-vars",
"properties": {
"category": "Variables"
}
}
]
5.3. 扫描内容的定义(run.artifacts)
定义组节点definitions下的run的下面有一个artifacts节点,artifacts是一个数组节点类型,每个数组元素参考定义组节点definitions下的artifact的定义。artifact包括:
- 描述(description);
- 位置信息(location,parentIndex,offset);
- 内容信息(contents,encoding,sourceLanguage,length);
- 改动信息(hashes,lastModifiedTimeUtc);
- 扩展信息(properties);
5.4. 扫描结果的定义(run.results)
定义组节点definitions下的run的下面有一个results节点,results是一个数组节点类型,每个数组元素参考定义组节点definitions下的result的定义。result包括:
-
result节点下面只有message是必选项,以确保结果有信息输出;
-
规则信息:
- ruleId:规则的编号;
- ruleIndex: 规则的索引编号;
- rule: rule的定义参考定义组节点definitions下的reportingDescriptor的定义, 同规则的那部分;
-
缺陷类型:
- kind: {“notApplicable”, “pass”, “fail”, “review”, “open”, “informational”};
- level: {“none”, “note”, “warning”, “error”};
- rank: -1.0 <= rank <= 100.0;
-
缺陷位置:
- analysisTarget:参考定义组节点definitions下的artifact的定义;
- locations:是一个数组节点类型,每个数组元素参考定义组节点definitions下的location的定义。location包括:
- id: 在一个缺陷内location的唯一值;
- physicalLocation: 参考定义组节点definitions下的physicalLocation的定义。 physicalLocation包括:
- address:通过地址域的相对或者绝对偏移量来表示问题位置;
- artifactLocation:文件的方式表示缺陷的位置;
- address 和 artifactLocation 是最少二选一的;
- region: 指定文件的区域;有以下三种方式:
- 通过代码行、列:startLine, startColumn, endLine, endColumn 来指定区域;
- 通过字符偏移量:charOffset,charLength来指定区域;
- 通过字节偏移量:byteOffset, byteLength来指定区域;可以用于二进制位置定位;
- snippet:代码片段, 参考定义组节点definitions下的artifactContent定义;
- 缺陷标识:
- guid: 缺陷的标识GUID;
- correlationGuid:根据缺陷特征生成的GUID;
- occurrenceCount:同样缺陷特征在本次扫描中出现的次数;
- partialFingerprints: 部分缺陷特征;
- fingerprints: 缺陷特征指纹;
- 缺陷追踪信息:
- stacks:缺陷的栈信息一个数组节点类型,每个数组元素参考定义组节点definitions下的stack的定义。statck包括:
- frames:代表按调用顺序排列的,按相反时间顺序排列的一系列调用,这些调用构成了调用堆栈。frames是statck中一个必须的节点,frames一个数组节点类型,每个数组元素参考定义组节点definitions下的stackFrame的定义。stackFrame包括:
- location: 位置信息,参考定义组节点definitions下的location的定义;
- module: 包含此堆栈框架代码的模块的名称;
- threadId:堆栈框架的线程编号;
- parameters:函数调用的参数信息, 是一个数组节点,每个数组元素表示一个参数;
- frames:代表按调用顺序排列的,按相反时间顺序排列的一系列调用,这些调用构成了调用堆栈。frames是statck中一个必须的节点,frames一个数组节点类型,每个数组元素参考定义组节点definitions下的stackFrame的定义。stackFrame包括:
- stacks:缺陷的栈信息一个数组节点类型,每个数组元素参考定义组节点definitions下的stack的定义。statck包括:
-
举例:
"results": [
{
"level": "error",
"message": {
"text": "'x' is assigned a value but never used."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js",
"index": 0
},
"region": {
"startLine": 1,
"startColumn": 5
}
}
}
],
"ruleId": "no-unused-vars",
"ruleIndex": 0
}
]
5.5. 小结
SARIF通过下面的结构,构成了一个扫描工具扫描结果的基本框架。
{
"version": "2.1.0",
"runs": [
{ // 运行结果
"tool": {
// 工具信息
"rules": [
// 规则的信息
]
}
},
"artifacts": [
//扫描文件的信息
],
"results": [ // 扫描结果
{
// 缺陷信息
"locations": [
// 缺陷位置信息
],
// 缺陷规则信息
}
]
}
]
}
6. 总结
- 本篇的前半部分介绍了SARIF的需求的提出,以及成为结构化信息标准促进组织(OASIS)的SARIF技术委员会的一个标准;
- 后半部分介绍了SARIF的基本概念,以及扫描结果的基本框架;
- 下篇将介绍对于复杂的静态分析工具报告需求的实现。
7. 参考
- Industry leaders collaborate to define SARIF interoperability standard for detecting software defects and vulnerabilities
- OASIS Awards 2018 Open Standards Cup to KMIP for Key Management Security and SARIF for Static Analysis Tools
- OASIS Static Analysis Results Interchange Format (SARIF) Technical Committee
- SARIF Specification
- SARIF Tutorials
- Vscode Extension: Sarif Viewer
- SARIF-SDK
- Fortify FPR to SARIF
- GrammaTech SARIF integration for GitHub
- Static Analysis Results: A Format and a Protocol: SARIF & SASP
- 点赞
- 收藏
- 关注作者
评论(0)