跳到主要内容

功能发布:Yaklang SCA 标准库

· 阅读需 6 分钟
Yak Project
网络安全垂直语言团队

什么是SCA

软件成分分析(SCA,Software Composition Analysis)是一种对二进的安全漏洞或者潜在的许可证授权问题,把这些风险排查在应用系统投产之前,也适用于应用系统运行中的诊断分析。在开源软件日益盛行的今天,开源安全威胁成为企业组织无法回避的话题,与此同时,应用交付规模和频率的正在快速增长制软件的组成部分进行识别、分析和追踪的技术。专门用于分析开发人员使用的各种源码、模块、框架和库,以识别和清点开源软件(OSS)的组件及其构成和依赖关系,并识别已知,软件成分分析对于安全合规风险管控和安全态势感知都是必不可少的能力。

镜像结构

01

镜像文件系统

镜像里面是一层层的文件系统,叫做 Union FS(联合文件系统),联合文件系统,可以将几层目录挂载(叠)到一起,形成一个虚拟文件系统,每一层文件系统叫做一层 layer,layer都是只读的,构建镜像的时候,从一个最基本的操作系统开始,每个构建提交的操作都相当于做一层的修改,增加了一层文件系统,一层层往上叠加,上层的修改会覆盖底层该位置的可见性。

02

镜像包结构

镜像可以通过docker save命令存储为一个tar包,其中的文件结构如下(以Ubuntu:latest)为例:

mainfest.json

这个文件存储了镜像结构和基础信息的描述文件,可以看到里面主要存储了Layers的哈希值和镜像的tags。

${hash}.json

这个文件存储了启动容器的配置,如启动命令,镜像ID,历史记录等。

${layer hash}/json

在镜像规范的v1版本中定义的描述该层(Layer)信息的元数据,但在 v1.2 版本中不需要依赖此文件。

${layer hash}/layer.tar

存储该层(Layer)文件系统的变更记录的归档包。

${layer hash}/VERSION

指定json文件内容的格式规范。

SCA本质

我觉得可以类比各类的AST(Application Security Test,应用安全测试)。如果说在运行中的容器中执行诸如apt list, dpkg-query -l的命令是DAST(动态应用程序安全测试),那么SCA实际上是SAST(Static Application Security Testing,静态应用程序安全测试)。SCA落到最本质的技术实际上就是分析文件,从文件中解析出容器/镜像中存在的软件成分。

下面举几个简单的例子:

1、apt/dpkg

