fix: version comparison (#740)

* feat: add comparer

* refactor: rename lang with ecosystem

* feat(bundler): add comparer

* feat(node): add comparer

* feat(bundler): integrate comparer

* feat(cargo): integrate comparer

* feat(composer): add comparer

* feat(ghsa): integrate comparer

* feat(node): integrate comparer

* feat(python): integrate comparer

* test(bundler): add tests

* test(cargo): add tests

* test(composer): add tests

* test(ghsa): add tests

* test(node): add tests

* test(python): add tests

* refactor(utils): remove unnecessary functions

* test(utils): add tests

* test: rename bucket prefixes

* fix(detect): use string

* chore: update dependencies

* docs: add comments

* fix(cargo): handle unpatched vulnerability

* test(db): update trivy-db for integration tests

* test(integration): update a golden file

* test(cargo): Add a case for missing patched version

Signed-off-by: Simarpreet Singh <simar@linux.com>

* refactor(advisory): update comments

* refactor(node/advisory): change the receiver

* chore(mod): update dependencies

* refactor(comparer): unexport MatchVersion

* refactor: fix maligned structs

* test(node): add empty value

* refactor

* refactor: sort imports

* chore(mod): update trivy-db

Co-authored-by: Simarpreet Singh <simar@linux.com>
This commit is contained in:
Teppei Fukuda
2020-11-17 18:38:58 +09:00
committed by GitHub
parent 9dfb0fe3a9
commit b6d5b82c48
45 changed files with 1455 additions and 473 deletions

8
go.mod
View File

@@ -3,11 +3,13 @@ module github.com/aquasecurity/trivy
go 1.15
require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798
github.com/aquasecurity/go-version v0.0.0-20201115065329-578079e4ab05
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2
github.com/caarlos0/env/v6 v6.0.0
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cheggaaa/pb/v3 v3.0.3
@@ -27,7 +29,7 @@ require (
github.com/stretchr/testify v1.6.1
github.com/testcontainers/testcontainers-go v0.3.1
github.com/twitchtv/twirp v5.10.1+incompatible
github.com/urfave/cli/v2 v2.2.0
github.com/urfave/cli/v2 v2.3.0
go.uber.org/atomic v1.5.1 // indirect
go.uber.org/multierr v1.4.0 // indirect
go.uber.org/zap v1.13.0

23
go.sum
View File

@@ -34,15 +34,19 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible h1:3D2O4g8AwDwyWkM1HpMFVux/ccQJmGJHXsE004Wsu1Q=
github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3 h1:OZEIaBbMdUE/Js+BQKlpO81XlISgipr6yDJ+PSwsgi4=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.1 h1:pZdL8o72rK+avFWl+p9nE8RWi1JInZrWJYlnpfXJwHk=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
@@ -50,7 +54,9 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -58,8 +64,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0 h1:wykTgKwhVr2t2qs+xI020s6W5dt614QqCHV+7W9dg64=
github.com/GoogleCloudPlatform/docker-credential-gcr v1.5.0/go.mod h1:BB1eHdMLYEFuFdBlRMb0N7YGVdM5s6Pt0njxgvfbGGs=
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
@@ -87,10 +91,18 @@ github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882 h1:65VcAKqhkKwM
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882/go.mod h1:VP1+n6hMi6krpA0umEl0CkJp4hbg0R21kXWg0mjrekc=
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b h1:55Ulc/gvfWm4ylhVaR7MxOwujRjA6et7KhmUbSgUFf4=
github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b/go.mod h1:BpNTD9vHfrejKsED9rx04ldM1WIbeyXGYxUrqTVwxVQ=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s=
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 h1:eveqE9ivrt30CJ7dOajOfBavhZ4zPqHcZe/4tKp0alc=
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798/go.mod h1:hxbJZtKlO4P8sZ9nztizR6XLoE33O+BkPmuYQ4ACyz0=
github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a h1:SMEtDBnLyP/EVOeJhj4yeR8GYPFpBsFBk3lSrpjZ8yI=
github.com/aquasecurity/go-version v0.0.0-20201107203531-5e48ac5d022a/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
github.com/aquasecurity/go-version v0.0.0-20201115065329-578079e4ab05 h1:q0ZpFBjwzDk1ofey7gJ2kfA6ZNi2PeBWxNzmRPrfetA=
github.com/aquasecurity/go-version v0.0.0-20201115065329-578079e4ab05/go.mod h1:9Beu8XsUNNfzml7WBf3QmyPToP1wm1Gj/Vc5UJKqTzU=
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a h1:hsw7PpiymXP64evn/K7gsj3hWzMqLrdoeE6JkqDocVg=
github.com/aquasecurity/testdocker v0.0.0-20200426142840-5f05bce6f12a/go.mod h1:psfu0MVaiTDLpNxCoNsTeILSKY2EICBwv345f3M+Ffs=
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b h1:y4DTBr/S6WRhZ7jQX9nZ00bElwYM7k/653M/JfoaJgI=
github.com/aquasecurity/trivy-db v0.0.0-20201025093117-4ef51a6e2c4b/go.mod h1:+3+NEz0U0NCgO87Cyk0dy3SwH7CI6J4HUeCqqPj1fvQ=
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2 h1:AXA9aW464copH1GTKv35yCwztJsqDVZWKfCtBuMpI9U=
github.com/aquasecurity/trivy-db v0.0.0-20201117092632-b09c30858fc2/go.mod h1:+3+NEz0U0NCgO87Cyk0dy3SwH7CI6J4HUeCqqPj1fvQ=
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2 h1:xbdUfr2KE4THsFx9CFWtWpU91lF+YhgP46moV94nYTA=
github.com/aquasecurity/vuln-list-update v0.0.0-20191016075347-3d158c2bf9a2/go.mod h1:6NhOP0CjZJL27bZZcaHECtzWdwDDm2g6yCY0QgXEGQQ=
github.com/araddon/dateparse v0.0.0-20190426192744-0d74ffceef83/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
@@ -560,6 +572,8 @@ github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
@@ -937,6 +951,7 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@@ -7,11 +7,12 @@
"VulnerabilityID": "RUSTSEC-2019-0001",
"PkgName": "ammonia",
"InstalledVersion": "1.9.0",
"FixedVersion": "\u003e= 2.1.0",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "Uncontrolled recursion leads to abort in HTML serialization",
"Description": "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.\n",
"Description": "Affected versions of this crate did use recursion for serialization of HTML\nDOM trees.\n\nThis allows an attacker to cause abort due to stack overflow by providing\na pathologically nested input.\n\nThe flaw was corrected by serializing the DOM tree iteratively instead.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md#210"
@@ -21,53 +22,72 @@
"VulnerabilityID": "RUSTSEC-2016-0001",
"PkgName": "openssl",
"InstalledVersion": "0.8.3",
"FixedVersion": "\u003e= 0.9.0",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "SSL/TLS MitM vulnerability due to insecure defaults",
"Description": "All versions of rust-openssl prior to 0.9.0 contained numerous insecure defaults\nincluding off-by-default certificate verification and no API to perform hostname\nverification.\n\nUnless configured correctly by a developer, these defaults could allow an attacker\nto perform man-in-the-middle attacks.\n\nThe problem was addressed in newer versions by enabling certificate verification\nby default and exposing APIs to perform hostname verification. Use the\n`SslConnector` and `SslAcceptor` types to take advantage of these new features\n(as opposed to the lower-level `SslContext` type).\n",
"Description": "All versions of rust-openssl prior to 0.9.0 contained numerous insecure defaults\nincluding off-by-default certificate verification and no API to perform hostname\nverification.\n\nUnless configured correctly by a developer, these defaults could allow an attacker\nto perform man-in-the-middle attacks.\n\nThe problem was addressed in newer versions by enabling certificate verification\nby default and exposing APIs to perform hostname verification. Use the\n`SslConnector` and `SslAcceptor` types to take advantage of these new features\n(as opposed to the lower-level `SslContext` type).",
"Severity": "UNKNOWN",
"References": [
"https://github.com/sfackler/rust-openssl/releases/tag/v0.9.0"
]
},
{
"VulnerabilityID": "RUSTSEC-2018-0010",
"PkgName": "openssl",
"InstalledVersion": "0.8.3",
"VulnerabilityID": "RUSTSEC-2019-0035",
"PkgName": "rand_core",
"InstalledVersion": "0.3.1",
"FixedVersion": "\u003e= 0.4.2",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "Use after free in CMS Signing",
"Description": "Affected versions of the OpenSSL crate used structures after they'd been freed.",
"Title": "Unaligned memory access",
"Description": "Affected versions of this crate violated alignment when casting byte slices to\ninteger slices, resulting in undefined behavior.\n\nThe flaw was corrected by Ralf Jung and Diggory Hardy.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/sfackler/rust-openssl/pull/942"
"https://github.com/rust-random/rand/blob/master/rand_core/CHANGELOG.md#050---2019-06-06"
]
},
{
"VulnerabilityID": "RUSTSEC-2018-0003",
"PkgName": "smallvec",
"InstalledVersion": "0.6.9",
"VulnerabilityID": "RUSTSEC-2019-0035",
"PkgName": "rand_core",
"InstalledVersion": "0.4.0",
"FixedVersion": "\u003e= 0.4.2",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "Possible double free during unwinding in SmallVec::insert_many",
"Description": "If an iterator passed to `SmallVec::insert_many` panicked in `Iterator::next`,\ndestructors were run during unwinding while the vector was in an inconsistent\nstate, possibly causing a double free (a destructor running on two copies of\nthe same value).\n\nThis is fixed in smallvec 0.6.3 by ensuring that the vector's length is not\nupdated to include moved items until they have been removed from their\noriginal positions. Items may now be leaked if `Iterator::next` panics, but\nthey will not be dropped more than once.\n\nThank you to @Vurich for reporting this bug.\n",
"Title": "Unaligned memory access",
"Description": "Affected versions of this crate violated alignment when casting byte slices to\ninteger slices, resulting in undefined behavior.\n\nThe flaw was corrected by Ralf Jung and Diggory Hardy.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/servo/rust-smallvec/issues/96"
"https://github.com/rust-random/rand/blob/master/rand_core/CHANGELOG.md#050---2019-06-06"
]
},
{
"VulnerabilityID": "RUSTSEC-2018-0018",
"PkgName": "smallvec",
"InstalledVersion": "0.6.9",
"FixedVersion": "\u003e= 0.6.13",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "smallvec creates uninitialized value of any type",
"Description": "Affected versions of this crate called `mem::uninitialized()` to create values of a user-supplied type `T`.\nThis is unsound e.g. if `T` is a reference type (which must be non-null and thus may not remain uninitialized).\n \nThe flaw was corrected by avoiding the use of `mem::uninitialized()`, using `MaybeUninit` instead.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/servo/rust-smallvec/issues/126"
]
},
{
"VulnerabilityID": "RUSTSEC-2019-0009",
"PkgName": "smallvec",
"InstalledVersion": "0.6.9",
"FixedVersion": "\u003e= 0.6.10",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "Double-free and use-after-free in SmallVec::grow()",
"Description": "Attempting to call `grow` on a spilled SmallVec with a value equal to the current capacity causes it to free the existing data. This performs a double free immediately and may lead to use-after-free on subsequent accesses to the SmallVec contents.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.\n",
"Description": "Attempting to call `grow` on a spilled SmallVec with a value equal to the current capacity causes it to free the existing data. This performs a double free immediately and may lead to use-after-free on subsequent accesses to the SmallVec contents.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/servo/rust-smallvec/issues/148"
@@ -77,15 +97,30 @@
"VulnerabilityID": "RUSTSEC-2019-0012",
"PkgName": "smallvec",
"InstalledVersion": "0.6.9",
"FixedVersion": "\u003e= 0.6.10",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "Memory corruption in SmallVec::grow()",
"Description": "Attempting to call `grow` on a spilled SmallVec with a value less than the current capacity causes corruption of memory allocator data structures.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.\n",
"Description": "Attempting to call `grow` on a spilled SmallVec with a value less than the current capacity causes corruption of memory allocator data structures.\n\nAn attacker that controls the value passed to `grow` may exploit this flaw to obtain memory contents or gain remote code execution.\n\nCredits to @ehuss for discovering, reporting and fixing the bug.",
"Severity": "UNKNOWN",
"References": [
"https://github.com/servo/rust-smallvec/issues/149"
]
},
{
"VulnerabilityID": "RUSTSEC-2018-0017",
"PkgName": "tempdir",
"InstalledVersion": "0.3.7",
"Layer": {
"DiffID": "sha256:ea6f6933da66090da8bfe233d68f083792a68f944cd2d8f9fbb52da795813a4f"
},
"Title": "`tempdir` crate has been deprecated; use `tempfile` instead",
"Description": "The [`tempdir`](https://crates.io/crates/tempdir) crate has been deprecated\nand the functionality is merged into [`tempfile`](https://crates.io/crates/tempfile).",
"Severity": "UNKNOWN",
"References": [
"https://github.com/rust-lang-deprecated/tempdir/pull/46"
]
}
]
}

