当前位置:网站首页>trivy如何从非关系型数据库查询数据
trivy如何从非关系型数据库查询数据
2022-07-29 13:15:00 【huzai9527】
一、trivyDB的存储内容
一张图概括

从已有的db中查看
- 这里我写了个demo,主要分析
vulnerability bucket以及ubuntu 20.04 bucket, 其中ubuntu 20.04 bucket是一个嵌套 bucket 里面嵌套了很多pkgName bucket
package main
import (
"fmt"
"time"
bolt "go.etcd.io/bbolt"
)
func main() {
db, err := bolt.Open("trivy.db", 0600, &bolt.Options{
Timeout: 1 * time.Second})
if err != nil {
panic("init trivydb failed")
}
db.View(func(tx *bolt.Tx) error {
dataSource := tx.Bucket([]byte("vulnerability"))
dataSource.ForEach(func(k, v []byte) error {
if string(k) == "CVE-2022-24809" {
fmt.Println(string(k))
fmt.Println(string(v))
}
return nil
})
return nil
})
db.View(func(tx *bolt.Tx) error {
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
fmt.Println(string(name))
if string(name) == "ubuntu 20.04" {
fmt.Println(string(name))
b.ForEach(func(k, v []byte) error {
// fmt.Println(string(k))
if string(k) == "curl" {
fmt.Println(string(k))
nestedbucket := b.Bucket([]byte("curl"))
nestedbucket.ForEach(func(k, v []byte) error {
fmt.Println(string(k))
fmt.Println(string(v))
return nil
})
}
return nil
})
}
return nil
})
return nil
})
}
- 阅读并运行该脚本,我们可以发现
vulnerablity bucket里面存储的KV键值对是{cve 编号 :cve 详细信息} - 此外还有很多
os-release bucket里面嵌套了pkgName bucket - 具体的
pkgName bucket中存储的KV键值对是{cve 编号:pkg修复信息等}


