Compare commits

..

24 Commits

Author SHA1 Message Date
MaineK00n
bfc3828ce1 chore(deps): update goval-dictionary and gost (#1452) 2022-04-27 13:03:11 +09:00
dependabot[bot]
c7eac4e7fe chore(deps): bump github.com/aquasecurity/trivy from 0.25.4 to 0.27.0 (#1451)
* chore(deps): bump github.com/aquasecurity/trivy from 0.25.4 to 0.27.0

Bumps [github.com/aquasecurity/trivy](https://github.com/aquasecurity/trivy) from 0.25.4 to 0.27.0.
- [Release notes](https://github.com/aquasecurity/trivy/releases)
- [Changelog](https://github.com/aquasecurity/trivy/blob/main/goreleaser.yml)
- [Commits](https://github.com/aquasecurity/trivy/compare/v0.25.4...v0.27.0)

---
updated-dependencies:
- dependency-name: github.com/aquasecurity/trivy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(library): support go.mod scan

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: MaineK00n <mainek00n.1229@gmail.com>
2022-04-27 12:46:47 +09:00
MaineK00n
cc63a0eccf feat(ubuntu): add Jammy Jellyfish(22.04) (#1431)
* feat(ubuntu): add Jammy Jellyfish(22.04)

* chore(deps): gost update

* chore(oval/ubuntu): fill kernel package name temporarily
2022-04-27 11:04:00 +09:00
Satoru Nihei
fd18df1dd4 feat: parse OS version from result of trivy-scan (#1444)
* chore(deps): bump github.com/aquasecurity/trivy from 0.24.2 to 0.25.4

Bumps [github.com/aquasecurity/trivy](https://github.com/aquasecurity/trivy) from 0.24.2 to 0.25.4.
- [Release notes](https://github.com/aquasecurity/trivy/releases)
- [Changelog](https://github.com/aquasecurity/trivy/blob/main/goreleaser.yml)
- [Commits](https://github.com/aquasecurity/trivy/compare/v0.24.2...v0.25.4)

---
updated-dependencies:
- dependency-name: github.com/aquasecurity/trivy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* test: add testcase

* feat: parse metadata

* refactor: change detect logic

* refactor: change parsing logic

* refactor: refactor check logic before detect

* fix: impl without reuseScannedCves

* feat: complement :latest tag

* Update contrib/trivy/parser/v2/parser.go

Co-authored-by: MaineK00n <mainek00n.1229@gmail.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: MaineK00n <mainek00n.1229@gmail.com>
2022-04-27 10:28:20 +09:00
MaineK00n
8775b5efdf chore: fix lint error (#1438)
* chore: fix lint: revive error

* chore: golanci-lint uses go 1.18

* chore: refactor tasks in GNUmakefile

* chore: add trivy binary in fvuls image
2022-04-15 18:12:13 +09:00
dependabot[bot]
a9f29a6c5d chore(deps): bump github.com/aquasecurity/trivy from 0.24.2 to 0.25.1 (#1436)
* chore(deps): bump github.com/aquasecurity/trivy from 0.24.2 to 0.25.0

Bumps [github.com/aquasecurity/trivy](https://github.com/aquasecurity/trivy) from 0.24.2 to 0.25.0.
- [Release notes](https://github.com/aquasecurity/trivy/releases)
- [Changelog](https://github.com/aquasecurity/trivy/blob/main/goreleaser.yml)
- [Commits](https://github.com/aquasecurity/trivy/compare/v0.24.2...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/aquasecurity/trivy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(deps): bump up Go to 1.18 and trivy v0.25.1

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: MaineK00n <mainek00n.1229@gmail.com>
2022-04-05 13:27:49 +09:00
Satoru Nihei
05fdde48f9 feat: support server scan for suse with text/plain (#1433) 2022-04-04 12:45:44 +09:00
MaineK00n
3dfbd6b616 chore(mod): update go-exploitdb module (#1428)
* chore(mod): update go-exploitdb module

* docs: add inthewild datasource

* Unique because URLs sometimes duplicate on GitHub and InTheWild

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2022-03-26 05:26:06 +09:00
MaineK00n
04f246cf8b chore: add fvuls image (#1426) 2022-03-25 06:17:33 +09:00
MaineK00n
7500f41655 chore(mod): update go-kev module (#1425) 2022-03-25 06:15:06 +09:00
MaineK00n
a1cc152e81 feat(library): add auto detect library (#1417) 2022-03-17 18:08:40 +09:00
Masato Yagi
1c77bc1ba3 feat: replace NVD-column with packages-column at output of report (#1414)
* replace NVD-col with packages-col

* fix typo

* set table row line
2022-03-17 17:14:41 +09:00
Satoru Nihei
ec31c54caf chore: update trivy from 0.23.0 to 0.24.02 (#1407)
* chore: update trivy from 0.23.0 to 0.24.2

* chore: deal with changing structs

see: 11f4f81123
2022-03-04 16:00:08 +09:00
Satoru Nihei
2f05864813 fix: handling when image contains no trivy-target (#1405)
* fix: handling when image contains no trivy-target

* refactor: use scanResult.Optional

* fix: add suppoted list to error message
2022-03-02 06:13:26 +09:00
Kota Kanbe
2fbc0a001e fix: nil pointer when no match for any OS (#1401)
* refactor: rename serverapi.go to scanner.go

* fix: nil pointer if no match for any OS
2022-02-24 07:58:29 +09:00
MaineK00n
7d8a24ee1a refactor(detector): standardize db.NewDB to db.CloseDB (#1380)
* feat(subcmds/report,server): read environment variables when configPath is ""

* refactor: standardize db.NewDB to db.CloseDB

* chore: clean up import

* chore: error wrap

* chore: update goval-dictionary

* fix(oval): return Pseudo instead of nil for client

* chore: fix comment

* fix: lint error
2022-02-19 09:20:45 +09:00
MaineK00n
7750347010 fix(oval/suse): use def.Advisory.Cves[0].CveID instead of def.Title (#1397) 2022-02-17 19:16:14 +09:00
MaineK00n
9bcffcd721 fix(configtest,scan): fix validateSSHConfig (#1395)
* fix(configtest,scan): support StrictHostKeyChecking no

* fix(configtest,scan): support ServerTypePseudo

* fix(configtest,scan): skip if using proxy
2022-02-17 08:15:23 +09:00
MaineK00n
787604de6a fix(suse): fix openSUSE, openSUSE Leap, SLES, SLED scan (#1384)
* fix(suse): fix openSUSE, openSUSE Leap scan

* docs: update README

* fix: unknown CveContent.Type

* fix: tui reporting

* fix: listening port was duplicated in format-full-text

* fix .gitignore

* fix: add EOL data for SLES12.5

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2022-02-15 17:11:54 +09:00
MaineK00n
5164fb1423 fix(util): Major() behavior for major version (#1393) 2022-02-15 07:59:29 +09:00
MaineK00n
07335617d3 fix(configtest,scan): support SSH config file (#1388)
* fix(configtest,scan): support SSH config file

* chore(subcmds): remove askKeyPassword flag
2022-02-12 21:50:56 +09:00
MaineK00n
e5855922c1 fix(redhat): detect RedHat version (#1387)
* fix(redhat): detect RedHat version

* fix err fmt string

Co-authored-by: Kota Kanbe <kotakanbe@gmail.com>
2022-02-12 20:09:51 +09:00
MaineK00n
671be3f2f7 feat(configtest,scan): detect known_hosts error (#1386) 2022-02-11 12:54:17 +09:00
MaineK00n
fe8d252c51 feat(debian): validate running kernel version (#1382)
* feat(debian): validate running kernel version

* chore(gost/debian): only stash when there is linux package
2022-02-11 12:36:48 +09:00
70 changed files with 2132 additions and 2072 deletions

View File

@@ -1,7 +1,6 @@
.dockerignore
Dockerfile
vendor/
cve.sqlite3*
oval.sqlite3*
*.sqlite3*
setup/
img/
img/

View File

@@ -20,26 +20,48 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker meta
id: meta
- name: vuls/vuls image meta
id: oss-meta
uses: docker/metadata-action@v3
with:
images: vuls/vuls
tags: |
type=ref,event=tag
- name: vuls/fvuls image meta
id: fvuls-meta
uses: docker/metadata-action@v3
with:
images: vuls/fvuls
tags: |
type=ref,event=tag
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
- name: OSS image build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: |
vuls/vuls:latest
${{ steps.meta.outputs.tags }}
${{ steps.oss-meta.outputs.tags }}
secrets: |
"github_token=${{ secrets.GITHUB_TOKEN }}"
- name: FutureVuls image build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./contrib/Dockerfile
push: true
tags: |
vuls/fvuls:latest
${{ steps.fvuls-meta.outputs.tags }}
secrets: |
"github_token=${{ secrets.GITHUB_TOKEN }}"

View File

@@ -16,7 +16,7 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.42
version: v1.45
args: --timeout=10m
# Optional: working directory, useful for monorepos

View File

@@ -19,7 +19,7 @@ jobs:
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.18
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2

View File

@@ -11,7 +11,7 @@ jobs:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: 1.16.x
go-version: 1.18.x
id: go
- name: Check out code into the Go module directory

2
.gitignore vendored
View File

@@ -18,3 +18,5 @@ dist/
vuls.*
vuls
!cmd/vuls
future-vuls
trivy-to-vuls

View File

@@ -1,5 +1,9 @@
name: golang-ci
run:
timeout: 10m
go: '1.18'
linters-settings:
revive:
# see https://github.com/mgechev/revive#available-rules for details.

View File

@@ -10,7 +10,7 @@ ENV REPOSITORY github.com/future-architect/vuls
COPY . $GOPATH/src/$REPOSITORY
RUN cd $GOPATH/src/$REPOSITORY && make install
FROM alpine:3.14
FROM alpine:3.15
ENV LOGDIR /var/log/vuls
ENV WORKDIR /vuls

View File

@@ -23,12 +23,9 @@ CGO_UNABLED := CGO_ENABLED=0 go
GO_OFF := GO111MODULE=off go
all: b
all: build test
build: ./cmd/vuls/main.go pretest fmt
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
b: ./cmd/vuls/main.go
build: ./cmd/vuls/main.go
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
install: ./cmd/vuls/main.go
@@ -41,13 +38,14 @@ install-scanner: ./cmd/scanner/main.go
$(CGO_UNABLED) install -tags=scanner -ldflags "$(LDFLAGS)" ./cmd/scanner
lint:
$(GO_OFF) get -u github.com/mgechev/revive
$(GO) install github.com/mgechev/revive@latest
revive -config ./.revive.toml -formatter plain $(PKGS)
vet:
echo $(PKGS) | xargs env $(GO) vet || exit;
golangci:
$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
golangci-lint run
fmt:
@@ -59,9 +57,9 @@ mlint:
fmtcheck:
$(foreach file,$(SRCS),gofmt -s -d $(file);)
pretest: lint vet fmtcheck golangci
pretest: lint vet fmtcheck
test:
test: pretest
$(GO) test -cover -v ./... || exit;
unused:
@@ -76,13 +74,12 @@ clean:
echo $(PKGS) | xargs go clean || exit;
# trivy-to-vuls
build-trivy-to-vuls: pretest fmt
$(GO) build -a -ldflags "$(LDFLAGS)" -o trivy-to-vuls contrib/trivy/cmd/*.go
build-trivy-to-vuls: ./contrib/trivy/cmd/main.go
$(GO) build -a -ldflags "$(LDFLAGS)" -o trivy-to-vuls ./contrib/trivy/cmd
# future-vuls
build-future-vuls: pretest fmt
$(GO) build -a -ldflags "$(LDFLAGS)" -o future-vuls contrib/future-vuls/cmd/*.go
build-future-vuls: ./contrib/future-vuls/cmd/main.go
$(GO) build -a -ldflags "$(LDFLAGS)" -o future-vuls ./contrib/future-vuls/cmd
# integration-test
BASE_DIR := '${PWD}/integration/results'

View File

@@ -50,7 +50,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
[Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html)
- Alpine, Amazon Linux, CentOS, Alma Linux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, Fedora, and Ubuntu
- Alpine, Amazon Linux, CentOS, AlmaLinux, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, openSUSE, openSUSE Leap, SUSE Enterprise Linux, Fedora, and Ubuntu
- FreeBSD
- Cloud, on-premise, Running Docker Container
@@ -82,6 +82,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
- [Metasploit-Framework modules](https://www.rapid7.com/db/?q=&type=metasploit)
- [qazbnm456/awesome-cve-poc](https://github.com/qazbnm456/awesome-cve-poc)
- [nomi-sec/PoC-in-GitHub](https://github.com/nomi-sec/PoC-in-GitHub)
- [gmatuz/inthewilddb](https://github.com/gmatuz/inthewilddb)
- CERT
- [US-CERT](https://www.us-cert.gov/ncas/alerts)

View File

@@ -307,6 +307,13 @@ func (l Distro) MajorVersion() (int, error) {
if 0 < len(l.Release) {
return strconv.Atoi(strings.Split(strings.TrimPrefix(l.Release, "stream"), ".")[0])
}
case constant.OpenSUSE:
if l.Release != "" {
if l.Release == "tumbleweed" {
return 0, nil
}
return strconv.Atoi(strings.Split(l.Release, ".")[0])
}
default:
if 0 < len(l.Release) {
return strconv.Atoi(strings.Split(l.Release, ".")[0])

View File

@@ -1,10 +1,9 @@
package config
// Load loads configuration
func Load(path, keyPass string) error {
var loader Loader
loader = TOMLLoader{}
return loader.Load(path, keyPass)
func Load(path string) error {
loader := TOMLLoader{}
return loader.Load(path)
}
// Loader is interface of concrete loader

View File

@@ -136,19 +136,90 @@ func GetEOL(family, release string) (eol EOL, found bool) {
"19.10": {Ended: true},
"20.04": {
StandardSupportUntil: time.Date(2025, 4, 1, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2030, 4, 1, 23, 59, 59, 0, time.UTC),
},
"20.10": {
StandardSupportUntil: time.Date(2021, 7, 22, 23, 59, 59, 0, time.UTC),
},
"21.04": {
StandardSupportUntil: time.Date(2022, 1, 22, 23, 59, 59, 0, time.UTC),
StandardSupportUntil: time.Date(2022, 1, 20, 23, 59, 59, 0, time.UTC),
},
"21.10": {
StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
StandardSupportUntil: time.Date(2022, 7, 14, 23, 59, 59, 0, time.UTC),
},
"22.04": {
StandardSupportUntil: time.Date(2027, 4, 1, 23, 59, 59, 0, time.UTC),
ExtendedSupportUntil: time.Date(2032, 4, 1, 23, 59, 59, 0, time.UTC),
},
}[release]
case constant.OpenSUSE:
// https://en.opensuse.org/Lifetime
eol, found = map[string]EOL{
"10.2": {Ended: true},
"10.3": {Ended: true},
"11.0": {Ended: true},
"11.1": {Ended: true},
"11.2": {Ended: true},
"11.3": {Ended: true},
"11.4": {Ended: true},
"12.1": {Ended: true},
"12.2": {Ended: true},
"12.3": {Ended: true},
"13.1": {Ended: true},
"13.2": {Ended: true},
"tumbleweed": {},
}[release]
case constant.OpenSUSELeap:
// https://en.opensuse.org/Lifetime
eol, found = map[string]EOL{
"42.1": {Ended: true},
"42.2": {Ended: true},
"42.3": {Ended: true},
"15.0": {Ended: true},
"15.1": {Ended: true},
"15.2": {Ended: true},
"15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)},
"15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)},
}[release]
case constant.SUSEEnterpriseServer:
//TODO
// https://www.suse.com/lifecycle
eol, found = map[string]EOL{
"11": {Ended: true},
"11.1": {Ended: true},
"11.2": {Ended: true},
"11.3": {Ended: true},
"11.4": {Ended: true},
"12": {Ended: true},
"12.1": {Ended: true},
"12.2": {Ended: true},
"12.3": {Ended: true},
"12.4": {Ended: true},
"12.5": {StandardSupportUntil: time.Date(2024, 10, 31, 23, 59, 59, 0, time.UTC)},
"15": {Ended: true},
"15.1": {Ended: true},
"15.2": {Ended: true},
"15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)},
"15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)},
}[release]
case constant.SUSEEnterpriseDesktop:
// https://www.suse.com/lifecycle
eol, found = map[string]EOL{
"11": {Ended: true},
"11.1": {Ended: true},
"11.2": {Ended: true},
"11.3": {Ended: true},
"11.4": {Ended: true},
"12": {Ended: true},
"12.1": {Ended: true},
"12.2": {Ended: true},
"12.3": {Ended: true},
"12.4": {Ended: true},
"15": {Ended: true},
"15.1": {Ended: true},
"15.2": {Ended: true},
"15.3": {StandardSupportUntil: time.Date(2022, 11, 30, 23, 59, 59, 0, time.UTC)},
"15.4": {StandardSupportUntil: time.Date(2023, 11, 30, 23, 59, 59, 0, time.UTC)},
}[release]
case constant.Alpine:
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19
// https://alpinelinux.org/releases/

View File

@@ -204,28 +204,12 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
},
//Ubuntu
{
name: "Ubuntu 18.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
name: "Ubuntu 12.10 not found",
fields: fields{family: Ubuntu, release: "12.10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
found: false,
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 18.04 ext supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2025, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: false,
found: true,
},
{
name: "Ubuntu 16.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 14.04 eol",
@@ -244,12 +228,44 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
found: true,
},
{
name: "Ubuntu 12.10 not found",
fields: fields{family: Ubuntu, release: "12.10"},
name: "Ubuntu 16.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
found: false,
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 18.04 supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: false,
extEnded: false,
found: true,
},
{
name: "Ubuntu 18.04 ext supported",
fields: fields{family: Ubuntu, release: "18.04"},
now: time.Date(2025, 1, 6, 23, 59, 59, 0, time.UTC),
stdEnded: true,
extEnded: false,
found: true,
},
{
name: "Ubuntu 20.04 supported",
fields: fields{family: Ubuntu, release: "20.04"},
now: time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: false,
extEnded: false,
},
{
name: "Ubuntu 20.04 ext supported",
fields: fields{family: Ubuntu, release: "20.04"},
now: time.Date(2025, 5, 1, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: true,
extEnded: false,
},
{
name: "Ubuntu 20.10 supported",
@@ -267,6 +283,22 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
stdEnded: false,
extEnded: false,
},
{
name: "Ubuntu 21.10 supported",
fields: fields{family: Ubuntu, release: "21.10"},
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: false,
extEnded: false,
},
{
name: "Ubuntu 22.04 supported",
fields: fields{family: Ubuntu, release: "22.04"},
now: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC),
found: true,
stdEnded: false,
extEnded: false,
},
//Debian
{
name: "Debian 9 supported",

View File

@@ -15,7 +15,7 @@ type TOMLLoader struct {
}
// Load load the configuration TOML file specified by path arg.
func (c TOMLLoader) Load(pathToToml, _ string) error {
func (c TOMLLoader) Load(pathToToml string) error {
// util.Log.Infof("Loading config: %s", pathToToml)
if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
return err
@@ -149,18 +149,11 @@ func setDefaultIfEmpty(server *ServerInfo) error {
}
if server.Port == "" {
if Conf.Default.Port != "" {
server.Port = Conf.Default.Port
} else {
server.Port = "22"
}
server.Port = Conf.Default.Port
}
if server.User == "" {
server.User = Conf.Default.User
if server.User == "" && server.Port != "local" {
return xerrors.Errorf("server.user is empty")
}
}
if server.SSHConfigPath == "" {

View File

@@ -24,7 +24,7 @@ const (
Rocky = "rocky"
// Fedora is
// Fedora = "fedora"
Fedora = "fedora"
// Amazon is
Amazon = "amazon"
@@ -53,9 +53,6 @@ const (
// SUSEEnterpriseDesktop is
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
// SUSEOpenstackCloud is
SUSEOpenstackCloud = "suse.openstack.cloud"
// Alpine is
Alpine = "alpine"
@@ -64,7 +61,4 @@ const (
// DeepSecurity is
DeepSecurity = "deepsecurity"
//Fedora is
Fedora = "fedora"
)

33
contrib/Dockerfile Normal file
View File

@@ -0,0 +1,33 @@
FROM golang:alpine as builder
RUN apk add --no-cache \
git \
make \
gcc \
musl-dev
ENV REPOSITORY github.com/future-architect/vuls
COPY . $GOPATH/src/$REPOSITORY
RUN cd $GOPATH/src/$REPOSITORY && \
make build-scanner && mv vuls $GOPATH/bin && \
make build-trivy-to-vuls && mv trivy-to-vuls $GOPATH/bin && \
make build-future-vuls && mv future-vuls $GOPATH/bin
FROM alpine:3.15
ENV LOGDIR /var/log/vuls
ENV WORKDIR /vuls
RUN apk add --no-cache \
openssh-client \
ca-certificates \
git \
nmap \
&& mkdir -p $WORKDIR $LOGDIR
COPY --from=builder /go/bin/vuls /go/bin/trivy-to-vuls /go/bin/future-vuls /usr/local/bin/
COPY --from=aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
VOLUME ["$WORKDIR", "$LOGDIR"]
WORKDIR $WORKDIR
ENV PWD $WORKDIR

View File

@@ -2,9 +2,11 @@ package v2
import (
"encoding/json"
"regexp"
"time"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/trivy/pkg"
@@ -17,7 +19,7 @@ type ParserV2 struct {
// Parse trivy's JSON and convert to the Vuls struct
func (p ParserV2) Parse(vulnJSON []byte) (result *models.ScanResult, err error) {
var report report.Report
var report types.Report
if err = json.Unmarshal(vulnJSON, &report); err != nil {
return nil, err
}
@@ -27,34 +29,34 @@ func (p ParserV2) Parse(vulnJSON []byte) (result *models.ScanResult, err error)
return nil, err
}
setScanResultMeta(scanResult, &report)
if err := setScanResultMeta(scanResult, &report); err != nil {
return nil, err
}
return scanResult, nil
}
func setScanResultMeta(scanResult *models.ScanResult, report *report.Report) {
for _, r := range report.Results {
const trivyTarget = "trivy-target"
if pkg.IsTrivySupportedOS(r.Type) {
scanResult.Family = r.Type
scanResult.ServerName = r.Target
scanResult.Optional = map[string]interface{}{
trivyTarget: r.Target,
}
} else if pkg.IsTrivySupportedLib(r.Type) {
if scanResult.Family == "" {
scanResult.Family = constant.ServerTypePseudo
}
if scanResult.ServerName == "" {
scanResult.ServerName = "library scan by trivy"
}
if _, ok := scanResult.Optional[trivyTarget]; !ok {
scanResult.Optional = map[string]interface{}{
trivyTarget: r.Target,
}
}
}
scanResult.ScannedAt = time.Now()
scanResult.ScannedBy = "trivy"
scanResult.ScannedVia = "trivy"
var dockerTagPattern = regexp.MustCompile(`:.+$`)
func setScanResultMeta(scanResult *models.ScanResult, report *types.Report) error {
if len(report.Results) == 0 {
return xerrors.Errorf("scanned images or libraries are not supported by Trivy. see https://aquasecurity.github.io/trivy/dev/vulnerability/detection/os/, https://aquasecurity.github.io/trivy/dev/vulnerability/detection/language/")
}
scanResult.ServerName = report.ArtifactName
if report.ArtifactType == "container_image" && !dockerTagPattern.MatchString(scanResult.ServerName) {
scanResult.ServerName += ":latest" // Complement if the tag is omitted
}
if report.Metadata.OS != nil {
scanResult.Family = report.Metadata.OS.Family
scanResult.Release = report.Metadata.OS.Name
} else {
scanResult.Family = constant.ServerTypePseudo
}
scanResult.ScannedAt = time.Now()
scanResult.ScannedBy = "trivy"
scanResult.ScannedVia = "trivy"
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/d4l3k/messagediff"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/models"
)
@@ -202,8 +203,9 @@ var redisTrivy = []byte(`
`)
var redisSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "redis (debian 10.10)",
ServerName: "redis:latest",
Family: "debian",
Release: "10.10",
ScannedBy: "trivy",
ScannedVia: "trivy",
ScannedCves: models.VulnInfos{
@@ -261,9 +263,7 @@ var redisSR = &models.ScanResult{
BinaryNames: []string{"bsdutils", "pkgA"},
},
},
Optional: map[string]interface{}{
"trivy-target": "redis (debian 10.10)",
},
Optional: nil,
}
var strutsTrivy = []byte(`
@@ -372,7 +372,7 @@ var strutsTrivy = []byte(`
var strutsSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "library scan by trivy",
ServerName: "/data/struts-1.2.7/lib",
Family: "pseudo",
ScannedBy: "trivy",
ScannedVia: "trivy",
@@ -458,9 +458,7 @@ var strutsSR = &models.ScanResult{
},
Packages: models.Packages{},
SrcPackages: models.SrcPackages{},
Optional: map[string]interface{}{
"trivy-target": "Java",
},
Optional: nil,
}
var osAndLibTrivy = []byte(`
@@ -632,8 +630,9 @@ var osAndLibTrivy = []byte(`
var osAndLibSR = &models.ScanResult{
JSONVersion: 4,
ServerName: "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
ServerName: "quay.io/fluentd_elasticsearch/fluentd:v2.9.0",
Family: "debian",
Release: "10.2",
ScannedBy: "trivy",
ScannedVia: "trivy",
ScannedCves: models.VulnInfos{
@@ -719,7 +718,82 @@ var osAndLibSR = &models.ScanResult{
BinaryNames: []string{"libgnutls30"},
},
},
Optional: map[string]interface{}{
"trivy-target": "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
},
Optional: nil,
}
func TestParseError(t *testing.T) {
cases := map[string]struct {
vulnJSON []byte
expected error
}{
"image hello-world": {
vulnJSON: helloWorldTrivy,
expected: xerrors.Errorf("scanned images or libraries are not supported by Trivy. see https://aquasecurity.github.io/trivy/dev/vulnerability/detection/os/, https://aquasecurity.github.io/trivy/dev/vulnerability/detection/language/"),
},
}
for testcase, v := range cases {
_, err := ParserV2{}.Parse(v.vulnJSON)
diff, equal := messagediff.PrettyDiff(
v.expected,
err,
messagediff.IgnoreStructField("frame"),
)
if !equal {
t.Errorf("test: %s, diff %s", testcase, diff)
}
}
}
var helloWorldTrivy = []byte(`
{
"SchemaVersion": 2,
"ArtifactName": "hello-world:latest",
"ArtifactType": "container_image",
"Metadata": {
"ImageID": "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412",
"DiffIDs": [
"sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"
],
"RepoTags": [
"hello-world:latest"
],
"RepoDigests": [
"hello-world@sha256:97a379f4f88575512824f3b352bc03cd75e239179eea0fecc38e597b2209f49a"
],
"ImageConfig": {
"architecture": "amd64",
"container": "8746661ca3c2f215da94e6d3f7dfdcafaff5ec0b21c9aff6af3dc379a82fbc72",
"created": "2021-09-23T23:47:57.442225064Z",
"docker_version": "20.10.7",
"history": [
{
"created": "2021-09-23T23:47:57Z",
"created_by": "/bin/sh -c #(nop) COPY file:50563a97010fd7ce1ceebd1fa4f4891ac3decdf428333fb2683696f4358af6c2 in / "
},
{
"created": "2021-09-23T23:47:57Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/hello\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359"
]
},
"config": {
"Cmd": [
"/hello"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Image": "sha256:b9935d4e8431fb1a7f0989304ec86b3329a99a25f5efdc7f09f3f8c41434ca6d"
}
}
}
}`)

View File

@@ -4,16 +4,14 @@ import (
"sort"
"time"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/future-architect/vuls/models"
)
// Convert :
func Convert(results report.Results) (result *models.ScanResult, err error) {
func Convert(results types.Results) (result *models.ScanResult, err error) {
scanResult := &models.ScanResult{
JSONVersion: models.JSONVersion,
ScannedCves: models.VulnInfos{},
@@ -79,8 +77,8 @@ func Convert(results report.Results) (result *models.ScanResult, err error) {
LastModified: lastModified,
}},
}
// do onlyIif image type is Vuln
if IsTrivySupportedOS(trivyResult.Type) {
// do only if image type is Vuln
if isTrivySupportedOS(trivyResult.Type) {
pkgs[vuln.PkgName] = models.Package{
Name: vuln.PkgName,
Version: vuln.InstalledVersion,
@@ -111,7 +109,7 @@ func Convert(results report.Results) (result *models.ScanResult, err error) {
}
// --list-all-pkgs flg of trivy will output all installed packages, so collect them.
if trivyResult.Class == report.ClassOSPkg {
if trivyResult.Class == types.ClassOSPkg {
for _, p := range trivyResult.Packages {
pkgs[p.Name] = models.Package{
Name: p.Name,
@@ -130,7 +128,7 @@ func Convert(results report.Results) (result *models.ScanResult, err error) {
}
}
}
} else if trivyResult.Class == report.ClassLangPkg {
} else if trivyResult.Class == types.ClassLangPkg {
libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
libScanner.Type = trivyResult.Type
for _, p := range trivyResult.Packages {
@@ -178,51 +176,25 @@ func Convert(results report.Results) (result *models.ScanResult, err error) {
return scanResult, nil
}
// IsTrivySupportedOS :
func IsTrivySupportedOS(family string) bool {
supportedFamilies := map[string]interface{}{
os.RedHat: struct{}{},
os.Debian: struct{}{},
os.Ubuntu: struct{}{},
os.CentOS: struct{}{},
os.Rocky: struct{}{},
os.Alma: struct{}{},
os.Fedora: struct{}{},
os.Amazon: struct{}{},
os.Oracle: struct{}{},
os.Windows: struct{}{},
os.OpenSUSE: struct{}{},
os.OpenSUSELeap: struct{}{},
os.OpenSUSETumbleweed: struct{}{},
os.SLES: struct{}{},
os.Photon: struct{}{},
os.Alpine: struct{}{},
// os.Fedora: struct{}{}, not supported yet
func isTrivySupportedOS(family string) bool {
supportedFamilies := map[string]struct{}{
os.RedHat: {},
os.Debian: {},
os.Ubuntu: {},
os.CentOS: {},
os.Rocky: {},
os.Alma: {},
os.Fedora: {},
os.Amazon: {},
os.Oracle: {},
os.Windows: {},
os.OpenSUSE: {},
os.OpenSUSELeap: {},
os.OpenSUSETumbleweed: {},
os.SLES: {},
os.Photon: {},
os.Alpine: {},
}
_, ok := supportedFamilies[family]
return ok
}
// IsTrivySupportedLib :
func IsTrivySupportedLib(typestr string) bool {
supportedLibs := map[string]interface{}{
ftypes.Bundler: struct{}{},
ftypes.GemSpec: struct{}{},
ftypes.Cargo: struct{}{},
ftypes.Composer: struct{}{},
ftypes.Npm: struct{}{},
ftypes.NuGet: struct{}{},
ftypes.Pip: struct{}{},
ftypes.Pipenv: struct{}{},
ftypes.Poetry: struct{}{},
ftypes.PythonPkg: struct{}{},
ftypes.NodePkg: struct{}{},
ftypes.Yarn: struct{}{},
ftypes.Jar: struct{}{},
ftypes.Pom: struct{}{},
ftypes.GoBinary: struct{}{},
ftypes.GoMod: struct{}{},
}
_, ok := supportedLibs[typestr]
return ok
}

View File

@@ -22,40 +22,27 @@ import (
)
type goCveDictClient struct {
cnf config.VulnDictInterface
driver cvedb.DB
driver cvedb.DB
baseURL string
}
func newGoCveDictClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goCveDictClient, error) {
if err := cvelog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, err
return nil, xerrors.Errorf("Failed to set go-cve-dictionary logger. err: %w", err)
}
driver, locked, err := newCveDB(cnf)
if locked {
return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return nil, err
driver, err := newCveDB(cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newCveDB. err: %w", err)
}
return &goCveDictClient{cnf: cnf, driver: driver}, nil
return &goCveDictClient{driver: driver, baseURL: cnf.GetURL()}, nil
}
func (api goCveDictClient) closeDB() error {
if api.driver == nil {
func (client goCveDictClient) closeDB() error {
if client.driver == nil {
return nil
}
return api.driver.CloseDB()
}
func (api goCveDictClient) fetchCveDetails(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
m, err := api.driver.GetMulti(cveIDs)
if err != nil {
return nil, xerrors.Errorf("Failed to GetMulti. err: %w", err)
}
for _, v := range m {
cveDetails = append(cveDetails, v)
}
return cveDetails, nil
return client.driver.CloseDB()
}
type response struct {
@@ -63,57 +50,67 @@ type response struct {
CveDetail cvemodels.CveDetail
}
func (api goCveDictClient) fetchCveDetailsViaHTTP(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
reqChan := make(chan string, len(cveIDs))
resChan := make(chan response, len(cveIDs))
errChan := make(chan error, len(cveIDs))
defer close(reqChan)
defer close(resChan)
defer close(errChan)
func (client goCveDictClient) fetchCveDetails(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
if client.driver == nil {
reqChan := make(chan string, len(cveIDs))
resChan := make(chan response, len(cveIDs))
errChan := make(chan error, len(cveIDs))
defer close(reqChan)
defer close(resChan)
defer close(errChan)
go func() {
for _, cveID := range cveIDs {
reqChan <- cveID
}
}()
go func() {
for _, cveID := range cveIDs {
reqChan <- cveID
}
}()
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range cveIDs {
tasks <- func() {
select {
case cveID := <-reqChan:
url, err := util.URLPathJoin(api.cnf.GetURL(), "cves", cveID)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
api.httpGet(cveID, url, resChan, errChan)
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range cveIDs {
tasks <- func() {
select {
case cveID := <-reqChan:
url, err := util.URLPathJoin(client.baseURL, "cves", cveID)
if err != nil {
errChan <- err
} else {
logging.Log.Debugf("HTTP Request to %s", url)
httpGet(cveID, url, resChan, errChan)
}
}
}
}
}
timeout := time.After(2 * 60 * time.Second)
var errs []error
for range cveIDs {
select {
case res := <-resChan:
cveDetails = append(cveDetails, res.CveDetail)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching CVE")
timeout := time.After(2 * 60 * time.Second)
var errs []error
for range cveIDs {
select {
case res := <-resChan:
cveDetails = append(cveDetails, res.CveDetail)
case err := <-errChan:
errs = append(errs, err)
case <-timeout:
return nil, xerrors.New("Timeout Fetching CVE")
}
}
if len(errs) != 0 {
return nil,
xerrors.Errorf("Failed to fetch CVE. err: %w", errs)
}
} else {
m, err := client.driver.GetMulti(cveIDs)
if err != nil {
return nil, xerrors.Errorf("Failed to GetMulti. err: %w", err)
}
for _, v := range m {
cveDetails = append(cveDetails, v)
}
}
if len(errs) != 0 {
return nil,
xerrors.Errorf("Failed to fetch CVE. err: %w", errs)
}
return
return cveDetails, nil
}
func (api goCveDictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
func httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
var body string
var errs []error
var resp *http.Response
@@ -144,21 +141,21 @@ func (api goCveDictClient) httpGet(key, url string, resChan chan<- response, err
}
}
func (api goCveDictClient) detectCveByCpeURI(cpeURI string, useJVN bool) (cves []cvemodels.CveDetail, err error) {
if api.cnf.IsFetchViaHTTP() {
url, err := util.URLPathJoin(api.cnf.GetURL(), "cpes")
func (client goCveDictClient) detectCveByCpeURI(cpeURI string, useJVN bool) (cves []cvemodels.CveDetail, err error) {
if client.driver == nil {
url, err := util.URLPathJoin(client.baseURL, "cpes")
if err != nil {
return nil, err
return nil, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
query := map[string]string{"name": cpeURI}
logging.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
if cves, err = api.httpPost(url, query); err != nil {
return nil, err
if cves, err = httpPost(url, query); err != nil {
return nil, xerrors.Errorf("Failed to post HTTP Request. err: %w", err)
}
} else {
if cves, err = api.driver.GetByCpeURI(cpeURI); err != nil {
return nil, err
if cves, err = client.driver.GetByCpeURI(cpeURI); err != nil {
return nil, xerrors.Errorf("Failed to get CVEs by CPEURI. err: %w", err)
}
}
@@ -177,7 +174,7 @@ func (api goCveDictClient) detectCveByCpeURI(cpeURI string, useJVN bool) (cves [
return nvdCves, nil
}
func (api goCveDictClient) httpPost(url string, query map[string]string) ([]cvemodels.CveDetail, error) {
func httpPost(url string, query map[string]string) ([]cvemodels.CveDetail, error) {
var body string
var errs []error
var resp *http.Response
@@ -208,18 +205,20 @@ func (api goCveDictClient) httpPost(url string, query map[string]string) ([]cvem
return cveDetails, nil
}
func newCveDB(cnf config.VulnDictInterface) (driver cvedb.DB, locked bool, err error) {
func newCveDB(cnf config.VulnDictInterface) (cvedb.DB, error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
driver, locked, err = cvedb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), cvedb.Option{})
driver, locked, err := cvedb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), cvedb.Option{})
if err != nil {
err = xerrors.Errorf("Failed to init CVE DB. err: %w, path: %s", err, path)
return nil, locked, err
if locked {
return nil, xerrors.Errorf("Failed to init CVE DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
}
return nil, xerrors.Errorf("Failed to init CVE DB. DB Path: %s, err: %w", path, err)
}
return driver, false, nil
return driver, nil
}

View File

@@ -8,6 +8,8 @@ import (
"strings"
"time"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
@@ -19,7 +21,6 @@ import (
"github.com/future-architect/vuls/reporter"
"github.com/future-architect/vuls/util"
cvemodels "github.com/vulsio/go-cve-dictionary/models"
"golang.org/x/xerrors"
)
// Cpe :
@@ -47,7 +48,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}
@@ -91,7 +92,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to detect WordPress Cves: %w", err)
}
if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost); err != nil {
if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to fill with gost: %w", err)
}
@@ -99,19 +100,19 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
return nil, xerrors.Errorf("Failed to fill with CVE: %w", err)
}
nExploitCve, err := FillWithExploit(&r, config.Conf.Exploit)
nExploitCve, err := FillWithExploit(&r, config.Conf.Exploit, config.Conf.LogOpts)
if err != nil {
return nil, xerrors.Errorf("Failed to fill with exploit: %w", err)
}
logging.Log.Infof("%s: %d PoC are detected", r.FormatServerName(), nExploitCve)
nMetasploitCve, err := FillWithMetasploit(&r, config.Conf.Metasploit)
nMetasploitCve, err := FillWithMetasploit(&r, config.Conf.Metasploit, config.Conf.LogOpts)
if err != nil {
return nil, xerrors.Errorf("Failed to fill with metasploit: %w", err)
}
logging.Log.Infof("%s: %d exploits are detected", r.FormatServerName(), nMetasploitCve)
if err := FillWithKEVuln(&r, config.Conf.KEVuln); err != nil {
if err := FillWithKEVuln(&r, config.Conf.KEVuln, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to fill with Known Exploited Vulnerabilities: %w", err)
}
@@ -143,7 +144,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
if config.Conf.DiffPlus || config.Conf.DiffMinus {
prevs, err := loadPrevious(rs, config.Conf.ResultsDir)
if err != nil {
return nil, err
return nil, xerrors.Errorf("Failed to load previous results. err: %w", err)
}
rs = diff(rs, prevs, config.Conf.DiffPlus, config.Conf.DiffMinus)
}
@@ -205,33 +206,23 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
// DetectPkgCves detects OS pkg cves
// pass 2 configs
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf) error {
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf, logOpts logging.LogOpts) error {
// Pkg Scan
if r.Release != "" {
if len(r.Packages)+len(r.SrcPackages) > 0 {
// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
if r.Family == constant.Raspbian {
r = r.RemoveRaspbianPackFromResult()
}
// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
}
// gost
if err := detectPkgsCvesWithGost(gostCnf, r); err != nil {
return xerrors.Errorf("Failed to detect CVE with gost: %w", err)
}
} else {
logging.Log.Infof("Number of packages is 0. Skip OVAL and gost detection")
if isPkgCvesDetactable(r) {
// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
if r.Family == constant.Raspbian {
r = r.RemoveRaspbianPackFromResult()
}
// OVAL
if err := detectPkgsCvesWithOval(ovalCnf, r, logOpts); err != nil {
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
}
// gost
if err := detectPkgsCvesWithGost(gostCnf, r, logOpts); err != nil {
return xerrors.Errorf("Failed to detect CVE with gost: %w", err)
}
} else if reuseScannedCves(r) {
logging.Log.Infof("r.Release is empty. Use CVEs as it as.")
} else if r.Family == constant.ServerTypePseudo {
logging.Log.Infof("pseudo type. Skip OVAL and gost detection")
} else {
logging.Log.Infof("r.Release is empty. detect as pseudo type. Skip OVAL and gost detection")
}
for i, v := range r.ScannedCves {
@@ -264,6 +255,31 @@ func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf c
return nil
}
// isPkgCvesDetactable checks whether CVEs is detactable with gost and oval from the result
func isPkgCvesDetactable(r *models.ScanResult) bool {
if r.Release == "" {
logging.Log.Infof("r.Release is empty. Skip OVAL and gost detection")
return false
}
if r.ScannedBy == "trivy" {
logging.Log.Infof("r.ScannedBy is trivy. Skip OVAL and gost detection")
return false
}
switch r.Family {
case constant.FreeBSD, constant.ServerTypePseudo:
logging.Log.Infof("%s type. Skip OVAL and gost detection", r.Family)
return false
default:
if len(r.Packages)+len(r.SrcPackages) == 0 {
logging.Log.Infof("Number of packages is 0. Skip OVAL and gost detection")
return false
}
return true
}
}
// DetectGitHubCves fetches CVEs from GitHub Security Alerts
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHubConf) error {
if len(githubConfs) == 0 {
@@ -308,7 +324,7 @@ func FillCvesWithNvdJvn(r *models.ScanResult, cnf config.GoCveDictConf, logOpts
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return err
return xerrors.Errorf("Failed to newGoCveDictClient. err: %w", err)
}
defer func() {
if err := client.closeDB(); err != nil {
@@ -316,14 +332,9 @@ func FillCvesWithNvdJvn(r *models.ScanResult, cnf config.GoCveDictConf, logOpts
}
}()
var ds []cvemodels.CveDetail
if cnf.IsFetchViaHTTP() {
ds, err = client.fetchCveDetailsViaHTTP(cveIDs)
} else {
ds, err = client.fetchCveDetails(cveIDs)
}
ds, err := client.fetchCveDetails(cveIDs)
if err != nil {
return err
return xerrors.Errorf("Failed to fetchCveDetails. err: %w", err)
}
for _, d := range ds {
@@ -391,37 +402,43 @@ func fillCertAlerts(cvedetail *cvemodels.CveDetail) (dict models.AlertDict) {
}
// detectPkgsCvesWithOval fetches OVAL database
func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult) error {
ovalClient, err := oval.NewOVALClient(r.Family, cnf)
func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult, logOpts logging.LogOpts) error {
client, err := oval.NewOVALClient(r.Family, cnf, logOpts)
if err != nil {
return err
}
if ovalClient == nil {
return nil
}
defer func() {
if err := client.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close the OVAL DB. err: %+v", err)
}
}()
logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
ok, err := ovalClient.CheckIfOvalFetched(r.Family, r.Release)
ok, err := client.CheckIfOvalFetched(r.Family, r.Release)
if err != nil {
return err
}
if !ok {
if r.Family == constant.Debian {
switch r.Family {
case constant.Debian:
logging.Log.Infof("Skip OVAL and Scan with gost alone.")
logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
return nil
case constant.Windows, constant.FreeBSD, constant.ServerTypePseudo:
return nil
default:
return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/vulsio/goval-dictionary#usage`", r.Family, r.Release)
}
return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/vulsio/goval-dictionary#usage`", r.Family, r.Release)
}
logging.Log.Debugf("Check if oval fresh: %s %s", r.Family, r.Release)
_, err = ovalClient.CheckIfOvalFresh(r.Family, r.Release)
_, err = client.CheckIfOvalFresh(r.Family, r.Release)
if err != nil {
return err
}
logging.Log.Debugf("Fill with oval: %s %s", r.Family, r.Release)
nCVEs, err := ovalClient.FillWithOval(r)
nCVEs, err := client.FillWithOval(r)
if err != nil {
return err
}
@@ -430,12 +447,11 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult) erro
return nil
}
func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
client, err := gost.NewClient(cnf, r.Family)
func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult, logOpts logging.LogOpts) error {
client, err := gost.NewGostClient(cnf, r.Family, logOpts)
if err != nil {
return xerrors.Errorf("Failed to new a gost client: %w", err)
}
defer func() {
if err := client.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close the gost DB. err: %+v", err)
@@ -464,7 +480,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
func DetectCpeURIsCves(r *models.ScanResult, cpes []Cpe, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return err
return xerrors.Errorf("Failed to newGoCveDictClient. err: %w", err)
}
defer func() {
if err := client.closeDB(); err != nil {
@@ -476,7 +492,7 @@ func DetectCpeURIsCves(r *models.ScanResult, cpes []Cpe, cnf config.GoCveDictCon
for _, cpe := range cpes {
details, err := client.detectCveByCpeURI(cpe.CpeURI, cpe.UseJVN)
if err != nil {
return err
return xerrors.Errorf("Failed to detectCveByCpeURI. err: %w", err)
}
for _, detail := range details {

View File

@@ -9,33 +9,73 @@ import (
"time"
"github.com/cenkalti/backoff"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
exploitdb "github.com/vulsio/go-exploitdb/db"
exploitmodels "github.com/vulsio/go-exploitdb/models"
"golang.org/x/xerrors"
exploitlog "github.com/vulsio/go-exploitdb/util"
)
// goExploitDBClient is a DB Driver
type goExploitDBClient struct {
driver exploitdb.DB
baseURL string
}
// closeDB close a DB connection
func (client goExploitDBClient) closeDB() error {
if client.driver == nil {
return nil
}
return client.driver.CloseDB()
}
func newGoExploitDBClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goExploitDBClient, error) {
if err := exploitlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, xerrors.Errorf("Failed to set go-exploitdb logger. err: %w", err)
}
db, err := newExploitDB(cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newExploitDB. err: %w", err)
}
return &goExploitDBClient{driver: db, baseURL: cnf.GetURL()}, nil
}
// FillWithExploit fills exploit information that has in Exploit
func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf) (nExploitCve int, err error) {
if cnf.IsFetchViaHTTP() {
func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf, logOpts logging.LogOpts) (nExploitCve int, err error) {
client, err := newGoExploitDBClient(&cnf, logOpts)
if err != nil {
return 0, xerrors.Errorf("Failed to newGoExploitDBClient. err: %w", err)
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if client.driver == nil {
var cveIDs []string
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
prefix, _ := util.URLPathJoin(cnf.GetURL(), "cves")
prefix, err := util.URLPathJoin(client.baseURL, "cves")
if err != nil {
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
responses, err := getExploitsViaHTTP(cveIDs, prefix)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Exploits via HTTP. err: %w", err)
}
for _, res := range responses {
exps := []exploitmodels.Exploit{}
if err := json.Unmarshal([]byte(res.json), &exps); err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
}
exploits := ConvertToModelsExploit(exps)
v, ok := r.ScannedCves[res.request.cveID]
@@ -46,25 +86,13 @@ func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf) (nExploitCve
nExploitCve++
}
} else {
driver, locked, err := newExploitDB(&cnf)
if locked {
return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
for cveID, vuln := range r.ScannedCves {
if cveID == "" {
continue
}
es, err := driver.GetExploitByCveID(cveID)
es, err := client.driver.GetExploitByCveID(cveID)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Exploits by CVE-ID. err: %w", err)
}
if len(es) == 0 {
continue
@@ -203,19 +231,20 @@ func httpGetExploit(url string, req exploitRequest, resChan chan<- exploitRespon
}
}
func newExploitDB(cnf config.VulnDictInterface) (driver exploitdb.DB, locked bool, err error) {
func newExploitDB(cnf config.VulnDictInterface) (driver exploitdb.DB, err error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = exploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), exploitdb.Option{}); err != nil {
driver, locked, err := exploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), exploitdb.Option{})
if err != nil {
if locked {
return nil, true, xerrors.Errorf("exploitDB is locked. err: %w", err)
return nil, xerrors.Errorf("Failed to init exploit DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
}
return nil, false, err
return nil, xerrors.Errorf("Failed to init exploit DB. DB Path: %s, err: %w", path, err)
}
return driver, false, nil
return driver, nil
}

View File

@@ -9,25 +9,62 @@ import (
"time"
"github.com/cenkalti/backoff"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
kevulndb "github.com/vulsio/go-kev/db"
kevulnmodels "github.com/vulsio/go-kev/models"
kevulnlog "github.com/vulsio/go-kev/utils"
)
// goKEVulnDBClient is a DB Driver
type goKEVulnDBClient struct {
driver kevulndb.DB
baseURL string
}
// closeDB close a DB connection
func (client goKEVulnDBClient) closeDB() error {
if client.driver == nil {
return nil
}
return client.driver.CloseDB()
}
func newGoKEVulnDBClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goKEVulnDBClient, error) {
if err := kevulnlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, xerrors.Errorf("Failed to set go-kev logger. err: %w", err)
}
db, err := newKEVulnDB(cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newKEVulnDB. err: %w", err)
}
return &goKEVulnDBClient{driver: db, baseURL: cnf.GetURL()}, nil
}
// FillWithKEVuln :
func FillWithKEVuln(r *models.ScanResult, cnf config.KEVulnConf) error {
if cnf.IsFetchViaHTTP() {
func FillWithKEVuln(r *models.ScanResult, cnf config.KEVulnConf, logOpts logging.LogOpts) error {
client, err := newGoKEVulnDBClient(&cnf, logOpts)
if err != nil {
return err
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if client.driver == nil {
var cveIDs []string
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
prefix, err := util.URLPathJoin(cnf.GetURL(), "cves")
prefix, err := util.URLPathJoin(client.baseURL, "cves")
if err != nil {
return err
}
@@ -57,23 +94,11 @@ func FillWithKEVuln(r *models.ScanResult, cnf config.KEVulnConf) error {
r.ScannedCves[res.request.cveID] = v
}
} else {
driver, locked, err := newKEVulnDB(&cnf)
if locked {
return xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
for cveID, vuln := range r.ScannedCves {
if cveID == "" {
continue
}
kevulns, err := driver.GetKEVulnByCveID(cveID)
kevulns, err := client.driver.GetKEVulnByCveID(cveID)
if err != nil {
return err
}
@@ -196,19 +221,20 @@ func httpGetKEVuln(url string, req kevulnRequest, resChan chan<- kevulnResponse,
}
}
func newKEVulnDB(cnf config.VulnDictInterface) (driver kevulndb.DB, locked bool, err error) {
func newKEVulnDB(cnf config.VulnDictInterface) (kevulndb.DB, error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = kevulndb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), kevulndb.Option{}); err != nil {
driver, locked, err := kevulndb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), kevulndb.Option{})
if err != nil {
if locked {
return nil, true, xerrors.Errorf("kevulnDB is locked. err: %w", err)
return nil, xerrors.Errorf("Failed to init kevuln DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
}
return nil, false, err
return nil, xerrors.Errorf("Failed to init kevuln DB. DB Path: %s, err: %w", path, err)
}
return driver, false, nil
return driver, nil
}

View File

@@ -9,35 +9,73 @@ import (
"time"
"github.com/cenkalti/backoff"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
metasploitdb "github.com/vulsio/go-msfdb/db"
metasploitmodels "github.com/vulsio/go-msfdb/models"
"golang.org/x/xerrors"
metasploitlog "github.com/vulsio/go-msfdb/utils"
)
// goMetasploitDBClient is a DB Driver
type goMetasploitDBClient struct {
driver metasploitdb.DB
baseURL string
}
// closeDB close a DB connection
func (client goMetasploitDBClient) closeDB() error {
if client.driver == nil {
return nil
}
return client.driver.CloseDB()
}
func newGoMetasploitDBClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goMetasploitDBClient, error) {
if err := metasploitlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, xerrors.Errorf("Failed to set go-msfdb logger. err: %w", err)
}
db, err := newMetasploitDB(cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newMetasploitDB. err: %w", err)
}
return &goMetasploitDBClient{driver: db, baseURL: cnf.GetURL()}, nil
}
// FillWithMetasploit fills metasploit module information that has in module
func FillWithMetasploit(r *models.ScanResult, cnf config.MetasploitConf) (nMetasploitCve int, err error) {
if cnf.IsFetchViaHTTP() {
func FillWithMetasploit(r *models.ScanResult, cnf config.MetasploitConf, logOpts logging.LogOpts) (nMetasploitCve int, err error) {
client, err := newGoMetasploitDBClient(&cnf, logOpts)
if err != nil {
return 0, xerrors.Errorf("Failed to newGoMetasploitDBClient. err: %w", err)
}
defer func() {
if err := client.closeDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if client.driver == nil {
var cveIDs []string
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
prefix, err := util.URLPathJoin(cnf.GetURL(), "cves")
prefix, err := util.URLPathJoin(client.baseURL, "cves")
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
responses, err := getMetasploitsViaHTTP(cveIDs, prefix)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Metasploits via HTTP. err: %w", err)
}
for _, res := range responses {
msfs := []metasploitmodels.Metasploit{}
if err := json.Unmarshal([]byte(res.json), &msfs); err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
}
metasploits := ConvertToModelsMsf(msfs)
v, ok := r.ScannedCves[res.request.cveID]
@@ -48,25 +86,13 @@ func FillWithMetasploit(r *models.ScanResult, cnf config.MetasploitConf) (nMetas
nMetasploitCve++
}
} else {
driver, locked, err := newMetasploitDB(&cnf)
if locked {
return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
for cveID, vuln := range r.ScannedCves {
if cveID == "" {
continue
}
ms, err := driver.GetModuleByCveID(cveID)
ms, err := client.driver.GetModuleByCveID(cveID)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Metasploits by CVE-ID. err: %w", err)
}
if len(ms) == 0 {
continue
@@ -199,19 +225,20 @@ func ConvertToModelsMsf(ms []metasploitmodels.Metasploit) (modules []models.Meta
return modules
}
func newMetasploitDB(cnf config.VulnDictInterface) (driver metasploitdb.DB, locked bool, err error) {
func newMetasploitDB(cnf config.VulnDictInterface) (metasploitdb.DB, error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = metasploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), metasploitdb.Option{}); err != nil {
driver, locked, err := metasploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), metasploitdb.Option{})
if err != nil {
if locked {
return nil, true, xerrors.Errorf("metasploitDB is locked. err: %w", err)
return nil, xerrors.Errorf("Failed to init metasploit DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
}
return nil, false, err
return nil, xerrors.Errorf("Failed to init metasploit DB. DB Path: %s, err: %w", path, err)
}
return driver, false, nil
return driver, nil
}

View File

@@ -26,12 +26,7 @@ func reuseScannedCves(r *models.ScanResult) bool {
case constant.FreeBSD, constant.Raspbian:
return true
}
return isTrivyResult(r)
}
func isTrivyResult(r *models.ScanResult) bool {
_, ok := r.Optional["trivy-target"]
return ok
return r.ScannedBy == "trivy"
}
func needToRefreshCve(r models.ScanResult) bool {
@@ -130,7 +125,7 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
previousCveIDsSet[previousVulnInfo.CveID] = true
}
new := models.VulnInfos{}
newer := models.VulnInfos{}
updated := models.VulnInfos{}
for _, v := range current.ScannedCves {
if previousCveIDsSet[v.CveID] {
@@ -150,17 +145,17 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
logging.Log.Debugf("same: %s", v.CveID)
}
} else {
logging.Log.Debugf("new: %s", v.CveID)
logging.Log.Debugf("newer: %s", v.CveID)
v.DiffStatus = models.DiffPlus
new[v.CveID] = v
newer[v.CveID] = v
}
}
if len(updated) == 0 && len(new) == 0 {
if len(updated) == 0 && len(newer) == 0 {
logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
}
for cveID, vuln := range new {
for cveID, vuln := range newer {
updated[cveID] = vuln
}
return updated

143
go.mod
View File

@@ -1,35 +1,28 @@
module github.com/future-architect/vuls
go 1.17
go 1.18
require (
github.com/Azure/azure-sdk-for-go v61.2.0+incompatible
github.com/BurntSushi/toml v1.0.0
github.com/Azure/azure-sdk-for-go v63.0.0+incompatible
github.com/BurntSushi/toml v1.1.0
github.com/Ullaakut/nmap/v2 v2.1.2-0.20210406060955-59a52fe80a4f
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/aquasecurity/fanal v0.0.0-20220129174924-b9e05fcccc57
github.com/aquasecurity/go-dep-parser v0.0.0-20220110153540-4a30ebc4b509
github.com/aquasecurity/trivy v0.23.0
github.com/aquasecurity/trivy-db v0.0.0-20220130223604-df65ebde46f4
github.com/aquasecurity/fanal v0.0.0-20220424145104-2e3e0044128c
github.com/aquasecurity/go-dep-parser v0.0.0-20220412145205-d0501f906d90
github.com/aquasecurity/trivy v0.27.0
github.com/aquasecurity/trivy-db v0.0.0-20220327074450-74195d9604b2
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/aws/aws-sdk-go v1.42.30
github.com/aws/aws-sdk-go v1.43.31
github.com/boltdb/bolt v1.3.1
github.com/briandowns/spinner v1.16.0 // indirect
github.com/briandowns/spinner v1.18.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheggaaa/pb/v3 v3.0.8 // indirect
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.14.0
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-redis/redis/v8 v8.11.4 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/google/subcommands v1.2.0
github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
github.com/hashicorp/go-version v1.4.0
github.com/jesseduffield/gocui v0.3.0
github.com/k0kubun/pp v3.0.1+incompatible
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
@@ -38,7 +31,6 @@ require (
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
github.com/kotakanbe/go-pingscanner v0.1.0
github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/go-homedir v1.1.0
@@ -46,41 +38,36 @@ require (
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
github.com/olekukonko/tablewriter v0.0.5
github.com/parnurzeal/gorequest v0.2.16
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.3.0
github.com/spf13/cobra v1.4.0
github.com/vulsio/go-cve-dictionary v0.8.2-0.20211028094424-0a854f8e8f85
github.com/vulsio/go-exploitdb v0.4.2-0.20211028071949-1ebf9c4f6c4d
github.com/vulsio/go-kev v0.1.0
github.com/vulsio/go-exploitdb v0.4.2
github.com/vulsio/go-kev v0.1.1-0.20220118062020-5f69b364106f
github.com/vulsio/go-msfdb v0.2.1-0.20211028071756-4a9759bd9f14
github.com/vulsio/gost v0.4.1-0.20211028071837-7ad032a6ffa8
github.com/vulsio/goval-dictionary v0.7.0
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
github.com/vulsio/gost v0.4.1
github.com/vulsio/goval-dictionary v0.7.3
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
gopkg.in/ini.v1 v1.66.3 // indirect
gorm.io/driver/mysql v1.2.3 // indirect
gorm.io/driver/postgres v1.2.3 // indirect
gorm.io/driver/sqlite v1.2.6 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f
gopkg.in/ini.v1 v1.66.4 // indirect
gorm.io/driver/mysql v1.3.3 // indirect
gorm.io/driver/postgres v1.3.5 // indirect
gorm.io/driver/sqlite v1.3.2 // indirect
)
require (
cloud.google.com/go v0.99.0 // indirect
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.5.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.14.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest v0.11.25 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/PuerkitoBio/goquery v1.6.1 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce // indirect
@@ -88,85 +75,93 @@ require (
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/caarlos0/env/v6 v6.0.0 // indirect
github.com/caarlos0/env/v6 v6.9.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheggaaa/pb/v3 v3.0.8 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/cli v20.10.11+incompatible // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.12+incompatible // indirect
github.com/docker/docker v20.10.14+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-containerregistry v0.7.1-0.20211214010025-a65b7844a475 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/google/go-containerregistry v0.8.0 // indirect
github.com/googleapis/gax-go/v2 v2.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grokify/html-strip-tags-go v0.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.5.2 // indirect
github.com/hashicorp/go-getter v1.5.11 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/htcat/htcat v1.0.2 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.11.0 // indirect
github.com/jackc/pgconn v1.12.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.10.0 // indirect
github.com/jackc/pgx/v4 v4.15.0 // indirect
github.com/jackc/pgtype v1.11.0 // indirect
github.com/jackc/pgx/v4 v4.16.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/klauspost/compress v1.14.2 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-sqlite3 v1.14.11 // indirect
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 // indirect
github.com/owenrumney/go-sarif/v2 v2.0.17 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/afero v1.8.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.10.0 // indirect
github.com/spf13/viper v1.11.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/ymomoi/goval-parser v0.0.0-20170813122243-0a0be1dd9d08 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/goleak v1.1.12 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.20.0 // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
google.golang.org/api v0.62.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/api v0.74.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gorm.io/gorm v1.22.5 // indirect
gorm.io/gorm v1.23.5 // indirect
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
moul.io/http2curl v1.0.0 // indirect
)

1064
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,13 @@ package gost
import (
"encoding/json"
debver "github.com/knqyf263/go-deb-version"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
debver "github.com/knqyf263/go-deb-version"
gostmodels "github.com/vulsio/gost/models"
"golang.org/x/xerrors"
)
// Debian is Gost client for Debian GNU/Linux
@@ -46,27 +47,36 @@ func (deb Debian) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
// Add linux and set the version of running kernel to search Gost.
if r.Container.ContainerID == "" {
newVer := ""
if p, ok := r.Packages["linux-image-"+r.RunningKernel.Release]; ok {
newVer = p.NewVersion
}
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
if r.RunningKernel.Version != "" {
newVer := ""
if p, ok := r.Packages["linux-image-"+r.RunningKernel.Release]; ok {
newVer = p.NewVersion
}
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
}
} else {
logging.Log.Warnf("Since the exact kernel version is not available, the vulnerability in the linux package is not detected.")
}
}
stashLinuxPackage := r.Packages["linux"]
var stashLinuxPackage models.Package
if linux, ok := r.Packages["linux"]; ok {
stashLinuxPackage = linux
}
nFixedCVEs, err := deb.detectCVEsWithFixState(r, "resolved")
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to detect fixed CVEs. err: %w", err)
}
r.Packages["linux"] = stashLinuxPackage
if stashLinuxPackage.Name != "" {
r.Packages["linux"] = stashLinuxPackage
}
nUnfixedCVEs, err := deb.detectCVEsWithFixState(r, "open")
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to detect unfixed CVEs. err: %w", err)
}
return (nFixedCVEs + nUnfixedCVEs), nil
@@ -78,22 +88,25 @@ func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string)
}
packCvesList := []packCves{}
if deb.DBDriver.Cnf.IsFetchViaHTTP() {
url, _ := util.URLPathJoin(deb.DBDriver.Cnf.GetURL(), "debian", major(r.Release), "pkgs")
if deb.driver == nil {
url, err := util.URLPathJoin(deb.baseURL, "debian", major(r.Release), "pkgs")
if err != nil {
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
s := "unfixed-cves"
if s == "resolved" {
s = "fixed-cves"
}
responses, err := getCvesWithFixStateViaHTTP(r, url, s)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get CVEs via HTTP. err: %w", err)
}
for _, res := range responses {
debCves := map[string]gostmodels.DebianCVE{}
if err := json.Unmarshal([]byte(res.json), &debCves); err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
}
cves := []models.CveContent{}
fixes := []models.PackageFixStatus{}
@@ -109,13 +122,10 @@ func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string)
})
}
} else {
if deb.DBDriver.DB == nil {
return 0, nil
}
for _, pack := range r.Packages {
cves, fixes, err := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get CVEs for Package. err: %w", err)
}
packCvesList = append(packCvesList, packCves{
packName: pack.Name,
@@ -129,7 +139,7 @@ func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string)
for _, pack := range r.SrcPackages {
cves, fixes, err := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get CVEs for SrcPackage. err: %w", err)
}
packCvesList = append(packCvesList, packCves{
packName: pack.Name,
@@ -230,11 +240,11 @@ func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string)
func isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
vera, err := debver.NewVersion(versionRelease)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", versionRelease, err)
}
verb, err := debver.NewVersion(gostVersion)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", gostVersion, err)
}
return vera.LessThan(verb), nil
}
@@ -242,13 +252,13 @@ func isGostDefAffected(versionRelease, gostVersion string) (affected bool, err e
func (deb Debian) getCvesDebianWithfixStatus(fixStatus, release, pkgName string) ([]models.CveContent, []models.PackageFixStatus, error) {
var f func(string, string) (map[string]gostmodels.DebianCVE, error)
if fixStatus == "resolved" {
f = deb.DBDriver.DB.GetFixedCvesDebian
f = deb.driver.GetFixedCvesDebian
} else {
f = deb.DBDriver.DB.GetUnfixedCvesDebian
f = deb.driver.GetUnfixedCvesDebian
}
debCves, err := f(release, pkgName)
if err != nil {
return nil, nil, err
return nil, nil, xerrors.Errorf("Failed to get CVEs. fixStatus: %s, release: %s, src package: %s, err: %w", fixStatus, release, pkgName, err)
}
cves := []models.CveContent{}

View File

@@ -4,22 +4,17 @@
package gost
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/vulsio/gost/db"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
gostdb "github.com/vulsio/gost/db"
gostlog "github.com/vulsio/gost/util"
)
// DBDriver is a DB Driver
type DBDriver struct {
DB db.DB
Cnf config.VulnDictInterface
}
// Client is the interface of OVAL client.
// Client is the interface of Gost client.
type Client interface {
DetectCVEs(*models.ScanResult, bool) (int, error)
CloseDB() error
@@ -27,74 +22,79 @@ type Client interface {
// Base is a base struct
type Base struct {
DBDriver DBDriver
driver gostdb.DB
baseURL string
}
// CloseDB close a DB connection
func (b Base) CloseDB() error {
if b.DBDriver.DB == nil {
if b.driver == nil {
return nil
}
return b.DBDriver.DB.CloseDB()
return b.driver.CloseDB()
}
// FillCVEsWithRedHat fills CVE detailed with Red Hat Security
func FillCVEsWithRedHat(r *models.ScanResult, cnf config.GostConf) error {
db, locked, err := newGostDB(cnf)
if locked {
return xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
func FillCVEsWithRedHat(r *models.ScanResult, cnf config.GostConf, o logging.LogOpts) error {
if err := gostlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return err
}
defer func() {
if db != nil {
if err := db.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}
}()
return RedHat{Base{DBDriver{DB: db, Cnf: &cnf}}}.fillCvesWithRedHatAPI(r)
}
// NewClient make Client by family
func NewClient(cnf config.GostConf, family string) (Client, error) {
db, locked, err := newGostDB(cnf)
if locked {
return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
} else if err != nil {
return nil, err
db, err := newGostDB(&cnf)
if err != nil {
return xerrors.Errorf("Failed to newGostDB. err: %w", err)
}
driver := DBDriver{DB: db, Cnf: &cnf}
client := RedHat{Base{driver: db, baseURL: cnf.GetURL()}}
defer func() {
if err := client.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
return client.fillCvesWithRedHatAPI(r)
}
// NewGostClient make Client by family
func NewGostClient(cnf config.GostConf, family string, o logging.LogOpts) (Client, error) {
if err := gostlog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, xerrors.Errorf("Failed to set gost logger. err: %w", err)
}
db, err := newGostDB(&cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newGostDB. err: %w", err)
}
base := Base{driver: db, baseURL: cnf.GetURL()}
switch family {
case constant.RedHat, constant.CentOS, constant.Rocky, constant.Alma:
return RedHat{Base{DBDriver: driver}}, nil
return RedHat{base}, nil
case constant.Debian, constant.Raspbian:
return Debian{Base{DBDriver: driver}}, nil
return Debian{base}, nil
case constant.Ubuntu:
return Ubuntu{Base{DBDriver: driver}}, nil
return Ubuntu{base}, nil
case constant.Windows:
return Microsoft{Base{DBDriver: driver}}, nil
return Microsoft{base}, nil
default:
return Pseudo{Base{DBDriver: driver}}, nil
return Pseudo{base}, nil
}
}
// NewGostDB returns db client for Gost
func newGostDB(cnf config.GostConf) (driver db.DB, locked bool, err error) {
func newGostDB(cnf config.VulnDictInterface) (gostdb.DB, error) {
if cnf.IsFetchViaHTTP() {
return nil, false, nil
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
if driver, locked, err = db.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), db.Option{}); err != nil {
driver, locked, err := gostdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), gostdb.Option{})
if err != nil {
if locked {
return nil, true, xerrors.Errorf("gostDB is locked. err: %w", err)
return nil, xerrors.Errorf("Failed to init gost DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
}
return nil, false, err
return nil, xerrors.Errorf("Failed to init gost DB. DB Path: %s, err: %w", path, err)
}
return driver, false, nil
return driver, nil
}

View File

@@ -18,14 +18,14 @@ type Microsoft struct {
// DetectCVEs fills cve information that has in Gost
func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
if ms.DBDriver.DB == nil {
if ms.driver == nil {
return 0, nil
}
cveIDs := []string{}
for cveID := range r.ScannedCves {
cveIDs = append(cveIDs, cveID)
}
msCves, err := ms.DBDriver.DB.GetMicrosoftMulti(cveIDs)
msCves, err := ms.driver.GetMicrosoftMulti(cveIDs)
if err != nil {
return 0, nil
}
@@ -34,7 +34,7 @@ func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err err
continue
}
cveCont, mitigations := ms.ConvertToModel(&msCve)
v, _ := r.ScannedCves[cveID]
v := r.ScannedCves[cveID]
if v.CveContents == nil {
v.CveContents = models.CveContents{}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/future-architect/vuls/models"
)
// Pseudo is Gost client except for RedHat family and Debian
// Pseudo is Gost client except for RedHat family, Debian, Ubuntu and Windows
type Pseudo struct {
Base
}

View File

@@ -8,7 +8,8 @@ import (
"strconv"
"strings"
"github.com/future-architect/vuls/config"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
@@ -26,17 +27,20 @@ func (red RedHat) DetectCVEs(r *models.ScanResult, ignoreWillNotFix bool) (nCVEs
if r.Family == constant.CentOS {
gostRelease = strings.TrimPrefix(r.Release, "stream")
}
if red.DBDriver.Cnf.IsFetchViaHTTP() {
prefix, _ := util.URLPathJoin(red.DBDriver.Cnf.GetURL(), "redhat", major(gostRelease), "pkgs")
if red.driver == nil {
prefix, err := util.URLPathJoin(red.baseURL, "redhat", major(gostRelease), "pkgs")
if err != nil {
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Unfixed CVEs via HTTP. err: %w", err)
}
for _, res := range responses {
// CVE-ID: RedhatCVE
cves := map[string]gostmodels.RedhatCVE{}
if err := json.Unmarshal([]byte(res.json), &cves); err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
}
for _, cve := range cves {
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
@@ -45,14 +49,11 @@ func (red RedHat) DetectCVEs(r *models.ScanResult, ignoreWillNotFix bool) (nCVEs
}
}
} else {
if red.DBDriver.DB == nil {
return 0, nil
}
for _, pack := range r.Packages {
// CVE-ID: RedhatCVE
cves, err := red.DBDriver.DB.GetUnfixedCvesRedhat(major(gostRelease), pack.Name, ignoreWillNotFix)
cves, err := red.driver.GetUnfixedCvesRedhat(major(gostRelease), pack.Name, ignoreWillNotFix)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Unfixed CVEs. err: %w", err)
}
for _, cve := range cves {
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
@@ -73,8 +74,11 @@ func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error {
cveIDs = append(cveIDs, cveID)
}
if red.DBDriver.Cnf.IsFetchViaHTTP() {
prefix, _ := util.URLPathJoin(config.Conf.Gost.URL, "redhat", "cves")
if red.driver == nil {
prefix, err := util.URLPathJoin(red.baseURL, "redhat", "cves")
if err != nil {
return err
}
responses, err := getCvesViaHTTP(cveIDs, prefix)
if err != nil {
return err
@@ -90,10 +94,7 @@ func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error {
red.setFixedCveToScanResult(&redCve, r)
}
} else {
if red.DBDriver.DB == nil {
return nil
}
redCves, err := red.DBDriver.DB.GetRedhatMulti(cveIDs)
redCves, err := red.driver.GetRedhatMulti(cveIDs)
if err != nil {
return err
}

View File

@@ -7,6 +7,8 @@ import (
"encoding/json"
"strings"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
@@ -23,9 +25,12 @@ func (ubu Ubuntu) supported(version string) bool {
"1404": "trusty",
"1604": "xenial",
"1804": "bionic",
"1910": "eoan",
"2004": "focal",
"2010": "groovy",
"2104": "hirsute",
"2110": "impish",
"2204": "jammy",
}[version]
return ok
}
@@ -53,17 +58,20 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
}
packCvesList := []packCves{}
if ubu.DBDriver.Cnf.IsFetchViaHTTP() {
url, _ := util.URLPathJoin(ubu.DBDriver.Cnf.GetURL(), "ubuntu", ubuReleaseVer, "pkgs")
if ubu.driver == nil {
url, err := util.URLPathJoin(ubu.baseURL, "ubuntu", ubuReleaseVer, "pkgs")
if err != nil {
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
responses, err := getAllUnfixedCvesViaHTTP(r, url)
if err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to get Unfixed CVEs via HTTP. err: %w", err)
}
for _, res := range responses {
ubuCves := map[string]gostmodels.UbuntuCVE{}
if err := json.Unmarshal([]byte(res.json), &ubuCves); err != nil {
return 0, err
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
}
cves := []models.CveContent{}
for _, ubucve := range ubuCves {
@@ -76,13 +84,10 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
})
}
} else {
if ubu.DBDriver.DB == nil {
return 0, nil
}
for _, pack := range r.Packages {
ubuCves, err := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
ubuCves, err := ubu.driver.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
if err != nil {
return 0, nil
return 0, xerrors.Errorf("Failed to get Unfixed CVEs For Package. err: %w", err)
}
cves := []models.CveContent{}
for _, ubucve := range ubuCves {
@@ -97,9 +102,9 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
// SrcPack
for _, pack := range r.SrcPackages {
ubuCves, err := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
ubuCves, err := ubu.driver.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
if err != nil {
return 0, nil
return 0, xerrors.Errorf("Failed to get Unfixed CVEs For SrcPackage. err: %w", err)
}
cves := []models.CveContent{}
for _, ubucve := range ubuCves {

View File

@@ -4,6 +4,8 @@ import (
"sort"
"strings"
"time"
"github.com/future-architect/vuls/constant"
)
// CveContents has CveContent
@@ -333,6 +335,8 @@ func NewCveContentType(name string) CveContentType {
return DebianSecurityTracker
case "ubuntu_api":
return UbuntuAPI
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
return SUSE
case "microsoft":
return Microsoft
case "wordpress":

View File

@@ -3,13 +3,14 @@ package models
import (
"path/filepath"
ftypes "github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/trivy-db/pkg/db"
trivyDBTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/detector/library"
"github.com/future-architect/vuls/logging"
"github.com/aquasecurity/trivy/pkg/types"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/logging"
)
// LibraryScanners is an array of LibraryScanner
@@ -132,25 +133,53 @@ func getCveContents(cveID string, vul trivyDBTypes.Vulnerability) (contents map[
// LibraryMap is filename and library type
var LibraryMap = map[string]string{
"package-lock.json": "node",
"yarn.lock": "node",
"Gemfile.lock": "ruby",
"Cargo.lock": "rust",
"composer.lock": "php",
"Pipfile.lock": "python",
"poetry.lock": "python",
"packages.lock.json": ".net",
"go.sum": "gomod",
ftypes.NpmPkgLock: "node",
ftypes.YarnLock: "node",
ftypes.GemfileLock: "ruby",
ftypes.CargoLock: "rust",
ftypes.ComposerLock: "php",
ftypes.PipRequirements: "python",
ftypes.PipfileLock: "python",
ftypes.PoetryLock: "python",
ftypes.NuGetPkgsLock: ".net",
ftypes.NuGetPkgsConfig: ".net",
ftypes.GoMod: "gomod",
ftypes.GoSum: "gomod",
ftypes.MavenPom: "java",
"*.jar": "java",
"*.war": "java",
"*.ear": "java",
"*.par": "java",
}
// GetLibraryKey returns target library key
func (s LibraryScanner) GetLibraryKey() string {
fileName := filepath.Base(s.LockfilePath)
switch s.Type {
case "jar", "war", "ear":
case ftypes.Bundler, ftypes.GemSpec:
return "ruby"
case ftypes.Cargo:
return "rust"
case ftypes.Composer:
return "php"
case ftypes.GoBinary, ftypes.GoModule:
return "gomod"
case ftypes.Jar, ftypes.Pom:
return "java"
case ftypes.Npm, ftypes.Yarn, ftypes.NodePkg, ftypes.JavaScript:
return "node"
case ftypes.NuGet:
return ".net"
case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg:
return "python"
default:
filename := filepath.Base(s.LockfilePath)
switch filepath.Ext(filename) {
case ".jar", ".war", ".ear", ".par":
return "java"
default:
return LibraryMap[filename]
}
}
return LibraryMap[fileName]
}
// LibraryFixedIn has library fixed information

View File

@@ -510,7 +510,7 @@ func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
// Cvss3Scores returns CVSS V3 Score
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
order := []CveContentType{RedHatAPI, RedHat, Nvd, Jvn}
order := []CveContentType{RedHatAPI, RedHat, SUSE, Nvd, Jvn}
for _, ctype := range order {
if conts, found := v.CveContents[ctype]; found {
for _, cont := range conts {
@@ -549,7 +549,7 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
}
}
// Memo: Only RedHat, Oracle and Amazon has severity data in advisory.
// Memo: Only RedHat, SUSE, Oracle and Amazon has severity data in advisory.
for _, adv := range v.DistroAdvisories {
if adv.Severity != "" {
score := severityToCvssScoreRoughly(adv.Severity)

View File

@@ -4,10 +4,12 @@
package oval
import (
"github.com/future-architect/vuls/config"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
ovaldb "github.com/vulsio/goval-dictionary/db"
)
// Alpine is the struct of Alpine Linux
@@ -16,11 +18,12 @@ type Alpine struct {
}
// NewAlpine creates OVAL client for SUSE
func NewAlpine(cnf config.VulnDictInterface) Alpine {
func NewAlpine(driver ovaldb.DB, baseURL string) Alpine {
return Alpine{
Base{
family: constant.Alpine,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Alpine,
},
}
}
@@ -28,23 +31,13 @@ func NewAlpine(cnf config.VulnDictInterface) Alpine {
// FillWithOval returns scan result after updating CVE info by OVAL
func (o Alpine) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
var relatedDefs ovalResult
if o.Cnf.IsFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
return 0, err
if o.driver == nil {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
}
} else {
driver, err := newOvalDB(o.Cnf)
if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
return 0, err
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
}
}
for _, defPacks := range relatedDefs.entries {

View File

@@ -7,11 +7,13 @@ import (
"fmt"
"strings"
"github.com/future-architect/vuls/config"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
ovaldb "github.com/vulsio/goval-dictionary/db"
ovalmodels "github.com/vulsio/goval-dictionary/models"
)
@@ -122,12 +124,13 @@ type Debian struct {
}
// NewDebian creates OVAL client for Debian
func NewDebian(cnf config.VulnDictInterface) Debian {
func NewDebian(driver ovaldb.DB, baseURL string) Debian {
return Debian{
DebianBase{
Base{
family: constant.Debian,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Debian,
},
},
}
@@ -141,35 +144,29 @@ func (o Debian) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
// Add linux and set the version of running kernel to search OVAL.
if r.Container.ContainerID == "" {
newVer := ""
if p, ok := r.Packages[linuxImage]; ok {
newVer = p.NewVersion
}
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
if r.RunningKernel.Version != "" {
newVer := ""
if p, ok := r.Packages[linuxImage]; ok {
newVer = p.NewVersion
}
r.Packages["linux"] = models.Package{
Name: "linux",
Version: r.RunningKernel.Version,
NewVersion: newVer,
}
} else {
logging.Log.Warnf("Since the exact kernel version is not available, the vulnerability in the linux package is not detected.")
}
}
var relatedDefs ovalResult
if o.Cnf.IsFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
return 0, err
if o.driver == nil {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
}
} else {
driver, err := newOvalDB(o.Cnf)
if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
return 0, err
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
}
}
@@ -209,12 +206,13 @@ type Ubuntu struct {
}
// NewUbuntu creates OVAL client for Debian
func NewUbuntu(cnf config.VulnDictInterface) Ubuntu {
func NewUbuntu(driver ovaldb.DB, baseURL string) Ubuntu {
return Ubuntu{
DebianBase{
Base{
family: constant.Ubuntu,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Ubuntu,
},
},
}
@@ -397,6 +395,35 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
"linux-virtual",
}
return o.fillWithOval(r, kernelNamesInOval)
case "22":
kernelNamesInOval := []string{
"linux-aws",
"linux-azure",
"linux-gcp",
"linux-generic",
"linux-gke",
"linux-header-aws",
"linux-header-azure",
"linux-header-gcp",
"linux-header-generic",
"linux-header-gke",
"linux-header-oracle",
"linux-image-aws",
"linux-image-azure",
"linux-image-gcp",
"linux-image-generic",
"linux-image-gke",
"linux-image-oracle",
"linux-oracle",
"linux-tools-aws",
"linux-tools-azure",
"linux-tools-common",
"linux-tools-gcp",
"linux-tools-generic",
"linux-tools-gke",
"linux-tools-oracle",
}
return o.fillWithOval(r, kernelNamesInOval)
}
return 0, fmt.Errorf("Ubuntu %s is not support for now", r.Release)
}
@@ -467,23 +494,13 @@ func (o Ubuntu) fillWithOval(r *models.ScanResult, kernelNamesInOval []string) (
}
var relatedDefs ovalResult
if o.Cnf.IsFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
return 0, err
if o.driver == nil {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
}
} else {
driver, err := newOvalDB(o.Cnf)
if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
return 0, err
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
}
}

View File

@@ -8,14 +8,15 @@ import (
"strings"
"time"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/parnurzeal/gorequest"
"github.com/vulsio/goval-dictionary/db"
"golang.org/x/xerrors"
ovaldb "github.com/vulsio/goval-dictionary/db"
)
// Client is the interface of OVAL client.
@@ -23,51 +24,56 @@ type Client interface {
FillWithOval(*models.ScanResult) (int, error)
CheckIfOvalFetched(string, string) (bool, error)
CheckIfOvalFresh(string, string) (bool, error)
CloseDB() error
}
// Base is a base struct
type Base struct {
family string
Cnf config.VulnDictInterface
driver ovaldb.DB
baseURL string
family string
}
// CloseDB close a DB connection
func (b Base) CloseDB() error {
if b.driver == nil {
return nil
}
return b.driver.CloseDB()
}
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err error) {
func (b Base) CheckIfOvalFetched(osFamily, release string) (bool, error) {
ovalFamily, err := GetFamilyInOval(osFamily)
if err != nil {
return false, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
}
if ovalFamily == "" {
return false, nil
}
ovalRelease := release
if osFamily == constant.CentOS {
ovalRelease = strings.TrimPrefix(release, "stream")
}
if !b.Cnf.IsFetchViaHTTP() {
driver, err := newOvalDB(b.Cnf)
if err != nil {
return false, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
count, err := driver.CountDefs(ovalFamily, ovalRelease)
var count int
if b.driver == nil {
url, err := util.URLPathJoin(b.baseURL, "count", ovalFamily, ovalRelease)
if err != nil {
return false, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
}
if err := json.Unmarshal([]byte(body), &count); err != nil {
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
} else {
count, err = b.driver.CountDefs(ovalFamily, ovalRelease)
if err != nil {
return false, xerrors.Errorf("Failed to count OVAL defs: %s, %s, %w", ovalFamily, ovalRelease, err)
}
logging.Log.Infof("OVAL %s %s found. defs: %d", ovalFamily, ovalRelease, count)
return 0 < count, nil
}
url, _ := util.URLPathJoin(config.Conf.OvalDict.URL, "count", ovalFamily, ovalRelease)
resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
}
count := 0
if err := json.Unmarshal([]byte(body), &count); err != nil {
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
logging.Log.Infof("OVAL %s %s found. defs: %d", ovalFamily, ovalRelease, count)
return 0 < count, nil
@@ -79,35 +85,32 @@ func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
if err != nil {
return false, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
}
if ovalFamily == "" {
return false, nil
}
ovalRelease := release
if osFamily == constant.CentOS {
ovalRelease = strings.TrimPrefix(release, "stream")
}
var lastModified time.Time
if !b.Cnf.IsFetchViaHTTP() {
driver, err := newOvalDB(b.Cnf)
if b.driver == nil {
url, err := util.URLPathJoin(b.baseURL, "lastmodified", ovalFamily, ovalRelease)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to join URLPath. err: %w", err)
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
lastModified, err = driver.GetLastModified(ovalFamily, ovalRelease)
if err != nil {
return false, xerrors.Errorf("Failed to GetLastModified: %w", err)
}
} else {
url, _ := util.URLPathJoin(config.Conf.OvalDict.URL, "lastmodified", ovalFamily, ovalRelease)
resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
}
if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
} else {
lastModified, err = b.driver.GetLastModified(ovalFamily, ovalRelease)
if err != nil {
return false, xerrors.Errorf("Failed to GetLastModified: %w", err)
}
}
since := time.Now()
@@ -122,23 +125,20 @@ func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
}
// NewOvalDB returns oval db client
func newOvalDB(cnf config.VulnDictInterface) (driver db.DB, err error) {
func newOvalDB(cnf config.VulnDictInterface) (ovaldb.DB, error) {
if cnf.IsFetchViaHTTP() {
return nil, nil
}
path := cnf.GetURL()
if cnf.GetType() == "sqlite3" {
path = cnf.GetSQLite3Path()
}
driver, locked, err := db.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), db.Option{})
driver, locked, err := ovaldb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), ovaldb.Option{})
if err != nil {
if locked {
err = xerrors.Errorf("SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
return nil, xerrors.Errorf("Failed to init OVAL DB. SQLite3: %s is locked. err: %w, ", cnf.GetSQLite3Path(), err)
}
err = xerrors.Errorf("Failed to new OVAL DB. err: %w", err)
return nil, err
return nil, xerrors.Errorf("Failed to init OVAL DB. DB Path: %s, err: %w", path, err)
}
return driver, nil
}

24
oval/pseudo.go Normal file
View File

@@ -0,0 +1,24 @@
package oval
import "github.com/future-architect/vuls/models"
// Pseudo is OVAL client for Windows, FreeBSD and Pseudo
type Pseudo struct {
Base
}
// NewPseudo creates OVAL client for Windows, FreeBSD and Pseudo
func NewPseudo(family string) Pseudo {
return Pseudo{
Base{
driver: nil,
baseURL: "",
family: family,
},
}
}
// FillWithOval is a mock function for operating systems that do not use OVAL
func (pse Pseudo) FillWithOval(_ *models.ScanResult) (int, error) {
return 0, nil
}

View File

@@ -5,13 +5,14 @@ package oval
import (
"fmt"
"strconv"
"strings"
"github.com/future-architect/vuls/config"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
ovaldb "github.com/vulsio/goval-dictionary/db"
ovalmodels "github.com/vulsio/goval-dictionary/models"
)
@@ -23,23 +24,13 @@ type RedHatBase struct {
// FillWithOval returns scan result after updating CVE info by OVAL
func (o RedHatBase) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
var relatedDefs ovalResult
if o.Cnf.IsFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
return 0, err
if o.driver == nil {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
}
} else {
driver, err := newOvalDB(o.Cnf)
if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
return 0, err
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
}
}
@@ -225,8 +216,8 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo
continue
}
score2, vec2 := o.parseCvss2(cve.Cvss2)
score3, vec3 := o.parseCvss3(cve.Cvss3)
score2, vec2 := parseCvss2(cve.Cvss2)
score3, vec3 := parseCvss3(cve.Cvss3)
sev2, sev3, severity := "", "", def.Advisory.Severity
if cve.Impact != "" {
@@ -262,51 +253,19 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo
return nil
}
// ParseCvss2 divide CVSSv2 string into score and vector
// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P
func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string) {
var err error
ss := strings.Split(scoreVector, "/")
if 1 < len(ss) {
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.Join(ss[1:], "/")
}
return 0, ""
}
// ParseCvss3 divide CVSSv3 string into score and vector
// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string) {
var err error
for _, s := range []string{
"/CVSS:3.0/",
"/CVSS:3.1/",
} {
ss := strings.Split(scoreVector, s)
if 1 < len(ss) {
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.TrimPrefix(s, "/") + ss[1]
}
}
return 0, ""
}
// RedHat is the interface for RedhatBase OVAL
type RedHat struct {
RedHatBase
}
// NewRedhat creates OVAL client for Redhat
func NewRedhat(cnf config.VulnDictInterface) RedHat {
func NewRedhat(driver ovaldb.DB, baseURL string) RedHat {
return RedHat{
RedHatBase{
Base{
family: constant.RedHat,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.RedHat,
},
},
}
@@ -318,12 +277,13 @@ type CentOS struct {
}
// NewCentOS creates OVAL client for CentOS
func NewCentOS(cnf config.VulnDictInterface) CentOS {
func NewCentOS(driver ovaldb.DB, baseURL string) CentOS {
return CentOS{
RedHatBase{
Base{
family: constant.CentOS,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.CentOS,
},
},
}
@@ -335,12 +295,13 @@ type Oracle struct {
}
// NewOracle creates OVAL client for Oracle
func NewOracle(cnf config.VulnDictInterface) Oracle {
func NewOracle(driver ovaldb.DB, baseURL string) Oracle {
return Oracle{
RedHatBase{
Base{
family: constant.Oracle,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Oracle,
},
},
}
@@ -353,12 +314,13 @@ type Amazon struct {
}
// NewAmazon creates OVAL client for Amazon Linux
func NewAmazon(cnf config.VulnDictInterface) Amazon {
func NewAmazon(driver ovaldb.DB, baseURL string) Amazon {
return Amazon{
RedHatBase{
Base{
family: constant.Amazon,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Amazon,
},
},
}
@@ -371,12 +333,13 @@ type Alma struct {
}
// NewAlma creates OVAL client for Alma Linux
func NewAlma(cnf config.VulnDictInterface) Alma {
func NewAlma(driver ovaldb.DB, baseURL string) Alma {
return Alma{
RedHatBase{
Base{
family: constant.Alma,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Alma,
},
},
}
@@ -389,12 +352,13 @@ type Rocky struct {
}
// NewRocky creates OVAL client for Rocky Linux
func NewRocky(cnf config.VulnDictInterface) Rocky {
func NewRocky(driver ovaldb.DB, baseURL string) Rocky {
return Rocky{
RedHatBase{
Base{
family: constant.Rocky,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Rocky,
},
},
}
@@ -407,12 +371,13 @@ type Fedora struct {
}
// NewFedora creates OVAL client for Fedora Linux
func NewFedora(cnf config.VulnDictInterface) Fedora {
func NewFedora(driver ovaldb.DB, baseURL string) Fedora {
return Fedora{
RedHatBase{
Base{
family: constant.Fedora,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: constant.Fedora,
},
},
}

View File

@@ -11,79 +11,6 @@ import (
ovalmodels "github.com/vulsio/goval-dictionary/models"
)
func TestParseCvss2(t *testing.T) {
type out struct {
score float64
vector string
}
var tests = []struct {
in string
out out
}{
{
in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P",
out: out{
score: 5.0,
vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
},
},
{
in: "",
out: out{
score: 0,
vector: "",
},
},
}
for _, tt := range tests {
s, v := RedHatBase{}.parseCvss2(tt.in)
if s != tt.out.score || v != tt.out.vector {
t.Errorf("\nexpected: %f, %s\n actual: %f, %s",
tt.out.score, tt.out.vector, s, v)
}
}
}
func TestParseCvss3(t *testing.T) {
type out struct {
score float64
vector string
}
var tests = []struct {
in string
out out
}{
{
in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
out: out{
score: 5.6,
vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
},
},
{
in: "6.1/CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
out: out{
score: 6.1,
vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
},
},
{
in: "",
out: out{
score: 0,
vector: "",
},
},
}
for _, tt := range tests {
s, v := RedHatBase{}.parseCvss3(tt.in)
if s != tt.out.score || v != tt.out.vector {
t.Errorf("\nexpected: %f, %s\n actual: %f, %s",
tt.out.score, tt.out.vector, s, v)
}
}
}
func TestPackNamesOfUpdate(t *testing.T) {
var tests = []struct {
in models.ScanResult

View File

@@ -4,10 +4,13 @@
package oval
import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"fmt"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
ovaldb "github.com/vulsio/goval-dictionary/db"
ovalmodels "github.com/vulsio/goval-dictionary/models"
)
@@ -17,12 +20,13 @@ type SUSE struct {
}
// NewSUSE creates OVAL client for SUSE
func NewSUSE(cnf config.VulnDictInterface) SUSE {
func NewSUSE(driver ovaldb.DB, baseURL, family string) SUSE {
// TODO implement other family
return SUSE{
Base{
family: constant.SUSEEnterpriseServer,
Cnf: cnf,
driver: driver,
baseURL: baseURL,
family: family,
},
}
}
@@ -30,23 +34,13 @@ func NewSUSE(cnf config.VulnDictInterface) SUSE {
// FillWithOval returns scan result after updating CVE info by OVAL
func (o SUSE) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
var relatedDefs ovalResult
if o.Cnf.IsFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
return 0, err
if o.driver == nil {
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.baseURL); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions via HTTP. err: %w", err)
}
} else {
driver, err := newOvalDB(o.Cnf)
if err != nil {
return 0, err
}
defer func() {
if err := driver.CloseDB(); err != nil {
logging.Log.Errorf("Failed to close DB. err: %+v", err)
}
}()
if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
return 0, err
if relatedDefs, err = getDefsByPackNameFromOvalDB(r, o.driver); err != nil {
return 0, xerrors.Errorf("Failed to get Definitions from DB. err: %w", err)
}
}
for _, defPacks := range relatedDefs.entries {
@@ -56,7 +50,7 @@ func (o SUSE) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
for _, vuln := range r.ScannedCves {
if conts, ok := vuln.CveContents[models.SUSE]; ok {
for i, cont := range conts {
cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
cont.SourceLink = fmt.Sprintf("https://www.suse.com/security/cve/%s.html", cont.CveID)
vuln.CveContents[models.SUSE][i] = cont
}
}
@@ -65,27 +59,30 @@ func (o SUSE) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
}
func (o SUSE) update(r *models.ScanResult, defpacks defPacks) {
ovalContent := *o.convertToModel(&defpacks.def)
ovalContent := o.convertToModel(&defpacks.def)
if ovalContent == nil {
return
}
ovalContent.Type = models.NewCveContentType(o.family)
vinfo, ok := r.ScannedCves[defpacks.def.Title]
vinfo, ok := r.ScannedCves[ovalContent.CveID]
if !ok {
logging.Log.Debugf("%s is newly detected by OVAL", defpacks.def.Title)
logging.Log.Debugf("%s is newly detected by OVAL", ovalContent.CveID)
vinfo = models.VulnInfo{
CveID: defpacks.def.Title,
CveID: ovalContent.CveID,
Confidences: models.Confidences{models.OvalMatch},
CveContents: models.NewCveContents(ovalContent),
CveContents: models.NewCveContents(*ovalContent),
}
} else {
cveContents := vinfo.CveContents
ctype := models.NewCveContentType(o.family)
if _, ok := vinfo.CveContents[ctype]; ok {
logging.Log.Debugf("%s OVAL will be overwritten", defpacks.def.Title)
logging.Log.Debugf("%s OVAL will be overwritten", ovalContent.CveID)
} else {
logging.Log.Debugf("%s is also detected by OVAL", defpacks.def.Title)
logging.Log.Debugf("%s is also detected by OVAL", ovalContent.CveID)
cveContents = models.CveContents{}
}
vinfo.Confidences.AppendIfMissing(models.OvalMatch)
cveContents[ctype] = []models.CveContent{ovalContent}
cveContents[ctype] = []models.CveContent{*ovalContent}
vinfo.CveContents = cveContents
}
@@ -105,10 +102,15 @@ func (o SUSE) update(r *models.ScanResult, defpacks defPacks) {
}
vinfo.AffectedPackages = collectBinpkgFixstat.toPackStatuses()
vinfo.AffectedPackages.Sort()
r.ScannedCves[defpacks.def.Title] = vinfo
r.ScannedCves[ovalContent.CveID] = vinfo
}
func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
if len(def.Advisory.Cves) != 1 {
logging.Log.Warnf("Unknown Oval format. Please register the issue as it needs to be investigated. https://github.com/vulsio/goval-dictionary/issues family: %s, defID: %s", o.family, def.DefinitionID)
return nil
}
refs := []models.Reference{}
for _, r := range def.References {
refs = append(refs, models.Reference{
@@ -117,11 +119,15 @@ func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
RefID: r.RefID,
})
}
cve := def.Advisory.Cves[0]
score3, vec3 := parseCvss3(cve.Cvss3)
return &models.CveContent{
CveID: def.Title,
Title: def.Title,
Summary: def.Description,
References: refs,
Title: def.Title,
Summary: def.Description,
CveID: cve.CveID,
Cvss3Score: score3,
Cvss3Vector: vec3,
Cvss3Severity: cve.Impact,
References: refs,
}
}

View File

@@ -9,22 +9,25 @@ import (
"net/http"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/cenkalti/backoff"
apkver "github.com/knqyf263/go-apk-version"
debver "github.com/knqyf263/go-deb-version"
rpmver "github.com/knqyf263/go-rpm-version"
"github.com/parnurzeal/gorequest"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
apkver "github.com/knqyf263/go-apk-version"
debver "github.com/knqyf263/go-deb-version"
rpmver "github.com/knqyf263/go-rpm-version"
"github.com/parnurzeal/gorequest"
"github.com/vulsio/goval-dictionary/db"
ovaldb "github.com/vulsio/goval-dictionary/db"
ovallog "github.com/vulsio/goval-dictionary/log"
ovalmodels "github.com/vulsio/goval-dictionary/models"
"golang.org/x/xerrors"
)
type ovalResult struct {
@@ -244,7 +247,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
}
}
func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDefs ovalResult, err error) {
func getDefsByPackNameFromOvalDB(r *models.ScanResult, driver ovaldb.DB) (relatedDefs ovalResult, err error) {
requests := []request{}
for _, pack := range r.Packages {
requests = append(requests, request{
@@ -398,7 +401,10 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
constant.Fedora,
constant.Amazon,
constant.Oracle,
constant.OpenSUSE,
constant.OpenSUSELeap,
constant.SUSEEnterpriseServer,
constant.SUSEEnterpriseDesktop,
constant.Debian,
constant.Raspbian,
constant.Ubuntu:
@@ -437,27 +443,30 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
constant.Raspbian:
vera, err := debver.NewVersion(newVer)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", newVer, err)
}
verb, err := debver.NewVersion(packInOVAL.Version)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", packInOVAL.Version, err)
}
return vera.LessThan(verb), nil
case constant.Alpine:
vera, err := apkver.NewVersion(newVer)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", newVer, err)
}
verb, err := apkver.NewVersion(packInOVAL.Version)
if err != nil {
return false, err
return false, xerrors.Errorf("Failed to parse version. version: %s, err: %w", packInOVAL.Version, err)
}
return vera.LessThan(verb), nil
case constant.Oracle,
constant.OpenSUSE,
constant.OpenSUSELeap,
constant.SUSEEnterpriseServer,
constant.SUSEEnterpriseDesktop,
constant.Amazon,
constant.Fedora:
vera := rpmver.NewVersion(newVer)
@@ -484,35 +493,49 @@ func rhelRebuildOSVersionToRHEL(ver string) string {
}
// NewOVALClient returns a client for OVAL database
func NewOVALClient(family string, cnf config.GovalDictConf) (Client, error) {
func NewOVALClient(family string, cnf config.GovalDictConf, o logging.LogOpts) (Client, error) {
if err := ovallog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
return nil, xerrors.Errorf("Failed to set goval-dictionary logger. err: %w", err)
}
driver, err := newOvalDB(&cnf)
if err != nil {
return nil, xerrors.Errorf("Failed to newOvalDB. err: %w", err)
}
switch family {
case constant.Debian, constant.Raspbian:
return NewDebian(&cnf), nil
return NewDebian(driver, cnf.GetURL()), nil
case constant.Ubuntu:
return NewUbuntu(&cnf), nil
return NewUbuntu(driver, cnf.GetURL()), nil
case constant.RedHat:
return NewRedhat(&cnf), nil
return NewRedhat(driver, cnf.GetURL()), nil
case constant.CentOS:
return NewCentOS(&cnf), nil
return NewCentOS(driver, cnf.GetURL()), nil
case constant.Alma:
return NewAlma(&cnf), nil
return NewAlma(driver, cnf.GetURL()), nil
case constant.Rocky:
return NewRocky(&cnf), nil
return NewRocky(driver, cnf.GetURL()), nil
case constant.Oracle:
return NewOracle(&cnf), nil
return NewOracle(driver, cnf.GetURL()), nil
case constant.OpenSUSE:
return NewSUSE(driver, cnf.GetURL(), constant.OpenSUSE), nil
case constant.OpenSUSELeap:
return NewSUSE(driver, cnf.GetURL(), constant.OpenSUSELeap), nil
case constant.SUSEEnterpriseServer:
// TODO other suse family
return NewSUSE(&cnf), nil
return NewSUSE(driver, cnf.GetURL(), constant.SUSEEnterpriseServer), nil
case constant.SUSEEnterpriseDesktop:
return NewSUSE(driver, cnf.GetURL(), constant.SUSEEnterpriseDesktop), nil
case constant.Alpine:
return NewAlpine(&cnf), nil
return NewAlpine(driver, cnf.GetURL()), nil
case constant.Amazon:
return NewAmazon(&cnf), nil
return NewAmazon(driver, cnf.GetURL()), nil
case constant.Fedora:
return NewFedora(&cnf), nil
return NewFedora(driver, cnf.GetURL()), nil
case constant.FreeBSD, constant.Windows:
return nil, nil
return NewPseudo(family), nil
case constant.ServerTypePseudo:
return nil, nil
return NewPseudo(family), nil
default:
if family == "" {
return nil, xerrors.New("Probably an error occurred during scanning. Check the error message")
@@ -535,9 +558,14 @@ func GetFamilyInOval(familyInScanResult string) (string, error) {
return constant.Fedora, nil
case constant.Oracle:
return constant.Oracle, nil
case constant.OpenSUSE:
return constant.OpenSUSE, nil
case constant.OpenSUSELeap:
return constant.OpenSUSELeap, nil
case constant.SUSEEnterpriseServer:
// TODO other suse family
return constant.SUSEEnterpriseServer, nil
case constant.SUSEEnterpriseDesktop:
return constant.SUSEEnterpriseDesktop, nil
case constant.Alpine:
return constant.Alpine, nil
case constant.Amazon:
@@ -554,3 +582,36 @@ func GetFamilyInOval(familyInScanResult string) (string, error) {
}
}
// ParseCvss2 divide CVSSv2 string into score and vector
// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P
func parseCvss2(scoreVector string) (score float64, vector string) {
var err error
ss := strings.Split(scoreVector, "/")
if 1 < len(ss) {
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.Join(ss[1:], "/")
}
return 0, ""
}
// ParseCvss3 divide CVSSv3 string into score and vector
// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
func parseCvss3(scoreVector string) (score float64, vector string) {
var err error
for _, s := range []string{
"/CVSS:3.0/",
"/CVSS:3.1/",
} {
ss := strings.Split(scoreVector, s)
if 1 < len(ss) {
if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
return 0, ""
}
return score, strings.TrimPrefix(s, "/") + ss[1]
}
}
return 0, ""
}

View File

@@ -2049,3 +2049,76 @@ func Test_ovalResult_Sort(t *testing.T) {
})
}
}
func TestParseCvss2(t *testing.T) {
type out struct {
score float64
vector string
}
var tests = []struct {
in string
out out
}{
{
in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P",
out: out{
score: 5.0,
vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
},
},
{
in: "",
out: out{
score: 0,
vector: "",
},
},
}
for _, tt := range tests {
s, v := parseCvss2(tt.in)
if s != tt.out.score || v != tt.out.vector {
t.Errorf("\nexpected: %f, %s\n actual: %f, %s",
tt.out.score, tt.out.vector, s, v)
}
}
}
func TestParseCvss3(t *testing.T) {
type out struct {
score float64
vector string
}
var tests = []struct {
in string
out out
}{
{
in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
out: out{
score: 5.6,
vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
},
},
{
in: "6.1/CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
out: out{
score: 6.1,
vector: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
},
},
{
in: "",
out: out{
score: 0,
vector: "",
},
},
}
for _, tt := range tests {
s, v := parseCvss3(tt.in)
if s != tt.out.score || v != tt.out.vector {
t.Errorf("\nexpected: %f, %s\n actual: %f, %s",
tt.out.score, tt.out.vector, s, v)
}
}
}

View File

@@ -255,6 +255,7 @@ No CVE-IDs are found in updatable packages.
// v2max := vinfo.MaxCvss2Score().Value.Score
// v3max := vinfo.MaxCvss3Score().Value.Score
packnames := strings.Join(vinfo.AffectedPackages.Names(), ", ")
// packname := vinfo.AffectedPackages.FormatTuiSummary()
// packname += strings.Join(vinfo.CpeURIs, ", ")
@@ -263,12 +264,12 @@ No CVE-IDs are found in updatable packages.
exploits = "POC"
}
link := ""
if strings.HasPrefix(vinfo.CveID, "CVE-") {
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
}
// link := ""
// if strings.HasPrefix(vinfo.CveID, "CVE-") {
// link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
// } else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
// link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
// }
data = append(data, []string{
vinfo.CveIDDiffFormat(),
@@ -279,7 +280,7 @@ No CVE-IDs are found in updatable packages.
exploits,
fmt.Sprintf("%9s", vinfo.AlertDict.FormatSource()),
fmt.Sprintf("%7s", vinfo.PatchStatus(r.Packages)),
link,
packnames,
})
}
@@ -294,9 +295,11 @@ No CVE-IDs are found in updatable packages.
"PoC",
"Alert",
"Fixed",
"NVD",
// "NVD",
"Packages",
})
table.SetBorder(true)
table.SetRowLine(true)
table.AppendBulk(data)
table.Render()
return fmt.Sprintf("%s\n%s", header, b.String())
@@ -373,8 +376,8 @@ No CVE-IDs are found in updatable packages.
if len(pack.AffectedProcs) != 0 {
for _, p := range pack.AffectedProcs {
if len(p.ListenPortStats) == 0 {
data = append(data, []string{"",
fmt.Sprintf(" - PID: %s %s, Port: []", p.PID, p.Name)})
data = append(data, []string{"", fmt.Sprintf(" - PID: %s %s", p.PID, p.Name)})
continue
}
var ports []string
@@ -412,8 +415,7 @@ No CVE-IDs are found in updatable packages.
wp.Name, p.Version, p.Update, wp.FixedIn, p.Status)})
}
} else {
data = append(data, []string{"WordPress",
fmt.Sprintf("%s", wp.Name)})
data = append(data, []string{"WordPress", wp.Name})
}
}
@@ -462,9 +464,16 @@ No CVE-IDs are found in updatable packages.
for _, url := range cweURLs {
data = append(data, []string{"CWE", url})
}
m := map[string]struct{}{}
for _, exploit := range vuln.Exploits {
if _, ok := m[exploit.URL]; ok {
continue
}
data = append(data, []string{string(exploit.ExploitType), exploit.URL})
m[exploit.URL] = struct{}{}
}
for _, url := range top10URLs {
data = append(data, []string{"OWASP Top10", url})
}
@@ -614,7 +623,7 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
previousCveIDsSet[previousVulnInfo.CveID] = true
}
new := models.VulnInfos{}
newer := models.VulnInfos{}
updated := models.VulnInfos{}
for _, v := range current.ScannedCves {
if previousCveIDsSet[v.CveID] {
@@ -634,17 +643,17 @@ func getPlusDiffCves(previous, current models.ScanResult) models.VulnInfos {
logging.Log.Debugf("same: %s", v.CveID)
}
} else {
logging.Log.Debugf("new: %s", v.CveID)
logging.Log.Debugf("newer: %s", v.CveID)
v.DiffStatus = models.DiffPlus
new[v.CveID] = v
newer[v.CveID] = v
}
}
if len(updated) == 0 && len(new) == 0 {
if len(updated) == 0 && len(newer) == 0 {
logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
}
for cveID, vuln := range new {
for cveID, vuln := range newer {
updated[cveID] = vuln
}
return updated

View File

@@ -19,8 +19,8 @@ func TestMain(m *testing.M) {
func TestIsCveInfoUpdated(t *testing.T) {
f := "2006-01-02"
old, _ := time.Parse(f, "2015-12-15")
new, _ := time.Parse(f, "2015-12-16")
base, _ := time.Parse(f, "2015-12-15")
newer, _ := time.Parse(f, "2015-12-16")
type In struct {
cveID string
@@ -78,7 +78,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
models.CveContent{
Type: models.Jvn,
CveID: "CVE-2017-0002",
LastModified: old,
LastModified: base,
},
),
},
@@ -92,7 +92,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
models.CveContent{
Type: models.Jvn,
CveID: "CVE-2017-0002",
LastModified: old,
LastModified: base,
},
),
},
@@ -114,7 +114,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
models.CveContent{
Type: models.Nvd,
CveID: "CVE-2017-0002",
LastModified: new,
LastModified: newer,
},
),
},
@@ -129,7 +129,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
models.CveContent{
Type: models.Nvd,
CveID: "CVE-2017-0002",
LastModified: old,
LastModified: base,
},
),
},
@@ -151,7 +151,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
models.CveContent{
Type: models.Nvd,
CveID: "CVE-2017-0002",
LastModified: old,
LastModified: base,
},
),
},

View File

@@ -18,6 +18,7 @@ import (
"github.com/aquasecurity/fanal/analyzer"
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
debver "github.com/knqyf263/go-deb-version"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
@@ -133,6 +134,10 @@ func (l *base) runningKernel() (release, version string, err error) {
if 6 < len(ss) {
version = ss[6]
}
if _, err := debver.NewVersion(version); err != nil {
l.log.Warnf("kernel running version is invalid. skip kernel vulnerability detection. actual kernel version: %s, err: %s", version, err)
version = ""
}
}
return
}
@@ -587,12 +592,12 @@ func (l *base) scanLibraries() (err error) {
if l.ServerInfo.FindLock {
findopt := ""
for filename := range models.LibraryMap {
findopt += fmt.Sprintf("-name %q -o ", "*"+filename)
findopt += fmt.Sprintf("-name %q -o ", filename)
}
// delete last "-o "
// find / -name "*package-lock.json" -o -name "*yarn.lock" ... 2>&1 | grep -v "find: "
cmd := fmt.Sprintf(`find / ` + findopt[:len(findopt)-3] + ` 2>&1 | grep -v "find: "`)
// find / -type f -and \( -name "package-lock.json" -o -name "yarn.lock" ... \) 2>&1 | grep -v "find: "
cmd := fmt.Sprintf(`find / -type f -and \( ` + findopt[:len(findopt)-3] + ` \) 2>&1 | grep -v "find: "`)
r := exec(l.ServerInfo, cmd, noSudo)
if r.ExitStatus != 0 && r.ExitStatus != 1 {
return xerrors.Errorf("Failed to find lock files")
@@ -681,6 +686,7 @@ func AnalyzeLibraries(ctx context.Context, libFilemap map[string]LibFile, isOffl
analyzer.TypeJSON,
analyzer.TypeDockerfile,
analyzer.TypeHCL,
analyzer.TypeSecret,
}
anal := analyzer.NewAnalyzerGroup(analyzer.GroupBuiltin, disabledAnalyzers)
@@ -696,6 +702,7 @@ func AnalyzeLibraries(ctx context.Context, libFilemap map[string]LibFile, isOffl
path,
&DummyFileInfo{size: int64(len(f.Contents)), filemode: f.Filemode},
func() (dio.ReadSeekCloserAt, error) { return dio.NopCloser(bytes.NewReader(f.Contents)), nil },
nil,
analyzer.AnalysisOptions{Offline: isOffline},
); err != nil {
return nil, xerrors.Errorf("Failed to get libs. err: %w", err)

View File

@@ -48,8 +48,7 @@ func detectDebian(c config.ServerInfo) (bool, osTypeInterface, error) {
return false, nil, nil
}
if r.ExitStatus == 255 {
deb := newDebian(c) // Panic occur when return value 2 is nil and 3 is non-nil
return false, deb, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r)
return false, &unknown{base{ServerInfo: c}}, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings.\n%s", r)
}
logging.Log.Debugf("Not Debian like Linux. %s", r)
return false, nil, nil

View File

@@ -186,10 +186,10 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
return execResult{Error: err}
}
defaultSSHArgs := []string{"-tt"}
args := []string{"-tt"}
if 0 < len(c.SSHConfigPath) {
defaultSSHArgs = append(defaultSSHArgs, "-F", c.SSHConfigPath)
if c.SSHConfigPath != "" {
args = append(args, "-F", c.SSHConfigPath)
} else {
home, err := homedir.Dir()
if err != nil {
@@ -200,7 +200,7 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
}
controlPath := filepath.Join(home, ".vuls", `controlmaster-%r-`+c.ServerName+`.%p`)
defaultSSHArgs = append(defaultSSHArgs,
args = append(args,
"-o", "StrictHostKeyChecking=yes",
"-o", "LogLevel=quiet",
"-o", "ConnectionAttempts=3",
@@ -212,19 +212,22 @@ func sshExecExternal(c config.ServerInfo, cmd string, sudo bool) (result execRes
}
if config.Conf.Vvv {
defaultSSHArgs = append(defaultSSHArgs, "-vvv")
args = append(args, "-vvv")
}
if len(c.JumpServer) != 0 {
defaultSSHArgs = append(defaultSSHArgs, "-J", strings.Join(c.JumpServer, ","))
args = append(args, "-J", strings.Join(c.JumpServer, ","))
}
args := append(defaultSSHArgs, fmt.Sprintf("%s@%s", c.User, c.Host))
args = append(args, "-p", c.Port)
if 0 < len(c.KeyPath) {
if c.User != "" {
args = append(args, "-l", c.User)
}
if c.Port != "" {
args = append(args, "-p", c.Port)
}
if c.KeyPath != "" {
args = append(args, "-i", c.KeyPath)
args = append(args, "-o", "PasswordAuthentication=no")
}
args = append(args, c.Host)
cmd = decorateCmd(c, cmd, sudo)
cmd = fmt.Sprintf("stty cols 1000; %s", cmd)

View File

@@ -26,17 +26,17 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
fed := newFedora(c)
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse Fedora version: %s", r)
fed.setErrs([]error{xerrors.Errorf("Failed to parse /etc/fedora-release. r.Stdout: %s", r.Stdout)})
return true, fed
}
release := result[2]
ver, err := strconv.Atoi(release)
major, err := strconv.Atoi(util.Major(release))
if err != nil {
logging.Log.Warnf("Failed to parse Fedora version: %s", release)
fed.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, fed
}
if ver < 32 {
logging.Log.Warnf("Versions prior to Fedora 32 are not supported, detected version is %s", release)
if major < 32 {
fed.setErrs([]error{xerrors.Errorf("Failed to init Fedora. err: not supported major version. versions prior to Fedora 32 are not supported, detected version is %s", release)})
return true, fed
}
fed.setDistro(constant.Fedora, release)
@@ -48,14 +48,22 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
// Need to discover Oracle Linux first, because it provides an
// /etc/redhat-release that matches the upstream distribution
if r := exec(c, "cat /etc/oracle-release", noSudo); r.isSuccess() {
ora := newOracle(c)
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse Oracle Linux version: %s", r)
return true, newOracle(c)
ora.setErrs([]error{xerrors.Errorf("Failed to parse /etc/oracle-release. r.Stdout: %s", r.Stdout)})
return true, ora
}
ora := newOracle(c)
release := result[2]
major, err := strconv.Atoi(util.Major(release))
if err != nil {
ora.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, ora
}
if major < 5 {
ora.setErrs([]error{xerrors.Errorf("Failed to init Oracle Linux. err: not supported major version. versions prior to Oracle Linux 5 are not supported, detected version is %s", release)})
return true, ora
}
ora.setDistro(constant.Oracle, release)
return true, ora
}
@@ -63,40 +71,60 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
if r := exec(c, "ls /etc/almalinux-release", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/almalinux-release", noSudo); r.isSuccess() {
alma := newAlma(c)
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse Alma version: %s", r)
return true, newAlma(c)
alma.setErrs([]error{xerrors.Errorf("Failed to parse /etc/almalinux-release. r.Stdout: %s", r.Stdout)})
return true, alma
}
release := result[2]
major, err := strconv.Atoi(util.Major(release))
if err != nil {
alma.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, alma
}
if major < 8 {
alma.setErrs([]error{xerrors.Errorf("Failed to init AlmaLinux. err: not supported major version. versions prior to AlmaLinux 8 are not supported, detected version is %s", release)})
return true, alma
}
switch strings.ToLower(result[1]) {
case "alma", "almalinux":
alma := newAlma(c)
alma.setDistro(constant.Alma, release)
return true, alma
default:
logging.Log.Warnf("Failed to parse Alma: %s", r)
alma.setErrs([]error{xerrors.Errorf("Failed to parse AlmaLinux Name. release: %s", release)})
return true, alma
}
}
}
if r := exec(c, "ls /etc/rocky-release", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/rocky-release", noSudo); r.isSuccess() {
rocky := newRocky(c)
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse Rocky version: %s", r)
return true, newRocky(c)
rocky.setErrs([]error{xerrors.Errorf("Failed to parse /etc/rocky-release. r.Stdout: %s", r.Stdout)})
return true, rocky
}
release := result[2]
major, err := strconv.Atoi(util.Major(release))
if err != nil {
rocky.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, rocky
}
if major < 8 {
rocky.setErrs([]error{xerrors.Errorf("Failed to init Rocky Linux. err: not supported major version. versions prior to Rocky Linux 8 are not supported, detected version is %s", release)})
return true, rocky
}
switch strings.ToLower(result[1]) {
case "rocky", "rocky linux":
rocky := newRocky(c)
rocky.setDistro(constant.Rocky, release)
return true, rocky
default:
logging.Log.Warnf("Failed to parse Rocky: %s", r)
rocky.setErrs([]error{xerrors.Errorf("Failed to parse Rocky Linux Name. release: %s", release)})
return true, rocky
}
}
}
@@ -107,30 +135,55 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
if r := exec(c, "cat /etc/centos-release", noSudo); r.isSuccess() {
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse CentOS version: %s", r)
return true, newCentOS(c)
cent := newCentOS(c)
cent.setErrs([]error{xerrors.Errorf("Failed to parse /etc/centos-release. r.Stdout: %s", r.Stdout)})
return true, cent
}
release := result[2]
major, err := strconv.Atoi(util.Major(release))
if err != nil {
cent := newCentOS(c)
cent.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, cent
}
switch strings.ToLower(result[1]) {
case "centos", "centos linux":
cent := newCentOS(c)
if major < 5 {
cent.setErrs([]error{xerrors.Errorf("Failed to init CentOS. err: not supported major version. versions prior to CentOS 5 are not supported, detected version is %s", release)})
return true, cent
}
cent.setDistro(constant.CentOS, release)
return true, cent
case "centos stream":
cent := newCentOS(c)
if major < 8 {
cent.setErrs([]error{xerrors.Errorf("Failed to init CentOS Stream. err: not supported major version. versions prior to CentOS Stream 8 are not supported, detected version is %s", release)})
return true, cent
}
cent.setDistro(constant.CentOS, fmt.Sprintf("stream%s", release))
return true, cent
case "alma", "almalinux":
alma := newAlma(c)
if major < 8 {
alma.setErrs([]error{xerrors.Errorf("Failed to init AlmaLinux. err: not supported major version. versions prior to AlmaLinux 8 are not supported, detected version is %s", release)})
return true, alma
}
alma.setDistro(constant.Alma, release)
return true, alma
case "rocky", "rocky linux":
rocky := newRocky(c)
if major < 8 {
rocky.setErrs([]error{xerrors.Errorf("Failed to init Rocky Linux. err: not supported major version. versions prior to Rocky Linux 8 are not supported, detected version is %s", release)})
return true, rocky
}
rocky.setDistro(constant.Rocky, release)
return true, rocky
default:
logging.Log.Warnf("Failed to parse CentOS: %s", r)
cent := newCentOS(c)
cent.setErrs([]error{xerrors.Errorf("Failed to parse CentOS Name. release: %s", release)})
return true, cent
}
}
}
@@ -139,47 +192,74 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
// e.g.
// $ cat /etc/redhat-release
// Red Hat Enterprise Linux Server release 6.8 (Santiago)
// CentOS release 6.5 (Final)
// CentOS Stream release 8
// AlmaLinux release 8.5 (Arctic Sphynx)
// Rocky Linux release 8.5 (Green Obsidian)
// Fedora release 35 (Thirty Five)
if r := exec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
result := releasePattern.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
logging.Log.Warnf("Failed to parse RedHat/CentOS version: %s", r)
return true, newCentOS(c)
rhel := newRHEL(c)
rhel.setErrs([]error{xerrors.Errorf("Failed to parse /etc/redhat-release. r.Stdout: %s", r.Stdout)})
return true, rhel
}
release := result[2]
ver, err := strconv.Atoi(release)
major, err := strconv.Atoi(util.Major(release))
if err != nil {
logging.Log.Warnf("Failed to parse RedHat/CentOS version number: %s", release)
return true, newCentOS(c)
}
if ver < 5 {
logging.Log.Warnf("Versions prior to RedHat/CentOS 5 are not supported, detected version is %s", release)
rhel := newRHEL(c)
rhel.setErrs([]error{xerrors.Errorf("Failed to parse major version from release: %s", release)})
return true, rhel
}
switch strings.ToLower(result[1]) {
case "fedora":
fed := newFedora(c)
if major < 32 {
fed.setErrs([]error{xerrors.Errorf("Failed to init Fedora. err: not supported major version. versions prior to Fedora 32 are not supported, detected version is %s", release)})
return true, fed
}
fed.setDistro(constant.Fedora, release)
return true, fed
case "centos", "centos linux":
cent := newCentOS(c)
if major < 5 {
cent.setErrs([]error{xerrors.Errorf("Failed to init CentOS. err: not supported major version. versions prior to CentOS 5 are not supported, detected version is %s", release)})
return true, cent
}
cent.setDistro(constant.CentOS, release)
return true, cent
case "centos stream":
cent := newCentOS(c)
if major < 8 {
cent.setErrs([]error{xerrors.Errorf("Failed to init CentOS Stream. err: not supported major version. versions prior to CentOS Stream 8 are not supported, detected version is %s", release)})
return true, cent
}
cent.setDistro(constant.CentOS, fmt.Sprintf("stream%s", release))
return true, cent
case "alma", "almalinux":
alma := newAlma(c)
if major < 8 {
alma.setErrs([]error{xerrors.Errorf("Failed to init AlmaLinux. err: not supported major version. versions prior to AlmaLinux 8 are not supported, detected version is %s", release)})
return true, alma
}
alma.setDistro(constant.Alma, release)
return true, alma
case "rocky", "rocky linux":
rocky := newRocky(c)
if major < 8 {
rocky.setErrs([]error{xerrors.Errorf("Failed to init Rocky Linux. err: not supported major version. versions prior to Rocky Linux 8 are not supported, detected version is %s", release)})
return true, rocky
}
rocky.setDistro(constant.Rocky, release)
return true, rocky
default:
// RHEL
rhel := newRHEL(c)
if major < 5 {
rhel.setErrs([]error{xerrors.Errorf("Failed to init RedHat Enterprise Linux. err: not supported major version. versions prior to RedHat Enterprise Linux 5 are not supported, detected version is %s", release)})
return true, rhel
}
rhel.setDistro(constant.RedHat, release)
return true, rhel
}
@@ -534,12 +614,7 @@ func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error
func (o *redhatBase) isExecYumPS() bool {
switch o.Distro.Family {
case constant.Oracle,
constant.OpenSUSE,
constant.OpenSUSELeap,
constant.SUSEEnterpriseServer,
constant.SUSEEnterpriseDesktop,
constant.SUSEOpenstackCloud:
case constant.Oracle:
return false
}
return !o.getServerInfo().Mode.IsFast()
@@ -547,21 +622,33 @@ func (o *redhatBase) isExecYumPS() bool {
func (o *redhatBase) isExecNeedsRestarting() bool {
switch o.Distro.Family {
case constant.OpenSUSE,
constant.OpenSUSELeap,
constant.SUSEEnterpriseServer,
constant.SUSEEnterpriseDesktop,
constant.SUSEOpenstackCloud:
// TODO zypper ps
// https://github.com/future-architect/vuls/issues/696
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
if o.getServerInfo().Mode.IsOffline() {
return false
} else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() {
return true
}
return false
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle, constant.Fedora:
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky, constant.Oracle:
majorVersion, err := o.Distro.MajorVersion()
if err != nil || majorVersion < 6 {
o.log.Errorf("Not implemented yet: %s, err: %+v", o.Distro, err)
return false
}
if o.getServerInfo().Mode.IsOffline() {
return false
} else if o.getServerInfo().Mode.IsFastRoot() || o.getServerInfo().Mode.IsDeep() {
return true
}
return false
case constant.Fedora:
majorVersion, err := o.Distro.MajorVersion()
if err != nil || majorVersion < 13 {
o.log.Errorf("Not implemented yet: %s, err: %+v", o.Distro, err)
return false
}
if o.getServerInfo().Mode.IsOffline() {
return false
} else if o.getServerInfo().Mode.IsFastRoot() ||
@@ -571,10 +658,7 @@ func (o *redhatBase) isExecNeedsRestarting() bool {
return false
}
if o.getServerInfo().Mode.IsFast() {
return false
}
return true
return !o.getServerInfo().Mode.IsFast()
}
func (o *redhatBase) needsRestarting() error {
@@ -700,35 +784,49 @@ func (o *redhatBase) getOwnerPkgs(paths []string) (names []string, _ error) {
func (o *redhatBase) rpmQa() string {
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
const newer = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
switch o.Distro.Family {
case constant.SUSEEnterpriseServer:
case constant.OpenSUSE:
if o.Distro.Release == "tumbleweed" {
return newer
}
return old
case constant.OpenSUSELeap:
return newer
case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
if v, _ := o.Distro.MajorVersion(); v < 12 {
return old
}
return new
return newer
default:
if v, _ := o.Distro.MajorVersion(); v < 6 {
return old
}
return new
return newer
}
}
func (o *redhatBase) rpmQf() string {
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
const newer = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
switch o.Distro.Family {
case constant.SUSEEnterpriseServer:
case constant.OpenSUSE:
if o.Distro.Release == "tumbleweed" {
return newer
}
return old
case constant.OpenSUSELeap:
return newer
case constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
if v, _ := o.Distro.MajorVersion(); v < 12 {
return old
}
return new
return newer
default:
if v, _ := o.Distro.MajorVersion(); v < 6 {
return old
}
return new
return newer
}
}

View File

@@ -5,15 +5,19 @@ import (
"math/rand"
"net/http"
"os"
ex "os/exec"
"strings"
"time"
debver "github.com/knqyf263/go-deb-version"
"golang.org/x/xerrors"
"github.com/future-architect/vuls/cache"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/constant"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"golang.org/x/xerrors"
)
const (
@@ -23,10 +27,9 @@ const (
)
var (
errOSFamilyHeader = xerrors.New("X-Vuls-OS-Family header is required")
errOSReleaseHeader = xerrors.New("X-Vuls-OS-Release header is required")
errKernelVersionHeader = xerrors.New("X-Vuls-Kernel-Version header is required")
errServerNameHeader = xerrors.New("X-Vuls-Server-Name header is required")
errOSFamilyHeader = xerrors.New("X-Vuls-OS-Family header is required")
errOSReleaseHeader = xerrors.New("X-Vuls-OS-Release header is required")
errServerNameHeader = xerrors.New("X-Vuls-Server-Name header is required")
)
var servers, errServers []osTypeInterface
@@ -162,8 +165,15 @@ func ViaHTTP(header http.Header, body string, toLocalFile bool) (models.ScanResu
}
kernelVersion := header.Get("X-Vuls-Kernel-Version")
if family == constant.Debian && kernelVersion == "" {
return models.ScanResult{}, errKernelVersionHeader
if family == constant.Debian {
if kernelVersion == "" {
logging.Log.Warn("X-Vuls-Kernel-Version is empty. skip kernel vulnerability detection.")
} else {
if _, err := debver.NewVersion(kernelVersion); err != nil {
logging.Log.Warnf("X-Vuls-Kernel-Version is invalid. skip kernel vulnerability detection. actual kernelVersion: %s, err: %s", kernelVersion, err)
kernelVersion = ""
}
}
}
serverName := header.Get("X-Vuls-Server-Name")
@@ -227,6 +237,8 @@ func ParseInstalledPkgs(distro config.Distro, kernel models.Kernel, pkgList stri
osType = &amazon{redhatBase: redhatBase{base: base}}
case constant.Fedora:
osType = &fedora{redhatBase: redhatBase{base: base}}
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
osType = &suse{redhatBase: redhatBase{base: base}}
default:
return models.Packages{}, models.SrcPackages{}, xerrors.Errorf("Server mode for %s is not implemented yet", base.Distro.Family)
}
@@ -278,6 +290,12 @@ func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
logging.Log.Debugf("Panic: %s on %s", p, srv.ServerName)
}
}()
if err := validateSSHConfig(&srv); err != nil {
checkOS := unknown{base{ServerInfo: srv}}
checkOS.setErrs([]error{err})
osTypeChan <- &checkOS
return
}
osTypeChan <- s.detectOS(srv)
}(target)
}
@@ -288,12 +306,10 @@ func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
case res := <-osTypeChan:
if 0 < len(res.getErrs()) {
errServers = append(errServers, res)
logging.Log.Errorf("(%d/%d) Failed: %s, err: %+v",
i+1, len(s.Targets), res.getServerInfo().ServerName, res.getErrs())
logging.Log.Errorf("(%d/%d) Failed: %s, err: %+v", i+1, len(s.Targets), res.getServerInfo().ServerName, res.getErrs())
} else {
servers = append(servers, res)
logging.Log.Infof("(%d/%d) Detected: %s: %s",
i+1, len(s.Targets), res.getServerInfo().ServerName, res.getDistro())
logging.Log.Infof("(%d/%d) Detected: %s: %s", i+1, len(s.Targets), res.getServerInfo().ServerName, res.getDistro())
}
case <-timeout:
msg := "Timed out while detecting servers"
@@ -319,6 +335,132 @@ func (s Scanner) detectServerOSes() (servers, errServers []osTypeInterface) {
return
}
func validateSSHConfig(c *config.ServerInfo) error {
if isLocalExec(c.Port, c.Host) || c.Type == constant.ServerTypePseudo {
return nil
}
logging.Log.Debugf("Validating SSH Settings for Server:%s ...", c.GetServerName())
sshBinaryPath, err := ex.LookPath("ssh")
if err != nil {
return xerrors.Errorf("Failed to lookup ssh binary path. err: %w", err)
}
sshKeygenBinaryPath, err := ex.LookPath("ssh-keygen")
if err != nil {
return xerrors.Errorf("Failed to lookup ssh-keygen binary path. err: %w", err)
}
sshConfigCmd := []string{sshBinaryPath, "-G"}
if c.SSHConfigPath != "" {
sshConfigCmd = append(sshConfigCmd, "-F", c.SSHConfigPath)
}
if c.Port != "" {
sshConfigCmd = append(sshConfigCmd, "-p", c.Port)
}
if c.User != "" {
sshConfigCmd = append(sshConfigCmd, "-l", c.User)
}
if len(c.JumpServer) > 0 {
sshConfigCmd = append(sshConfigCmd, "-J", strings.Join(c.JumpServer, ","))
}
sshConfigCmd = append(sshConfigCmd, c.Host)
cmd := strings.Join(sshConfigCmd, " ")
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
r := localExec(*c, cmd, noSudo)
if !r.isSuccess() {
return xerrors.Errorf("Failed to print SSH configuration. err: %w", r.Error)
}
var (
hostname string
strictHostKeyChecking string
globalKnownHosts string
userKnownHosts string
proxyCommand string
proxyJump string
)
for _, line := range strings.Split(r.Stdout, "\n") {
switch {
case strings.HasPrefix(line, "user "):
user := strings.TrimPrefix(line, "user ")
logging.Log.Debugf("Setting SSH User:%s for Server:%s ...", user, c.GetServerName())
c.User = user
case strings.HasPrefix(line, "hostname "):
hostname = strings.TrimPrefix(line, "hostname ")
case strings.HasPrefix(line, "port "):
port := strings.TrimPrefix(line, "port ")
logging.Log.Debugf("Setting SSH Port:%s for Server:%s ...", port, c.GetServerName())
c.Port = port
case strings.HasPrefix(line, "stricthostkeychecking "):
strictHostKeyChecking = strings.TrimPrefix(line, "stricthostkeychecking ")
case strings.HasPrefix(line, "globalknownhostsfile "):
globalKnownHosts = strings.TrimPrefix(line, "globalknownhostsfile ")
case strings.HasPrefix(line, "userknownhostsfile "):
userKnownHosts = strings.TrimPrefix(line, "userknownhostsfile ")
case strings.HasPrefix(line, "proxycommand "):
proxyCommand = strings.TrimPrefix(line, "proxycommand ")
case strings.HasPrefix(line, "proxyjump "):
proxyJump = strings.TrimPrefix(line, "proxyjump ")
}
}
if c.User == "" || c.Port == "" {
return xerrors.New("Failed to find User or Port setting. Please check the User or Port settings for SSH")
}
if strictHostKeyChecking == "false" || proxyCommand != "" || proxyJump != "" {
return nil
}
logging.Log.Debugf("Checking if the host's public key is in known_hosts...")
knownHostsPaths := []string{}
for _, knownHosts := range []string{userKnownHosts, globalKnownHosts} {
for _, knownHost := range strings.Split(knownHosts, " ") {
if knownHost != "" && knownHost != "/dev/null" {
knownHostsPaths = append(knownHostsPaths, knownHost)
}
}
}
if len(knownHostsPaths) == 0 {
return xerrors.New("Failed to find any known_hosts to use. Please check the UserKnownHostsFile and GlobalKnownHostsFile settings for SSH")
}
for _, knownHosts := range knownHostsPaths {
if c.Port != "" && c.Port != "22" {
cmd := fmt.Sprintf("%s -F %s -f %s", sshKeygenBinaryPath, fmt.Sprintf("\"[%s]:%s\"", hostname, c.Port), knownHosts)
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
if r := localExec(*c, cmd, noSudo); r.isSuccess() {
return nil
}
}
cmd := fmt.Sprintf("%s -F %s -f %s", sshKeygenBinaryPath, hostname, knownHosts)
logging.Log.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
if r := localExec(*c, cmd, noSudo); r.isSuccess() {
return nil
}
}
sshConnArgs := []string{}
sshKeyScanArgs := []string{"-H"}
if c.SSHConfigPath != "" {
sshConnArgs = append(sshConnArgs, "-F", c.SSHConfigPath)
}
if c.KeyPath != "" {
sshConnArgs = append(sshConnArgs, "-i", c.KeyPath)
}
if c.Port != "" {
sshConnArgs = append(sshConnArgs, "-p", c.Port)
sshKeyScanArgs = append(sshKeyScanArgs, "-p", c.Port)
}
if c.User != "" {
sshConnArgs = append(sshConnArgs, "-l", c.User)
}
sshConnArgs = append(sshConnArgs, c.Host)
sshKeyScanArgs = append(sshKeyScanArgs, fmt.Sprintf("%s >> %s", hostname, knownHostsPaths[0]))
sshConnCmd := fmt.Sprintf("ssh %s", strings.Join(sshConnArgs, " "))
sshKeyScancmd := fmt.Sprintf("ssh-keyscan %s", strings.Join(sshKeyScanArgs, " "))
return xerrors.Errorf("Failed to find the host in known_hosts. Please exec `$ %s` or `$ %s`", sshConnCmd, sshKeyScancmd)
}
func (s Scanner) detectContainerOSes(hosts []osTypeInterface) (actives, inactives []osTypeInterface) {
logging.Log.Info("Detecting OS of containers... ")
osTypesChan := make(chan []osTypeInterface, len(hosts))
@@ -440,49 +582,42 @@ func (s Scanner) detectContainerOSesOnServer(containerHost osTypeInterface) (ose
return oses
}
func (s Scanner) detectOS(c config.ServerInfo) (osType osTypeInterface) {
var itsMe bool
var fatalErr error
if itsMe, osType, _ = detectPseudo(c); itsMe {
return
func (s Scanner) detectOS(c config.ServerInfo) osTypeInterface {
if itsMe, osType, _ := detectPseudo(c); itsMe {
return osType
}
itsMe, osType, fatalErr = s.detectDebianWithRetry(c)
if fatalErr != nil {
osType.setErrs([]error{
xerrors.Errorf("Failed to detect OS: %w", fatalErr)})
return
if itsMe, osType, fatalErr := s.detectDebianWithRetry(c); fatalErr != nil {
osType.setErrs([]error{xerrors.Errorf("Failed to detect OS: %w", fatalErr)})
return osType
} else if itsMe {
logging.Log.Debugf("Debian based Linux. Host: %s:%s", c.Host, c.Port)
return osType
}
if itsMe {
logging.Log.Debugf("Debian like Linux. Host: %s:%s", c.Host, c.Port)
return
if itsMe, osType := detectRedhat(c); itsMe {
logging.Log.Debugf("Redhat based Linux. Host: %s:%s", c.Host, c.Port)
return osType
}
if itsMe, osType = detectRedhat(c); itsMe {
logging.Log.Debugf("Redhat like Linux. Host: %s:%s", c.Host, c.Port)
return
}
if itsMe, osType = detectSUSE(c); itsMe {
if itsMe, osType := detectSUSE(c); itsMe {
logging.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port)
return
return osType
}
if itsMe, osType = detectFreebsd(c); itsMe {
if itsMe, osType := detectFreebsd(c); itsMe {
logging.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
return
return osType
}
if itsMe, osType = detectAlpine(c); itsMe {
if itsMe, osType := detectAlpine(c); itsMe {
logging.Log.Debugf("Alpine. Host: %s:%s", c.Host, c.Port)
return
return osType
}
//TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb
osType := &unknown{base{ServerInfo: c}}
osType.setErrs([]error{xerrors.New("Unknown OS Type")})
return
return osType
}
// Retry as it may stall on the first SSH connection

View File

@@ -34,14 +34,6 @@ func TestViaHTTP(t *testing.T) {
},
wantErr: errOSReleaseHeader,
},
{
header: map[string]string{
"X-Vuls-OS-Family": "debian",
"X-Vuls-OS-Release": "8",
"X-Vuls-Kernel-Release": "2.6.32-695.20.3.el6.x86_64",
},
wantErr: errKernelVersionHeader,
},
{
header: map[string]string{
"X-Vuls-OS-Family": "centos",
@@ -95,6 +87,22 @@ func TestViaHTTP(t *testing.T) {
},
},
},
{
header: map[string]string{
"X-Vuls-OS-Family": "debian",
"X-Vuls-OS-Release": "8.10",
"X-Vuls-Kernel-Release": "3.16.0-4-amd64",
},
body: "",
expectedResult: models.ScanResult{
Family: "debian",
Release: "8.10",
RunningKernel: models.Kernel{
Release: "3.16.0-4-amd64",
Version: "",
},
},
},
}
for _, tt := range tests {

View File

@@ -19,7 +19,7 @@ type suse struct {
redhatBase
}
// NewRedhat is constructor
// newSUSE is constructor
func newSUSE(c config.ServerInfo) *suse {
r := &suse{
redhatBase: redhatBase{
@@ -43,6 +43,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
s := newSUSE(c)
name, ver := s.parseOSRelease(r.Stdout)
if name == "" || ver == "" {
s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/os-release: %s", r.Stdout)})
return true, s
}
s.setDistro(name, ver)
return true, s
}
@@ -50,11 +54,10 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
} else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() {
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() {
s := newSUSE(c)
re := regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
//TODO check opensuse or opensuse.leap
s := newSUSE(c)
s.setDistro(constant.OpenSUSE, result[1])
return true, s
}
@@ -66,14 +69,12 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
s := newSUSE(c)
s.setDistro(constant.SUSEEnterpriseServer,
fmt.Sprintf("%s.%s", version, result[1]))
s.setDistro(constant.SUSEEnterpriseServer, fmt.Sprintf("%s.%s", version, result[1]))
return true, s
}
}
logging.Log.Warnf("Failed to parse SUSE Linux version: %s", r)
return true, newSUSE(c)
s.setErrs([]error{xerrors.Errorf("Failed to parse /etc/SuSE-release: %s", r.Stdout)})
return true, s
}
}
}
@@ -82,23 +83,24 @@ func detectSUSE(c config.ServerInfo) (bool, osTypeInterface) {
}
func (o *suse) parseOSRelease(content string) (name string, ver string) {
if strings.Contains(content, "ID=opensuse") {
//TODO check opensuse or opensuse.leap
if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:opensuse`) {
name = constant.OpenSUSE
} else if strings.Contains(content, `NAME="SLES"`) {
name = constant.SUSEEnterpriseServer
} else if strings.Contains(content, `NAME="SLES_SAP"`) {
} else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:tumbleweed`) {
return constant.OpenSUSE, "tumbleweed"
} else if strings.Contains(content, `CPE_NAME="cpe:/o:opensuse:leap`) {
name = constant.OpenSUSELeap
} else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sles`) {
name = constant.SUSEEnterpriseServer
} else if strings.Contains(content, `CPE_NAME="cpe:/o:suse:sled`) {
name = constant.SUSEEnterpriseDesktop
} else {
logging.Log.Warnf("Failed to parse SUSE edition: %s", content)
return "unknown", "unknown"
return "", ""
}
re := regexp.MustCompile(`VERSION_ID=\"(.+)\"`)
result := re.FindStringSubmatch(strings.TrimSpace(content))
if len(result) != 2 {
logging.Log.Warnf("Failed to parse SUSE Linux version: %s", content)
return "unknown", "unknown"
return "", ""
}
return name, result[1]
}
@@ -108,14 +110,60 @@ func (o *suse) checkScanMode() error {
}
func (o *suse) checkDeps() error {
o.log.Infof("Dependencies... No need")
return nil
if o.getServerInfo().Mode.IsFast() {
return o.execCheckDeps(o.depsFast())
} else if o.getServerInfo().Mode.IsFastRoot() {
return o.execCheckDeps(o.depsFastRoot())
} else if o.getServerInfo().Mode.IsDeep() {
return o.execCheckDeps(o.depsDeep())
}
return xerrors.New("Unknown scan mode")
}
func (o *suse) depsFast() []string {
return []string{}
}
func (o *suse) depsFastRoot() []string {
return []string{}
}
func (o *suse) depsDeep() []string {
return o.depsFastRoot()
}
func (o *suse) checkIfSudoNoPasswd() error {
// SUSE doesn't need root privilege
o.log.Infof("sudo ... No need")
return nil
if o.getServerInfo().Mode.IsFast() {
return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFast())
} else if o.getServerInfo().Mode.IsFastRoot() {
return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsFastRoot())
} else {
return o.execCheckIfSudoNoPasswd(o.sudoNoPasswdCmdsDeep())
}
}
func (o *suse) sudoNoPasswdCmdsFast() []cmd {
return []cmd{}
}
func (o *suse) sudoNoPasswdCmdsDeep() []cmd {
return o.sudoNoPasswdCmdsFastRoot()
}
func (o *suse) sudoNoPasswdCmdsFastRoot() []cmd {
if !o.ServerInfo.IsContainer() {
return []cmd{
{"zypper ps -s", exitStatusZero},
{"which which", exitStatusZero},
{"stat /proc/1/exe", exitStatusZero},
{"ls -l /proc/1/exe", exitStatusZero},
{"cat /proc/1/maps", exitStatusZero},
{"lsof -i -P -n", exitStatusZero},
}
}
return []cmd{
{"zypper ps -s", exitStatusZero},
}
}
func (o *suse) scanPackages() error {
@@ -176,13 +224,14 @@ func (o *suse) scanUpdatablePackages() (models.Packages, error) {
return o.parseZypperLULines(r.Stdout)
}
var warnRepoPattern = regexp.MustCompile(`Warning: Repository '.+' appears to be outdated\. Consider using a different mirror or server\.`)
func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) {
updatables := models.Packages{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
if strings.Index(line, "S | Repository") != -1 ||
strings.Index(line, "--+----------------") != -1 {
if strings.Contains(line, "S | Repository") || strings.Contains(line, "--+----------------") || warnRepoPattern.MatchString(line) {
continue
}
pack, err := o.parseZypperLUOneLine(line)
@@ -213,3 +262,107 @@ func (o *suse) hasZypperColorOption() bool {
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
return len(r.Stdout) > 0
}
func (o *suse) postScan() error {
if o.isExecYumPS() {
if err := o.pkgPs(o.getOwnerPkgs); err != nil {
err = xerrors.Errorf("Failed to execute zypper-ps: %w", err)
o.log.Warnf("err: %+v", err)
o.warns = append(o.warns, err)
// Only warning this error
}
}
if o.isExecNeedsRestarting() {
if err := o.needsRestarting(); err != nil {
err = xerrors.Errorf("Failed to execute need-restarting: %w", err)
o.log.Warnf("err: %+v", err)
o.warns = append(o.warns, err)
// Only warning this error
}
}
return nil
}
func (o *suse) needsRestarting() error {
initName, err := o.detectInitSystem()
if err != nil {
o.log.Warn(err)
// continue scanning
}
cmd := "LANGUAGE=en_US.UTF-8 zypper ps -s"
r := o.exec(cmd, sudo)
if !r.isSuccess() {
return xerrors.Errorf("Failed to SSH: %w", r)
}
procs := o.parseNeedsRestarting(r.Stdout)
for _, proc := range procs {
//TODO refactor
fqpn, err := o.procPathToFQPN(proc.Path)
if err != nil {
o.log.Warnf("Failed to detect a package name of need restarting process from the command path: %s, %s",
proc.Path, err)
continue
}
pack, err := o.Packages.FindByFQPN(fqpn)
if err != nil {
return err
}
if initName == systemd {
name, err := o.detectServiceName(proc.PID)
if err != nil {
o.log.Warn(err)
// continue scanning
}
proc.ServiceName = name
proc.InitSystem = systemd
}
pack.NeedRestartProcs = append(pack.NeedRestartProcs, proc)
o.Packages[pack.Name] = *pack
}
return nil
}
func (o *suse) parseNeedsRestarting(stdout string) []models.NeedRestartProcess {
procs := []models.NeedRestartProcess{}
// PID | PPID | UID | User | Command | Service
// ----+------+-----+------+---------+-----------
// 9 | 7 | 0 | root | bash | containerd
// 53 | 9 | 0 | root | zypper | containerd
// 55 | 53 | 0 | root | lsof |
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
ss := strings.Split(line, " | ")
if len(ss) < 6 {
continue
}
pid := strings.TrimSpace(ss[0])
if strings.HasPrefix(pid, "PID") {
continue
}
// https://unix.stackexchange.com/a/419375
if pid == "1" {
continue
}
cmd := strings.TrimSpace(ss[4])
whichCmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 which %s", cmd)
r := o.exec(whichCmd, sudo)
if !r.isSuccess() {
o.log.Debugf("Failed to exec which %s: %s", cmd, r)
continue
}
path := strings.TrimSpace(r.Stdout)
procs = append(procs, models.NeedRestartProcess{
PID: pid,
Path: path,
HasInit: true,
})
}
return procs
}

View File

@@ -105,28 +105,41 @@ func TestParseOSRelease(t *testing.T) {
ver string
}{
{
in: `NAME="openSUSE Leap"
ID=opensuse
VERSION_ID="42.3.4"`,
in: `CPE_NAME="cpe:/o:opensuse:opensuse:13.2"
VERSION_ID="13.2"`,
name: constant.OpenSUSE,
ver: "13.2",
},
{
in: `CPE_NAME="cpe:/o:opensuse:tumbleweed:20220124"
VERSION_ID="20220124"`,
name: constant.OpenSUSE,
ver: "tumbleweed",
},
{
in: `CPE_NAME="cpe:/o:opensuse:leap:42.3"
VERSION_ID="42.3.4"`,
name: constant.OpenSUSELeap,
ver: "42.3.4",
},
{
in: `NAME="SLES"
VERSION="12-SP1"
VERSION_ID="12.1"
ID="sles"`,
in: `CPE_NAME="cpe:/o:suse:sles:12:sp1"
VERSION_ID="12.1"`,
name: constant.SUSEEnterpriseServer,
ver: "12.1",
},
{
in: `NAME="SLES_SAP"
VERSION="12-SP1"
VERSION_ID="12.1.0.1"
ID="sles"`,
in: `CPE_NAME="cpe:/o:suse:sles_sap:12:sp1"
VERSION_ID="12.1.0.1"`,
name: constant.SUSEEnterpriseServer,
ver: "12.1.0.1",
},
{
in: `CPE_NAME="cpe:/o:suse:sled:15"
VERSION_ID="15"`,
name: constant.SUSEEnterpriseDesktop,
ver: "15",
},
}
r := newSUSE(config.ServerInfo{})

View File

@@ -16,7 +16,7 @@ import (
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
switch family {
case constant.SUSEEnterpriseServer:
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
if pack.Name == "kernel-default" {
// Remove the last period and later because uname don't show that.
ss := strings.Split(pack.Release, ".")

View File

@@ -62,14 +62,14 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
if err := detector.DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
if err := detector.DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost, config.Conf.LogOpts); err != nil {
logging.Log.Errorf("Failed to detect Pkg CVE: %+v", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
logging.Log.Infof("Fill CVE detailed with gost")
if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost); err != nil {
if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost, config.Conf.LogOpts); err != nil {
logging.Log.Errorf("Failed to fill with gost: %+v", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
@@ -80,21 +80,21 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
nExploitCve, err := detector.FillWithExploit(&r, config.Conf.Exploit)
nExploitCve, err := detector.FillWithExploit(&r, config.Conf.Exploit, config.Conf.LogOpts)
if err != nil {
logging.Log.Errorf("Failed to fill with exploit: %+v", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
logging.Log.Infof("%s: %d PoC detected", r.FormatServerName(), nExploitCve)
nMetasploitCve, err := detector.FillWithMetasploit(&r, config.Conf.Metasploit)
nMetasploitCve, err := detector.FillWithMetasploit(&r, config.Conf.Metasploit, config.Conf.LogOpts)
if err != nil {
logging.Log.Errorf("Failed to fill with metasploit: %+v", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}
logging.Log.Infof("%s: %d exploits are detected", r.FormatServerName(), nMetasploitCve)
if err := detector.FillWithKEVuln(&r, config.Conf.KEVuln); err != nil {
if err := detector.FillWithKEVuln(&r, config.Conf.KEVuln, config.Conf.LogOpts); err != nil {
logging.Log.Errorf("Failed to fill with Known Exploited Vulnerabilities: %+v", err)
http.Error(w, err.Error(), http.StatusServiceUnavailable)
}

View File

@@ -17,9 +17,8 @@ import (
// ConfigtestCmd is Subcommand
type ConfigtestCmd struct {
configPath string
askKeyPassword bool
timeoutSec int
configPath string
timeoutSec int
}
// Name return subcommand name
@@ -35,7 +34,6 @@ func (*ConfigtestCmd) Usage() string {
[-config=/path/to/config.toml]
[-log-to-file]
[-log-dir=/path/to/log]
[-ask-key-password]
[-timeout=300]
[-containers-only]
[-http-proxy=http://192.168.0.1:8080]
@@ -59,10 +57,6 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
"Ask ssh privatekey password before scanning",
)
f.StringVar(&config.Conf.HTTPProxy, "http-proxy", "",
"http://proxy-url:port (default: empty)")
@@ -79,18 +73,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
return subcommands.ExitUsageError
}
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logging.Log.Error(err)
return subcommands.ExitFailure
}
}
err = config.Load(p.configPath, keyPass)
if err != nil {
if err := config.Load(p.configPath); err != nil {
msg := []string{
fmt.Sprintf("Error loading %s", p.configPath),
"If you update Vuls and get this error, there may be incompatible changes in config.toml",

View File

@@ -185,12 +185,12 @@ func printConfigToml(ips []string) (err error) {
#keyPath = "/home/username/.ssh/id_rsa"
#scanMode = ["fast", "fast-root", "deep", "offline"]
#scanModules = ["ospkg", "wordpress", "lockfile", "port"]
#lockfiles = ["/path/to/package-lock.json"]
#cpeNames = [
# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
#]
#owaspDCXMLPath = "/tmp/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-6271"]
#containersOnly = false
#containerType = "docker" #or "lxd" or "lxc" default: docker
#containersIncluded = ["${running}"]
#containersExcluded = ["container_name_a"]
@@ -209,6 +209,8 @@ host = "{{$ip}}"
#scanModules = ["ospkg", "wordpress", "lockfile", "port"]
#type = "pseudo"
#memo = "DB Server"
#findLock = true
#lockfiles = ["/path/to/package-lock.json"]
#cpeNames = [ "cpe:/a:rubyonrails:ruby_on_rails:4.2.1" ]
#owaspDCXMLPath = "/path/to/dependency-check-report.xml"
#ignoreCves = ["CVE-2014-0160"]

View File

@@ -175,10 +175,24 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
logging.Log.Errorf("Error loading %s, %+v", p.configPath, err)
return subcommands.ExitUsageError
if p.configPath == "" {
for _, cnf := range []config.VulnDictInterface{
&config.Conf.CveDict,
&config.Conf.OvalDict,
&config.Conf.Gost,
&config.Conf.Exploit,
&config.Conf.Metasploit,
&config.Conf.KEVuln,
} {
cnf.Init()
}
} else {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
return subcommands.ExitUsageError
}
}
config.Conf.Slack.Enabled = p.toSlack
config.Conf.ChatWork.Enabled = p.toChatWork
config.Conf.GoogleChat.Enabled = p.toGoogleChat

View File

@@ -65,7 +65,7 @@ func (p *SaaSCmd) SetFlags(f *flag.FlagSet) {
func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s, %+v", p.configPath, err)
return subcommands.ExitUsageError
}

View File

@@ -20,7 +20,6 @@ import (
// ScanCmd is Subcommand of host discovery mode
type ScanCmd struct {
configPath string
askKeyPassword bool
timeoutSec int
scanTimeoutSec int
cacheDBPath string
@@ -43,7 +42,6 @@ func (*ScanCmd) Usage() string {
[-log-dir=/path/to/log]
[-cachedb-path=/path/to/cache.db]
[-http-proxy=http://192.168.0.1:8080]
[-ask-key-password]
[-timeout=300]
[-timeout-scan=7200]
[-debug]
@@ -80,10 +78,6 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&config.Conf.HTTPProxy, "http-proxy", "",
"http://proxy-url:port (default: empty)")
f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
"Ask ssh privatekey password before scanning",
)
f.BoolVar(&config.Conf.Pipe, "pipe", false, "Use stdin via PIPE")
f.BoolVar(&p.detectIPS, "ips", false, "retrieve IPS information")
@@ -116,18 +110,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
}
}
var keyPass string
var err error
if p.askKeyPassword {
prompt := "SSH key password: "
if keyPass, err = getPasswd(prompt); err != nil {
logging.Log.Error(err)
return subcommands.ExitFailure
}
}
err = config.Load(p.configPath, keyPass)
if err != nil {
if err := config.Load(p.configPath); err != nil {
msg := []string{
fmt.Sprintf("Error loading %s", p.configPath),
"If you update Vuls and get this error, there may be incompatible changes in config.toml",

View File

@@ -11,10 +11,11 @@ import (
"os"
"path/filepath"
"github.com/google/subcommands"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/logging"
"github.com/future-architect/vuls/server"
"github.com/google/subcommands"
)
// ServerCmd is subcommand for server
@@ -93,9 +94,23 @@ func (p *ServerCmd) SetFlags(f *flag.FlagSet) {
func (p *ServerCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
logging.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
return subcommands.ExitUsageError
if p.configPath == "" {
for _, cnf := range []config.VulnDictInterface{
&config.Conf.CveDict,
&config.Conf.OvalDict,
&config.Conf.Gost,
&config.Conf.Exploit,
&config.Conf.Metasploit,
&config.Conf.KEVuln,
} {
cnf.Init()
}
} else {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
return subcommands.ExitUsageError
}
}
logging.Log.Info("Validating config...")

View File

@@ -110,7 +110,7 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
logging.Log = logging.NewCustomLogger(config.Conf.Debug, config.Conf.Quiet, config.Conf.LogToFile, config.Conf.LogDir, "", "")
logging.Log.Infof("vuls-%s-%s", config.Version, config.Revision)
if err := config.Load(p.configPath, ""); err != nil {
if err := config.Load(p.configPath); err != nil {
logging.Log.Errorf("Error loading %s, err: %+v", p.configPath, err)
return subcommands.ExitUsageError
}

View File

@@ -1,29 +1,12 @@
package subcmds
import (
"fmt"
"os"
"path/filepath"
"github.com/howeyc/gopass"
homedir "github.com/mitchellh/go-homedir"
"golang.org/x/xerrors"
)
func getPasswd(prompt string) (string, error) {
for {
fmt.Print(prompt)
pass, err := gopass.GetPasswdMasked()
if err != nil {
return "", xerrors.New("Failed to read a password")
}
if 0 < len(pass) {
return string(pass), nil
}
}
}
func mkdirDotVuls() error {
home, err := homedir.Dir()
if err != nil {

View File

@@ -719,8 +719,7 @@ func setChangelogLayout(g *gocui.Gui) error {
if len(pack.AffectedProcs) != 0 {
for _, p := range pack.AffectedProcs {
if len(p.ListenPortStats) == 0 {
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: []",
p.PID, p.Name))
lines = append(lines, fmt.Sprintf(" * PID: %s %s", p.PID, p.Name))
continue
}
@@ -733,8 +732,7 @@ func setChangelogLayout(g *gocui.Gui) error {
}
}
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s",
p.PID, p.Name, ports))
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s", p.PID, p.Name, ports))
}
}
}
@@ -783,13 +781,18 @@ func setChangelogLayout(g *gocui.Gui) error {
lines = append(lines, adv.Format())
}
m := map[string]struct{}{}
if len(vinfo.Exploits) != 0 {
lines = append(lines, "\n",
"Exploit Codes",
"PoC",
"=============",
)
for _, exploit := range vinfo.Exploits {
if _, ok := m[exploit.URL]; ok {
continue
}
lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL))
m[exploit.URL] = struct{}{}
}
}

View File

@@ -176,7 +176,7 @@ func Major(version string) string {
} else {
ver = ss[1]
}
return ver[0:strings.Index(ver, ".")]
return strings.Split(ver, ".")[0]
}
// GetHTTPClient return http.Client

View File

@@ -164,6 +164,10 @@ func Test_major(t *testing.T) {
in: "",
expected: "",
},
{
in: "4",
expected: "4",
},
{
in: "4.1",
expected: "4",