Binary file not shown.

View File

@@ -4,51 +4,50 @@ import (
"fmt"
"strings"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-db/pkg/db"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
)
// Advisory represents security advisories for each programming language
type Advisory struct {
lang string
comparer comparer
ecosystem string
comparer comparer.Comparer
}
// NewAdvisory is the factory method of Advisory
func NewAdvisory(lang string) *Advisory {
func NewAdvisory(ecosystem string, comparer comparer.Comparer) *Advisory {
return &Advisory{
lang: lang,
comparer: newComparer(lang),
ecosystem: ecosystem,
comparer: comparer,
}
}
// DetectVulnerabilities scans buckets with the prefix according to the programming language in "Advisory".
// If "lang" is python, it looks for buckets with "python::" and gets security advisories from those buckets.
// It allows us to add a new data source with the lang prefix (e.g. python::new-data-source)
// DetectVulnerabilities scans buckets with the prefix according to the ecosystem in "Advisory".
// If "ecosystem" is pip, it looks for buckets with "pip::" and gets security advisories from those buckets.
// It allows us to add a new data source with the ecosystem prefix (e.g. pip::new-data-source)
// and detect vulnerabilities without specifying a specific bucket name.
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
// e.g. "python::"
prefix := fmt.Sprintf("%s::", s.lang)
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
// e.g. "pip::", "npm::"
prefix := fmt.Sprintf("%s::", s.ecosystem)
advisories, err := db.Config{}.GetAdvisories(prefix, pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.lang, err)
return nil, xerrors.Errorf("failed to get %s advisories: %w", s.ecosystem, err)
}
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
if !s.comparer.isVulnerable(pkgVer, advisory) {
if !s.comparer.IsVulnerable(pkgVer, advisory) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: pkgName,
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: s.createFixedVersions(advisory),
}
vulns = append(vulns, vuln)
@@ -74,29 +73,3 @@ func (s *Advisory) createFixedVersions(advisory dbTypes.Advisory) string {
}
return strings.Join(fixedVersions, ", ")
}
type comparer interface {
isVulnerable(pkgVer *semver.Version, advisory dbTypes.Advisory) bool
}
func newComparer(lang string) comparer {
switch lang {
// When another library is needed for version comparison, it can be added here.
}
return generalComparer{}
}
type generalComparer struct{}
func (c generalComparer) isVulnerable(pkgVer *semver.Version, advisory dbTypes.Advisory) bool {
if len(advisory.VulnerableVersions) != 0 {
return utils.MatchVersions(pkgVer, advisory.VulnerableVersions)
}
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) ||
utils.MatchVersions(pkgVer, advisory.UnaffectedVersions) {
return false
}
return true
}

View File

