refactor(cyclonedx): add intermediate representation (#4490)

This commit is contained in:
Teppei Fukuda
2023-06-01 05:50:47 +03:00
committed by GitHub
parent c15f269a99
commit 48b2e15c23
17 changed files with 1256 additions and 2202 deletions

View File

@@ -12,9 +12,6 @@ trivy sbom [flags] SBOM_PATH
# Scan CycloneDX and show the result in tables # Scan CycloneDX and show the result in tables
$ trivy sbom /path/to/report.cdx $ trivy sbom /path/to/report.cdx
# Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx
# Scan CycloneDX-type attestation and show the result in tables # Scan CycloneDX-type attestation and show the result in tables
$ trivy sbom /path/to/report.cdx.intoto.jsonl $ trivy sbom /path/to/report.cdx.intoto.jsonl

View File

@@ -28,10 +28,6 @@ Trivy supports CycloneDX as an input.
$ trivy sbom /path/to/cyclonedx.json $ trivy sbom /path/to/cyclonedx.json
``` ```
!!! note
If you want to generate a CycloneDX report from a CycloneDX input, please be aware that the output stores references to your original CycloneDX report and contains only detected vulnerabilities, not components.
The report is called [BOV](https://cyclonedx.org/capabilities/sbom/).
## SPDX ## SPDX
Trivy supports the SPDX SBOM as an input. Trivy supports the SPDX SBOM as an input.

View File

@@ -418,7 +418,7 @@ func TestClientServerWithCycloneDX(t *testing.T) {
Input: "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", Input: "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz",
}, },
wantComponentsCount: 161, wantComponentsCount: 161,
wantDependenciesCount: 80, wantDependenciesCount: 162,
}, },
} }

View File

@@ -30,16 +30,30 @@ func TestSBOM(t *testing.T) {
name: "centos7 cyclonedx", name: "centos7 cyclonedx",
args: args{ args: args{
input: "testdata/fixtures/sbom/centos-7-cyclonedx.json", input: "testdata/fixtures/sbom/centos-7-cyclonedx.json",
format: "cyclonedx", format: "json",
artifactType: "cyclonedx", artifactType: "cyclonedx",
}, },
golden: "testdata/centos-7-cyclonedx.json.golden", golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.json",
ArtifactType: ftypes.ArtifactType("cyclonedx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{PkgRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
},
},
},
},
}, },
{ {
name: "fluentd-multiple-lockfiles cyclonedx", name: "fluentd-multiple-lockfiles cyclonedx",
args: args{ args: args{
input: "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json", input: "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json",
format: "cyclonedx", format: "json",
artifactType: "cyclonedx", artifactType: "cyclonedx",
}, },
golden: "testdata/fluentd-multiple-lockfiles-cyclonedx.json.golden", golden: "testdata/fluentd-multiple-lockfiles-cyclonedx.json.golden",
@@ -48,10 +62,24 @@ func TestSBOM(t *testing.T) {
name: "centos7 in in-toto attestation", name: "centos7 in in-toto attestation",
args: args{ args: args{
input: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl", input: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl",
format: "cyclonedx", format: "json",
artifactType: "cyclonedx", artifactType: "cyclonedx",
}, },
golden: "testdata/centos-7-cyclonedx.json.golden", golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl",
ArtifactType: ftypes.ArtifactType("cyclonedx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{PkgRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
},
},
},
},
}, },
{ {
name: "centos7 spdx tag-value", name: "centos7 spdx tag-value",
@@ -131,8 +159,6 @@ func TestSBOM(t *testing.T) {
// Compare want and got // Compare want and got
switch tt.args.format { switch tt.args.format {
case "cyclonedx":
compareCycloneDX(t, tt.golden, outputFile)
case "json": case "json":
compareSBOMReports(t, tt.golden, outputFile, tt.override) compareSBOMReports(t, tt.golden, outputFile, tt.override)
default: default:
@@ -146,8 +172,12 @@ func TestSBOM(t *testing.T) {
func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant types.Report) { func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant types.Report) {
want := readReport(t, wantFile) want := readReport(t, wantFile)
if overrideWant.ArtifactName != "" {
want.ArtifactName = overrideWant.ArtifactName want.ArtifactName = overrideWant.ArtifactName
}
if overrideWant.ArtifactType != "" {
want.ArtifactType = overrideWant.ArtifactType want.ArtifactType = overrideWant.ArtifactType
}
want.Metadata.ImageID = "" want.Metadata.ImageID = ""
want.Metadata.ImageConfig = v1.ConfigFile{} want.Metadata.ImageConfig = v1.ConfigFile{}
want.Metadata.DiffIDs = nil want.Metadata.DiffIDs = nil

View File

@@ -1,528 +0,0 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2023-05-19T10:38:43+00:00",
"tools": [
{
"vendor": "aquasecurity",
"name": "trivy",
"version": "dev"
}
],
"component": {
"bom-ref": "urn:uuid:1455c02d-64ca-453e-a5df-ddfb70a7c804/1",
"type": "container",
"name": "integration/testdata/fixtures/images/centos-7.tar.gz"
}
},
"vulnerabilities": [
{
"id": "CVE-2019-18276",
"ratings": [
{
"source": {
"name": "cbl-mariner"
},
"severity": "high"
},
{
"source": {
"name": "nvd"
},
"score": 7.2,
"severity": "high",
"method": "CVSSv2",
"vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"
},
{
"source": {
"name": "nvd"
},
"score": 7.8,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "oracle-oval"
},
"severity": "low"
},
{
"source": {
"name": "photon"
},
"severity": "high"
},
{
"source": {
"name": "redhat"
},
"score": 7.8,
"severity": "low",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "ubuntu"
},
"severity": "low"
}
],
"cwes": [
273
],
"description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.",
"advisories": [
{
"url": "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html"
},
{
"url": "https://access.redhat.com/security/cve/CVE-2019-18276"
},
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276"
},
{
"url": "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff"
},
{
"url": "https://linux.oracle.com/cve/CVE-2019-18276.html"
},
{
"url": "https://linux.oracle.com/errata/ELSA-2021-1679.html"
},
{
"url": "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E"
},
{
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-18276"
},
{
"url": "https://security.gentoo.org/glsa/202105-34"
},
{
"url": "https://security.netapp.com/advisory/ntap-20200430-0003/"
},
{
"url": "https://www.youtube.com/watch?v=-wGtxJ8opa8"
}
],
"published": "2019-11-28T01:15:00+00:00",
"updated": "2021-05-26T12:15:00+00:00",
"affects": [
{
"ref": "urn:cdx:1455c02d-64ca-453e-a5df-ddfb70a7c804/1#pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64\u0026distro=centos-7.6.1810",
"versions": [
{
"version": "4.2.46-31.el7",
"status": "affected"
}
]
}
]
},
{
"id": "CVE-2019-1559",
"ratings": [
{
"source": {
"name": "amazon"
},
"severity": "medium"
},
{
"source": {
"name": "arch-linux"
},
"severity": "medium"
},
{
"source": {
"name": "nvd"
},
"score": 4.3,
"severity": "medium",
"method": "CVSSv2",
"vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N"
},
{
"source": {
"name": "nvd"
},
"score": 5.9,
"severity": "medium",
"method": "CVSSv3",
"vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
},
{
"source": {
"name": "oracle-oval"
},
"severity": "medium"
},
{
"source": {
"name": "redhat"
},
"score": 5.9,
"severity": "medium",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
},
{
"source": {
"name": "ubuntu"
},
"severity": "medium"
}
],
"cwes": [
203
],
"description": "If an application encounters a fatal protocol error and then calls SSL_shutdown() twice (once to send a close_notify, and once to receive one) then OpenSSL can respond differently to the calling application if a 0 byte record is received with invalid padding compared to if a 0 byte record is received with an invalid MAC. If the application then behaves differently based on that in a way that is detectable to the remote peer, then this amounts to a padding oracle that could be used to decrypt data. In order for this to be exploitable \"non-stitched\" ciphersuites must be in use. Stitched ciphersuites are optimised implementations of certain commonly used ciphersuites. Also the application must call SSL_shutdown() twice even if a protocol error has occurred (applications should not do this but some do anyway). Fixed in OpenSSL 1.0.2r (Affected 1.0.2-1.0.2q).",
"recommendation": "Upgrade openssl-libs to version 1:1.0.2k-19.el7",
"advisories": [
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-03/msg00041.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00019.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00046.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-04/msg00047.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-05/msg00049.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00080.html"
},
{
"url": "http://www.securityfocus.com/bid/107174"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:2304"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:2437"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:2439"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:2471"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3929"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3931"
},
{
"url": "https://access.redhat.com/security/cve/CVE-2019-1559"
},
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1559"
},
{
"url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=e9bbefbf0f24c57645e7ad6a5a71ae649d18ac8e"
},
{
"url": "https://github.com/RUB-NDS/TLS-Padding-Oracles"
},
{
"url": "https://kc.mcafee.com/corporate/index?page=content\u0026id=SB10282"
},
{
"url": "https://linux.oracle.com/cve/CVE-2019-1559.html"
},
{
"url": "https://linux.oracle.com/errata/ELSA-2019-2471.html"
},
{
"url": "https://lists.debian.org/debian-lts-announce/2019/03/msg00003.html"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/"
},
{
"url": "https://security.gentoo.org/glsa/201903-10"
},
{
"url": "https://security.netapp.com/advisory/ntap-20190301-0001/"
},
{
"url": "https://security.netapp.com/advisory/ntap-20190301-0002/"
},
{
"url": "https://security.netapp.com/advisory/ntap-20190423-0002/"
},
{
"url": "https://support.f5.com/csp/article/K18549143"
},
{
"url": "https://support.f5.com/csp/article/K18549143?utm_source=f5support\u0026amp;utm_medium=RSS"
},
{
"url": "https://ubuntu.com/security/notices/USN-3899-1"
},
{
"url": "https://ubuntu.com/security/notices/USN-4376-2"
},
{
"url": "https://usn.ubuntu.com/3899-1/"
},
{
"url": "https://usn.ubuntu.com/4376-2/"
},
{
"url": "https://www.debian.org/security/2019/dsa-4400"
},
{
"url": "https://www.openssl.org/news/secadv/20190226.txt"
},
{
"url": "https://www.oracle.com/security-alerts/cpujan2020.html"
},
{
"url": "https://www.oracle.com/security-alerts/cpujan2021.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html"
},
{
"url": "https://www.tenable.com/security/tns-2019-02"
},
{
"url": "https://www.tenable.com/security/tns-2019-03"
}
],
"published": "2019-02-27T23:29:00+00:00",
"updated": "2021-01-20T15:15:00+00:00",
"affects": [
{
"ref": "urn:cdx:1455c02d-64ca-453e-a5df-ddfb70a7c804/1#pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026epoch=1\u0026distro=centos-7.6.1810",
"versions": [
{
"version": "1:1.0.2k-16.el7",
"status": "affected"
}
]
}
]
},
{
"id": "CVE-2018-0734",
"ratings": [
{
"source": {
"name": "amazon"
},
"severity": "medium"
},
{
"source": {
"name": "arch-linux"
},
"severity": "low"
},
{
"source": {
"name": "cbl-mariner"
},
"severity": "medium"
},
{
"source": {
"name": "nvd"
},
"score": 4.3,
"severity": "medium",
"method": "CVSSv2",
"vector": "AV:N/AC:M/Au:N/C:P/I:N/A:N"
},
{
"source": {
"name": "nvd"
},
"score": 5.9,
"severity": "medium",
"method": "CVSSv3",
"vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
},
{
"source": {
"name": "oracle-oval"
},
"severity": "low"
},
{
"source": {
"name": "photon"
},
"severity": "medium"
},
{
"source": {
"name": "redhat"
},
"score": 5.1,
"severity": "low",
"method": "CVSSv3",
"vector": "CVSS:3.0/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
},
{
"source": {
"name": "ubuntu"
},
"severity": "low"
}
],
"cwes": [
327
],
"description": "The OpenSSL DSA signature algorithm has been shown to be vulnerable to a timing side channel attack. An attacker could use variations in the signing algorithm to recover the private key. Fixed in OpenSSL 1.1.1a (Affected 1.1.1). Fixed in OpenSSL 1.1.0j (Affected 1.1.0-1.1.0i). Fixed in OpenSSL 1.0.2q (Affected 1.0.2-1.0.2p).",
"recommendation": "Upgrade openssl-libs to version 1:1.0.2k-19.el7",
"advisories": [
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-06/msg00030.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-07/msg00056.html"
},
{
"url": "http://www.securityfocus.com/bid/105758"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:2304"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3700"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3932"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3933"
},
{
"url": "https://access.redhat.com/errata/RHSA-2019:3935"
},
{
"url": "https://access.redhat.com/security/cve/CVE-2018-0734"
},
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0734"
},
{
"url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=43e6a58d4991a451daf4891ff05a48735df871ac"
},
{
"url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=8abfe72e8c1de1b95f50aa0d9134803b4d00070f"
},
{
"url": "https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff;h=ef11e19d1365eea2b1851e6f540a0bf365d303e7"
},
{
"url": "https://linux.oracle.com/cve/CVE-2018-0734.html"
},
{
"url": "https://linux.oracle.com/errata/ELSA-2019-3700.html"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/EWC42UXL5GHTU5G77VKBF6JYUUNGSHOM/"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/Y3IVFGSERAZLNJCK35TEM2R4726XIH3Z/"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZBEV5QGDRFUZDMNECFXUSN5FMYOZDE4V/"
},
{
"url": "https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/"
},
{
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-0734"
},
{
"url": "https://security.netapp.com/advisory/ntap-20181105-0002/"
},
{
"url": "https://security.netapp.com/advisory/ntap-20190118-0002/"
},
{
"url": "https://security.netapp.com/advisory/ntap-20190423-0002/"
},
{
"url": "https://ubuntu.com/security/notices/USN-3840-1"
},
{
"url": "https://usn.ubuntu.com/3840-1/"
},
{
"url": "https://www.debian.org/security/2018/dsa-4348"
},
{
"url": "https://www.debian.org/security/2018/dsa-4355"
},
{
"url": "https://www.openssl.org/news/secadv/20181030.txt"
},
{
"url": "https://www.oracle.com/security-alerts/cpuapr2020.html"
},
{
"url": "https://www.oracle.com/security-alerts/cpujan2020.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpujan2019-5072801.html"
},
{
"url": "https://www.oracle.com/technetwork/security-advisory/cpujul2019-5072835.html"
},
{
"url": "https://www.tenable.com/security/tns-2018-16"
},
{
"url": "https://www.tenable.com/security/tns-2018-17"
}
],
"published": "2018-10-30T12:29:00+00:00",
"updated": "2020-08-24T17:37:00+00:00",
"affects": [
{
"ref": "urn:cdx:1455c02d-64ca-453e-a5df-ddfb70a7c804/1#pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64\u0026epoch=1\u0026distro=centos-7.6.1810",
"versions": [
{
"version": "1:1.0.2k-16.el7",
"status": "affected"
}
]
}
]
}
]
}