阅读trivy-db源码
- 源码中定义的相关的数据结构
// VulnerabilityDetail 漏洞的详细信息
type VulnerabilityDetail struct {
ID string `json:",omitempty"` // e.g. CVE-2019-8331, OSVDB-104365
CvssScore float64 `json:",omitempty"`
CvssVector string `json:",omitempty"`
CvssScoreV3 float64 `json:",omitempty"`
CvssVectorV3 string `json:",omitempty"`
Severity Severity `json:",omitempty"`
SeverityV3 Severity `json:",omitempty"`
CweIDs []string `json:",omitempty"` // e.g. CWE-78, CWE-89
References []string `json:",omitempty"`
Title string `json:",omitempty"`
Description string `json:",omitempty"`
PublishedDate *time.Time `json:",omitempty"` // Take from NVD
LastModifiedDate *time.Time `json:",omitempty"` // Take from NVD
}
// AdvisoryDetail 对应os-release 下 pkg 可能存在的漏洞
type AdvisoryDetail struct {
PlatformName string
PackageName string
AdvisoryItem interface{
}
}
// SourceID represents data source such as NVD.
type SourceID string
// DataSource 漏洞信息来源
type DataSource struct {
ID SourceID `json:",omitempty"`
Name string `json:",omitempty"`
URL string `json:",omitempty"`
}
// Advisory 有关漏洞的建议,实际上是初筛报告
// 后面会根据的版本进行筛选
type Advisory struct {
VulnerabilityID string `json:",omitempty"` // CVE-ID or vendor ID
VendorIDs []string `json:",omitempty"` // e.g. RHSA-ID and DSA-ID
// Rpm packages have advisories for different architectures with same package name
// This field is required to separate these packages.
Arches []string `json:"-"`
// It is filled only when FixedVersion is empty since it is obvious the state is "Fixed" when FixedVersion is not empty.
// e.g. Will not fix and Affected
State string `json:",omitempty"`
// Trivy DB has "vulnerability" bucket and severities are usually stored in the bucket per a vulnerability ID.
// In some cases, the advisory may have multiple severities depending on the packages.
// For example, CVE-2015-2328 in Debian has "unimportant" for mongodb and "low" for pcre3.
// e.g. https://security-tracker.debian.org/tracker/CVE-2015-2328
Severity Severity `json:",omitempty"`
// Versions for os package
FixedVersion string `json:",omitempty"`
AffectedVersion string `json:",omitempty"` // Only for Arch Linux
// MajorVersion ranges for language-specific package
// Some advisories provide VulnerableVersions only, others provide PatchedVersions and UnaffectedVersions
VulnerableVersions []string `json:",omitempty"`
PatchedVersions []string `json:",omitempty"`
UnaffectedVersions []string `json:",omitempty"`
// DataSource holds where the advisory comes from
DataSource *DataSource `json:",omitempty"`
// Custom is basically for extensibility and is not supposed to be used in OSS
Custom interface{
} `json:",omitempty"`
}
type Vulnerability struct {
Title string `json:",omitempty"`
Description string `json:",omitempty"`
Severity string `json:",omitempty"` // Selected from VendorSeverity, depending on a scan target
CweIDs []string `json:",omitempty"` // e.g. CWE-78, CWE-89
VendorSeverity VendorSeverity `json:",omitempty"`
CVSS VendorCVSS `json:",omitempty"`
References []string `json:",omitempty"`
PublishedDate *time.Time `json:",omitempty"` // Take from NVD
LastModifiedDate *time.Time `json:",omitempty"` // Take from NVD
// Custom is basically for extensibility and is not supposed to be used in OSS
Custom interface{
} `json:",omitempty"`
}
// Ecosystem represents language-specific ecosystem
type Ecosystem string
从查询的函数中推测
// 从这个方法能够推出的存储结构
// 有很多类似 ubuntu 20.04 的 bucket 里面嵌套了很多叫 pkg 的 bucket 存储的是 cveId:value
// 有一个 data-source 的 bucket 存储的是 {os-release : dataSource}
func (dbc Config) forEach(bktNames []string) (map[string]Value, error) {
if len(bktNames) < 2 {
return nil, xerrors.Errorf("bucket must be nested: %v", bktNames)
}
// just like {"ubuntu 20.04","curl"}
// rootBucket = "ubuntu 20.04"
// nestedBuckets = "curl"
rootBucket, nestedBuckets := bktNames[0], bktNames[1:]
values := map[string]Value{
}
err := db.View(func(tx *bolt.Tx) error {
var rootBuckets []string
if strings.Contains(rootBucket, "::") {
// e.g. "pip::", "rubygems::"
prefix := []byte(rootBucket)
c := tx.Cursor()
for k, _ := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, _ = c.Next() {
rootBuckets = append(rootBuckets, string(k))
}
} else {
// e.g. "GitHub Security Advisory Composer"
rootBuckets = append(rootBuckets, rootBucket)
}
for _, r := range rootBuckets {
// 获取 "ubuntu 20.04" 对应的 bucket
root := tx.Bucket([]byte(r))
if root == nil {
continue
}
// 获取关于 "ubuntu 20.04 的 source,
// "data-source"
// 存放的 key 是 os + release
// 对应的 value 是 source{id, name, url}
source, err := dbc.getDataSource(tx, r)
if err != nil {
log.Logger.Debugf("Data source error: %s", err)
}
bkt := root
for _, nestedBkt := range nestedBuckets {
// 这里是嵌套的 bucket
// bucket ubuntu 20.04 下面还有许多关于pkg的bucket
bkt = bkt.Bucket([]byte(nestedBkt))
if bkt == nil {
break
}
}
if bkt == nil {
continue
}
// 将获取到的 pkg 的 source 和 content 打包成 value
// 放进 values map 中, values 的 key 对应 cveId
err = bkt.ForEach(func(k, v []byte) error {
values[string(k)] = Value{
Source: source,
Content: v,
}
return nil
})
if err != nil {
return xerrors.Errorf("db foreach error: %w", err)
}
}
return nil
})
if err != nil {
return nil, xerrors.Errorf("failed to get all key/value in the specified bucket: %w", err)
}
return values, nil
}
二、查询逻辑
实际上查询到最后就是上面的函数
首先查询的时候调用的是 Detect 函数
- 首先从db中获取(os-release,pkgName)对应的所有 CVE
- 然后再从所有相关的CVE中,通过版本筛选最终的CVE
// Detect scans and returns the vulnerabilities // 不同的 OS 对应不同的 Detect 方法,这个我在 trivy 源码阅读中说明过 func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) { log.Logger.Info("Detecting Ubuntu vulnerabilities...") log.Logger.Debugf("ubuntu: os version: %s", osVer) log.Logger.Debugf("ubuntu: the number of packages: %d", len(pkgs)) var vulns []types.DetectedVulnerability for _, pkg := range pkgs { // 获取 os-release 以及对应 pkgName 的所有CVE advisories, err := s.vs.Get(osVer, pkg.SrcName) if err != nil { return nil, xerrors.Errorf("failed to get Ubuntu advisories: %w", err) } installed := utils.FormatSrcVersion(pkg) installedVersion, err := version.NewVersion(installed) if err != nil { log.Logger.Debugf("failed to parse Ubuntu installed package version: %w", err) continue } // 从所有的相关的CVE中,找出版本相关的 CVE for _, adv := range advisories { vuln := types.DetectedVulnerability{ VulnerabilityID: adv.VulnerabilityID, PkgName: pkg.Name, InstalledVersion: installed, FixedVersion: adv.FixedVersion, Ref: pkg.Ref, Layer: pkg.Layer, Custom: adv.Custom, DataSource: adv.DataSource, } if adv.FixedVersion == "" { vulns = append(vulns, vuln) continue } fixedVersion, err := version.NewVersion(adv.FixedVersion) if err != nil { log.Logger.Debugf("failed to parse Ubuntu package version: %w", err) continue } if installedVersion.LessThan(fixedVersion) { vulns = append(vulns, vuln) } } } return vulns, nil }版本判断很好懂,直接看函数就行,我们主要看一下如何获取所有CVE的 Get 方法
- Get 的调用链如下
Get -> GetAdvisories -> ForEachAdvisory -> forEach - 这实际上就是上面提到的从函数中推测
forEach,我概括一下主要步骤 - 首先获取 os-release 对应的 bucket, 这里面有一个嵌套的 pkgName 的的 bucket
- 通过pkgName 获取对应饿的 pkg bucket,里面就有 CVE 以及对应的 pkg 版本信息
- 然后通过 当前pkg 的版本和 CVE中的版本进行比对,就可以得到最终的CVE
- Get 的调用链如下
边栏推荐
- 少儿编程 电子学会图形化编程等级考试Scratch二级真题解析(选择题)2022年6月
- 栈题目:标签验证器
- Dataset:FIFA 2018 Statistics数据集(Predict FIFA 2018 Man of the Match预测2018年国际足联最佳球员)的简介、下载、使用方法之详细攻略
- 开关电源-半桥LLC控制
- 万字长文,揭秘华为数据治理体系!
- 传奇人形怪爆率怎么设置?人形怪增加教程
- JS_ deleting the invalid data in the array undefined '0' null false NaN
- leetcode134. 加油站
- frp-免费内网穿透
- 推荐几款2022年好用的设备管理系统(软件)
猜你喜欢
随机推荐
[Numpy] 创建数组
How Navicat Connects to MySQL
开放式耳机推荐哪款最好最实用、最好的开放式耳机推荐
Nacos分级存储模型-集群配置与NacosRule负载均衡
开关电源-PWM外设简介及MCC配置
栈题目:标签验证器
程序员入门的第一个程序,打印输出 “ HelloWorld “
Nacos hierarchical storage model - the cluster configuration and NacosRule load balance
hash table 实现代码
Leetcode65. 有效数字
用支持LaTex的Markdown语句编辑一个数学公式
大一(下)暑假作业
一口气说出4种主流数据库ID自增长,面试官懵了
浅谈防勒索病毒方案之主机加固
即时通讯场景下安全合规的实践和经验
mariadbackup物理备份使用——筑梦之路
Linux下 mysql5.7的彻底卸载
Research on the thinking and application methods of the frontier of ESI research
第二轮Okaleido Tiger热卖的背后,是背后生态机构战略支持
Scala 简介一