@@ -4,29 +4,28 @@ import (
"os"
"testing"
"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
"github.com/aquasecurity/trivy/pkg/detector/library"
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type fields struct {
lang string
}
type args struct {
pkgName string
pkgVer *semver.Version
pkgVer string
}
tests := []struct {
name string
fixtures []string
fields fields
ecosystem string
comparer comparer.Comparer
args args
want []types.DetectedVulnerability
wantErr string
@@ -34,10 +33,11 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
{
name: "happy path",
fixtures: []string{"testdata/fixtures/php.yaml"},
fields: fields{lang: vulnerability.PHP},
ecosystem: vulnerability.Composer,
comparer: comparer.GenericComparer{},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.2.6"),
pkgVer: "4.2.6",
},
want: []types.DetectedVulnerability{
{
@@ -51,10 +51,11 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
{
name: "no patched versions in the advisory",
fixtures: []string{"testdata/fixtures/php.yaml"},
fields: fields{lang: vulnerability.PHP},
ecosystem: vulnerability.Composer,
comparer: comparer.GenericComparer{},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.4.6"),
pkgVer: "4.4.6",
},
want: []types.DetectedVulnerability{
{
@@ -68,10 +69,11 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
{
name: "no vulnerable versions in the advisory",
fixtures: []string{"testdata/fixtures/ruby.yaml"},
fields: fields{lang: vulnerability.Ruby},
ecosystem: vulnerability.RubyGems,
comparer: bundler.RubyGemsComparer{},
args: args{
pkgName: "activesupport",
pkgVer: semver.MustParse("4.1.1"),
pkgVer: "4.1.1",
},
want: []types.DetectedVulnerability{
{
@@ -85,10 +87,11 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
{
name: "no vulnerability",
fixtures: []string{"testdata/fixtures/php.yaml"},
fields: fields{lang: vulnerability.PHP},
ecosystem: vulnerability.Composer,
comparer: comparer.GenericComparer{},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.4.7"),
pkgVer: "4.4.7",
},
},
}
@@ -99,7 +102,7 @@ func TestAdvisory_DetectVulnerabilities(t *testing.T) {
defer os.RemoveAll(dir)
defer db.Close()
adv := library.NewAdvisory(tt.fields.lang)
adv := library.NewAdvisory(tt.ecosystem, tt.comparer)
got, err := adv.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
switch {

View File

@@ -3,11 +3,10 @@ package bundler
import (
"strings"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -18,6 +17,7 @@ type VulnSrc interface {
// Advisory implements the bundler VulnSrc
type Advisory struct {
comparer RubyGemsComparer
vs VulnSrc
}
@@ -25,11 +25,12 @@ type Advisory struct {
func NewAdvisory() *Advisory {
return &Advisory{
vs: bundlerSrc.NewVulnSrc(),
comparer: RubyGemsComparer{},
}
}
// DetectVulnerabilities scans and returns Vulnerability in bundler
func (a *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
func (a *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
advisories, err := a.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get bundler advisories: %w", err)
@@ -37,17 +38,18 @@ func (a *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) {
continue
adv := dbTypes.Advisory{
UnaffectedVersions: advisory.UnaffectedVersions,
PatchedVersions: advisory.PatchedVersions,
}
if utils.MatchVersions(pkgVer, advisory.UnaffectedVersions) {
if !a.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: strings.TrimSpace(pkgName),
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
}
vulns = append(vulns, vuln)

View File

@@ -1,63 +1,84 @@
package bundler
package bundler_test
import (
"os"
"testing"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/Masterminds/semver/v3"
bundlerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/bundler"
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
type MockVulnSrc struct {
mock.Mock
}
func (_m *MockVulnSrc) Get(pkgName string) ([]bundlerSrc.Advisory, error) {
ret := _m.Called(pkgName)
ret0 := ret.Get(0)
if ret0 == nil {
return nil, ret.Error(1)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type args struct {
pkgName string
pkgVer string
}
advisories, ok := ret0.([]bundlerSrc.Advisory)
if !ok {
return nil, ret.Error(1)
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
args: args{
pkgName: "activesupport",
pkgVer: "4.1.1",
},
fixtures: []string{"testdata/fixtures/gem.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "activesupport",
InstalledVersion: "4.1.1",
VulnerabilityID: "CVE-2015-3226",
FixedVersion: ">= 4.2.2, ~> 4.1.11",
},
},
},
{
name: "not detected",
args: args{
pkgName: "activesupport",
pkgVer: "4.1.0.a",
},
fixtures: []string{"testdata/fixtures/gem.yaml"},
want: nil,
},
{
name: "invalid JSON",
args: args{
pkgName: "activesupport",
pkgVer: "4.1.0",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
want: nil,
wantErr: "failed to unmarshal advisory JSON",
},
}
return advisories, ret.Error(1)
}
func TestScanner_Detect(t *testing.T) {
log.InitLogger(false, true)
t.Run("Issue #108", func(t *testing.T) {
// https://github.com/aquasecurity/trivy/issues/108
// Validate that the massaging that happens when parsing the lockfile
// allows us to better handle the platform metadata
mockVulnSrc := new(MockVulnSrc)
mockVulnSrc.On("Get", "ffi").Return(
[]bundlerSrc.Advisory{
{
VulnerabilityID: "NotDetected",
PatchedVersions: []string{">= 1.9.24"},
},
{
VulnerabilityID: "Detected",
PatchedVersions: []string{">= 1.9.26"},
},
}, nil)
s := Advisory{
vs: mockVulnSrc,
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := bundler.NewAdvisory()
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
versionStr := "1.9.25-x64-mingw32"
v, err := semver.NewVersion(versionStr)
assert.NoError(t, err)
vulns, err := s.DetectVulnerabilities("ffi", v)
assert.NoError(t, err)
assert.Equal(t, 1, len(vulns))
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,32 @@
package bundler
import (
"golang.org/x/xerrors"
"github.com/aquasecurity/go-gem-version"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
)
// RubyGemsComparer represents a comparer for RubyGems
type RubyGemsComparer struct{}
// IsVulnerable checks if the package version is vulnerable to the advisory.
func (r RubyGemsComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool {
return comparer.IsVulnerable(ver, advisory, r.matchVersion)
}
// matchVersion checks if the package version satisfies the given constraint.
func (r RubyGemsComparer) matchVersion(currentVersion, constraint string) (bool, error) {
v, err := gem.NewVersion(currentVersion)
if err != nil {
return false, xerrors.Errorf("RubyGems version error (%s): %s", currentVersion, err)
}
c, err := gem.NewConstraints(constraint)
if err != nil {
return false, xerrors.Errorf("RubyGems constraint error (%s): %s", constraint, err)
}
return c.Check(v), nil
}

View File

@@ -0,0 +1,104 @@
package bundler_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
"github.com/aquasecurity/trivy/pkg/log"
)
func TestRubyGemsComparer_IsVulnerable(t *testing.T) {
type args struct {
currentVersion string
advisory types.Advisory
}
tests := []struct {
name string
args args
want bool
}{
{
name: "happy path",
args: args{
currentVersion: "1.2.3",
advisory: types.Advisory{
PatchedVersions: []string{">=1.2.0"},
},
},
want: false,
},
{
name: "pre-release",
args: args{
currentVersion: "1.2.3.a",
advisory: types.Advisory{
PatchedVersions: []string{">=1.2.3"},
},
},
want: true,
},
{
name: "pre-release without dot",
args: args{
currentVersion: "4.1a",
advisory: types.Advisory{
UnaffectedVersions: []string{"< 4.2b1"},
},
},
want: false,
},
{
// https://github.com/aquasecurity/trivy/issues/108
name: "hyphen",
args: args{
currentVersion: "1.9.25-x86-mingw32",
advisory: types.Advisory{
PatchedVersions: []string{">=1.9.24"},
},
},
want: false,
},
{
// https://github.com/aquasecurity/trivy/issues/108
name: "pessimistic",
args: args{
currentVersion: "1.8.6-java",
advisory: types.Advisory{
PatchedVersions: []string{"~> 1.5.5", "~> 1.6.8", ">= 1.7.7"},
},
},
want: false,
},
{
name: "invalid version",
args: args{
currentVersion: "1.2..4",
advisory: types.Advisory{
PatchedVersions: []string{">=1.2.3"},
},
},
want: false,
},
{
name: "invalid constraint",
args: args{
currentVersion: "1.2.4",
advisory: types.Advisory{
PatchedVersions: []string{"!1.2.0"},
},
},
want: false,
},
}
log.InitLogger(false, false)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := bundler.RubyGemsComparer{}
got := r.IsVulnerable(tt.args.currentVersion, tt.args.advisory)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,11 @@
- bucket: ruby-advisory-db
pairs:
- bucket: activesupport
pairs:
- key: CVE-2015-3226
value:
PatchedVersions:
- ">= 4.2.2"
- "~> 4.1.11"
UnaffectedVersions:
- "< 4.1.0"

View File

@@ -0,0 +1,7 @@
- bucket: ruby-advisory-db
pairs:
- bucket: activesupport
pairs:
- key: CVE-2015-3226
value:
PatchedVersions: dummy

View File

@@ -3,28 +3,30 @@ package cargo
import (
"strings"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
cargoSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/cargo"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
)
// Advisory encapsulates the cargo vulnerability scanner
type Advisory struct {
vs cargoSrc.VulnSrc
comparer comparer.Comparer
}
// NewAdvisory is the factory method to return cargo Scanner
func NewAdvisory() *Advisory {
return &Advisory{
vs: cargoSrc.NewVulnSrc(),
comparer: comparer.GenericComparer{},
}
}
// DetectVulnerabilities scans and returns the cargo vulnerabilities
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
advisories, err := s.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get cargo advisories: %w", err)
@@ -32,14 +34,23 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
if utils.MatchVersions(pkgVer, advisory.PatchedVersions) {
adv := dbTypes.Advisory{
UnaffectedVersions: advisory.UnaffectedVersions,
PatchedVersions: advisory.PatchedVersions,
}
if len(adv.UnaffectedVersions) == 0 && len(adv.PatchedVersions) == 0 {
// No patched version
adv.VulnerableVersions = []string{">=0.0.0"}
}
if !s.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: strings.TrimSpace(pkgName),
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
}
vulns = append(vulns, vuln)

View File

@@ -0,0 +1,100 @@
package cargo_test
import (
"os"
"testing"
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type args struct {
pkgName string
pkgVer string
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
args: args{
pkgName: "bumpalo",
pkgVer: "3.2.0",
},
fixtures: []string{"testdata/fixtures/cargo.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "bumpalo",
InstalledVersion: "3.2.0",
VulnerabilityID: "RUSTSEC-2020-0006",
FixedVersion: ">= 3.2.1",
},
},
},
{
name: "not detected",
args: args{
pkgName: "bumpalo",
pkgVer: "3.2.1",
},
fixtures: []string{"testdata/fixtures/cargo.yaml"},
want: nil,
},
{
name: "no patched version",
args: args{
pkgName: "bumpalo",
pkgVer: "3.2.0",
},
fixtures: []string{"testdata/fixtures/no-patched-version.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "bumpalo",
InstalledVersion: "3.2.0",
VulnerabilityID: "RUSTSEC-2020-0006",
},
},
},
{
name: "invalid JSON",
args: args{
pkgName: "bumpalo",
pkgVer: "3.2.1",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
want: nil,
wantErr: "failed to unmarshal advisory JSON",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := cargo.NewAdvisory()
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,10 @@
- bucket: rust-advisory-db
pairs:
- bucket: bumpalo
pairs:
- key: RUSTSEC-2020-0006
value:
PatchedVersions:
- ">= 3.2.1"
UnaffectedVersions:
- "< 3.0.0"

View File

@@ -0,0 +1,7 @@
- bucket: rust-advisory-db
pairs:
- bucket: bumpalo
pairs:
- key: RUSTSEC-2020-0006
value:
PatchedVersions: foo

View File

@@ -0,0 +1,8 @@
- bucket: rust-advisory-db
pairs:
- bucket: bumpalo
pairs:
- key: RUSTSEC-2020-0006
value:
PatchedVersions:
UnaffectedVersions:

View File

@@ -0,0 +1,72 @@
package comparer
import (
"strings"
"golang.org/x/xerrors"
"github.com/aquasecurity/go-version/pkg/version"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/log"
)
// Comparer is an interface for version comparison
type Comparer interface {
IsVulnerable(currentVersion string, advisory dbTypes.Advisory) bool
}
type matchVersion func(currentVersion, constraint string) (bool, error)
// IsVulnerable checks if the package version is vulnerable to the advisory.
func IsVulnerable(pkgVer string, advisory dbTypes.Advisory, match matchVersion) bool {
var matched bool
var err error
if len(advisory.VulnerableVersions) != 0 {
matched, err = match(pkgVer, strings.Join(advisory.VulnerableVersions, " || "))
if err != nil {
log.Logger.Warn(err)
return false
} else if !matched {
// the version is not vulnerable
return false
}
}
secureVersions := append(advisory.PatchedVersions, advisory.UnaffectedVersions...)
if len(secureVersions) == 0 {
// the version matches vulnerable versions and patched/unaffected versions are not provided
// or all values are empty
return matched
}
matched, err = match(pkgVer, strings.Join(secureVersions, " || "))
if err != nil {
log.Logger.Warn(err)
return false
}
return !matched
}
// GenericComparer represents a comparer for semver-like versioning
type GenericComparer struct{}
// IsVulnerable checks if the package version is vulnerable to the advisory.
func (v GenericComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool {
return IsVulnerable(ver, advisory, v.matchVersion)
}
// matchVersion checks if the package version satisfies the given constraint.
func (v GenericComparer) matchVersion(currentVersion, constraint string) (bool, error) {
ver, err := version.Parse(currentVersion)
if err != nil {
return false, xerrors.Errorf("version error (%s): %s", currentVersion, err)
}
c, err := version.NewConstraints(constraint)
if err != nil {
return false, xerrors.Errorf("constraint error (%s): %s", currentVersion, err)
}
return c.Check(ver), nil
}

View File

@@ -0,0 +1,96 @@
package comparer_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/log"
)
func TestGenericComparer_IsVulnerable(t *testing.T) {
type args struct {
ver string
advisory types.Advisory
}
tests := []struct {
name string
args args
want bool
}{
{
name: "happy path",
args: args{
ver: "1.2.3",
advisory: types.Advisory{
VulnerableVersions: []string{"<=1.0"},
PatchedVersions: []string{">=1.1"},
},
},
},
{
name: "no patch",
args: args{
ver: "1.2.3",
advisory: types.Advisory{
VulnerableVersions: []string{"<=99.999.99999"},
PatchedVersions: []string{"<0.0.0"},
},
},
want: true,
},
{
name: "pre-release",
args: args{
ver: "1.2.2-alpha",
advisory: types.Advisory{
VulnerableVersions: []string{"<=1.2.2"},
PatchedVersions: []string{">=1.2.2"},
},
},
want: true,
},
{
name: "multiple constraints",
args: args{
ver: "2.0.0",
advisory: types.Advisory{
VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"},
PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"},
},
},
want: true,
},
{
name: "invalid version",
args: args{
ver: "1.2..4",
advisory: types.Advisory{
VulnerableVersions: []string{"<1.0.0"},
},
},
want: false,
},
{
name: "improper constraint",
args: args{
ver: "1.2.3",
advisory: types.Advisory{
VulnerableVersions: []string{"*"},
PatchedVersions: nil,
},
},
want: false,
},
}
log.InitLogger(false, false)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := comparer.GenericComparer{}
got := v.IsVulnerable(tt.args.ver, tt.args.advisory)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -4,30 +4,30 @@ import (
"fmt"
"strings"
"github.com/aquasecurity/trivy/pkg/types"
"golang.org/x/xerrors"
"github.com/Masterminds/semver/v3"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
composerSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/composer"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
)
// Advisory encapsulates composer.VulnSrc
type Advisory struct {
vs composerSrc.VulnSrc
comparer comparer.Comparer // TODO: implement a comparer for Composer
}
// NewAdvisory is the factory method of Advisory
func NewAdvisory() *Advisory {
return &Advisory{
vs: composerSrc.NewVulnSrc(),
comparer: comparer.GenericComparer{},
}
}
// DetectVulnerabilities returns the vulnerabilities in a package
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
ref := fmt.Sprintf("composer://%s", pkgName)
advisories, err := s.vs.Get(ref)
if err != nil {
@@ -47,14 +47,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
affectedVersions = append(affectedVersions, strings.Join(branch.Versions, ", "))
}
if !utils.MatchVersions(pkgVer, affectedVersions) {
adv := dbTypes.Advisory{VulnerableVersions: affectedVersions}
if !s.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: pkgName,
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: strings.Join(patchedVersions, ", "),
}
vulns = append(vulns, vuln)

View File

@@ -0,0 +1,84 @@
package composer_test
import (
"os"
"testing"
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type args struct {
pkgName string
pkgVer string
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
args: args{
pkgName: "aws/aws-sdk-php",
pkgVer: "3.2.0",
},
fixtures: []string{"testdata/fixtures/composer.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "aws/aws-sdk-php",
InstalledVersion: "3.2.0",
VulnerabilityID: "CVE-2015-5723",
FixedVersion: "3.2.1",
},
},
},
{
name: "not detected",
args: args{
pkgName: "guzzlehttp/guzzle",
pkgVer: "5.3.1",
},
fixtures: []string{"testdata/fixtures/composer.yaml"},
want: nil,
},
{
name: "malformed JSON",
args: args{
pkgName: "aws/aws-sdk-php",
pkgVer: "3.2.0",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
wantErr: "failed to unmarshal advisory JSON",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := composer.NewAdvisory()
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,28 @@
- bucket: php-security-advisories
pairs:
- bucket: "composer://aws/aws-sdk-php"
pairs:
- key: CVE-2015-5723
value:
Branches:
3.x:
Versions:
- ">=3.0.0"
- "<3.2.1"
- bucket: "composer://guzzlehttp/guzzle"
pairs:
- key: CVE-2016-5385
value:
Branches:
4.x:
Versions:
- ">=4.0.0rc2"
- "<4.2.4"
5.3:
Versions:
- ">=5"
- "<5.3.1"
master:
Versions:
- ">=6"
- "<6.2.1"

View File

@@ -0,0 +1,7 @@
- bucket: php-security-advisories
pairs:
- bucket: "composer://aws/aws-sdk-php"
pairs:
- key: CVE-2015-5723
value:
Branches: invalid

View File

@@ -4,13 +4,11 @@ import (
"path/filepath"
"time"
"github.com/Masterminds/semver/v3"
"github.com/google/wire"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -57,14 +55,7 @@ func detect(driver Driver, libs []ftypes.LibraryInfo) ([]types.DetectedVulnerabi
log.Logger.Infof("Detecting %s vulnerabilities...", driver.Type())
var vulnerabilities []types.DetectedVulnerability
for _, lib := range libs {
v, err := semver.NewVersion(utils.FormatPatchVersion(lib.Library.Version))
if err != nil {
log.Logger.Debugf("invalid version, library: %s, version: %s, error: %s\n",
lib.Library.Name, lib.Library.Version, err)
continue
}
vulns, err := driver.Detect(lib.Library.Name, v)
vulns, err := driver.Detect(lib.Library.Name, lib.Library.Version)
if err != nil {
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", driver.Type(), err)
}

View File

@@ -3,13 +3,13 @@ package library
import (
"fmt"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
ecosystem "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
"github.com/aquasecurity/trivy/pkg/detector/library/bundler"
"github.com/aquasecurity/trivy/pkg/detector/library/cargo"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/detector/library/composer"
"github.com/aquasecurity/trivy/pkg/detector/library/ghsa"
"github.com/aquasecurity/trivy/pkg/detector/library/node"
@@ -23,7 +23,7 @@ type Factory interface {
}
type advisory interface {
DetectVulnerabilities(string, *semver.Version) ([]types.DetectedVulnerability, error)
DetectVulnerabilities(string, string) ([]types.DetectedVulnerability, error)
}
// DriverFactory implements Factory
@@ -31,19 +31,18 @@ type DriverFactory struct{}
// NewDriver factory method for driver
func (d DriverFactory) NewDriver(filename string) (Driver, error) {
// TODO: use DI
var driver Driver
switch filename {
case "Gemfile.lock":
driver = newRubyDriver()
driver = newRubyGemsDriver()
case "Cargo.lock":
driver = newRustDriver()
driver = newCargoDriver()
case "composer.lock":
driver = newPHPDriver()
driver = newComposerDriver()
case "package-lock.json", "yarn.lock":
driver = newNodejsDriver()
driver = newNpmDriver()
case "Pipfile.lock", "poetry.lock":
driver = newPythonDriver()
driver = newPipDriver()
default:
return Driver{}, xerrors.New(fmt.Sprintf("unsupport filename %s", filename))
}
@@ -52,20 +51,20 @@ func (d DriverFactory) NewDriver(filename string) (Driver, error) {
// Driver implements the advisory
type Driver struct {
lang string
ecosystem string
advisories []advisory
}
// NewDriver is the factory method from drier
func NewDriver(lang string, advisories ...advisory) Driver {
return Driver{lang: lang, advisories: advisories}
func NewDriver(advisories ...advisory) Driver {
return Driver{advisories: advisories}
}
// Detect scans and returns vulnerabilities
func (d *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
func (d *Driver) Detect(pkgName string, pkgVer string) ([]types.DetectedVulnerability, error) {
var detectedVulnerabilities []types.DetectedVulnerability
uniqVulnIDMap := make(map[string]struct{})
for _, d := range append(d.advisories, NewAdvisory(d.lang)) {
for _, d := range d.advisories {
vulns, err := d.DetectVulnerabilities(pkgName, pkgVer)
if err != nil {
return nil, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
@@ -82,27 +81,36 @@ func (d *Driver) Detect(pkgName string, pkgVer *semver.Version) ([]types.Detecte
return detectedVulnerabilities, nil
}
// Type returns the driver lang
// Type returns the driver ecosystem
func (d *Driver) Type() string {
return d.lang
return d.ecosystem
}
func newRubyDriver() Driver {
return NewDriver(vulnerability.Ruby, ghsa.NewAdvisory(ecosystem.Rubygems), bundler.NewAdvisory())
func newRubyGemsDriver() Driver {
c := bundler.RubyGemsComparer{}
return NewDriver(ghsa.NewAdvisory(ecosystem.Rubygems, c), bundler.NewAdvisory(),
NewAdvisory(vulnerability.RubyGems, c))
}
func newPHPDriver() Driver {
return NewDriver(vulnerability.PHP, ghsa.NewAdvisory(ecosystem.Composer), composer.NewAdvisory())
func newComposerDriver() Driver {
c := comparer.GenericComparer{}
return NewDriver(
ghsa.NewAdvisory(ecosystem.Composer, c), composer.NewAdvisory(),
NewAdvisory(vulnerability.Composer, c))
}
func newRustDriver() Driver {
return NewDriver(vulnerability.Rust, cargo.NewAdvisory())
func newCargoDriver() Driver {
return NewDriver(cargo.NewAdvisory(), NewAdvisory(vulnerability.Cargo, comparer.GenericComparer{}))
}
func newNodejsDriver() Driver {
return NewDriver(vulnerability.Nodejs, ghsa.NewAdvisory(ecosystem.Npm), node.NewAdvisory())
func newNpmDriver() Driver {
c := node.NpmComparer{}
return NewDriver(ghsa.NewAdvisory(ecosystem.Npm, c), node.NewAdvisory(),
NewAdvisory(vulnerability.Npm, c))
}
func newPythonDriver() Driver {
return NewDriver(vulnerability.Python, ghsa.NewAdvisory(ecosystem.Pip), python.NewAdvisory())
func newPipDriver() Driver {
c := comparer.GenericComparer{}
return NewDriver(ghsa.NewAdvisory(ecosystem.Pip, c), python.NewAdvisory(),
NewAdvisory(vulnerability.Pip, c))
}

View File

@@ -4,7 +4,6 @@ import (
"os"
"testing"
"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -20,7 +19,7 @@ func TestDriver_Detect(t *testing.T) {
}
type args struct {
pkgName string
pkgVer *semver.Version
pkgVer string
}
tests := []struct {
name string
@@ -36,7 +35,7 @@ func TestDriver_Detect(t *testing.T) {
fields: fields{fileName: "composer.lock"},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.2.6"),
pkgVer: "4.2.6",
},
want: []types.DetectedVulnerability{
{
@@ -53,7 +52,7 @@ func TestDriver_Detect(t *testing.T) {
fields: fields{fileName: "composer.lock"},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.2.6"),
pkgVer: "4.2.6",
},
want: []types.DetectedVulnerability{
{
@@ -70,7 +69,7 @@ func TestDriver_Detect(t *testing.T) {
fields: fields{fileName: "composer.lock"},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.4.6"),
pkgVer: "4.4.6",
},
want: []types.DetectedVulnerability{
{
@@ -87,7 +86,7 @@ func TestDriver_Detect(t *testing.T) {
fields: fields{fileName: "Gemfile.lock"},
args: args{
pkgName: "activesupport",
pkgVer: semver.MustParse("4.1.1"),
pkgVer: "4.1.1",
},
want: []types.DetectedVulnerability{
{
@@ -104,7 +103,7 @@ func TestDriver_Detect(t *testing.T) {
fields: fields{fileName: "composer.lock"},
args: args{
pkgName: "symfony/symfony",
pkgVer: semver.MustParse("4.4.7"),
pkgVer: "4.4.7",
},
},
}

View File

@@ -3,11 +3,11 @@ package ghsa
import (
"strings"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -19,17 +19,19 @@ type VulnSrc interface {
// Advisory implements VulnSrc
type Advisory struct {
vs VulnSrc
comparer comparer.Comparer
}
// NewAdvisory is the factory method to return advisory
func NewAdvisory(ecosystem ghsa.Ecosystem) *Advisory {
func NewAdvisory(ecosystem ghsa.Ecosystem, comparer comparer.Comparer) *Advisory {
return &Advisory{
vs: ghsa.NewVulnSrc(ecosystem),
comparer: comparer,
}
}
// DetectVulnerabilities scans package for vulnerabilities
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
advisories, err := s.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get ghsa advisories: %w", err)
@@ -37,14 +39,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
if !utils.MatchVersions(pkgVer, advisory.VulnerableVersions) {
adv := dbTypes.Advisory{VulnerableVersions: advisory.VulnerableVersions}
if !s.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: pkgName,
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: strings.Join(advisory.PatchedVersions, ", "),
}
vulns = append(vulns, vuln)

View File

@@ -0,0 +1,102 @@
package ghsa_test
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ghsaSrc "github.com/aquasecurity/trivy-db/pkg/vulnsrc/ghsa"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/detector/library/ghsa"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type fields struct {
ecosystem ghsaSrc.Ecosystem
comparer comparer.Comparer
}
type args struct {
pkgName string
pkgVer string
}
tests := []struct {
name string
args args
fields fields
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
fields: fields{
ecosystem: ghsaSrc.Composer,
comparer: comparer.GenericComparer{},
},
args: args{
pkgName: "symfony/symfony",
pkgVer: "5.1.5-alpha",
},
fixtures: []string{"testdata/fixtures/ghsa.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "symfony/symfony",
InstalledVersion: "5.1.5-alpha",
VulnerabilityID: "CVE-2020-15094",
FixedVersion: "5.1.5, 4.4.13",
},
},
},
{
name: "not detected",
fields: fields{
ecosystem: ghsaSrc.Composer,
comparer: comparer.GenericComparer{},
},
args: args{
pkgName: "symfony/symfony",
pkgVer: "5.1.5",
},
fixtures: []string{"testdata/fixtures/ghsa.yaml"},
want: nil,
},
{
name: "malformed JSON",
fields: fields{
ecosystem: ghsaSrc.Composer,
comparer: comparer.GenericComparer{},
},
args: args{
pkgName: "symfony/symfony",
pkgVer: "5.1.5",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
wantErr: "failed to unmarshal advisory JSON",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := ghsa.NewAdvisory(tt.fields.ecosystem, tt.fields.comparer)
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,12 @@
- bucket: GitHub Security Advisory Composer
pairs:
- bucket: "symfony/symfony"
pairs:
- key: CVE-2020-15094
value:
PatchedVersions:
- 5.1.5
- 4.4.13
VulnerableVersions:
- ">= 5.0.0, < 5.1.5"
- ">= 4.4.0, < 4.4.13"

View File

@@ -0,0 +1,7 @@
- bucket: GitHub Security Advisory Composer
pairs:
- bucket: "symfony/symfony"
pairs:
- key: CVE-2020-15094
value:
PatchedVersions: malformed

View File

@@ -5,15 +5,14 @@ import (
"golang.org/x/xerrors"
"github.com/Masterminds/semver/v3"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/node"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
)
// Advisory encapsulate Node vulnerability source
type Advisory struct {
comparer NpmComparer
vs node.VulnSrc
}
@@ -21,48 +20,54 @@ type Advisory struct {
func NewAdvisory() *Advisory {
return &Advisory{
vs: node.NewVulnSrc(),
comparer: NpmComparer{},
}
}
// DetectVulnerabilities scans and return vulnerability using Node package scanner
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
replacer := strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc", " <", ", <", " >", ", >")
advisories, err := s.vs.Get(pkgName)
func (a *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
advisories, err := a.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get node advisories: %w", err)
}
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
// e.g. <= 2.15.0 || >= 3.0.0 <= 3.8.2
// => {"<=2.15.0", ">= 3.0.0, <= 3.8.2"}
var vulnerableVersions []string
for _, version := range strings.Split(advisory.VulnerableVersions, " || ") {
version = strings.TrimSpace(version)
vulnerableVersions = append(vulnerableVersions, replacer.Replace(version))
}
if !utils.MatchVersions(pkgVer, vulnerableVersions) {
continue
}
var patchedVersions []string
for _, version := range strings.Split(advisory.PatchedVersions, " || ") {
version = strings.TrimSpace(version)
patchedVersions = append(patchedVersions, replacer.Replace(version))
}
if utils.MatchVersions(pkgVer, patchedVersions) {
adv := convertToGenericAdvisory(advisory)
if !a.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: pkgName,
InstalledVersion: pkgVer.String(),
FixedVersion: strings.Join(patchedVersions, ", "),
InstalledVersion: pkgVer,
FixedVersion: createFixedVersions(advisory.PatchedVersions),
}
vulns = append(vulns, vuln)
}
return vulns, nil
}
func convertToGenericAdvisory(advisory node.Advisory) dbTypes.Advisory {
var vulnerable, patched []string
if advisory.VulnerableVersions != "" {
vulnerable = strings.Split(advisory.VulnerableVersions, "||")
}
if advisory.PatchedVersions != "" {
patched = strings.Split(advisory.PatchedVersions, "||")
}
return dbTypes.Advisory{
VulnerableVersions: vulnerable,
PatchedVersions: patched,
}
}
func createFixedVersions(patchedVersions string) string {
var fixedVersions []string
for _, s := range strings.Split(patchedVersions, "||") {
fixedVersions = append(fixedVersions, strings.TrimSpace(s))
}
return strings.Join(fixedVersions, ", ")
}

View File

@@ -0,0 +1,91 @@
package node_test
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/detector/library/node"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type args struct {
pkgName string
pkgVer string
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
args: args{
pkgName: "electron",
pkgVer: "2.0.17",
},
fixtures: []string{"testdata/fixtures/npm.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "electron",
InstalledVersion: "2.0.17",
VulnerabilityID: "CVE-2019-5786",
FixedVersion: "^2.0.18, ^3.0.16, ^3.1.6, ^4.0.8, ^5.0.0-beta.5",
},
},
},
{
name: "not detected",
args: args{
pkgName: "electron",
pkgVer: "2.0.18",
},
fixtures: []string{"testdata/fixtures/npm.yaml"},
want: nil,
},
{
name: "empty value",
args: args{
pkgName: "electron",
pkgVer: "2.0.18",
},
fixtures: []string{"testdata/fixtures/no-value.yaml"},
want: nil,
},
{name: "malformed JSON",
args: args{
pkgName: "electron",
pkgVer: "2.0.18",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
wantErr: "failed to unmarshal advisory JSON",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := node.NewAdvisory()
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,32 @@
package node
import (
"golang.org/x/xerrors"
npm "github.com/aquasecurity/go-npm-version/pkg"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
)
// NpmComparer represents a comparer for npm
type NpmComparer struct{}
// IsVulnerable checks if the package version is vulnerable to the advisory.
func (n NpmComparer) IsVulnerable(ver string, advisory dbTypes.Advisory) bool {
return comparer.IsVulnerable(ver, advisory, n.matchVersion)
}
// matchVersion checks if the package version satisfies the given constraint.
func (n NpmComparer) matchVersion(currentVersion, constraint string) (bool, error) {
v, err := npm.NewVersion(currentVersion)
if err != nil {
return false, xerrors.Errorf("npm version error (%s): %s", currentVersion, err)
}
c, err := npm.NewConstraints(constraint)
if err != nil {
return false, xerrors.Errorf("npm constraint error (%s): %s", constraint, err)
}
return c.Check(v), nil
}

View File

@@ -0,0 +1,140 @@
package node_test
import (
"testing"
"github.com/stretchr/testify/assert"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library/node"
"github.com/aquasecurity/trivy/pkg/log"
)
func TestNpmComparer_MatchVersion(t *testing.T) {
type args struct {
currentVersion string
advisory dbTypes.Advisory
}
tests := []struct {
name string
args args
want bool
}{
{
name: "happy path",
args: args{
currentVersion: "1.0.0",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"<=1.0"},
PatchedVersions: []string{">=1.1"},
},
},
want: true,
},
{
name: "no patch",
args: args{
currentVersion: "1.2.3",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"<=99.999.99999"},
PatchedVersions: []string{"<0.0.0"},
},
},
want: true,
},
{
name: "no patch with wildcard",
args: args{
currentVersion: "1.2.3",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"*"},
PatchedVersions: nil,
},
},
want: true,
},
{
name: "pre-release",
args: args{
currentVersion: "1.2.3-alpha",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"<=1.2.2"},
PatchedVersions: []string{">=1.2.2"},
},
},
want: false,
},
{
name: "multiple constraints",
args: args{
currentVersion: "2.0.0",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{">=1.7.0 <1.7.16", ">=1.8.0 <1.8.8", ">=2.0.0 <2.0.8", ">=3.0.0-beta.1 <3.0.0-beta.7"},
PatchedVersions: []string{">=3.0.0-beta.7", ">=2.0.8 <3.0.0-beta.1", ">=1.8.8 <2.0.0", ">=1.7.16 <1.8.0"},
},
},
want: true,
},
{
name: "x",
args: args{
currentVersion: "2.0.1",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"2.0.x", "2.1.x"},
PatchedVersions: []string{">=2.2.x"},
},
},
want: true,
},
{
name: "exact versions",
args: args{
currentVersion: "2.1.0-M1",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"2.1.0-M1", "2.1.0-M2"},
PatchedVersions: []string{">=2.1.0"},
},
},
want: true,
},
{
name: "caret",
args: args{
currentVersion: "2.0.18",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"<2.0.18", "<3.0.16"},
PatchedVersions: []string{"^2.0.18", "^3.0.16"},
},
},
want: false,
},
{
name: "invalid version",
args: args{
currentVersion: "1.2..4",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"<1.0.0"},
},
},
want: false,
},
{
name: "invalid constraint",
args: args{
currentVersion: "1.2.4",
advisory: dbTypes.Advisory{
VulnerableVersions: []string{"!1.0.0"},
},
},
want: false,
},
}
log.InitLogger(false, false)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := node.NpmComparer{}
got := c.IsVulnerable(tt.args.currentVersion, tt.args.advisory)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,9 @@
- bucket: nodejs-security-wg
pairs:
- bucket: electron
pairs:
- key: CVE-2019-5786
value:
PatchedVersions:
- 1
- 2

View File

@@ -0,0 +1,6 @@
- bucket: nodejs-security-wg
pairs:
- bucket: electron
pairs:
- key: CVE-2019-5786
value:

View File

@@ -0,0 +1,8 @@
- bucket: nodejs-security-wg
pairs:
- bucket: electron
pairs:
- key: CVE-2019-5786
value:
PatchedVersions: "^2.0.18 || ^3.0.16 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5"
VulnerableVersions: "<2.0.18 || <3.0.16 || <3.1.6 || <4.0.8 || <5.0.0-beta.5"

View File

@@ -3,28 +3,30 @@ package python
import (
"strings"
"github.com/Masterminds/semver/v3"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/python"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/detector/library/comparer"
"github.com/aquasecurity/trivy/pkg/types"
)
// Advisory encapsulates the python vulnerability scanner
type Advisory struct {
vs python.VulnSrc
comparer comparer.Comparer
}
// NewAdvisory is the factory method to reutrn Python Advisory
// NewAdvisory is the factory method to return Python Advisory
func NewAdvisory() *Advisory {
return &Advisory{
vs: python.NewVulnSrc(),
comparer: comparer.GenericComparer{},
}
}
// DetectVulnerabilities scans and returns pythin vulnerabilities
func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version) ([]types.DetectedVulnerability, error) {
// DetectVulnerabilities scans and returns python vulnerabilities
func (s *Advisory) DetectVulnerabilities(pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
advisories, err := s.vs.Get(pkgName)
if err != nil {
return nil, xerrors.Errorf("failed to get python advisories: %w", err)
@@ -32,14 +34,15 @@ func (s *Advisory) DetectVulnerabilities(pkgName string, pkgVer *semver.Version)
var vulns []types.DetectedVulnerability
for _, advisory := range advisories {
if !utils.MatchVersions(pkgVer, advisory.Specs) {
adv := dbTypes.Advisory{VulnerableVersions: advisory.Specs}
if !s.comparer.IsVulnerable(pkgVer, adv) {
continue
}
vuln := types.DetectedVulnerability{
VulnerabilityID: advisory.VulnerabilityID,
PkgName: pkgName,
InstalledVersion: pkgVer.String(),
InstalledVersion: pkgVer,
FixedVersion: createFixedVersions(advisory.Specs),
}
vulns = append(vulns, vuln)

View File

@@ -0,0 +1,84 @@
package python_test
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/detector/library/python"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/utils"
)
func TestAdvisory_DetectVulnerabilities(t *testing.T) {
type args struct {
pkgName string
pkgVer string
}
tests := []struct {
name string
args args
fixtures []string
want []types.DetectedVulnerability
wantErr string
}{
{
name: "detected",
args: args{
pkgName: "django",
pkgVer: "2.2.11-alpha",
},
fixtures: []string{"testdata/fixtures/pip.yaml"},
want: []types.DetectedVulnerability{
{
PkgName: "django",
InstalledVersion: "2.2.11-alpha",
VulnerabilityID: "CVE-2020-9402",
FixedVersion: "1.11.29, 2.2.11, 3.0.4",
},
},
},
{
// https://github.com/aquasecurity/trivy/issues/713
name: "not detected",
args: args{
pkgName: "django",
pkgVer: "3.0.10",
},
fixtures: []string{"testdata/fixtures/pip.yaml"},
want: nil,
},
{
name: "malformed JSON",
args: args{
pkgName: "django",
pkgVer: "2.0.18",
},
fixtures: []string{"testdata/fixtures/invalid-type.yaml"},
wantErr: "failed to unmarshal advisory JSON",
},
}
log.InitLogger(false, true)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := utils.InitTestDB(t, tt.fixtures)
defer os.RemoveAll(dir)
a := python.NewAdvisory()
got, err := a.DetectVulnerabilities(tt.args.pkgName, tt.args.pkgVer)
if tt.wantErr != "" {
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,7 @@
- bucket: python-safety-db
pairs:
- bucket: django
pairs:
- key: CVE-2020-9402
value:
Specs: foo

View File

@@ -0,0 +1,14 @@
- bucket: python-safety-db
pairs:
- bucket: django
pairs:
- key: CVE-2019-19844
value:
Specs:
- "==3.0"
- key: CVE-2020-9402
value:
Specs:
- ">=1.11.0,<1.11.29"
- ">=2.2.0,<2.2.11"
- ">=3.0.0,<3.0.4"

View File

@@ -1,4 +1,4 @@
- bucket: "php::GitHub Security Advisory Composer"
- bucket: "composer::GitHub Security Advisory Composer"
pairs:
- bucket: symfony/symfony
pairs:
@@ -8,7 +8,7 @@
- 4.2.7
VulnerableVersions:
- ">= 4.2.0, < 4.2.7"
- bucket: "php::php-security-advisories"
- bucket: "composer::php-security-advisories"
pairs:
- bucket: symfony/symfony
pairs:

View File

@@ -1,4 +1,4 @@
- bucket: "ruby::ruby-advisory-db"
- bucket: "rubygems::ruby-advisory-db"
pairs:
- bucket: activesupport
pairs:

View File

@@ -2,60 +2,10 @@ package utils
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
)
var (
replacer = strings.NewReplacer(".alpha", "-alpha", ".beta", "-beta", ".rc", "-rc", "==", "=")
preReleaseSplitter = regexp.MustCompile(`(?P<Number>^[0-9]+)(?P<PreRelease>[a-z]*.*)`)
)
// MatchVersions runs comparison on currentVersion based on rangeVersions and return true/false
func MatchVersions(currentVersion *semver.Version, rangeVersions []string) bool {
for _, v := range rangeVersions {
v = replacer.Replace(v)
constraintParts := strings.Split(v, ",")
for j := range constraintParts {
constraintParts[j] = FormatPatchVersion(constraintParts[j])
}
v = strings.Join(constraintParts, ",")
if v == "" {
continue
}
c, err := semver.NewConstraint(v)
if err != nil {
log.Logger.Debug("NewConstraint", "error", err)
continue
}
// Validate a version against a constraint.
valid, msgs := c.Validate(currentVersion)
if valid {
return true
}
for _, m := range msgs {
// re-validate after removing the patch version
if strings.HasSuffix(m.Error(), "is a prerelease version and the constraint is only looking for release versions") {
v2, err := semver.NewVersion(fmt.Sprintf("%v.%v.%v", currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch()))
if err == nil {
valid, _ = c.Validate(v2)
if valid {
return true
}
}
}
}
}
return false
}
// FormatVersion formats the package version based on epoch, version & release
func FormatVersion(pkg types.Package) string {
return formatVersion(pkg.Epoch, pkg.Version, pkg.Release)
@@ -66,34 +16,6 @@ func FormatSrcVersion(pkg types.Package) string {
return formatVersion(pkg.SrcEpoch, pkg.SrcVersion, pkg.SrcRelease)
}
// FormatPatchVersion returns the semver compatible version string given non-semver version
func FormatPatchVersion(version string) string {
part := strings.Split(version, ".")
if len(part) > 3 {
if _, err := strconv.Atoi(part[2]); err == nil {
version = strings.Join(part[:3], ".") + "-" + strings.Join(part[3:], ".")
}
} else {
for i := range part {
res := preReleaseSplitter.FindStringSubmatch(part[i])
if res == nil {
continue
}
number := res[1]
preRelease := res[2]
if preRelease != "" {
if !strings.HasPrefix(preRelease, "-") {
preRelease = "-" + preRelease
}
part[i] = number + preRelease
break
}
}
version = strings.Join(part, ".")
}
return version
}
func formatVersion(epoch int, version, release string) string {
v := version
if release != "" {

View File

@@ -3,161 +3,71 @@ package utils
import (
"testing"
"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/fanal/types"
)
func TestMatchVersions(t *testing.T) {
testCases := []struct {
func TestFormatSrcVersion(t *testing.T) {
tests := []struct {
name string
currentVersion string
rangeVersion []string
expectedCheck bool
pkg types.Package
want string
}{
{
name: "pass: expect true when os/machine is in version string",
currentVersion: "1.9.25-x86-mingw32",
rangeVersion: []string{`>= 1.9.24`},
expectedCheck: true,
name: "happy path",
pkg: types.Package{
SrcVersion: "1.2.3",
SrcRelease: "1",
},
want: "1.2.3-1",
},
{
name: "pass: expect true when language is in version string",
currentVersion: "1.8.6-java",
rangeVersion: []string{`~> 1.5.5`, `~> 1.6.8`, `>= 1.7.7`},
expectedCheck: true,
name: "with epoch",
pkg: types.Package{
SrcEpoch: 2,
SrcVersion: "1.2.3",
SrcRelease: "alpha",
},
{
name: "expect false",
currentVersion: "1.9.23-x86-mingw32",
rangeVersion: []string{`>= 1.9.24`},
expectedCheck: false,
},
{
// passes if (>= 1.2.3, < 2.0.0)
name: "expect false",
currentVersion: "1.2.4",
rangeVersion: []string{`^1.2.3`},
expectedCheck: true,
},
{
// passes if (>= 1.2.3, < 2.0.0)
name: "expect false",
currentVersion: "2.0.0",
rangeVersion: []string{`^1.2.3`},
expectedCheck: false,
},
{
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
name: "expect false",
currentVersion: "3.1.16",
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
expectedCheck: true,
},
{
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
name: "expect false",
currentVersion: "6.0.0",
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
expectedCheck: false,
},
{
// passes if (>= 2.0.18, < 3.0.0) || (>= 3.1.16, < 4.0.0) || (>= 4.0.8, < 5.0.0) || ( >=5.0.0,<6.0.0)
name: "expect false",
currentVersion: "5.0.0-beta.5",
rangeVersion: []string{`^2.0.18 || ^3.1.6 || ^4.0.8 || ^5.0.0-beta.5`},
expectedCheck: true,
},
{
// Ruby GEM with more dots
name: "expect false",
currentVersion: "1.10.9-java",
rangeVersion: []string{`>= 1.6.7.1`},
expectedCheck: true,
},
{
name: "expect prerelease suffixed in minor version to work",
currentVersion: "4.1a",
rangeVersion: []string{`< 4.2b1`},
expectedCheck: true,
},
{
name: "expect prerelease suffixed in patch version to work",
currentVersion: "4.1.2a",
rangeVersion: []string{`< 4.2b1`},
expectedCheck: true,
},
{
name: "expect prerelease suffixed in patch version to work in failing case",
currentVersion: "4.1.2c",
rangeVersion: []string{`<= 4.1.2b`},
expectedCheck: false,
},
{
name: "expect double equal to work",
currentVersion: "1.7",
rangeVersion: []string{`==1.7`},
expectedCheck: true,
want: "2:1.2.3-alpha",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
v, err := semver.NewVersion(FormatPatchVersion(tc.currentVersion))
require.NoError(t, err)
match := MatchVersions(v, tc.rangeVersion)
assert.Equal(t, tc.expectedCheck, match)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FormatSrcVersion(tt.pkg)
assert.Equal(t, tt.want, got)
})
}
}
func TestFormatPatchVersio(t *testing.T) {
testCases := []struct {
func TestFormatVersion(t *testing.T) {
tests := []struct {
name string
currentVersion string
expectedVersion string
pkg types.Package
want string
}{
{
name: "patch with no dots should return version should be unchanged",
currentVersion: "1.2.3-beta",
expectedVersion: "1.2.3-beta",
name: "happy path",
pkg: types.Package{
Version: "1.2.3",
Release: "1",
},
want: "1.2.3-1",
},
{
name: "patch with dots after non-integer patch version should be unchanged",
currentVersion: "1.2.3-beta.1",
expectedVersion: "1.2.3-beta.1",
name: "with epoch",
pkg: types.Package{
Epoch: 2,
Version: "1.2.3",
Release: "alpha",
},
{
name: "patch with dots after integer patch version should append dash and join rest versions parts",
currentVersion: "1.2.3.4",
expectedVersion: "1.2.3-4",
},
{
name: "patch with dots after integer patch version should append dash and join extra versions parts",
currentVersion: "1.2.3.4.5",
expectedVersion: "1.2.3-4.5",
},
{
name: "unchanged case",
currentVersion: "1.2.3.4-5",
expectedVersion: "1.2.3-4-5",
},
{
name: "prerelease suffixed in minor",
currentVersion: "1.11a",
expectedVersion: "1.11-a",
},
{
name: "prerelease suffixed in patch",
currentVersion: "1.11.5rc",
expectedVersion: "1.11.5-rc",
want: "2:1.2.3-alpha",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := FormatPatchVersion(tc.currentVersion)
assert.Equal(t, tc.expectedVersion, got)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := FormatVersion(tt.pkg)
assert.Equal(t, tt.want, got)
})
}
}