以debian系的apt/dpkg为例,实际上通过这些命令安装的包信息会存储在/var/lib/dpkg/status/var/lib/dpkg/status.d/*中,一个简单的例子:

Package: curlStatus: install ok installedPriority: optionalSection: webInstalled-Size: 443Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>Architecture: amd64Multi-Arch: foreignVersion: 7.81.0-1ubuntu1.10Depends: libc6 (>= 2.34), libcurl4 (= 7.81.0-1ubuntu1.10), zlib1g (>= 1:1.1.4)Description: command line tool for transferring data with URL syntax curl is a command line tool for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP. . curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, kerberos...), file transfer resume, proxy tunneling and a busload of other useful tricks.Homepage: https://curl.haxx.seOriginal-Maintainer: Alessandro Ghedini <ghedo@debian.org>

可以看到这个文件存储可读的文本,其中包含了通过dpkg命令安装的包信息:包名,版本,依赖,描述等。

2、apk

通过alpine系的apk命令安装的包基本相同,其包信息会存储在/lib/apk/db/installed中,一个简单的例子:

C:Q1i+1SpSpyplqn9ztFds6RP7eIv8U=P:curlV:8.1.2-r0A:x86_64S:139967I:241664T:URL retrival utility and libraryU:https://curl.se/L:curlo:curlm:Natanael Copa <ncopa@alpinelinux.org>t:1685465048c:e8990bc463779592e3fb62eae7aacc48ed8abb3fD:libcurl=8.1.2-r0 so:libc.musl-x86_64.so.1 so:libcurl.so.4 so:libz.so.1p:cmd:curl=8.1.2-r0F:usrF:usr/binR:curla:0:0:755Z:Q1L2dyojkjpAQ7JazurYSkGNQZFTk=

这里面同样包含了安装的包信息:包名,版本,依赖,描述等。

SCA流程

初步成果

(想了解详细代码实现的读者请查看:https://github.com/yaklang/yaklang/pull/149

为yak提供了一个新的标准库:sca,这个标准库主要提供了以下功能:

// 主要函数ScanDockerImageFromContext(imageID string, opts ...dockerContextOption) (pkgs []*Package, err error) // 根据镜像id扫描镜像ScanDockerContainerFromContext(containerID string, opts ...dockerContextOption) (pkgs []*Package, err error) // 根据容器id扫描容器ScanDockerImageFromFile(path string, opts ...dockerContextOption) ([]*Package, error) // 根据文件路径扫描镜像,路径是docker save命令导出的tar文件路径// 选项/OptionendPoint(endpoint string) dockerContextOption // 设置docker的endpointscanMode(mode ScanMode) dockerContextOption // 设置扫描模式,默认使用所有分析器进行扫描concurrent(n int) dockerContextOption // 设置并发数量analayzers(a ...TypAnalyzer) dockerContextOption // 设置使用的分析器,默认使用所有的分析器进行扫描// 扫描模式ALL_MODE // 所有分析器PKG_MODE // 所有包管理器的分析器LANGUAGE_MODE // 所有语言的分析器// 所有分析器DPKG_ALALYZERRPM_ALALYZERAPK_ALALYZERRUBY_BUNDLER_ANALYZERRUST_CARGO_ANALYZERRUBY_GEMSPEC_ANALYZERPYTHON_POETRY_ANALYZERPYTHON_PIPENV_ANALYZERPYTHON_PIP_ANALYZERPYTHON_PACKAGING_ANALYZERPHP_COMPOSER_ANALYZERNODE_YARN_ANALYZERNODE_PNPM_ANALYZERNODE_NPM_ANALYZERJAVA_POM_ANALYZERJAVA_GRADLE_ANALYZERJAVA_JAR_ANALYZERGO_MOD_ANALYZERGO_BINARY_ANALYZERCLANG_CONAN_ANALYZER

扫描结束后返回了一个结构体指针数组,其结构如下所示:

type Package struct { Name           string Version        string IsVersionRange bool // Version is a version range FromFile     []string FromAnalyzer []string Verification string License []string UpStreamPackages   map[string]*Package DownStreamPackages map[string]*Package    // 这里存储临时数据,不准确,不要访问这里这个结构体成员 DependsOn PackageRelationShip Potential bool}

简单用法

扫描镜像

pkgs = sca.ScanImageFromContext("1318b700e415")~ // ubuntu:latest for pkg in pkgs {    printf("name: %s version: %s\n", pkg.Name, pkg.Version)}

扫描容器

pkgs = sca.ScanImageFromContext("fc6a11ef0e1c")~ // 容器idfor pkg in pkgs {    printf("name: %s version: %s\n", pkg.Name, pkg.Version)}

与cve标准库进行联动

pkgs = sca.ScanImageFromContext("1318b700e415")~ // ubuntu:latest cveOptions = make([]var, len(pkgs))for i, pkg = range pkgs {    cveOptions[i] = cve.product(pkg.Name, pkg.Version)}cveCh = cve.QueryEx(cveOptions...)for result in cveCh {    printf("%v\n", result.CVE)}/*CVE-2019-9923CVE-2022-0563CVE-2022-1271CVE-2022-1664CVE-2022-48303CVE-2009-2360CVE-2021-20193CVE-2021-32803CVE-2021-32804CVE-2021-37600CVE-2021-37701CVE-2021-37712CVE-2021-37713CVE-2021-3995CVE-2021-3996CVE-2020-3810CVE-2016-2781CVE-2018-1121*/

使用DOT画出依赖供应链

(只用于临时展示)

image.png

后续

后续我们会在yakit中提供一个新的页面,方便用户操作,用户提供一个镜像ID/容器ID/本地文件,yakit对其进行扫描后输出依赖供应链和可能存在的CVE。


本文首发于 Yak Project 公众号,阅读原文