View File

@@ -77,6 +77,12 @@
"pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json", "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json",
"pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json" "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json"
] ]
},
{
"ref": "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json"
},
{
"ref": "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json"
} }
], ],
"vulnerabilities": [] "vulnerabilities": []

View File

@@ -1 +1 @@
{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvYm9tIiwic3ViamVjdCI6W3sibmFtZSI6ImluZGV4LmRvY2tlci5pby9saWJyYXJ5L2NlbnRvcyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJiZTY1ZjQ4OGI3NzY0YWQzNjM4ZjIzNmI3YjUxNWIzNjc4MzY5YTUxMjRjNDdiOGQzMjkxNmQ2NDg3NDE4ZWE0In19XSwicHJlZGljYXRlIjp7ImJvbUZvcm1hdCI6IkN5Y2xvbmVEWCIsImNvbXBvbmVudHMiOlt7ImJvbS1yZWYiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjRcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwibGljZW5zZXMiOlt7ImV4cHJlc3Npb24iOiJHUEx2MysifV0sIm5hbWUiOiJiYXNoIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJiYXNoIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjQuMi40NiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIzMS5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL2Jhc2hANC4yLjQ2LTMxLmVsNz9hcmNoPXg4Nl82NFx1MDAyNmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJ0eXBlIjoibGlicmFyeSIsInZlcnNpb24iOiI0LjIuNDYtMzEuZWw3In0seyJib20tcmVmIjoicGtnOnJwbS9jZW50b3Mvb3BlbnNzbC1saWJzQDEuMC4yay0xNi5lbDc/YXJjaD14ODZfNjRcdTAwMjZlcG9jaD0xXHUwMDI2ZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiT3BlblNTTCJ9XSwibmFtZSI6Im9wZW5zc2wtbGlicyIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjTmFtZSIsInZhbHVlIjoib3BlbnNzbCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNWZXJzaW9uIiwidmFsdWUiOiIxLjAuMmsifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjUmVsZWFzZSIsInZhbHVlIjoiMTYuZWw3In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY0Vwb2NoIiwidmFsdWUiOiIxIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlnZXN0IiwidmFsdWUiOiJzaGEyNTY6YWM5MjA4MjA3YWRhYWMzYTQ4ZTU0YTRkYzZiNDljNjllNzhjMzA3MmQyYjNhZGQ3ZWZkYWJmODE0ZGIyMTMzYiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZmZJRCIsInZhbHVlIjoic2hhMjU2Ojg5MTY5ZDg3ZGJlMmI3MmJhNDJiZmJiMzU3OWM5NTczMjJiYWNhMjhlMDNhMWU1NTgwNzY1NDJhMWMxYjJiNGEifV0sInB1cmwiOiJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NFx1MDAyNmVwb2NoPTFcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIiwidHlwZSI6ImxpYnJhcnkiLCJ2ZXJzaW9uIjoiMS4wLjJrLTE2LmVsNyJ9LHsiYm9tLXJlZiI6IjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSIsIm5hbWUiOiJjZW50b3MiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlR5cGUiLCJ2YWx1ZSI6ImNlbnRvcyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpDbGFzcyIsInZhbHVlIjoib3MtcGtncyJ9XSwidHlwZSI6Im9wZXJhdGluZy1zeXN0ZW0iLCJ2ZXJzaW9uIjoiNy42LjE4MTAifV0sImRlcGVuZGVuY2llcyI6W3siZGVwZW5kc09uIjpbInBrZzpycG0vY2VudG9zL2Jhc2hANC4yLjQ2LTMxLmVsNz9hcmNoPXg4Nl82NFx1MDAyNmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NFx1MDAyNmVwb2NoPTFcdTAwMjZkaXN0cm89Y2VudG9zLTcuNi4xODEwIl0sInJlZiI6IjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJ9LHsiZGVwZW5kc09uIjpbIjAxNzVmNzMyLWRmOWQtNGJiOC05ZjU2LTg3MDg5OGUzZmY4OSJdLCJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIifV0sIm1ldGFkYXRhIjp7ImNvbXBvbmVudCI6eyJib20tcmVmIjoiZDBkNDFlMzAtOTY1MC00ODlkLTk0OGQtNDI1ZmYyZWQ2M2QyIiwibmFtZSI6ImludGVncmF0aW9uL3Rlc3RkYXRhL2ZpeHR1cmVzL2ltYWdlcy9jZW50b3MtNy50YXIuZ3oiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNjaGVtYVZlcnNpb24iLCJ2YWx1ZSI6IjIifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6SW1hZ2VJRCIsInZhbHVlIjoic2hhMjU2OmYxY2I3YzdkNThiNzNlYWM4NTljMzk1ODgyZWVjNDlkNTA2NTEyNDRlMzQyY2Q2YzY4YTVjNzgwOTc4NWY0MjcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6RGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XSwidHlwZSI6ImNvbnRhaW5lciJ9LCJ0aW1lc3RhbXAiOiIyMDIyLTA2LTE0VDE1OjA4OjQ4KzAwOjAwIiwidG9vbHMiOlt7Im5hbWUiOiJ0cml2eSIsInZlbmRvciI6ImFxdWFzZWN1cml0eSIsInZlcnNpb24iOiJkZXYifV19LCJzZXJpYWxOdW1iZXIiOiJ1cm46dXVpZDoxNDU1YzAyZC02NGNhLTQ1M2UtYTVkZi1kZGZiNzBhN2M4MDQiLCJzcGVjVmVyc2lvbiI6IjEuNCIsInZlcnNpb24iOjF9fQ==","signatures":[{"keyid":"","sig":"MEUCIQCtj78dipe+yzdlIsmwjn9QeaBTAPQacwIJAWfnrtp7FwIgcViOUgPA0WFYjimrIl7vbygdSpduM+ZzY3cqrDciH1U="}]} {"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2N5Y2xvbmVkeC5vcmcvYm9tIiwic3ViamVjdCI6W3sibmFtZSI6ImluZGV4LmRvY2tlci5pby9saWJyYXJ5L2NlbnRvcyIsImRpZ2VzdCI6eyJzaGEyNTYiOiJiZTY1ZjQ4OGI3NzY0YWQzNjM4ZjIzNmI3YjUxNWIzNjc4MzY5YTUxMjRjNDdiOGQzMjkxNmQ2NDg3NDE4ZWE0In19XSwicHJlZGljYXRlIjp7ImJvbUZvcm1hdCI6IkN5Y2xvbmVEWCIsInNwZWNWZXJzaW9uIjoiMS40Iiwic2VyaWFsTnVtYmVyIjoidXJuOnV1aWQ6MTQ1NWMwMmQtNjRjYS00NTNlLWE1ZGYtZGRmYjcwYTdjODA0IiwidmVyc2lvbiI6MSwibWV0YWRhdGEiOnsidGltZXN0YW1wIjoiMjAyMi0wNi0xNFQxNTowODo0OCswMDowMCIsInRvb2xzIjpbeyJ2ZW5kb3IiOiJhcXVhc2VjdXJpdHkiLCJuYW1lIjoidHJpdnkiLCJ2ZXJzaW9uIjoiZGV2In1dLCJjb21wb25lbnQiOnsiYm9tLXJlZiI6ImQwZDQxZTMwLTk2NTAtNDg5ZC05NDhkLTQyNWZmMmVkNjNkMiIsInR5cGUiOiJjb250YWluZXIiLCJuYW1lIjoiaW50ZWdyYXRpb24vdGVzdGRhdGEvZml4dHVyZXMvaW1hZ2VzL2NlbnRvcy03LnRhci5neiIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U2NoZW1hVmVyc2lvbiIsInZhbHVlIjoiMiJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpJbWFnZUlEIiwidmFsdWUiOiJzaGEyNTY6ZjFjYjdjN2Q1OGI3M2VhYzg1OWMzOTU4ODJlZWM0OWQ1MDY1MTI0NGUzNDJjZDZjNjhhNWM3ODA5Nzg1ZjQyNyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1Njo4OTE2OWQ4N2RiZTJiNzJiYTQyYmZiYjM1NzljOTU3MzIyYmFjYTI4ZTAzYTFlNTU4MDc2NTQyYTFjMWIyYjRhIn1dfX0sImNvbXBvbmVudHMiOlt7ImJvbS1yZWYiOiJwa2c6cnBtL2NlbnRvcy9iYXNoQDQuMi40Ni0zMS5lbDc/YXJjaD14ODZfNjQmZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInR5cGUiOiJsaWJyYXJ5IiwibmFtZSI6ImJhc2giLCJ2ZXJzaW9uIjoiNC4yLjQ2LTMxLmVsNyIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiR1BMdjMrIn1dLCJwdXJsIjoicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0JmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJwcm9wZXJ0aWVzIjpbeyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlBrZ0lEIiwidmFsdWUiOiJiYXNoQDQuMi40Ni0zMS5lbDcueDg2XzY0In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY05hbWUiLCJ2YWx1ZSI6ImJhc2gifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjVmVyc2lvbiIsInZhbHVlIjoiNC4yLjQ2In0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1JlbGVhc2UiLCJ2YWx1ZSI6IjMxLmVsNyJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpMYXllckRpZ2VzdCIsInZhbHVlIjoic2hhMjU2OmFjOTIwODIwN2FkYWFjM2E0OGU1NGE0ZGM2YjQ5YzY5ZTc4YzMwNzJkMmIzYWRkN2VmZGFiZjgxNGRiMjEzM2IifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWZmSUQiLCJ2YWx1ZSI6InNoYTI1Njo4OTE2OWQ4N2RiZTJiNzJiYTQyYmZiYjM1NzljOTU3MzIyYmFjYTI4ZTAzYTFlNTU4MDc2NTQyYTFjMWIyYjRhIn1dfSx7ImJvbS1yZWYiOiJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NCZlcG9jaD0xJmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJ0eXBlIjoibGlicmFyeSIsIm5hbWUiOiJvcGVuc3NsLWxpYnMiLCJ2ZXJzaW9uIjoiMS4wLjJrLTE2LmVsNyIsImxpY2Vuc2VzIjpbeyJleHByZXNzaW9uIjoiT3BlblNTTCJ9XSwicHVybCI6InBrZzpycG0vY2VudG9zL29wZW5zc2wtbGlic0AxLjAuMmstMTYuZWw3P2FyY2g9eDg2XzY0JmVwb2NoPTEmZGlzdHJvPWNlbnRvcy03LjYuMTgxMCIsInByb3BlcnRpZXMiOlt7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6UGtnSUQiLCJ2YWx1ZSI6Im9wZW5zc2wtbGlic0AxLjAuMmstMTYuZWw3Lng4Nl82NCJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNOYW1lIiwidmFsdWUiOiJvcGVuc3NsIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OlNyY1ZlcnNpb24iLCJ2YWx1ZSI6IjEuMC4yayJ9LHsibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpTcmNSZWxlYXNlIiwidmFsdWUiOiIxNi5lbDcifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6U3JjRXBvY2giLCJ2YWx1ZSI6IjEifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6TGF5ZXJEaWdlc3QiLCJ2YWx1ZSI6InNoYTI1NjphYzkyMDgyMDdhZGFhYzNhNDhlNTRhNGRjNmI0OWM2OWU3OGMzMDcyZDJiM2FkZDdlZmRhYmY4MTRkYjIxMzNiIn0seyJuYW1lIjoiYXF1YXNlY3VyaXR5OnRyaXZ5OkxheWVyRGlmZklEIiwidmFsdWUiOiJzaGEyNTY6ODkxNjlkODdkYmUyYjcyYmE0MmJmYmIzNTc5Yzk1NzMyMmJhY2EyOGUwM2ExZTU1ODA3NjU0MmExYzFiMmI0YSJ9XX0seyJib20tcmVmIjoiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5IiwidHlwZSI6Im9wZXJhdGluZy1zeXN0ZW0iLCJuYW1lIjoiY2VudG9zIiwidmVyc2lvbiI6IjcuNi4xODEwIiwicHJvcGVydGllcyI6W3sibmFtZSI6ImFxdWFzZWN1cml0eTp0cml2eTpUeXBlIiwidmFsdWUiOiJjZW50b3MifSx7Im5hbWUiOiJhcXVhc2VjdXJpdHk6dHJpdnk6Q2xhc3MiLCJ2YWx1ZSI6Im9zLXBrZ3MifV19XSwiZGVwZW5kZW5jaWVzIjpbeyJyZWYiOiIwMTc1ZjczMi1kZjlkLTRiYjgtOWY1Ni04NzA4OThlM2ZmODkiLCJkZXBlbmRzT24iOlsicGtnOnJwbS9jZW50b3MvYmFzaEA0LjIuNDYtMzEuZWw3P2FyY2g9eDg2XzY0JmRpc3Rybz1jZW50b3MtNy42LjE4MTAiLCJwa2c6cnBtL2NlbnRvcy9vcGVuc3NsLWxpYnNAMS4wLjJrLTE2LmVsNz9hcmNoPXg4Nl82NCZlcG9jaD0xJmRpc3Rybz1jZW50b3MtNy42LjE4MTAiXX0seyJyZWYiOiJkMGQ0MWUzMC05NjUwLTQ4OWQtOTQ4ZC00MjVmZjJlZDYzZDIiLCJkZXBlbmRzT24iOlsiMDE3NWY3MzItZGY5ZC00YmI4LTlmNTYtODcwODk4ZTNmZjg5Il19XX19Cg==","signatures":[{"keyid":"","sig":"MEUCIQCtj78dipe+yzdlIsmwjn9QeaBTAPQacwIJAWfnrtp7FwIgcViOUgPA0WFYjimrIl7vbygdSpduM+ZzY3cqrDciH1U="}]}

View File

@@ -45,6 +45,10 @@
], ],
"purl": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810", "purl": "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810",
"properties": [ "properties": [
{
"name": "aquasecurity:trivy:PkgID",
"value": "bash@4.2.46-31.el7.x86_64"
},
{ {
"name": "aquasecurity:trivy:SrcName", "name": "aquasecurity:trivy:SrcName",
"value": "bash" "value": "bash"
@@ -79,6 +83,10 @@
], ],
"purl": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810", "purl": "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810",
"properties": [ "properties": [
{
"name": "aquasecurity:trivy:PkgID",
"value": "openssl-libs@1.0.2k-16.el7.x86_64"
},
{ {
"name": "aquasecurity:trivy:SrcName", "name": "aquasecurity:trivy:SrcName",
"value": "openssl" "value": "openssl"

View File

@@ -1,346 +1,187 @@
{ {
"bomFormat": "CycloneDX", "SchemaVersion": 2,
"specVersion": "1.4", "ArtifactName": "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json",
"version": 1, "ArtifactType": "cyclonedx",
"metadata": { "Metadata": {
"timestamp": "2023-05-19T10:38:42+00:00", "OS": {
"tools": [ "Family": "debian",
{ "Name": "10.2"
"vendor": "aquasecurity", },
"name": "trivy", "ImageConfig": {
"version": "dev" "architecture": "",
"created": "0001-01-01T00:00:00Z",
"os": "",
"rootfs": {
"type": "",
"diff_ids": null
},
"config": {}
} }
},
"Results": [
{
"Target": "testdata/fixtures/sbom/fluentd-multiple-lockfiles-cyclonedx.json (debian 10.2)",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2019-18276",
"PkgName": "bash",
"InstalledVersion": "5.0-4",
"Layer": {},
"SeveritySource": "debian",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18276",
"PkgRef": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2",
"DataSource": {
"ID": "debian",
"Name": "Debian Security Tracker",
"URL": "https://salsa.debian.org/security-tracker-team/security-tracker"
},
"Title": "bash: when effective UID is not equal to its real UID the saved UID is not dropped",
"Description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.",
"Severity": "LOW",
"CweIDs": [
"CWE-273"
], ],
"component": { "CVSS": {
"bom-ref": "urn:uuid:31ee662c-480e-4f63-9765-23ea8afc754d/1", "nvd": {
"type": "container", "V2Vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C",
"name": "integration/testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz" "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"V2Score": 7.2,
"V3Score": 7.8
},
"redhat": {
"V3Vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"V3Score": 7.8
} }
}, },
"vulnerabilities": [ "References": [
{ "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html",
"id": "CVE-2020-8165", "https://access.redhat.com/security/cve/CVE-2019-18276",
"source": { "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276",
"name": "ghsa", "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff",
"url": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems" "https://linux.oracle.com/cve/CVE-2019-18276.html",
}, "https://linux.oracle.com/errata/ELSA-2021-1679.html",
"ratings": [ "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E",
{ "https://nvd.nist.gov/vuln/detail/CVE-2019-18276",
"source": { "https://security.gentoo.org/glsa/202105-34",
"name": "ghsa" "https://security.netapp.com/advisory/ntap-20200430-0003/",
}, "https://www.youtube.com/watch?v=-wGtxJ8opa8"
"severity": "high"
},
{
"source": {
"name": "nvd"
},
"score": 7.5,
"severity": "high",
"method": "CVSSv2",
"vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"
},
{
"source": {
"name": "nvd"
},
"score": 9.8,
"severity": "critical",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "redhat"
},
"score": 9.8,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
}
], ],
"cwes": [ "PublishedDate": "2019-11-28T01:15:00Z",
502 "LastModifiedDate": "2021-05-26T12:15:00Z"
},
{
"VulnerabilityID": "CVE-2019-18224",
"VendorIDs": [
"DSA-4613-1"
], ],
"description": "A deserialization of untrusted data vulnerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.", "PkgName": "libidn2-0",
"recommendation": "Upgrade activesupport to version 6.0.3.1, 5.2.4.3", "InstalledVersion": "2.0.5-1",
"advisories": [ "FixedVersion": "2.0.5-1+deb10u1",
{ "Layer": {},
"url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html" "SeveritySource": "nvd",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2019-18224",
"PkgRef": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2",
"DataSource": {
"ID": "debian",
"Name": "Debian Security Tracker",
"URL": "https://salsa.debian.org/security-tracker-team/security-tracker"
}, },
{ "Title": "libidn2: heap-based buffer overflow in idn2_to_ascii_4i in lib/lookup.c",
"url": "http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html" "Description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.",
}, "Severity": "CRITICAL",
{ "CweIDs": [
"url": "https://access.redhat.com/security/cve/CVE-2020-8165" "CWE-787"
},
{
"url": "https://github.com/advisories/GHSA-2p68-f74v-9wc6"
},
{
"url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml"
},
{
"url": "https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ"
},
{
"url": "https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c"
},
{
"url": "https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c"
},
{
"url": "https://hackerone.com/reports/413388"
},
{
"url": "https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html"
},
{
"url": "https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html"
},
{
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-8165"
},
{
"url": "https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/"
},
{
"url": "https://www.debian.org/security/2020/dsa-4766"
}
], ],
"published": "2020-06-19T18:15:00+00:00", "CVSS": {
"updated": "2020-10-17T12:15:00+00:00", "nvd": {
"affects": [ "V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
{ "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"ref": "urn:cdx:31ee662c-480e-4f63-9765-23ea8afc754d/1#pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", "V2Score": 7.5,
"versions": [ "V3Score": 9.8
{ },
"version": "6.0.2.1", "redhat": {
"status": "affected" "V3Vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
"V3Score": 5.6
} }
] },
"References": [
"http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html",
"http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html",
"https://access.redhat.com/security/cve/CVE-2019-18224",
"https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420",
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224",
"https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c",
"https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1",
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/",
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/",
"https://seclists.org/bugtraq/2020/Feb/4",
"https://security.gentoo.org/glsa/202003-63",
"https://ubuntu.com/security/notices/USN-4168-1",
"https://usn.ubuntu.com/4168-1/",
"https://www.debian.org/security/2020/dsa-4613"
],
"PublishedDate": "2019-10-21T17:15:00Z",
"LastModifiedDate": "2019-10-29T19:15:00Z"
} }
] ]
}, },
{ {
"id": "CVE-2019-18276", "Target": "Ruby",
"source": { "Class": "lang-pkgs",
"name": "debian", "Type": "gemspec",
"url": "https://salsa.debian.org/security-tracker-team/security-tracker" "Vulnerabilities": [
},
"ratings": [
{ {
"source": { "VulnerabilityID": "CVE-2020-8165",
"name": "cbl-mariner" "PkgName": "activesupport",
"InstalledVersion": "6.0.2.1",
"FixedVersion": "6.0.3.1, 5.2.4.3",
"Layer": {},
"SeveritySource": "ghsa",
"PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-8165",
"PkgRef": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec",
"DataSource": {
"ID": "ghsa",
"Name": "GitHub Security Advisory RubyGems",
"URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Arubygems"
}, },
"severity": "high" "Title": "rubygem-activesupport: potentially unintended unmarshalling of user-provided objects in MemCacheStore and RedisCacheStore",
}, "Description": "A deserialization of untrusted data vulnerability exists in rails \u003c 5.2.4.3, rails \u003c 6.0.3.1 that can allow an attacker to unmarshal user-provided objects in MemCacheStore and RedisCacheStore potentially resulting in an RCE.",
{ "Severity": "HIGH",
"source": { "CweIDs": [
"name": "nvd" "CWE-502"
},
"score": 7.2,
"severity": "high",
"method": "CVSSv2",
"vector": "AV:L/AC:L/Au:N/C:C/I:C/A:C"
},
{
"source": {
"name": "nvd"
},
"score": 7.8,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "oracle-oval"
},
"severity": "low"
},
{
"source": {
"name": "photon"
},
"severity": "high"
},
{
"source": {
"name": "redhat"
},
"score": 7.8,
"severity": "low",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "ubuntu"
},
"severity": "low"
}
], ],
"cwes": [ "CVSS": {
273 "nvd": {
"V2Vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"V2Score": 7.5,
"V3Score": 9.8
},
"redhat": {
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"V3Score": 9.8
}
},
"References": [
"http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00031.html",
"http://lists.opensuse.org/opensuse-security-announce/2020-10/msg00034.html",
"https://access.redhat.com/security/cve/CVE-2020-8165",
"https://github.com/advisories/GHSA-2p68-f74v-9wc6",
"https://github.com/rubysec/ruby-advisory-db/blob/master/gems/activesupport/CVE-2020-8165.yml",
"https://groups.google.com/forum/#!msg/rubyonrails-security/bv6fW4S0Y1c/KnkEqM7AAQAJ",
"https://groups.google.com/forum/#!topic/rubyonrails-security/bv6fW4S0Y1c",
"https://groups.google.com/g/rubyonrails-security/c/bv6fW4S0Y1c",
"https://hackerone.com/reports/413388",
"https://lists.debian.org/debian-lts-announce/2020/06/msg00022.html",
"https://lists.debian.org/debian-lts-announce/2020/07/msg00013.html",
"https://nvd.nist.gov/vuln/detail/CVE-2020-8165",
"https://weblog.rubyonrails.org/2020/5/18/Rails-5-2-4-3-and-6-0-3-1-have-been-released/",
"https://www.debian.org/security/2020/dsa-4766"
], ],
"description": "An issue was discovered in disable_priv_mode in shell.c in GNU Bash through 5.0 patch 11. By default, if Bash is run with its effective UID not equal to its real UID, it will drop privileges by setting its effective UID to its real UID. However, it does so incorrectly. On Linux and other systems that support \"saved UID\" functionality, the saved UID is not dropped. An attacker with command execution in the shell can use \"enable -f\" for runtime loading of a new builtin, which can be a shared object that calls setuid() and therefore regains privileges. However, binaries running with an effective UID of 0 are unaffected.", "PublishedDate": "2020-06-19T18:15:00Z",
"advisories": [ "LastModifiedDate": "2020-10-17T12:15:00Z"
{
"url": "http://packetstormsecurity.com/files/155498/Bash-5.0-Patch-11-Privilege-Escalation.html"
},
{
"url": "https://access.redhat.com/security/cve/CVE-2019-18276"
},
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18276"
},
{
"url": "https://github.com/bminor/bash/commit/951bdaad7a18cc0dc1036bba86b18b90874d39ff"
},
{
"url": "https://linux.oracle.com/cve/CVE-2019-18276.html"
},
{
"url": "https://linux.oracle.com/errata/ELSA-2021-1679.html"
},
{
"url": "https://lists.apache.org/thread.html/rf9fa47ab66495c78bb4120b0754dd9531ca2ff0430f6685ac9b07772@%3Cdev.mina.apache.org%3E"
},
{
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-18276"
},
{
"url": "https://security.gentoo.org/glsa/202105-34"
},
{
"url": "https://security.netapp.com/advisory/ntap-20200430-0003/"
},
{
"url": "https://www.youtube.com/watch?v=-wGtxJ8opa8"
}
],
"published": "2019-11-28T01:15:00+00:00",
"updated": "2021-05-26T12:15:00+00:00",
"affects": [
{
"ref": "urn:cdx:31ee662c-480e-4f63-9765-23ea8afc754d/1#pkg:deb/debian/bash@5.0-4?distro=debian-10.2",
"versions": [
{
"version": "5.0-4",
"status": "affected"
}
]
}
]
},
{
"id": "CVE-2019-18224",
"source": {
"name": "debian",
"url": "https://salsa.debian.org/security-tracker-team/security-tracker"
},
"ratings": [
{
"source": {
"name": "amazon"
},
"severity": "medium"
},
{
"source": {
"name": "nvd"
},
"score": 7.5,
"severity": "high",
"method": "CVSSv2",
"vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P"
},
{
"source": {
"name": "nvd"
},
"score": 9.8,
"severity": "critical",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
},
{
"source": {
"name": "redhat"
},
"score": 5.6,
"severity": "medium",
"method": "CVSSv3",
"vector": "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L"
},
{
"source": {
"name": "ubuntu"
},
"severity": "medium"
}
],
"cwes": [
787
],
"description": "idn2_to_ascii_4i in lib/lookup.c in GNU libidn2 before 2.1.1 has a heap-based buffer overflow via a long domain string.",
"recommendation": "Upgrade libidn2-0 to version 2.0.5-1+deb10u1",
"advisories": [
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00008.html"
},
{
"url": "http://lists.opensuse.org/opensuse-security-announce/2019-12/msg00009.html"
},
{
"url": "https://access.redhat.com/security/cve/CVE-2019-18224"
},
{
"url": "https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=12420"
},
{
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-18224"
},
{
"url": "https://github.com/libidn/libidn2/commit/e4d1558aa2c1c04a05066ee8600f37603890ba8c"
},
{
"url": "https://github.com/libidn/libidn2/compare/libidn2-2.1.0...libidn2-2.1.1"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JDQVQ2XPV5BTZUFINT7AFJSKNNBVURNJ/"
},
{
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MINU5RKDFE6TKAFY5DRFN3WSFDS4DYVS/"
},
{
"url": "https://seclists.org/bugtraq/2020/Feb/4"
},
{
"url": "https://security.gentoo.org/glsa/202003-63"
},
{
"url": "https://ubuntu.com/security/notices/USN-4168-1"
},
{
"url": "https://usn.ubuntu.com/4168-1/"
},
{
"url": "https://www.debian.org/security/2020/dsa-4613"
}
],
"published": "2019-10-21T17:15:00+00:00",
"updated": "2019-10-29T19:15:00+00:00",
"affects": [
{
"ref": "urn:cdx:31ee662c-480e-4f63-9765-23ea8afc754d/1#pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2",
"versions": [
{
"version": "2.0.5-1",
"status": "affected"
}
]
} }
] ]
} }

View File

@@ -1110,9 +1110,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
Example: ` # Scan CycloneDX and show the result in tables Example: ` # Scan CycloneDX and show the result in tables
$ trivy sbom /path/to/report.cdx $ trivy sbom /path/to/report.cdx
# Scan CycloneDX and generate a CycloneDX report
$ trivy sbom --format cyclonedx /path/to/report.cdx
# Scan CycloneDX-type attestation and show the result in tables # Scan CycloneDX-type attestation and show the result in tables
$ trivy sbom /path/to/report.cdx.intoto.jsonl $ trivy sbom /path/to/report.cdx.intoto.jsonl
`, `,

View File

@@ -6,8 +6,6 @@ import (
cdx "github.com/CycloneDX/cyclonedx-go" cdx "github.com/CycloneDX/cyclonedx-go"
"golang.org/x/xerrors" "golang.org/x/xerrors"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
) )
@@ -29,18 +27,7 @@ func NewWriter(output io.Writer, appVersion string) Writer {
// Write writes the results in CycloneDX format // Write writes the results in CycloneDX format
func (w Writer) Write(report types.Report) error { func (w Writer) Write(report types.Report) error {
var bom *cdx.BOM bom, err := w.marshaler.Marshal(report)
var err error
// When the input is CycloneDX, only vulnerabilities will be stored in CycloneDX.
// Each vulnerability has a reference to a component in the original CycloneDX.
// e.g. "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#jackson-databind-2.8.0"
if report.ArtifactType == ftypes.ArtifactCycloneDX {
log.Logger.Info("Components will not be exported in the CycloneDX report as the input is CycloneDX")
bom, err = w.marshaler.MarshalVulnerabilities(report)
} else {
bom, err = w.marshaler.Marshal(report)
}
if err != nil { if err != nil {
return xerrors.Errorf("CycloneDX marshal error: %w", err) return xerrors.Errorf("CycloneDX marshal error: %w", err)
} }

View File

@@ -0,0 +1,489 @@
package core
import (
"fmt"
"sort"
"strconv"
"strings"
cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/google/uuid"
"github.com/samber/lo"
"k8s.io/utils/clock"
dtypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
"github.com/aquasecurity/trivy/pkg/digest"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/types"
)
const (
ToolVendor = "aquasecurity"
ToolName = "trivy"
Namespace = ToolVendor + ":" + ToolName + ":"
// https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times
timeLayout = "2006-01-02T15:04:05+00:00"
)
type NewUUID func() uuid.UUID
type Option func(dx *CycloneDX)
func WithClock(clock clock.Clock) Option {
return func(opts *CycloneDX) {
opts.clock = clock
}
}
func WithNewUUID(newUUID NewUUID) Option {
return func(opts *CycloneDX) {
opts.newUUID = newUUID
}
}
type CycloneDX struct {
appVersion string
clock clock.Clock
newUUID NewUUID
}
type Component struct {
Type cdx.ComponentType
Name string
Version string
PackageURL *purl.PackageURL
Licenses []string
Hashes []digest.Digest
Supplier string
Properties map[string]string
Components []*Component
Vulnerabilities []types.DetectedVulnerability
}
func NewCycloneDX(version string, opts ...Option) *CycloneDX {
c := &CycloneDX{
appVersion: version,
clock: clock.RealClock{},
newUUID: uuid.New,
}
for _, opt := range opts {
opt(c)
}
return c
}
func (c *CycloneDX) Marshal(root *Component) *cdx.BOM {
bom := cdx.NewBOM()
bom.SerialNumber = c.newUUID().URN()
bom.Metadata = c.Metadata()
components := map[string]*cdx.Component{}
dependencies := map[string]*[]string{}
vulnerabilities := map[string]*cdx.Vulnerability{}
bom.Metadata.Component = c.MarshalComponent(root, components, dependencies, vulnerabilities)
// Remove metadata component
delete(components, bom.Metadata.Component.BOMRef)
bom.Components = c.Components(components)
bom.Dependencies = c.Dependencies(dependencies)
bom.Vulnerabilities = c.Vulnerabilities(vulnerabilities)
return bom
}
func (c *CycloneDX) MarshalComponent(component *Component, components map[string]*cdx.Component,
deps map[string]*[]string, vulns map[string]*cdx.Vulnerability) *cdx.Component {
bomRef := c.BOMRef(component)
// When multiple lock files have the same dependency with the same name and version,
// "BOM-Ref" (PURL technically) of "Library" components may conflict.
// In that case, only one "Library" component will be added and
// some "Application" components will refer to the same component.
// e.g.
// Application component (/app1/package-lock.json)
// |
// | Application component (/app2/package-lock.json)
// | |
// └----┴----> Library component (npm package, express-4.17.3)
//
if v, ok := components[bomRef]; ok {
return v
}
cdxComponent := &cdx.Component{
BOMRef: bomRef,
Type: component.Type,
Name: component.Name,
Version: component.Version,
PackageURL: c.PackageURL(component.PackageURL),
Supplier: c.Supplier(component.Supplier),
Hashes: c.Hashes(component.Hashes),
Licenses: c.Licenses(component.Licenses),
Properties: lo.ToPtr(c.Properties(component.Properties)),
}
components[cdxComponent.BOMRef] = cdxComponent
for _, v := range component.Vulnerabilities {
// If the same vulnerability affects multiple packages, those packages will be aggregated into one vulnerability.
// Vulnerability component (CVE-2020-26247)
// -> Library component (nokogiri /srv/app1/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec)
// -> Library component (nokogiri /srv/app2/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec)
if vuln, ok := vulns[v.VulnerabilityID]; ok {
*vuln.Affects = append(*vuln.Affects, cdxAffects(bomRef, v.InstalledVersion))
} else {
vulns[v.VulnerabilityID] = c.marshalVulnerability(cdxComponent.BOMRef, v)
}
}
var dependencies []string
for _, child := range component.Components {
childComponent := c.MarshalComponent(child, components, deps, vulns)
dependencies = append(dependencies, childComponent.BOMRef)
}
sort.Strings(dependencies)
deps[cdxComponent.BOMRef] = &dependencies
return cdxComponent
}
func (c *CycloneDX) marshalVulnerability(bomRef string, vuln types.DetectedVulnerability) *cdx.Vulnerability {
v := &cdx.Vulnerability{
ID: vuln.VulnerabilityID,
Source: cdxSource(vuln.DataSource),
Ratings: cdxRatings(vuln),
CWEs: cwes(vuln.CweIDs),
Description: vuln.Description,
Advisories: cdxAdvisories(vuln.References),
}
if vuln.FixedVersion != "" {
v.Recommendation = fmt.Sprintf("Upgrade %s to version %s", vuln.PkgName, vuln.FixedVersion)
}
if vuln.PublishedDate != nil {
v.Published = vuln.PublishedDate.Format(timeLayout)
}
if vuln.LastModifiedDate != nil {
v.Updated = vuln.LastModifiedDate.Format(timeLayout)
}
v.Affects = &[]cdx.Affects{cdxAffects(bomRef, vuln.InstalledVersion)}
return v
}
func (c *CycloneDX) BOMRef(component *Component) string {
// PURL takes precedence over UUID
if component.PackageURL == nil {
return c.newUUID().String()
}
return component.PackageURL.BOMRef()
}
func (c *CycloneDX) Metadata() *cdx.Metadata {
return &cdx.Metadata{
Timestamp: c.clock.Now().UTC().Format(timeLayout),
Tools: &[]cdx.Tool{
{
Vendor: ToolVendor,
Name: ToolName,
Version: c.appVersion,
},
},
}
}
func (c *CycloneDX) Components(uniq map[string]*cdx.Component) *[]cdx.Component {
// Convert components from map to slice and sort by BOM-Ref
components := lo.MapToSlice(uniq, func(_ string, value *cdx.Component) cdx.Component {
return *value
})
sort.Slice(components, func(i, j int) bool {
return components[i].BOMRef < components[j].BOMRef
})
return &components
}
func (c *CycloneDX) Dependencies(uniq map[string]*[]string) *[]cdx.Dependency {
// Convert dependencies from map to slice and sort by BOM-Ref
dependencies := lo.MapToSlice(uniq, func(bomRef string, value *[]string) cdx.Dependency {
return cdx.Dependency{
Ref: bomRef,
Dependencies: value,
}
})
sort.Slice(dependencies, func(i, j int) bool {
return dependencies[i].Ref < dependencies[j].Ref
})
return &dependencies
}
func (c *CycloneDX) Vulnerabilities(uniq map[string]*cdx.Vulnerability) *[]cdx.Vulnerability {
vulns := lo.MapToSlice(uniq, func(bomRef string, value *cdx.Vulnerability) cdx.Vulnerability {
sort.Slice(*value.Affects, func(i, j int) bool {
return (*value.Affects)[i].Ref < (*value.Affects)[j].Ref
})
return *value
})
sort.Slice(vulns, func(i, j int) bool {
return vulns[i].BOMRef < vulns[j].BOMRef
})
return &vulns
}
func (c *CycloneDX) PackageURL(purl *purl.PackageURL) string {
if purl == nil {
return ""
}
return purl.String()
}
func (c *CycloneDX) Supplier(supplier string) *cdx.OrganizationalEntity {
if supplier == "" {
return nil
}
return &cdx.OrganizationalEntity{
Name: supplier,
}
}
func (c *CycloneDX) Hashes(hashes []digest.Digest) *[]cdx.Hash {
if len(hashes) == 0 {
return nil
}
var cdxHashes []cdx.Hash
for _, hash := range hashes {
var alg cdx.HashAlgorithm
switch hash.Algorithm() {
case digest.SHA1:
alg = cdx.HashAlgoSHA1
case digest.SHA256:
alg = cdx.HashAlgoSHA256
case digest.MD5:
alg = cdx.HashAlgoMD5
default:
log.Logger.Debugf("Unable to convert %q algorithm to CycloneDX format", hash.Algorithm())
continue
}
cdxHashes = append(cdxHashes, cdx.Hash{
Algorithm: alg,
Value: hash.Encoded(),
})
}
return &cdxHashes
}
func (c *CycloneDX) Licenses(licenses []string) *cdx.Licenses {
if len(licenses) == 0 {
return nil
}
choices := lo.Map(licenses, func(license string, i int) cdx.LicenseChoice {
return cdx.LicenseChoice{Expression: license}
})
return lo.ToPtr(cdx.Licenses(choices))
}
func (c *CycloneDX) Properties(props map[string]string) []cdx.Property {
properties := lo.MapToSlice(props, func(k, v string) cdx.Property {
return cdx.Property{
Name: Namespace + k,
Value: v,
}
})
sort.Slice(properties, func(i, j int) bool {
return properties[i].Name < properties[j].Name
})
return properties
}
func IsTrivySBOM(c *cdx.BOM) bool {
if c == nil || c.Metadata == nil || c.Metadata.Tools == nil {
return false
}
for _, tool := range *c.Metadata.Tools {
if tool.Vendor == ToolVendor && tool.Name == ToolName {
return true
}
}
return false
}
func LookupProperty(properties *[]cdx.Property, key string) string {
for _, p := range lo.FromPtr(properties) {
if p.Name == Namespace+key {
return p.Value
}
}
return ""
}
func UnmarshalProperties(properties *[]cdx.Property) map[string]string {
props := map[string]string{}
for _, prop := range lo.FromPtr(properties) {
if !strings.HasPrefix(prop.Name, Namespace) {
continue
}
props[strings.TrimPrefix(prop.Name, Namespace)] = prop.Value
}
return props
}
func cdxAdvisories(refs []string) *[]cdx.Advisory {
var advs []cdx.Advisory
for _, ref := range refs {
advs = append(advs, cdx.Advisory{
URL: ref,
})
}
return &advs
}
func cwes(cweIDs []string) *[]int {
// to skip cdx.Vulnerability.CWEs when generating json
// we should return 'clear' nil without 'type' interface part
if cweIDs == nil {
return nil
}
var ret []int
for _, cweID := range cweIDs {
number, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(cweID), "cwe-"))
if err != nil {
log.Logger.Debugf("cwe id parse error: %s", err)
continue
}
ret = append(ret, number)
}
return &ret
}
func cdxRatings(vulnerability types.DetectedVulnerability) *[]cdx.VulnerabilityRating {
rates := make([]cdx.VulnerabilityRating, 0) // To export an empty array in JSON
for sourceID, severity := range vulnerability.VendorSeverity {
// When the vendor also provides CVSS score/vector
if cvss, ok := vulnerability.CVSS[sourceID]; ok {
if cvss.V2Score != 0 || cvss.V2Vector != "" {
rates = append(rates, cdxRatingV2(sourceID, severity, cvss))
}
if cvss.V3Score != 0 || cvss.V3Vector != "" {
rates = append(rates, cdxRatingV3(sourceID, severity, cvss))
}
} else { // When the vendor provides only severity
rate := cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Severity: toCDXSeverity(severity),
}
rates = append(rates, rate)
}
}
// For consistency
sort.Slice(rates, func(i, j int) bool {
if rates[i].Source.Name != rates[j].Source.Name {
return rates[i].Source.Name < rates[j].Source.Name
}
if rates[i].Method != rates[j].Method {
return rates[i].Method < rates[j].Method
}
if rates[i].Score != nil && rates[j].Score != nil {
return *rates[i].Score < *rates[j].Score
}
return rates[i].Vector < rates[j].Vector
})
return &rates
}
func cdxRatingV2(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating {
cdxSeverity := toCDXSeverity(severity)
// Trivy keeps only CVSSv3 severity for NVD.
// The CVSSv2 severity must be calculated according to CVSSv2 score.
if sourceID == vulnerability.NVD {
cdxSeverity = nvdSeverityV2(cvss.V2Score)
}
return cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Score: &cvss.V2Score,
Method: cdx.ScoringMethodCVSSv2,
Severity: cdxSeverity,
Vector: cvss.V2Vector,
}
}
func cdxRatingV3(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating {
rate := cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Score: &cvss.V3Score,
Method: cdx.ScoringMethodCVSSv3,
Severity: toCDXSeverity(severity),
Vector: cvss.V3Vector,
}
if strings.HasPrefix(cvss.V3Vector, "CVSS:3.1") {
rate.Method = cdx.ScoringMethodCVSSv31
}
return rate
}
func nvdSeverityV2(score float64) cdx.Severity {
// cf. https://nvd.nist.gov/vuln-metrics/cvss
switch {
case score < 4.0:
return cdx.SeverityInfo
case 4.0 <= score && score < 7.0:
return cdx.SeverityMedium
case 7.0 <= score:
return cdx.SeverityHigh
}
return cdx.SeverityUnknown
}
func toCDXSeverity(s dtypes.Severity) cdx.Severity {
switch s {
case dtypes.SeverityLow:
return cdx.SeverityLow
case dtypes.SeverityMedium:
return cdx.SeverityMedium
case dtypes.SeverityHigh:
return cdx.SeverityHigh
case dtypes.SeverityCritical:
return cdx.SeverityCritical
default:
return cdx.SeverityUnknown
}
}
func cdxSource(source *dtypes.DataSource) *cdx.Source {
if source == nil {
return nil
}
return &cdx.Source{
Name: string(source.ID),
URL: source.URL,
}
}
func cdxAffects(ref, version string) cdx.Affects {
return cdx.Affects{
Ref: ref,
Range: &[]cdx.AffectedVersions{
{
Version: version,
Status: cdx.VulnerabilityStatusAffected,
// "AffectedVersions.Range" is not included, because it does not exist in DetectedVulnerability.
},
},
}
}

View File

@@ -2,32 +2,22 @@ package cyclonedx
import ( import (
"fmt" "fmt"
"sort"
"strconv" "strconv"
"strings" "strings"
cdx "github.com/CycloneDX/cyclonedx-go" cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/google/uuid"
"github.com/samber/lo" "github.com/samber/lo"
"golang.org/x/exp/maps"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"k8s.io/utils/clock"
dtypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
"github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/digest"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/purl" "github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core"
"github.com/aquasecurity/trivy/pkg/scanner/utils" "github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
) )
const ( const (
ToolVendor = "aquasecurity"
ToolName = "trivy"
Namespace = ToolVendor + ":" + ToolName + ":"
PropertySchemaVersion = "SchemaVersion" PropertySchemaVersion = "SchemaVersion"
PropertyType = "Type" PropertyType = "Type"
PropertyClass = "Class" PropertyClass = "Class"
@@ -50,9 +40,6 @@ const (
PropertyFilePath = "FilePath" PropertyFilePath = "FilePath"
PropertyLayerDigest = "LayerDigest" PropertyLayerDigest = "LayerDigest"
PropertyLayerDiffID = "LayerDiffID" PropertyLayerDiffID = "LayerDiffID"
// https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times
timeLayout = "2006-01-02T15:04:05+00:00"
) )
var ( var (
@@ -60,223 +47,44 @@ var (
) )
type Marshaler struct { type Marshaler struct {
appVersion string // Trivy version core *core.CycloneDX
clock clock.Clock
newUUID newUUID
} }
type newUUID func() uuid.UUID func NewMarshaler(version string, opts ...core.Option) *Marshaler {
return &Marshaler{
type marshalOption func(*Marshaler) core: core.NewCycloneDX(version, opts...),
func WithClock(clock clock.Clock) marshalOption {
return func(opts *Marshaler) {
opts.clock = clock
} }
} }
func WithNewUUID(newUUID newUUID) marshalOption {
return func(opts *Marshaler) {
opts.newUUID = newUUID
}
}
func NewMarshaler(version string, opts ...marshalOption) *Marshaler {
e := &Marshaler{
appVersion: version,
clock: clock.RealClock{},
newUUID: uuid.New,
}
for _, opt := range opts {
opt(e)
}
return e
}
// Marshal converts the Trivy report to the CycloneDX format // Marshal converts the Trivy report to the CycloneDX format
func (e *Marshaler) Marshal(report types.Report) (*cdx.BOM, error) { func (e *Marshaler) Marshal(report types.Report) (*cdx.BOM, error) {
bom := cdx.NewBOM() // Convert
bom.SerialNumber = e.newUUID().URN() root, err := e.MarshalReport(report)
metadataComponent, err := e.reportToCdxComponent(report)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to parse metadata component: %w", err) return nil, xerrors.Errorf("failed to marshal report: %w", err)
} }
bom.Metadata = e.cdxMetadata() return e.core.Marshal(root), nil
bom.Metadata.Component = metadataComponent
bom.Components, bom.Dependencies, bom.Vulnerabilities, err = e.marshalComponents(report, bom.Metadata.Component.BOMRef)
if err != nil {
return nil, xerrors.Errorf("failed to parse components: %w", err)
}
return bom, nil
} }
// MarshalVulnerabilities converts the Trivy report to the CycloneDX format only with vulnerabilities. func (e *Marshaler) MarshalReport(r types.Report) (*core.Component, error) {
// The output refers to another CycloneDX SBOM. // Metadata component
func (e *Marshaler) MarshalVulnerabilities(report types.Report) (*cdx.BOM, error) { root, err := e.rootComponent(r)
vulnMap := map[string]cdx.Vulnerability{}
for _, result := range report.Results {
for _, vuln := range result.Vulnerabilities {
ref, err := externalRef(report.CycloneDX.SerialNumber, vuln.PkgRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if v, ok := vulnMap[vuln.VulnerabilityID]; ok {
*v.Affects = append(*v.Affects, cdxAffects(ref, vuln.InstalledVersion))
} else {
vulnMap[vuln.VulnerabilityID] = toCdxVulnerability(ref, vuln)
}
}
}
vulns := maps.Values(vulnMap)
sort.Slice(vulns, func(i, j int) bool {
return vulns[i].ID > vulns[j].ID
})
bom := cdx.NewBOM()
bom.Metadata = e.cdxMetadata()
// Fill the detected vulnerabilities
bom.Vulnerabilities = &vulns
// Use the original component as is
bom.Metadata.Component = &cdx.Component{
Name: report.CycloneDX.Metadata.Component.Name,
Version: report.CycloneDX.Metadata.Component.Version,
Type: cdx.ComponentType(report.CycloneDX.Metadata.Component.Type),
}
// Overwrite the bom ref as it must be the BOM ref of the original CycloneDX.
// e.g.
// "metadata" : {
// "timestamp" : "2022-07-02T00:00:00Z",
// "component" : {
// "name" : "Acme Product",
// "version": "2.4.0",
// "type" : "application",
// "bom-ref" : "urn:cdx:f08a6ccd-4dce-4759-bd84-c626675d60a7/1"
// }
// },
if report.CycloneDX.SerialNumber != "" { // bomRef is optional field - https://cyclonedx.org/docs/1.4/json/#metadata_component_bom-ref
bom.Metadata.Component.BOMRef = fmt.Sprintf("%s/%d", report.CycloneDX.SerialNumber, report.CycloneDX.Version)
}
return bom, nil
}
func (e *Marshaler) cdxMetadata() *cdx.Metadata {
return &cdx.Metadata{
Timestamp: e.clock.Now().UTC().Format(timeLayout),
Tools: &[]cdx.Tool{
{
Vendor: ToolVendor,
Name: ToolName,
Version: e.appVersion,
},
},
}
}
func externalRef(bomLink string, bomRef string) (string, error) {
// bomLink is optional field: https://cyclonedx.org/docs/1.4/json/#vulnerabilities_items_bom-ref
if bomLink == "" {
return bomRef, nil
}
if !strings.HasPrefix(bomLink, "urn:uuid:") {
return "", xerrors.Errorf("%q: %w", bomLink, ErrInvalidBOMLink)
}
return fmt.Sprintf("%s/%d#%s", strings.Replace(bomLink, "uuid", "cdx", 1), cdx.BOMFileFormatJSON, bomRef), nil
}
func (e *Marshaler) marshalComponents(r types.Report, bomRef string) (*[]cdx.Component, *[]cdx.Dependency, *[]cdx.Vulnerability, error) {
components := make([]cdx.Component, 0) // To export an empty array in JSON
// we use map to avoid duplicate components
dependencies := map[string]cdx.Dependency{}
metadataDependencies := make([]string, 0) // To export an empty array in JSON
libraryUniqMap := map[string]struct{}{}
vulnMap := map[string]cdx.Vulnerability{}
for _, result := range r.Results { for _, result := range r.Results {
bomRefMap := map[string]string{} components, err := e.marshalResult(r.Metadata, result)
pkgIDToRef := map[string]string{}
var directDepRefs []string
// Get dependency parents first
parents := ftypes.Packages(result.Packages).ParentDeps()
for _, pkg := range result.Packages {
pkgComponent, err := pkgToCdxComponent(result.Type, r.Metadata, result.Class, pkg)
if err != nil { if err != nil {
return nil, nil, nil, xerrors.Errorf("failed to parse pkg: %w", err) return nil, err
}
pkgID := packageID(result.Target, pkg.Name, utils.FormatVersion(pkg), pkg.FilePath)
bomRefMap[pkgID] = pkgComponent.BOMRef
if pkg.ID != "" {
pkgIDToRef[pkg.ID] = pkgComponent.BOMRef
}
// This package is a direct dependency
if !pkg.Indirect || len(parents[pkg.ID]) == 0 {
directDepRefs = append(directDepRefs, pkgComponent.BOMRef)
}
// When multiple lock files have the same dependency with the same name and version,
// "bom-ref" (PURL technically) of Library components may conflict.
// In that case, only one Library component will be added and
// some Application components will refer to the same component.
// e.g.
// Application component (/app1/package-lock.json)
// |
// | Application component (/app2/package-lock.json)
// | |
// └----┴----> Library component (npm package, express-4.17.3)
//
if _, ok := libraryUniqMap[pkgComponent.BOMRef]; !ok {
libraryUniqMap[pkgComponent.BOMRef] = struct{}{}
// For components
// ref. https://cyclonedx.org/use-cases/#inventory
components = append(components, pkgComponent)
}
}
// Iterate packages again to build dependency graph
for _, pkg := range result.Packages {
deps := lo.FilterMap(pkg.DependsOn, func(dep string, _ int) (string, bool) {
if ref, ok := pkgIDToRef[dep]; ok {
return ref, true
}
return "", false
})
if len(deps) == 0 {
continue
}
sort.Strings(deps)
ref := pkgIDToRef[pkg.ID]
dependencies[ref] = cdx.Dependency{
Ref: ref,
Dependencies: &deps,
}
}
sort.Strings(directDepRefs)
for _, vuln := range result.Vulnerabilities {
// Take a bom-ref
pkgID := packageID(result.Target, vuln.PkgName, vuln.InstalledVersion, vuln.PkgPath)
ref := bomRefMap[pkgID]
if v, ok := vulnMap[vuln.VulnerabilityID]; ok {
// If a vulnerability depends on multiple packages,
// it will be commonised into a single vulnerability.
// Vulnerability component (CVE-2020-26247)
// -> Library component (nokogiri /srv/app1/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec)
// -> Library component (nokogiri /srv/app2/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec)
*v.Affects = append(*v.Affects, cdxAffects(ref, vuln.InstalledVersion))
} else {
vulnMap[vuln.VulnerabilityID] = toCdxVulnerability(ref, vuln)
} }
root.Components = append(root.Components, components...)
} }
return root, nil
}
func (e *Marshaler) marshalResult(metadata types.Metadata, result types.Result) ([]*core.Component, error) {
if result.Type == ftypes.NodePkg || result.Type == ftypes.PythonPkg || if result.Type == ftypes.NodePkg || result.Type == ftypes.PythonPkg ||
result.Type == ftypes.GemSpec || result.Type == ftypes.Jar || result.Type == ftypes.CondaPkg { result.Type == ftypes.GemSpec || result.Type == ftypes.Jar || result.Type == ftypes.CondaPkg {
// If a package is language-specific package that isn't associated with a lock file, // If a package is language-specific package that isn't associated with a lock file,
@@ -289,7 +97,11 @@ func (e *Marshaler) marshalComponents(r types.Report, bomRef string) (*[]cdx.Com
// ref. https://cyclonedx.org/use-cases/#inventory // ref. https://cyclonedx.org/use-cases/#inventory
// Dependency graph from #1 to #2 // Dependency graph from #1 to #2
metadataDependencies = append(metadataDependencies, directDepRefs...) components, err := e.marshalPackages(metadata, result)
if err != nil {
return nil, err
}
return components, nil
} else if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg { } else if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg {
// If a package is OS package, it will be a dependency of "Operating System" component. // If a package is OS package, it will be a dependency of "Operating System" component.
// e.g. // e.g.
@@ -308,120 +120,149 @@ func (e *Marshaler) marshalComponents(r types.Report, bomRef string) (*[]cdx.Com
// -> Library component (npm package, lodash-4.17.21) --- #3 // -> Library component (npm package, lodash-4.17.21) --- #3
// -> etc. // -> etc.
resultComponent := e.resultToCdxComponent(result, r.Metadata.OS) // #2
components = append(components, resultComponent) appComponent := e.resultComponent(result, metadata.OS)
// #3
components, err := e.marshalPackages(metadata, result)
if err != nil {
return nil, err
}
// Dependency graph from #2 to #3 // Dependency graph from #2 to #3
dependencies[resultComponent.BOMRef] = cdx.Dependency{ appComponent.Components = components
Ref: resultComponent.BOMRef,
Dependencies: &directDepRefs,
}
// Dependency graph from #1 to #2 // Dependency graph from #1 to #2
metadataDependencies = append(metadataDependencies, resultComponent.BOMRef) return []*core.Component{appComponent}, nil
}
} }
return nil, nil
}
vulns := maps.Values(vulnMap) func (e *Marshaler) marshalPackages(metadata types.Metadata, result types.Result) ([]*core.Component, error) {
sort.Slice(vulns, func(i, j int) bool { // Get dependency parents first
return vulns[i].ID > vulns[j].ID parents := ftypes.Packages(result.Packages).ParentDeps()
// Group vulnerabilities by package ID
vulns := lo.GroupBy(result.Vulnerabilities, func(v types.DetectedVulnerability) string {
return lo.Ternary(v.PkgID == "", fmt.Sprintf("%s@%s", v.PkgName, v.InstalledVersion), v.PkgID)
}) })
dependencies[bomRef] = cdx.Dependency{ // Create package map
Ref: bomRef, pkgs := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, Package) {
Dependencies: &metadataDependencies, pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, utils.FormatVersion(pkg)), pkg.ID)
return pkgID, Package{
Type: result.Type,
Metadata: metadata,
Package: pkg,
Vulnerabilities: vulns[pkgID],
} }
dependencyList := maps.Values(dependencies)
sort.Slice(dependencyList, func(i, j int) bool {
return dependencyList[i].Ref < dependencyList[j].Ref
}) })
return &components, &dependencyList, &vulns, nil
var directComponents []*core.Component
for _, pkg := range pkgs {
// Skip indirect dependencies
if pkg.Indirect && len(parents[pkg.ID]) != 0 {
continue
}
// Recursive packages from direct dependencies
if component, err := e.marshalPackage(pkg, pkgs, map[string]*core.Component{}); err != nil {
return nil, nil
} else if component != nil {
directComponents = append(directComponents, component)
}
}
return directComponents, nil
} }
func packageID(target, pkgName, pkgVersion, pkgFilePath string) string { type Package struct {
return fmt.Sprintf("%s/%s/%s/%s", target, pkgName, pkgVersion, pkgFilePath) ftypes.Package
Type string
Metadata types.Metadata
Vulnerabilities []types.DetectedVulnerability
} }
func toCdxVulnerability(bomRef string, vuln types.DetectedVulnerability) cdx.Vulnerability { func (e *Marshaler) marshalPackage(pkg Package, pkgs map[string]Package, components map[string]*core.Component,
v := cdx.Vulnerability{ ) (*core.Component, error) {
ID: vuln.VulnerabilityID, if c, ok := components[pkg.ID]; ok {
Source: cdxSource(vuln.DataSource), return c, nil
Ratings: cdxRatings(vuln),
CWEs: cwes(vuln.CweIDs),
Description: vuln.Description,
Advisories: cdxAdvisories(vuln.References),
}
if vuln.FixedVersion != "" {
v.Recommendation = fmt.Sprintf("Upgrade %s to version %s", vuln.PkgName, vuln.FixedVersion)
}
if vuln.PublishedDate != nil {
v.Published = vuln.PublishedDate.Format(timeLayout)
}
if vuln.LastModifiedDate != nil {
v.Updated = vuln.LastModifiedDate.Format(timeLayout)
} }
v.Affects = &[]cdx.Affects{cdxAffects(bomRef, vuln.InstalledVersion)} component, err := pkgComponent(pkg)
if err != nil {
return nil, xerrors.Errorf("failed to parse pkg: %w", err)
}
components[pkg.ID] = component
return v // Iterate dependencies
for _, dep := range pkg.DependsOn {
childPkg, ok := pkgs[dep]
if !ok {
continue
}
child, err := e.marshalPackage(childPkg, pkgs, components)
if err != nil {
return nil, xerrors.Errorf("failed to parse pkg: %w", err)
}
component.Components = append(component.Components, child)
}
return component, nil
} }
func (e *Marshaler) reportToCdxComponent(r types.Report) (*cdx.Component, error) { func (e *Marshaler) rootComponent(r types.Report) (*core.Component, error) {
component := &cdx.Component{ root := &core.Component{
Name: r.ArtifactName, Name: r.ArtifactName,
} }
properties := []cdx.Property{ props := map[string]string{
cdxProperty(PropertySchemaVersion, strconv.Itoa(r.SchemaVersion)), PropertySchemaVersion: strconv.Itoa(r.SchemaVersion),
}
if r.Metadata.Size != 0 {
properties = appendProperties(properties, PropertySize, strconv.FormatInt(r.Metadata.Size, 10))
} }
switch r.ArtifactType { switch r.ArtifactType {
case ftypes.ArtifactContainerImage: case ftypes.ArtifactContainerImage:
component.Type = cdx.ComponentTypeContainer root.Type = cdx.ComponentTypeContainer
props[PropertyImageID] = r.Metadata.ImageID
p, err := purl.NewPackageURL(purl.TypeOCI, r.Metadata, ftypes.Package{}) p, err := purl.NewPackageURL(purl.TypeOCI, r.Metadata, ftypes.Package{})
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to new package url for oci: %w", err) return nil, xerrors.Errorf("failed to new package url for oci: %w", err)
} else if p.Type != "" {
root.PackageURL = &p
} }
properties = appendProperties(properties, PropertyImageID, r.Metadata.ImageID)
if p.Type == "" {
component.BOMRef = e.newUUID().String()
} else {
component.BOMRef = p.ToString()
component.PackageURL = p.ToString()
}
case ftypes.ArtifactVM: case ftypes.ArtifactVM:
component.Type = cdx.ComponentTypeContainer root.Type = cdx.ComponentTypeContainer
component.BOMRef = e.newUUID().String()
case ftypes.ArtifactFilesystem, ftypes.ArtifactRemoteRepository: case ftypes.ArtifactFilesystem, ftypes.ArtifactRemoteRepository:
component.Type = cdx.ComponentTypeApplication root.Type = cdx.ComponentTypeApplication
component.BOMRef = e.newUUID().String()
} }
for _, d := range r.Metadata.RepoDigests { if r.Metadata.Size != 0 {
properties = appendProperties(properties, PropertyRepoDigest, d) props[PropertySize] = strconv.FormatInt(r.Metadata.Size, 10)
}
for _, d := range r.Metadata.DiffIDs {
properties = appendProperties(properties, PropertyDiffID, d)
}
for _, t := range r.Metadata.RepoTags {
properties = appendProperties(properties, PropertyRepoTag, t)
} }
component.Properties = &properties if len(r.Metadata.RepoDigests) > 0 {
props[PropertyRepoDigest] = strings.Join(r.Metadata.RepoDigests, ",")
}
if len(r.Metadata.DiffIDs) > 0 {
props[PropertyDiffID] = strings.Join(r.Metadata.DiffIDs, ",")
}
if len(r.Metadata.RepoTags) > 0 {
props[PropertyRepoTag] = strings.Join(r.Metadata.RepoTags, ",")
}
return component, nil root.Properties = filterProperties(props)
return root, nil
} }
func (e *Marshaler) resultToCdxComponent(r types.Result, osFound *ftypes.OS) cdx.Component { func (e *Marshaler) resultComponent(r types.Result, osFound *ftypes.OS) *core.Component {
component := cdx.Component{ component := &core.Component{
Name: r.Target, Name: r.Target,
Properties: &[]cdx.Property{ Properties: map[string]string{
cdxProperty(PropertyType, r.Type), PropertyType: r.Type,
cdxProperty(PropertyClass, string(r.Class)), PropertyClass: string(r.Class),
}, },
} }
@@ -429,7 +270,6 @@ func (e *Marshaler) resultToCdxComponent(r types.Result, osFound *ftypes.OS) cdx
case types.ClassOSPkg: case types.ClassOSPkg:
// UUID needs to be generated since Operating System Component cannot generate PURL. // UUID needs to be generated since Operating System Component cannot generate PURL.
// https://cyclonedx.org/use-cases/#known-vulnerabilities // https://cyclonedx.org/use-cases/#known-vulnerabilities
component.BOMRef = e.newUUID().String()
if osFound != nil { if osFound != nil {
component.Name = osFound.Family component.Name = osFound.Family
component.Version = osFound.Name component.Version = osFound.Name
@@ -438,296 +278,46 @@ func (e *Marshaler) resultToCdxComponent(r types.Result, osFound *ftypes.OS) cdx
case types.ClassLangPkg: case types.ClassLangPkg:
// UUID needs to be generated since Application Component cannot generate PURL. // UUID needs to be generated since Application Component cannot generate PURL.
// https://cyclonedx.org/use-cases/#known-vulnerabilities // https://cyclonedx.org/use-cases/#known-vulnerabilities
component.BOMRef = e.newUUID().String()
component.Type = cdx.ComponentTypeApplication component.Type = cdx.ComponentTypeApplication
case types.ClassConfig:
// TODO: Config support
component.BOMRef = e.newUUID().String()
component.Type = cdx.ComponentTypeFile
} }
return component return component
} }
func pkgToCdxComponent(pkgType string, meta types.Metadata, class types.ResultClass, pkg ftypes.Package) (cdx.Component, error) { func pkgComponent(pkg Package) (*core.Component, error) {
pu, err := purl.NewPackageURL(pkgType, meta, pkg) pu, err := purl.NewPackageURL(pkg.Type, pkg.Metadata, pkg.Package)
if err != nil { if err != nil {
return cdx.Component{}, xerrors.Errorf("failed to new package purl: %w", err) return nil, xerrors.Errorf("failed to new package purl: %w", err)
}
properties := cdxProperties(pkgType, pkg)
var hashes *[]cdx.Hash
if pkg.Digest != "" && class == types.ClassOSPkg {
if alg := cdxHashAlgorithm(pkg.Digest.Algorithm()); alg != "" {
hashes = &[]cdx.Hash{
{
Algorithm: alg,
Value: pkg.Digest.Encoded(),
},
}
} }
properties := map[string]string{
PropertyPkgID: pkg.ID,
PropertyPkgType: pkg.Type,
PropertyFilePath: pkg.FilePath,
PropertySrcName: pkg.SrcName,
PropertySrcVersion: pkg.SrcVersion,
PropertySrcRelease: pkg.SrcRelease,
PropertySrcEpoch: strconv.Itoa(pkg.SrcEpoch),
PropertyModularitylabel: pkg.Modularitylabel,
PropertyLayerDigest: pkg.Layer.Digest,
PropertyLayerDiffID: pkg.Layer.DiffID,
} }
component := cdx.Component{
return &core.Component{
Type: cdx.ComponentTypeLibrary, Type: cdx.ComponentTypeLibrary,
Name: pkg.Name, Name: pkg.Name,
Version: pu.Version, Version: pu.Version,
BOMRef: pu.BOMRef(), PackageURL: &pu,
PackageURL: pu.ToString(), Supplier: pkg.Maintainer,
Properties: properties, Licenses: pkg.Licenses,
Hashes: hashes, Hashes: lo.Ternary(pkg.Digest == "", nil, []digest.Digest{pkg.Digest}),
} Properties: filterProperties(properties),
Vulnerabilities: pkg.Vulnerabilities,
}, nil
}
if len(pkg.Licenses) != 0 { func filterProperties(props map[string]string) map[string]string {
choices := lo.Map(pkg.Licenses, func(license string, i int) cdx.LicenseChoice { return lo.OmitBy(props, func(key string, value string) bool {
return cdx.LicenseChoice{Expression: license} return value == "" || (key == PropertySrcEpoch && value == "0")
}) })
component.Licenses = lo.ToPtr(cdx.Licenses(choices))
}
if pkg.Maintainer != "" {
component.Supplier = &cdx.OrganizationalEntity{
Name: pkg.Maintainer,
}
}
return component, nil
}
func cdxProperties(pkgType string, pkg ftypes.Package) *[]cdx.Property {
props := []struct {
name string
value string
}{
{
PropertyPkgID,
pkg.ID,
},
{
PropertyPkgType,
pkgType,
},
{
PropertyFilePath,
pkg.FilePath,
},
{
PropertySrcName,
pkg.SrcName,
},
{
PropertySrcVersion,
pkg.SrcVersion,
},
{
PropertySrcRelease,
pkg.SrcRelease,
},
{
PropertySrcEpoch,
strconv.Itoa(pkg.SrcEpoch),
},
{
PropertyModularitylabel,
pkg.Modularitylabel,
},
{
PropertyLayerDigest,
pkg.Layer.Digest,
},
{
PropertyLayerDiffID,
pkg.Layer.DiffID,
},
}
var properties []cdx.Property
for _, prop := range props {
properties = appendProperties(properties, prop.name, prop.value)
}
if len(properties) == 0 {
return nil
}
return &properties
}
func appendProperties(properties []cdx.Property, key, value string) []cdx.Property {
if value == "" || (key == PropertySrcEpoch && value == "0") {
return properties
}
return append(properties, cdxProperty(key, value))
}
func cdxProperty(key, value string) cdx.Property {
return cdx.Property{
Name: Namespace + key,
Value: value,
}
}
func cdxAdvisories(refs []string) *[]cdx.Advisory {
var advs []cdx.Advisory
for _, ref := range refs {
advs = append(advs, cdx.Advisory{
URL: ref,
})
}
return &advs
}
func cwes(cweIDs []string) *[]int {
// to skip cdx.Vulnerability.CWEs when generating json
// we should return 'clear' nil without 'type' interface part
if cweIDs == nil {
return nil
}
var ret []int
for _, cweID := range cweIDs {
number, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(cweID), "cwe-"))
if err != nil {
log.Logger.Debugf("cwe id parse error: %s", err)
continue
}
ret = append(ret, number)
}
return &ret
}
func cdxRatings(vulnerability types.DetectedVulnerability) *[]cdx.VulnerabilityRating {
rates := make([]cdx.VulnerabilityRating, 0) // To export an empty array in JSON
for sourceID, severity := range vulnerability.VendorSeverity {
// When the vendor also provides CVSS score/vector
if cvss, ok := vulnerability.CVSS[sourceID]; ok {
if cvss.V2Score != 0 || cvss.V2Vector != "" {
rates = append(rates, cdxRatingV2(sourceID, severity, cvss))
}
if cvss.V3Score != 0 || cvss.V3Vector != "" {
rates = append(rates, cdxRatingV3(sourceID, severity, cvss))
}
} else { // When the vendor provides only severity
rate := cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Severity: toCDXSeverity(severity),
}
rates = append(rates, rate)
}
}
// For consistency
sort.Slice(rates, func(i, j int) bool {
if rates[i].Source.Name != rates[j].Source.Name {
return rates[i].Source.Name < rates[j].Source.Name
}
if rates[i].Method != rates[j].Method {
return rates[i].Method < rates[j].Method
}
if rates[i].Score != nil && rates[j].Score != nil {
return *rates[i].Score < *rates[j].Score
}
return rates[i].Vector < rates[j].Vector
})
return &rates
}
func cdxRatingV2(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating {
cdxSeverity := toCDXSeverity(severity)
// Trivy keeps only CVSSv3 severity for NVD.
// The CVSSv2 severity must be calculated according to CVSSv2 score.
if sourceID == vulnerability.NVD {
cdxSeverity = nvdSeverityV2(cvss.V2Score)
}
return cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Score: &cvss.V2Score,
Method: cdx.ScoringMethodCVSSv2,
Severity: cdxSeverity,
Vector: cvss.V2Vector,
}
}
func nvdSeverityV2(score float64) cdx.Severity {
// cf. https://nvd.nist.gov/vuln-metrics/cvss
switch {
case score < 4.0:
return cdx.SeverityInfo
case 4.0 <= score && score < 7.0:
return cdx.SeverityMedium
case 7.0 <= score:
return cdx.SeverityHigh
}
return cdx.SeverityUnknown
}
func cdxRatingV3(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating {
rate := cdx.VulnerabilityRating{
Source: &cdx.Source{
Name: string(sourceID),
},
Score: &cvss.V3Score,
Method: cdx.ScoringMethodCVSSv3,
Severity: toCDXSeverity(severity),
Vector: cvss.V3Vector,
}
if strings.HasPrefix(cvss.V3Vector, "CVSS:3.1") {
rate.Method = cdx.ScoringMethodCVSSv31
}
return rate
}
func toCDXSeverity(s dtypes.Severity) cdx.Severity {
switch s {
case dtypes.SeverityLow:
return cdx.SeverityLow
case dtypes.SeverityMedium:
return cdx.SeverityMedium
case dtypes.SeverityHigh:
return cdx.SeverityHigh
case dtypes.SeverityCritical:
return cdx.SeverityCritical
default:
return cdx.SeverityUnknown
}
}
func cdxSource(source *dtypes.DataSource) *cdx.Source {
if source == nil {
return nil
}
return &cdx.Source{
Name: string(source.ID),
URL: source.URL,
}
}
func cdxAffects(ref, version string) cdx.Affects {
return cdx.Affects{
Ref: ref,
Range: &[]cdx.AffectedVersions{
{
Version: version,
Status: cdx.VulnerabilityStatusAffected,
// "AffectedVersions.Range" is not included, because it does not exist in DetectedVulnerability.
},
},
}
}
func cdxHashAlgorithm(algorithm digest.Algorithm) cdx.HashAlgorithm {
switch algorithm {
case digest.SHA1:
return cdx.HashAlgoSHA1
case digest.SHA256:
return cdx.HashAlgoSHA256
case digest.MD5:
return cdx.HashAlgoMD5
default:
log.Logger.Debugf("Unable to convert %q algorithm to CycloneDX format", algorithm)
return ""
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@ import (
"io" "io"
"sort" "sort"
"strconv" "strconv"
"strings"
"github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core"
cdx "github.com/CycloneDX/cyclonedx-go" cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/samber/lo" "github.com/samber/lo"
@@ -23,7 +24,7 @@ var (
ErrPURLEmpty = errors.New("purl empty error") ErrPURLEmpty = errors.New("purl empty error")
) )
type CycloneDX struct { type BOM struct {
*types.SBOM *types.SBOM
dependencies map[string][]string dependencies map[string][]string
@@ -39,7 +40,7 @@ func DecodeJSON(r io.Reader) (*cdx.BOM, error) {
return bom, nil return bom, nil
} }
func (c *CycloneDX) UnmarshalJSON(b []byte) error { func (c *BOM) UnmarshalJSON(b []byte) error {
log.Logger.Debug("Unmarshaling CycloneDX JSON...") log.Logger.Debug("Unmarshaling CycloneDX JSON...")
if c.SBOM == nil { if c.SBOM == nil {
c.SBOM = &types.SBOM{} c.SBOM = &types.SBOM{}
@@ -49,7 +50,7 @@ func (c *CycloneDX) UnmarshalJSON(b []byte) error {
return xerrors.Errorf("CycloneDX decode error: %w", err) return xerrors.Errorf("CycloneDX decode error: %w", err)
} }
if !isTrivySBOM(bom) { if !core.IsTrivySBOM(bom) {
log.Logger.Warnf("Third-party SBOM may lead to inaccurate vulnerability detection") log.Logger.Warnf("Third-party SBOM may lead to inaccurate vulnerability detection")
log.Logger.Warnf("Recommend using Trivy to generate SBOMs") log.Logger.Warnf("Recommend using Trivy to generate SBOMs")
} }
@@ -90,7 +91,7 @@ func (c *CycloneDX) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (c *CycloneDX) parseSBOM(bom *cdx.BOM) error { func (c *BOM) parseSBOM(bom *cdx.BOM) error {
c.dependencies = dependencyMap(bom.Dependencies) c.dependencies = dependencyMap(bom.Dependencies)
c.components = componentMap(bom.Metadata, bom.Components) c.components = componentMap(bom.Metadata, bom.Components)
var seen = make(map[string]struct{}) var seen = make(map[string]struct{})
@@ -106,7 +107,7 @@ func (c *CycloneDX) parseSBOM(bom *cdx.BOM) error {
} }
c.Packages = append(c.Packages, pkgInfo) c.Packages = append(c.Packages, pkgInfo)
case cdx.ComponentTypeApplication: // It would be a lock file in a CycloneDX report generated by Trivy case cdx.ComponentTypeApplication: // It would be a lock file in a CycloneDX report generated by Trivy
if lookupProperty(component.Properties, PropertyType) == "" { if core.LookupProperty(component.Properties, PropertyType) == "" {
continue continue
} }
app, err := c.parseLangPkgs(component, seen) app, err := c.parseLangPkgs(component, seen)
@@ -160,7 +161,7 @@ func (c *CycloneDX) parseSBOM(bom *cdx.BOM) error {
return nil return nil
} }
func (c *CycloneDX) parseOSPkgs(component cdx.Component, seen map[string]struct{}) (ftypes.PackageInfo, error) { func (c *BOM) parseOSPkgs(component cdx.Component, seen map[string]struct{}) (ftypes.PackageInfo, error) {
components := c.walkDependencies(component.BOMRef, map[string]struct{}{}) components := c.walkDependencies(component.BOMRef, map[string]struct{}{})
pkgs, err := parsePkgs(components, seen) pkgs, err := parsePkgs(components, seen)
if err != nil { if err != nil {
@@ -172,7 +173,7 @@ func (c *CycloneDX) parseOSPkgs(component cdx.Component, seen map[string]struct{
}, nil }, nil
} }
func (c *CycloneDX) parseLangPkgs(component cdx.Component, seen map[string]struct{}) (*ftypes.Application, error) { func (c *BOM) parseLangPkgs(component cdx.Component, seen map[string]struct{}) (*ftypes.Application, error) {
components := c.walkDependencies(component.BOMRef, map[string]struct{}{}) components := c.walkDependencies(component.BOMRef, map[string]struct{}{})
components = lo.UniqBy(components, func(c cdx.Component) string { components = lo.UniqBy(components, func(c cdx.Component) string {
return c.BOMRef return c.BOMRef
@@ -205,7 +206,7 @@ func parsePkgs(components []cdx.Component, seen map[string]struct{}) ([]ftypes.P
} }
// walkDependencies takes all nested dependencies of the root component. // walkDependencies takes all nested dependencies of the root component.
func (c *CycloneDX) walkDependencies(rootRef string, uniqComponents map[string]struct{}) []cdx.Component { func (c *BOM) walkDependencies(rootRef string, uniqComponents map[string]struct{}) []cdx.Component {
// e.g. Library A, B, C, D and E will be returned as dependencies of Application 1. // e.g. Library A, B, C, D and E will be returned as dependencies of Application 1.
// type: Application 1 // type: Application 1
// - type: Library A // - type: Library A
@@ -325,7 +326,7 @@ func toOS(component cdx.Component) ftypes.OS {
func toApplication(component cdx.Component) *ftypes.Application { func toApplication(component cdx.Component) *ftypes.Application {
return &ftypes.Application{ return &ftypes.Application{
Type: lookupProperty(component.Properties, PropertyType), Type: core.LookupProperty(component.Properties, PropertyType),
FilePath: component.Name, FilePath: component.Name,
} }
} }
@@ -350,27 +351,25 @@ func toPackage(component cdx.Component) (bool, string, *ftypes.Package, error) {
pkg.Licenses = append(pkg.Licenses, license.Expression) pkg.Licenses = append(pkg.Licenses, license.Expression)
} }
for _, prop := range lo.FromPtr(component.Properties) { for key, value := range core.UnmarshalProperties(component.Properties) {
if strings.HasPrefix(prop.Name, Namespace) { switch key {
switch strings.TrimPrefix(prop.Name, Namespace) {
case PropertyPkgID: case PropertyPkgID:
pkg.ID = prop.Value pkg.ID = value
case PropertySrcName: case PropertySrcName:
pkg.SrcName = prop.Value pkg.SrcName = value
case PropertySrcVersion: case PropertySrcVersion:
pkg.SrcVersion = prop.Value pkg.SrcVersion = value
case PropertySrcRelease: case PropertySrcRelease:
pkg.SrcRelease = prop.Value pkg.SrcRelease = value
case PropertySrcEpoch: case PropertySrcEpoch:
pkg.SrcEpoch, err = strconv.Atoi(prop.Value) pkg.SrcEpoch, err = strconv.Atoi(value)
if err != nil { if err != nil {
return false, "", nil, xerrors.Errorf("failed to parse source epoch: %w", err) return false, "", nil, xerrors.Errorf("failed to parse source epoch: %w", err)
} }
case PropertyModularitylabel: case PropertyModularitylabel:
pkg.Modularitylabel = prop.Value pkg.Modularitylabel = value
case PropertyLayerDiffID: case PropertyLayerDiffID:
pkg.Layer.DiffID = prop.Value pkg.Layer.DiffID = value
}
} }
} }
@@ -404,25 +403,3 @@ func toTrivyCdxComponent(component cdx.Component) ftypes.Component {
PackageURL: component.PackageURL, PackageURL: component.PackageURL,
} }
} }
func lookupProperty(properties *[]cdx.Property, key string) string {
for _, p := range lo.FromPtr(properties) {
if p.Name == Namespace+key {
return p.Value
}
}
return ""
}
func isTrivySBOM(c *cdx.BOM) bool {
if c == nil || c.Metadata == nil || c.Metadata.Tools == nil {
return false
}
for _, tool := range *c.Metadata.Tools {
if tool.Vendor == ToolVendor && tool.Name == ToolName {
return true
}
}
return false
}

View File

@@ -315,7 +315,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer f.Close() defer f.Close()
var cdx cyclonedx.CycloneDX var cdx cyclonedx.BOM
err = json.NewDecoder(f).Decode(&cdx) err = json.NewDecoder(f).Decode(&cdx)
if tt.wantErr != "" { if tt.wantErr != "" {
require.Error(t, err) require.Error(t, err)

View File

@@ -187,14 +187,14 @@ func Decode(f io.Reader, format Format) (types.SBOM, error) {
switch format { switch format {
case FormatCycloneDXJSON: case FormatCycloneDXJSON:
v = &cyclonedx.CycloneDX{SBOM: &bom} v = &cyclonedx.BOM{SBOM: &bom}
decoder = json.NewDecoder(f) decoder = json.NewDecoder(f)
case FormatAttestCycloneDXJSON: case FormatAttestCycloneDXJSON:
// dsse envelope // dsse envelope
// => in-toto attestation // => in-toto attestation
// => CycloneDX JSON // => CycloneDX JSON
v = &attestation.Statement{ v = &attestation.Statement{
Predicate: &cyclonedx.CycloneDX{SBOM: &bom}, Predicate: &cyclonedx.BOM{SBOM: &bom},
} }
decoder = json.NewDecoder(f) decoder = json.NewDecoder(f)
case FormatLegacyCosignAttestCycloneDXJSON: case FormatLegacyCosignAttestCycloneDXJSON:
@@ -204,7 +204,7 @@ func Decode(f io.Reader, format Format) (types.SBOM, error) {
// => CycloneDX JSON // => CycloneDX JSON
v = &attestation.Statement{ v = &attestation.Statement{
Predicate: &attestation.CosignPredicate{ Predicate: &attestation.CosignPredicate{
Data: &cyclonedx.CycloneDX{SBOM: &bom}, Data: &cyclonedx.BOM{SBOM: &bom},
}, },
} }
decoder = json.NewDecoder(f) decoder = json.NewDecoder(f)