Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abdb081af7 | ||
|
|
e506125017 | ||
|
|
8ccaa8c3ef | ||
|
|
de1ed8ecaa | ||
|
|
947d668452 | ||
|
|
db21149f00 | ||
|
|
7f35f4e661 | ||
|
|
6682232b5c | ||
|
|
984debe929 | ||
|
|
a528362663 | ||
|
|
ee97d98c39 | ||
|
|
4e486dae1d | ||
|
|
897fef24a3 | ||
|
|
73f0adad95 | ||
|
|
704492963c | ||
|
|
1927ed344c | ||
|
|
ad2edbb844 | ||
|
|
bfe0db77b4 | ||
|
|
ff3b9cdc16 | ||
|
|
2deb1b9d32 | ||
|
|
ca64d7fc31 | ||
|
|
554ecc437e | ||
|
|
f6cd4d9223 | ||
|
|
03c59866d4 | ||
|
|
1d97e91341 | ||
|
|
96333f38c9 | ||
|
|
8b5d1c8e92 | ||
|
|
dea80f860c | ||
|
|
6eb4c5a5fe | ||
|
|
b219a8495e | ||
|
|
eb87d5d4e1 | ||
|
|
6963442a5e | ||
|
|
f7299b9dba | ||
|
|
379fc8a1a1 | ||
|
|
947fbbb29e | ||
|
|
06d2032c9c | ||
|
|
d055c48827 | ||
|
|
2a00339da1 | ||
|
|
2d959b3af8 | ||
|
|
595e26db41 | ||
|
|
1e457320c5 | ||
|
|
a06e689502 | ||
|
|
ca3f6b1dbf | ||
|
|
f1c78e42a2 | ||
|
|
2f3b8bf3cc | ||
|
|
ab54266f9e | ||
|
|
d79d138440 | ||
|
|
139f3a81b6 | ||
|
|
d1a617cfff | ||
|
|
48f7597bcf | ||
|
|
93731311a1 | ||
|
|
999529a05b | ||
|
|
847d820af7 | ||
|
|
5234306ded | ||
|
|
86b60e1478 | ||
|
|
42fdc08933 | ||
|
|
38b1d622f6 | ||
|
|
2477f9a8f8 | ||
|
|
ec6e90acd3 | ||
|
|
2aca2e4352 | ||
|
|
14518d925e | ||
|
|
948f8c0751 | ||
|
|
1c1e40058e | ||
|
|
2158fc6cb1 | ||
|
|
91ed318c5d | ||
|
|
bfc3828ce1 | ||
|
|
c7eac4e7fe | ||
|
|
cc63a0eccf | ||
|
|
fd18df1dd4 | ||
|
|
8775b5efdf | ||
|
|
a9f29a6c5d | ||
|
|
05fdde48f9 | ||
|
|
3dfbd6b616 | ||
|
|
04f246cf8b | ||
|
|
7500f41655 | ||
|
|
a1cc152e81 | ||
|
|
1c77bc1ba3 | ||
|
|
ec31c54caf | ||
|
|
2f05864813 | ||
|
|
2fbc0a001e | ||
|
|
7d8a24ee1a | ||
|
|
7750347010 | ||
|
|
9bcffcd721 | ||
|
|
787604de6a | ||
|
|
5164fb1423 | ||
|
|
07335617d3 | ||
|
|
e5855922c1 | ||
|
|
671be3f2f7 | ||
|
|
fe8d252c51 |
@@ -1,7 +1,6 @@
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
vendor/
|
||||
cve.sqlite3*
|
||||
oval.sqlite3*
|
||||
*.sqlite3*
|
||||
setup/
|
||||
img/
|
||||
img/
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
42
.github/workflows/docker-publish.yml
vendored
42
.github/workflows/docker-publish.yml
vendored
@@ -12,34 +12,58 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
- name: vuls/vuls image meta
|
||||
id: oss-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: vuls/vuls
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
|
||||
- name: vuls/fvuls image meta
|
||||
id: fvuls-meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: vuls/fvuls
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
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 }}"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- 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 }}"
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
13
.github/workflows/golangci.yml
vendored
13
.github/workflows/golangci.yml
vendored
@@ -11,12 +11,15 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
- uses: actions/setup-go@v3
|
||||
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
|
||||
go-version: 1.18
|
||||
- uses: actions/checkout@v3
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: v1.50.1
|
||||
args: --timeout=10m
|
||||
|
||||
# Optional: working directory, useful for monorepos
|
||||
|
||||
21
.github/workflows/goreleaser.yml
vendored
21
.github/workflows/goreleaser.yml
vendored
@@ -11,21 +11,24 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Unshallow
|
||||
run: git fetch --prune --unshallow
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version-file: go.mod
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e CGO_ENABLED=1 \
|
||||
-e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v `pwd`:/go/src/github.com/future-architect/vuls \
|
||||
-w /go/src/github.com/future-architect/vuls \
|
||||
ghcr.io/goreleaser/goreleaser-cross:v1.20 \
|
||||
release --clean
|
||||
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -9,13 +9,13 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
go-version: 1.18.x
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,3 +18,7 @@ dist/
|
||||
vuls.*
|
||||
vuls
|
||||
!cmd/vuls
|
||||
future-vuls
|
||||
trivy-to-vuls
|
||||
snmp2cpe
|
||||
!snmp2cpe/
|
||||
@@ -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.
|
||||
@@ -31,6 +35,9 @@ linters-settings:
|
||||
- name: unused-parameter
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
staticcheck:
|
||||
# https://staticcheck.io/docs/options#checks
|
||||
checks: ["all", "-SA1019"]
|
||||
# errcheck:
|
||||
#exclude: /path/to/file.txt
|
||||
|
||||
|
||||
@@ -6,11 +6,59 @@ release:
|
||||
owner: future-architect
|
||||
name: vuls
|
||||
builds:
|
||||
- id: vuls
|
||||
- id: vuls-linux-amd64
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=x86_64-linux-gnu-gcc
|
||||
main: ./cmd/vuls/main.go
|
||||
flags:
|
||||
- -a
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: vuls
|
||||
|
||||
- id: vuls-linux-arm64
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
main: ./cmd/vuls/main.go
|
||||
flags:
|
||||
- -a
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: vuls
|
||||
|
||||
- id: vuls-windows-amd64
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
main: ./cmd/vuls/main.go
|
||||
flags:
|
||||
- -a
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: vuls
|
||||
|
||||
- id: vuls-windows-arm64
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=/llvm-mingw/bin/aarch64-w64-mingw32-gcc
|
||||
main: ./cmd/vuls/main.go
|
||||
flags:
|
||||
- -a
|
||||
@@ -23,6 +71,7 @@ builds:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
@@ -42,6 +91,7 @@ builds:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
@@ -50,6 +100,8 @@ builds:
|
||||
tags:
|
||||
- scanner
|
||||
main: ./contrib/trivy/cmd/main.go
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: trivy-to-vuls
|
||||
|
||||
- id: future-vuls
|
||||
@@ -57,6 +109,7 @@ builds:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
@@ -66,15 +119,40 @@ builds:
|
||||
- -a
|
||||
tags:
|
||||
- scanner
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
main: ./contrib/future-vuls/cmd/main.go
|
||||
binary: future-vuls
|
||||
|
||||
- id: snmp2cpe
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
flags:
|
||||
- -a
|
||||
tags:
|
||||
- scanner
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
main: ./contrib/snmp2cpe/cmd/main.go
|
||||
binary: snmp2cpe
|
||||
|
||||
archives:
|
||||
|
||||
- id: vuls
|
||||
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
builds:
|
||||
- vuls
|
||||
- vuls-linux-amd64
|
||||
- vuls-linux-arm64
|
||||
- vuls-windows-amd64
|
||||
- vuls-windows-arm64
|
||||
format: tar.gz
|
||||
files:
|
||||
- LICENSE
|
||||
@@ -110,5 +188,16 @@ archives:
|
||||
- LICENSE
|
||||
- README*
|
||||
- CHANGELOG.md
|
||||
|
||||
- id: snmp2cpe
|
||||
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
builds:
|
||||
- snmp2cpe
|
||||
format: tar.gz
|
||||
files:
|
||||
- LICENSE
|
||||
- README*
|
||||
- CHANGELOG.md
|
||||
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
|
||||
@@ -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.16
|
||||
|
||||
ENV LOGDIR /var/log/vuls
|
||||
ENV WORKDIR /vuls
|
||||
|
||||
45
GNUmakefile
45
GNUmakefile
@@ -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,22 +74,25 @@ 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
|
||||
|
||||
# snmp2cpe
|
||||
build-snmp2cpe: ./contrib/snmp2cpe/cmd/main.go
|
||||
$(GO) build -a -ldflags "$(LDFLAGS)" -o snmp2cpe ./contrib/snmp2cpe/cmd
|
||||
|
||||
# integration-test
|
||||
BASE_DIR := '${PWD}/integration/results'
|
||||
# $(shell mkdir -p ${BASE_DIR})
|
||||
NOW=$(shell date --iso-8601=seconds)
|
||||
CURRENT := `find ${BASE_DIR} -type d -exec basename {} \; | sort -nr | head -n 1`
|
||||
NOW=$(shell date '+%Y-%m-%dT%H-%M-%S%z')
|
||||
NOW_JSON_DIR := '${BASE_DIR}/$(NOW)'
|
||||
ONE_SEC_AFTER=$(shell date -d '+1 second' --iso-8601=seconds)
|
||||
ONE_SEC_AFTER=$(shell date -d '+1 second' '+%Y-%m-%dT%H-%M-%S%z')
|
||||
ONE_SEC_AFTER_JSON_DIR := '${BASE_DIR}/$(ONE_SEC_AFTER)'
|
||||
LIBS := 'bundler' 'pip' 'pipenv' 'poetry' 'composer' 'npm' 'yarn' 'cargo' 'gomod' 'gobinary' 'jar' 'pom' 'nuget-lock' 'nuget-config' 'nvd_exact' 'nvd_rough' 'nvd_vendor_product' 'nvd_match_no_jvn' 'jvn_vendor_product' 'jvn_vendor_product_nover'
|
||||
LIBS := 'bundler' 'pip' 'pipenv' 'poetry' 'composer' 'npm' 'yarn' 'pnpm' 'cargo' 'gomod' 'gosum' 'gobinary' 'jar' 'pom' 'gradle' 'nuget-lock' 'nuget-config' 'dotnet-deps' 'conan' 'nvd_exact' 'nvd_rough' 'nvd_vendor_product' 'nvd_match_no_jvn' 'jvn_vendor_product' 'jvn_vendor_product_nover'
|
||||
|
||||
diff:
|
||||
# git clone git@github.com:vulsio/vulsctl.git
|
||||
@@ -109,14 +110,14 @@ endif
|
||||
mkdir -p ${NOW_JSON_DIR}
|
||||
sleep 1
|
||||
./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
|
||||
cp ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR}
|
||||
- cp integration/data/results/*.json ${NOW_JSON_DIR}
|
||||
./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
|
||||
|
||||
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
|
||||
sleep 1
|
||||
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
cp ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
- cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${ONE_SEC_AFTER}
|
||||
|
||||
@@ -142,14 +143,14 @@ endif
|
||||
mkdir -p ${NOW_JSON_DIR}
|
||||
sleep 1
|
||||
./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
|
||||
cp -f ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR}
|
||||
- cp integration/data/results/*.json ${NOW_JSON_DIR}
|
||||
./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${NOW}
|
||||
|
||||
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
|
||||
sleep 1
|
||||
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
cp -f ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
- cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
|
||||
|
||||
@@ -166,14 +167,14 @@ endif
|
||||
sleep 1
|
||||
# new vs new
|
||||
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
|
||||
cp -f ${BASE_DIR}/$(CURRENT)/*.json ${NOW_JSON_DIR}
|
||||
cp integration/data/results/*.json ${NOW_JSON_DIR}
|
||||
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
|
||||
|
||||
mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
|
||||
sleep 1
|
||||
./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
|
||||
cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
cp -f ${BASE_DIR}/$(CURRENT)/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
|
||||
./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -9,8 +9,8 @@
|
||||
|
||||

|
||||
|
||||
Vulnerability scanner for Linux/FreeBSD, agent-less, written in Go.
|
||||
We have a slack team. [Join slack team](http://goo.gl/forms/xm5KFo35tu)
|
||||
Vulnerability scanner for Linux/FreeBSD, agent-less, written in Go.
|
||||
We have a slack team. [Join slack team](https://join.slack.com/t/vuls-github/shared_invite/zt-1fculjwj4-6nex2JNE7DpOSiKZ1ztDFw)
|
||||
Twitter: [@vuls_en](https://twitter.com/vuls_en)
|
||||
|
||||

|
||||
@@ -48,10 +48,11 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
|
||||
### Scan for any vulnerabilities in Linux/FreeBSD Server
|
||||
|
||||
[Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html)
|
||||
[Supports major Linux/FreeBSD/Windows](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
|
||||
- Windows
|
||||
- Cloud, on-premise, Running Docker Container
|
||||
|
||||
### High-quality scan
|
||||
@@ -72,6 +73,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
- [Red Hat Security Advisories](https://access.redhat.com/security/security-updates/)
|
||||
- [Debian Security Bug Tracker](https://security-tracker.debian.org/tracker/)
|
||||
- [Ubuntu CVE Tracker](https://people.canonical.com/~ubuntu-security/cve/)
|
||||
- [Microsoft CVRF](https://api.msrc.microsoft.com/cvrf/v2.0/swagger/index)
|
||||
|
||||
- Commands(yum, zypper, pkg-audit)
|
||||
- RHSA / ALAS / ELSA / FreeBSD-SA
|
||||
@@ -82,6 +84,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)
|
||||
@@ -90,12 +93,11 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
- CISA(Cybersecurity & Infrastructure Security Agency)
|
||||
- [Known Exploited Vulnerabilities Catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
|
||||
|
||||
- Cyber Threat Intelligence(MITRE ATT&CK and CAPEC)
|
||||
- [mitre/cti](https://github.com/mitre/cti)
|
||||
|
||||
- Libraries
|
||||
- [Node.js Security Working Group](https://github.com/nodejs/security-wg)
|
||||
- [Ruby Advisory Database](https://github.com/rubysec/ruby-advisory-db)
|
||||
- [Safety DB(Python)](https://github.com/pyupio/safety-db)
|
||||
- [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories)
|
||||
- [RustSec Advisory Database](https://github.com/RustSec/advisory-db)
|
||||
- [aquasecurity/vuln-list](https://github.com/aquasecurity/vuln-list)
|
||||
|
||||
- WordPress
|
||||
- [wpscan](https://wpscan.com/api)
|
||||
|
||||
7
cache/bolt.go
vendored
7
cache/bolt.go
vendored
@@ -4,10 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Bolt holds a pointer of bolt.DB
|
||||
@@ -47,7 +48,7 @@ func (b Bolt) Close() error {
|
||||
return b.db.Close()
|
||||
}
|
||||
|
||||
// CreateBucketIfNotExists creates a bucket that is specified by arg.
|
||||
// CreateBucketIfNotExists creates a bucket that is specified by arg.
|
||||
func (b *Bolt) createBucketIfNotExists(name string) error {
|
||||
return b.db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(name))
|
||||
|
||||
3
cache/bolt_test.go
vendored
3
cache/bolt_test.go
vendored
@@ -5,7 +5,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -7,9 +9,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Version of Vuls
|
||||
@@ -21,7 +24,7 @@ var Revision string
|
||||
// Conf has Configuration
|
||||
var Conf Config
|
||||
|
||||
//Config is struct of Configuration
|
||||
// Config is struct of Configuration
|
||||
type Config struct {
|
||||
logging.LogOpts
|
||||
|
||||
@@ -42,6 +45,7 @@ type Config struct {
|
||||
Exploit ExploitConf `json:"exploit,omitempty"`
|
||||
Metasploit MetasploitConf `json:"metasploit,omitempty"`
|
||||
KEVuln KEVulnConf `json:"kevuln,omitempty"`
|
||||
Cti CtiConf `json:"cti,omitempty"`
|
||||
|
||||
Slack SlackConf `json:"-"`
|
||||
EMail SMTPConf `json:"-"`
|
||||
@@ -116,6 +120,9 @@ func (c Config) ValidateOnScan() bool {
|
||||
if es := server.PortScan.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
if es := server.Windows.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
@@ -178,6 +185,7 @@ func (c *Config) ValidateOnReport() bool {
|
||||
&Conf.Exploit,
|
||||
&Conf.Metasploit,
|
||||
&Conf.KEVuln,
|
||||
&Conf.Cti,
|
||||
} {
|
||||
if err := cnf.Validate(); err != nil {
|
||||
errs = append(errs, xerrors.Errorf("Failed to validate %s: %+v", cnf.GetName(), err))
|
||||
@@ -211,9 +219,11 @@ type WpScanConf struct {
|
||||
|
||||
// ServerInfo has SSH Info, additional CPE packages to scan.
|
||||
type ServerInfo struct {
|
||||
BaseName string `toml:"-" json:"-"`
|
||||
ServerName string `toml:"-" json:"serverName,omitempty"`
|
||||
User string `toml:"user,omitempty" json:"user,omitempty"`
|
||||
Host string `toml:"host,omitempty" json:"host,omitempty"`
|
||||
IgnoreIPAddresses []string `toml:"ignoreIPAddresses,omitempty" json:"ignoreIPAddresses,omitempty"`
|
||||
JumpServer []string `toml:"jumpServer,omitempty" json:"jumpServer,omitempty"`
|
||||
Port string `toml:"port,omitempty" json:"port,omitempty"`
|
||||
SSHConfigPath string `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
|
||||
@@ -236,10 +246,12 @@ type ServerInfo struct {
|
||||
Optional map[string]interface{} `toml:"optional,omitempty" json:"optional,omitempty"` // Optional key-value set that will be outputted to JSON
|
||||
Lockfiles []string `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"` // ie) path/to/package-lock.json
|
||||
FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"`
|
||||
FindLockDirs []string `toml:"findLockDirs,omitempty" json:"findLockDirs,omitempty"`
|
||||
Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or ""
|
||||
IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
|
||||
WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
|
||||
PortScan *PortScanConf `toml:"portscan,omitempty" json:"portscan,omitempty"`
|
||||
Windows *WindowsConf `toml:"windows,omitempty" json:"windows,omitempty"`
|
||||
|
||||
IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"`
|
||||
IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"`
|
||||
@@ -266,6 +278,7 @@ type WordPressConf struct {
|
||||
OSUser string `toml:"osUser,omitempty" json:"osUser,omitempty"`
|
||||
DocRoot string `toml:"docRoot,omitempty" json:"docRoot,omitempty"`
|
||||
CmdPath string `toml:"cmdPath,omitempty" json:"cmdPath,omitempty"`
|
||||
NoSudo bool `toml:"noSudo,omitempty" json:"noSudo,omitempty"`
|
||||
}
|
||||
|
||||
// IsZero return whether this struct is not specified in config.toml
|
||||
@@ -307,6 +320,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])
|
||||
|
||||
351
config/config_windows.go
Normal file
351
config/config_windows.go
Normal file
@@ -0,0 +1,351 @@
|
||||
//go:build windows
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
)
|
||||
|
||||
// Version of Vuls
|
||||
var Version = "`make build` or `make install` will show the version"
|
||||
|
||||
// Revision of Git
|
||||
var Revision string
|
||||
|
||||
// Conf has Configuration
|
||||
var Conf Config
|
||||
|
||||
// Config is struct of Configuration
|
||||
type Config struct {
|
||||
logging.LogOpts
|
||||
|
||||
// scan, report
|
||||
HTTPProxy string `valid:"url" json:"httpProxy,omitempty"`
|
||||
ResultsDir string `json:"resultsDir,omitempty"`
|
||||
Pipe bool `json:"pipe,omitempty"`
|
||||
|
||||
Default ServerInfo `json:"default,omitempty"`
|
||||
Servers map[string]ServerInfo `json:"servers,omitempty"`
|
||||
|
||||
ScanOpts
|
||||
|
||||
// report
|
||||
CveDict GoCveDictConf `json:"cveDict,omitempty"`
|
||||
OvalDict GovalDictConf `json:"ovalDict,omitempty"`
|
||||
Gost GostConf `json:"gost,omitempty"`
|
||||
Exploit ExploitConf `json:"exploit,omitempty"`
|
||||
Metasploit MetasploitConf `json:"metasploit,omitempty"`
|
||||
KEVuln KEVulnConf `json:"kevuln,omitempty"`
|
||||
Cti CtiConf `json:"cti,omitempty"`
|
||||
|
||||
Slack SlackConf `json:"-"`
|
||||
EMail SMTPConf `json:"-"`
|
||||
HTTP HTTPConf `json:"-"`
|
||||
AWS AWSConf `json:"-"`
|
||||
Azure AzureConf `json:"-"`
|
||||
ChatWork ChatWorkConf `json:"-"`
|
||||
GoogleChat GoogleChatConf `json:"-"`
|
||||
Telegram TelegramConf `json:"-"`
|
||||
WpScan WpScanConf `json:"-"`
|
||||
Saas SaasConf `json:"-"`
|
||||
|
||||
ReportOpts
|
||||
}
|
||||
|
||||
// ReportConf is an interface to Validate Report Config
|
||||
type ReportConf interface {
|
||||
Validate() []error
|
||||
}
|
||||
|
||||
// ScanOpts is options for scan
|
||||
type ScanOpts struct {
|
||||
Vvv bool `json:"vvv,omitempty"`
|
||||
}
|
||||
|
||||
// ReportOpts is options for report
|
||||
type ReportOpts struct {
|
||||
CvssScoreOver float64 `json:"cvssScoreOver,omitempty"`
|
||||
ConfidenceScoreOver int `json:"confidenceScoreOver,omitempty"`
|
||||
TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
|
||||
NoProgress bool `json:"noProgress,omitempty"`
|
||||
RefreshCve bool `json:"refreshCve,omitempty"`
|
||||
IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"`
|
||||
IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"`
|
||||
DiffPlus bool `json:"diffPlus,omitempty"`
|
||||
DiffMinus bool `json:"diffMinus,omitempty"`
|
||||
Diff bool `json:"diff,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
}
|
||||
|
||||
// ValidateOnConfigtest validates
|
||||
func (c Config) ValidateOnConfigtest() bool {
|
||||
errs := c.checkSSHKeyExist()
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
for _, err := range errs {
|
||||
logging.Log.Error(err)
|
||||
}
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
// ValidateOnScan validates configuration
|
||||
func (c Config) ValidateOnScan() bool {
|
||||
errs := c.checkSSHKeyExist()
|
||||
if len(c.ResultsDir) != 0 {
|
||||
if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
for _, server := range c.Servers {
|
||||
if !server.Module.IsScanPort() {
|
||||
continue
|
||||
}
|
||||
if es := server.PortScan.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
if es := server.Windows.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
logging.Log.Error(err)
|
||||
}
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
func (c Config) checkSSHKeyExist() (errs []error) {
|
||||
for serverName, v := range c.Servers {
|
||||
if v.Type == constant.ServerTypePseudo {
|
||||
continue
|
||||
}
|
||||
if v.KeyPath != "" {
|
||||
if _, err := os.Stat(v.KeyPath); err != nil {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
"%s is invalid. keypath: %s not exists", serverName, v.KeyPath))
|
||||
}
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// ValidateOnReport validates configuration
|
||||
func (c *Config) ValidateOnReport() bool {
|
||||
errs := []error{}
|
||||
|
||||
if len(c.ResultsDir) != 0 {
|
||||
if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
for _, rc := range []ReportConf{
|
||||
&c.EMail,
|
||||
&c.Slack,
|
||||
&c.ChatWork,
|
||||
&c.GoogleChat,
|
||||
&c.Telegram,
|
||||
&c.HTTP,
|
||||
&c.AWS,
|
||||
&c.Azure,
|
||||
} {
|
||||
if es := rc.Validate(); 0 < len(es) {
|
||||
errs = append(errs, es...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, cnf := range []VulnDictInterface{
|
||||
&Conf.CveDict,
|
||||
&Conf.OvalDict,
|
||||
&Conf.Gost,
|
||||
&Conf.Exploit,
|
||||
&Conf.Metasploit,
|
||||
&Conf.KEVuln,
|
||||
&Conf.Cti,
|
||||
} {
|
||||
if err := cnf.Validate(); err != nil {
|
||||
errs = append(errs, xerrors.Errorf("Failed to validate %s: %+v", cnf.GetName(), err))
|
||||
}
|
||||
if err := cnf.CheckHTTPHealth(); err != nil {
|
||||
errs = append(errs, xerrors.Errorf("Run %s as server mode before reporting: %+v", cnf.GetName(), err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, err := range errs {
|
||||
logging.Log.Error(err)
|
||||
}
|
||||
|
||||
return len(errs) == 0
|
||||
}
|
||||
|
||||
// ValidateOnSaaS validates configuration
|
||||
func (c Config) ValidateOnSaaS() bool {
|
||||
saaserrs := c.Saas.Validate()
|
||||
for _, err := range saaserrs {
|
||||
logging.Log.Error("Failed to validate SaaS conf: %+w", err)
|
||||
}
|
||||
return len(saaserrs) == 0
|
||||
}
|
||||
|
||||
// WpScanConf is wpscan.com config
|
||||
type WpScanConf struct {
|
||||
Token string `toml:"token,omitempty" json:"-"`
|
||||
DetectInactive bool `toml:"detectInactive,omitempty" json:"detectInactive,omitempty"`
|
||||
}
|
||||
|
||||
// ServerInfo has SSH Info, additional CPE packages to scan.
|
||||
type ServerInfo struct {
|
||||
BaseName string `toml:"-" json:"-"`
|
||||
ServerName string `toml:"-" json:"serverName,omitempty"`
|
||||
User string `toml:"user,omitempty" json:"user,omitempty"`
|
||||
Host string `toml:"host,omitempty" json:"host,omitempty"`
|
||||
IgnoreIPAddresses []string `toml:"ignoreIPAddresses,omitempty" json:"ignoreIPAddresses,omitempty"`
|
||||
JumpServer []string `toml:"jumpServer,omitempty" json:"jumpServer,omitempty"`
|
||||
Port string `toml:"port,omitempty" json:"port,omitempty"`
|
||||
SSHConfigPath string `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
|
||||
KeyPath string `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
|
||||
CpeNames []string `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
|
||||
ScanMode []string `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
|
||||
ScanModules []string `toml:"scanModules,omitempty" json:"scanModules,omitempty"`
|
||||
OwaspDCXMLPath string `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath,omitempty"`
|
||||
ContainersOnly bool `toml:"containersOnly,omitempty" json:"containersOnly,omitempty"`
|
||||
ContainersIncluded []string `toml:"containersIncluded,omitempty" json:"containersIncluded,omitempty"`
|
||||
ContainersExcluded []string `toml:"containersExcluded,omitempty" json:"containersExcluded,omitempty"`
|
||||
ContainerType string `toml:"containerType,omitempty" json:"containerType,omitempty"`
|
||||
Containers map[string]ContainerSetting `toml:"containers,omitempty" json:"containers,omitempty"`
|
||||
IgnoreCves []string `toml:"ignoreCves,omitempty" json:"ignoreCves,omitempty"`
|
||||
IgnorePkgsRegexp []string `toml:"ignorePkgsRegexp,omitempty" json:"ignorePkgsRegexp,omitempty"`
|
||||
GitHubRepos map[string]GitHubConf `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo
|
||||
UUIDs map[string]string `toml:"uuids,omitempty" json:"uuids,omitempty"`
|
||||
Memo string `toml:"memo,omitempty" json:"memo,omitempty"`
|
||||
Enablerepo []string `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, Alma, Rocky, RHEL, Amazon
|
||||
Optional map[string]interface{} `toml:"optional,omitempty" json:"optional,omitempty"` // Optional key-value set that will be outputted to JSON
|
||||
Lockfiles []string `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"` // ie) path/to/package-lock.json
|
||||
FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"`
|
||||
FindLockDirs []string `toml:"findLockDirs,omitempty" json:"findLockDirs,omitempty"`
|
||||
Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or ""
|
||||
IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
|
||||
WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
|
||||
PortScan *PortScanConf `toml:"portscan,omitempty" json:"portscan,omitempty"`
|
||||
Windows *WindowsConf `toml:"windows,omitempty" json:"windows,omitempty"`
|
||||
|
||||
IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"`
|
||||
IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"`
|
||||
IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"`
|
||||
|
||||
// internal use
|
||||
LogMsgAnsiColor string `toml:"-" json:"-"` // DebugLog Color
|
||||
Container Container `toml:"-" json:"-"`
|
||||
Distro Distro `toml:"-" json:"-"`
|
||||
Mode ScanMode `toml:"-" json:"-"`
|
||||
Module ScanModule `toml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// ContainerSetting is used for loading container setting in config.toml
|
||||
type ContainerSetting struct {
|
||||
Cpes []string `json:"cpes,omitempty"`
|
||||
OwaspDCXMLPath string `json:"owaspDCXMLPath,omitempty"`
|
||||
IgnorePkgsRegexp []string `json:"ignorePkgsRegexp,omitempty"`
|
||||
IgnoreCves []string `json:"ignoreCves,omitempty"`
|
||||
}
|
||||
|
||||
// WordPressConf used for WordPress Scanning
|
||||
type WordPressConf struct {
|
||||
OSUser string `toml:"osUser,omitempty" json:"osUser,omitempty"`
|
||||
DocRoot string `toml:"docRoot,omitempty" json:"docRoot,omitempty"`
|
||||
CmdPath string `toml:"cmdPath,omitempty" json:"cmdPath,omitempty"`
|
||||
NoSudo bool `toml:"noSudo,omitempty" json:"noSudo,omitempty"`
|
||||
}
|
||||
|
||||
// IsZero return whether this struct is not specified in config.toml
|
||||
func (cnf WordPressConf) IsZero() bool {
|
||||
return cnf.OSUser == "" && cnf.DocRoot == "" && cnf.CmdPath == ""
|
||||
}
|
||||
|
||||
// GitHubConf is used for GitHub Security Alerts
|
||||
type GitHubConf struct {
|
||||
Token string `json:"-"`
|
||||
IgnoreGitHubDismissed bool `json:"ignoreGitHubDismissed,omitempty"`
|
||||
}
|
||||
|
||||
// GetServerName returns ServerName if this serverInfo is about host.
|
||||
// If this serverInfo is about a container, returns containerID@ServerName
|
||||
func (s ServerInfo) GetServerName() string {
|
||||
if len(s.Container.ContainerID) == 0 {
|
||||
return s.ServerName
|
||||
}
|
||||
return fmt.Sprintf("%s@%s", s.Container.Name, s.ServerName)
|
||||
}
|
||||
|
||||
// Distro has distribution info
|
||||
type Distro struct {
|
||||
Family string
|
||||
Release string
|
||||
}
|
||||
|
||||
func (l Distro) String() string {
|
||||
return fmt.Sprintf("%s %s", l.Family, l.Release)
|
||||
}
|
||||
|
||||
// MajorVersion returns Major version
|
||||
func (l Distro) MajorVersion() (int, error) {
|
||||
switch l.Family {
|
||||
case constant.Amazon:
|
||||
return strconv.Atoi(getAmazonLinuxVersion(l.Release))
|
||||
case constant.CentOS:
|
||||
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])
|
||||
}
|
||||
}
|
||||
return 0, xerrors.New("Release is empty")
|
||||
}
|
||||
|
||||
// IsContainer returns whether this ServerInfo is about container
|
||||
func (s ServerInfo) IsContainer() bool {
|
||||
return 0 < len(s.Container.ContainerID)
|
||||
}
|
||||
|
||||
// SetContainer set container
|
||||
func (s *ServerInfo) SetContainer(d Container) {
|
||||
s.Container = d
|
||||
}
|
||||
|
||||
// Container has Container information.
|
||||
type Container struct {
|
||||
ContainerID string
|
||||
Name string
|
||||
Image string
|
||||
}
|
||||
@@ -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
|
||||
|
||||
251
config/os.go
251
config/os.go
@@ -41,8 +41,12 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
case constant.Amazon:
|
||||
eol, found = map[string]EOL{
|
||||
"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2": {},
|
||||
"2022": {},
|
||||
"2": {StandardSupportUntil: time.Date(2025, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2022": {StandardSupportUntil: time.Date(2026, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2023": {StandardSupportUntil: time.Date(2027, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2025": {StandardSupportUntil: time.Date(2029, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2027": {StandardSupportUntil: time.Date(2031, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2029": {StandardSupportUntil: time.Date(2033, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
}[getAmazonLinuxVersion(release)]
|
||||
case constant.RedHat:
|
||||
// https://access.redhat.com/support/policy/updates/errata
|
||||
@@ -56,9 +60,15 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
},
|
||||
"7": {
|
||||
StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2026, 6, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"8": {
|
||||
StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2031, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"9": {
|
||||
StandardSupportUntil: time.Date(2032, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2034, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case constant.CentOS:
|
||||
@@ -71,14 +81,17 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)},
|
||||
"stream8": {StandardSupportUntil: time.Date(2024, 5, 31, 23, 59, 59, 0, time.UTC)},
|
||||
"stream9": {StandardSupportUntil: time.Date(2027, 5, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Alma:
|
||||
eol, found = map[string]EOL{
|
||||
"8": {StandardSupportUntil: time.Date(2029, 12, 31, 23, 59, 59, 0, time.UTC)},
|
||||
"9": {StandardSupportUntil: time.Date(2032, 5, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Rocky:
|
||||
eol, found = map[string]EOL{
|
||||
"8": {StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC)},
|
||||
"9": {StandardSupportUntil: time.Date(2032, 5, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Oracle:
|
||||
eol, found = map[string]EOL{
|
||||
@@ -90,13 +103,19 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"5": {Ended: true},
|
||||
"6": {
|
||||
StandardSupportUntil: time.Date(2021, 3, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 3, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 6, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"7": {
|
||||
StandardSupportUntil: time.Date(2024, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2026, 6, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"8": {
|
||||
StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2031, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"9": {
|
||||
StandardSupportUntil: time.Date(2032, 6, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2034, 6, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case constant.Debian:
|
||||
@@ -115,18 +134,35 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
case constant.Ubuntu:
|
||||
// https://wiki.ubuntu.com/Releases
|
||||
eol, found = map[string]EOL{
|
||||
"14.10": {Ended: true},
|
||||
"6.06": {Ended: true},
|
||||
"6.10": {Ended: true},
|
||||
"7.04": {Ended: true},
|
||||
"7.10": {Ended: true},
|
||||
"8.04": {Ended: true},
|
||||
"8.10": {Ended: true},
|
||||
"9.04": {Ended: true},
|
||||
"9.10": {Ended: true},
|
||||
"10.04": {Ended: true},
|
||||
"10.10": {Ended: true},
|
||||
"11.04": {Ended: true},
|
||||
"11.10": {Ended: true},
|
||||
"12.04": {Ended: true},
|
||||
"12.10": {Ended: true},
|
||||
"13.04": {Ended: true},
|
||||
"13.10": {Ended: true},
|
||||
"14.04": {
|
||||
ExtendedSupportUntil: time.Date(2022, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"14.10": {Ended: true},
|
||||
"15.04": {Ended: true},
|
||||
"16.10": {Ended: true},
|
||||
"17.04": {Ended: true},
|
||||
"17.10": {Ended: true},
|
||||
"15.10": {Ended: true},
|
||||
"16.04": {
|
||||
StandardSupportUntil: time.Date(2021, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"16.10": {Ended: true},
|
||||
"17.04": {Ended: true},
|
||||
"17.10": {Ended: true},
|
||||
"18.04": {
|
||||
StandardSupportUntil: time.Date(2023, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2028, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
@@ -136,19 +172,96 @@ 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),
|
||||
},
|
||||
"22.10": {
|
||||
StandardSupportUntil: time.Date(2023, 7, 20, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
// "23.04": {
|
||||
// StandardSupportUntil: time.Date(2024, 1, 31, 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/
|
||||
@@ -177,6 +290,8 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"3.13": {StandardSupportUntil: time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.14": {StandardSupportUntil: time.Date(2023, 5, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.15": {StandardSupportUntil: time.Date(2023, 11, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.16": {StandardSupportUntil: time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC)},
|
||||
"3.17": {StandardSupportUntil: time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC)},
|
||||
}[majorDotMinor(release)]
|
||||
case constant.FreeBSD:
|
||||
// https://www.freebsd.org/security/
|
||||
@@ -186,18 +301,102 @@ func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
"9": {Ended: true},
|
||||
"10": {Ended: true},
|
||||
"11": {StandardSupportUntil: time.Date(2021, 9, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"12": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"12": {StandardSupportUntil: time.Date(2023, 12, 31, 23, 59, 59, 0, time.UTC)},
|
||||
"13": {StandardSupportUntil: time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Fedora:
|
||||
// https://docs.fedoraproject.org/en-US/releases/eol/
|
||||
// https://endoflife.date/fedora
|
||||
eol, found = map[string]EOL{
|
||||
"32": {StandardSupportUntil: time.Date(2021, 5, 25, 23, 59, 59, 0, time.UTC)},
|
||||
"33": {StandardSupportUntil: time.Date(2021, 11, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"34": {StandardSupportUntil: time.Date(2022, 5, 17, 23, 59, 59, 0, time.UTC)},
|
||||
"35": {StandardSupportUntil: time.Date(2022, 12, 7, 23, 59, 59, 0, time.UTC)},
|
||||
"32": {StandardSupportUntil: time.Date(2021, 5, 24, 23, 59, 59, 0, time.UTC)},
|
||||
"33": {StandardSupportUntil: time.Date(2021, 11, 29, 23, 59, 59, 0, time.UTC)},
|
||||
"34": {StandardSupportUntil: time.Date(2022, 6, 6, 23, 59, 59, 0, time.UTC)},
|
||||
"35": {StandardSupportUntil: time.Date(2022, 12, 12, 23, 59, 59, 0, time.UTC)},
|
||||
"36": {StandardSupportUntil: time.Date(2023, 5, 16, 23, 59, 59, 0, time.UTC)},
|
||||
"37": {StandardSupportUntil: time.Date(2023, 12, 15, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case constant.Windows:
|
||||
// https://learn.microsoft.com/ja-jp/lifecycle/products/?products=windows
|
||||
|
||||
lhs, rhs, _ := strings.Cut(strings.TrimSuffix(release, "(Server Core installation)"), "for")
|
||||
switch strings.TrimSpace(lhs) {
|
||||
case "Windows 7":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2013, 4, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
if strings.Contains(rhs, "Service Pack 1") {
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
}
|
||||
case "Windows 8":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2016, 1, 12, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 8.1":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2023, 1, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2017, 5, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1511":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2017, 10, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1607":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2018, 4, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1703":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2018, 10, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1709":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2019, 4, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1803":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2019, 11, 12, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1809":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 11, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1903":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 12, 8, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 1909":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2021, 5, 11, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 2004":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2021, 12, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 20H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2022, 5, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 21H1":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2022, 12, 13, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 21H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2023, 6, 13, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 10 Version 22H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2024, 5, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 11 Version 21H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2024, 10, 8, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows 11 Version 22H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2025, 10, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server 2008":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2011, 7, 12, 23, 59, 59, 0, time.UTC)}, true
|
||||
if strings.Contains(rhs, "Service Pack 2") {
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
}
|
||||
case "Windows Server 2008 R2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2013, 4, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
if strings.Contains(rhs, "Service Pack 1") {
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 1, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
}
|
||||
case "Windows Server 2012":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2023, 10, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server 2012 R2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2023, 10, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server 2016":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2027, 1, 12, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 1709":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2019, 4, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 1803":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2019, 11, 12, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 1809":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 11, 10, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server 2019":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2029, 1, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 1903":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2020, 12, 8, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 1909":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2021, 5, 11, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 2004":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2021, 12, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server, Version 20H2":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2022, 8, 9, 23, 59, 59, 0, time.UTC)}, true
|
||||
case "Windows Server 2022":
|
||||
eol, found = EOL{StandardSupportUntil: time.Date(2031, 10, 14, 23, 59, 59, 0, time.UTC)}, true
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -215,9 +414,25 @@ func majorDotMinor(osVer string) (majorDotMinor string) {
|
||||
}
|
||||
|
||||
func getAmazonLinuxVersion(osRelease string) string {
|
||||
ss := strings.Fields(osRelease)
|
||||
if len(ss) == 1 {
|
||||
switch s := strings.Fields(osRelease)[0]; s {
|
||||
case "1":
|
||||
return "1"
|
||||
case "2":
|
||||
return "2"
|
||||
case "2022":
|
||||
return "2022"
|
||||
case "2023":
|
||||
return "2023"
|
||||
case "2025":
|
||||
return "2025"
|
||||
case "2027":
|
||||
return "2027"
|
||||
case "2029":
|
||||
return "2029"
|
||||
default:
|
||||
if _, err := time.Parse("2006.01", s); err == nil {
|
||||
return "1"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
return ss[0]
|
||||
}
|
||||
|
||||
@@ -53,7 +53,31 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "amazon linux 2023 supported",
|
||||
fields: fields{family: Amazon, release: "2023"},
|
||||
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "amazon linux 2031 not found",
|
||||
fields: fields{family: Amazon, release: "2031"},
|
||||
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//RHEL
|
||||
{
|
||||
name: "RHEL6 eol",
|
||||
fields: fields{family: RedHat, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL7 supported",
|
||||
fields: fields{family: RedHat, release: "7"},
|
||||
@@ -71,22 +95,30 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL6 eol",
|
||||
fields: fields{family: RedHat, release: "6"},
|
||||
name: "RHEL9 supported",
|
||||
fields: fields{family: RedHat, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL9 not found",
|
||||
fields: fields{family: RedHat, release: "9"},
|
||||
name: "RHEL10 not found",
|
||||
fields: fields{family: RedHat, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//CentOS
|
||||
{
|
||||
name: "CentOS 6 eol",
|
||||
fields: fields{family: CentOS, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 7 supported",
|
||||
fields: fields{family: CentOS, release: "7"},
|
||||
@@ -104,16 +136,24 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 6 eol",
|
||||
fields: fields{family: CentOS, release: "6"},
|
||||
name: "CentOS stream8 supported",
|
||||
fields: fields{family: CentOS, release: "stream8"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 9 not found",
|
||||
fields: fields{family: CentOS, release: "9"},
|
||||
name: "CentOS stream9 supported",
|
||||
fields: fields{family: CentOS, release: "stream9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS stream10 Not Found",
|
||||
fields: fields{family: CentOS, release: "stream10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
@@ -129,16 +169,16 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alma Linux 8 EOL",
|
||||
fields: fields{family: Alma, release: "8"},
|
||||
now: time.Date(2029, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
name: "Alma Linux 9 supported",
|
||||
fields: fields{family: Alma, release: "9"},
|
||||
now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alma Linux 9 Not Found",
|
||||
fields: fields{family: Alma, release: "9"},
|
||||
name: "Alma Linux 10 Not Found",
|
||||
fields: fields{family: Alma, release: "10"},
|
||||
now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
@@ -154,22 +194,30 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Rocky Linux 8 EOL",
|
||||
fields: fields{family: Rocky, release: "8"},
|
||||
now: time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
name: "Rocky Linux 9 supported",
|
||||
fields: fields{family: Rocky, release: "9"},
|
||||
now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Rocky Linux 9 Not Found",
|
||||
fields: fields{family: Rocky, release: "9"},
|
||||
name: "Rocky Linux 10 Not Found",
|
||||
fields: fields{family: Rocky, release: "10"},
|
||||
now: time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//Oracle
|
||||
{
|
||||
name: "Oracle Linux 6 eol",
|
||||
fields: fields{family: Oracle, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 7 supported",
|
||||
fields: fields{family: Oracle, release: "7"},
|
||||
@@ -187,16 +235,16 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 6 eol",
|
||||
fields: fields{family: Oracle, release: "6"},
|
||||
name: "Oracle Linux 9 supported",
|
||||
fields: fields{family: Oracle, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 9 not found",
|
||||
fields: fields{family: Oracle, release: "9"},
|
||||
name: "Oracle Linux 10 not found",
|
||||
fields: fields{family: Oracle, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
@@ -204,28 +252,12 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
},
|
||||
//Ubuntu
|
||||
{
|
||||
name: "Ubuntu 18.04 supported",
|
||||
fields: fields{family: Ubuntu, release: "18.04"},
|
||||
name: "Ubuntu 5.10 not found",
|
||||
fields: fields{family: Ubuntu, release: "5.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 +276,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 +331,38 @@ 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,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 22.10 supported",
|
||||
fields: fields{family: Ubuntu, release: "22.10"},
|
||||
now: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC),
|
||||
found: true,
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
},
|
||||
// {
|
||||
// name: "Ubuntu 23.04 supported",
|
||||
// fields: fields{family: Ubuntu, release: "23.04"},
|
||||
// now: time.Date(2023, 3, 16, 23, 59, 59, 0, time.UTC),
|
||||
// found: true,
|
||||
// stdEnded: false,
|
||||
// extEnded: false,
|
||||
// },
|
||||
//Debian
|
||||
{
|
||||
name: "Debian 9 supported",
|
||||
@@ -358,14 +454,38 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.16 not found",
|
||||
name: "Alpine 3.16 supported",
|
||||
fields: fields{family: Alpine, release: "3.16"},
|
||||
now: time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.17 supported",
|
||||
fields: fields{family: Alpine, release: "3.17"},
|
||||
now: time.Date(2022, 1, 14, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.18 not found",
|
||||
fields: fields{family: Alpine, release: "3.18"},
|
||||
now: time.Date(2022, 1, 14, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
// freebsd
|
||||
{
|
||||
name: "freebsd 10 eol",
|
||||
fields: fields{family: FreeBSD, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "freebsd 11 supported",
|
||||
fields: fields{family: FreeBSD, release: "11"},
|
||||
@@ -398,27 +518,19 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "freebsd 10 eol",
|
||||
fields: fields{family: FreeBSD, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
// Fedora
|
||||
{
|
||||
name: "Fedora 32 supported",
|
||||
fields: fields{family: Fedora, release: "32"},
|
||||
now: time.Date(2021, 5, 25, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2021, 5, 24, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 32 eol on 2021-5-25",
|
||||
name: "Fedora 32 eol since 2021-5-25",
|
||||
fields: fields{family: Fedora, release: "32"},
|
||||
now: time.Date(2021, 5, 26, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2021, 5, 25, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
@@ -426,15 +538,15 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
{
|
||||
name: "Fedora 33 supported",
|
||||
fields: fields{family: Fedora, release: "33"},
|
||||
now: time.Date(2021, 11, 30, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2021, 11, 29, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 33 eol on 2021-5-26",
|
||||
name: "Fedora 33 eol since 2021-11-30",
|
||||
fields: fields{family: Fedora, release: "32"},
|
||||
now: time.Date(2021, 5, 27, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2021, 11, 30, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
@@ -442,15 +554,15 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
{
|
||||
name: "Fedora 34 supported",
|
||||
fields: fields{family: Fedora, release: "34"},
|
||||
now: time.Date(2022, 5, 17, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2022, 6, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 32 eol on 2022-5-17",
|
||||
name: "Fedora 34 eol since 2022-6-7",
|
||||
fields: fields{family: Fedora, release: "34"},
|
||||
now: time.Date(2022, 5, 18, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2022, 6, 7, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
@@ -458,19 +570,75 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
{
|
||||
name: "Fedora 35 supported",
|
||||
fields: fields{family: Fedora, release: "35"},
|
||||
now: time.Date(2022, 12, 7, 23, 59, 59, 0, time.UTC),
|
||||
now: time.Date(2022, 12, 12, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 35 eol on 2022-12-7",
|
||||
name: "Fedora 35 eol since 2022-12-13",
|
||||
fields: fields{family: Fedora, release: "35"},
|
||||
now: time.Date(2022, 12, 13, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 36 supported",
|
||||
fields: fields{family: Fedora, release: "36"},
|
||||
now: time.Date(2023, 5, 16, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 36 eol since 2023-05-17",
|
||||
fields: fields{family: Fedora, release: "36"},
|
||||
now: time.Date(2023, 5, 17, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 37 supported",
|
||||
fields: fields{family: Fedora, release: "37"},
|
||||
now: time.Date(2023, 12, 15, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 37 eol since 2023-12-16",
|
||||
fields: fields{family: Fedora, release: "37"},
|
||||
now: time.Date(2023, 12, 16, 0, 0, 0, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Fedora 38 not found",
|
||||
fields: fields{family: Fedora, release: "38"},
|
||||
now: time.Date(2023, 12, 15, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
{
|
||||
name: "Windows 10 EOL",
|
||||
fields: fields{family: Windows, release: "Windows 10 for x64-based Systems"},
|
||||
now: time.Date(2022, 12, 8, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Windows 10 Version 22H2 supported",
|
||||
fields: fields{family: Windows, release: "Windows 10 Version 22H2 for x64-based Systems"},
|
||||
now: time.Date(2022, 12, 8, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -536,3 +704,58 @@ func Test_majorDotMinor(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getAmazonLinuxVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
release string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
release: "2017.09",
|
||||
want: "1",
|
||||
},
|
||||
{
|
||||
release: "2018.03",
|
||||
want: "1",
|
||||
},
|
||||
{
|
||||
release: "1",
|
||||
want: "1",
|
||||
},
|
||||
{
|
||||
release: "2",
|
||||
want: "2",
|
||||
},
|
||||
{
|
||||
release: "2022",
|
||||
want: "2022",
|
||||
},
|
||||
{
|
||||
release: "2023",
|
||||
want: "2023",
|
||||
},
|
||||
{
|
||||
release: "2025",
|
||||
want: "2025",
|
||||
},
|
||||
{
|
||||
release: "2027",
|
||||
want: "2027",
|
||||
},
|
||||
{
|
||||
release: "2029",
|
||||
want: "2029",
|
||||
},
|
||||
{
|
||||
release: "2031",
|
||||
want: "unknown",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.release, func(t *testing.T) {
|
||||
if got := getAmazonLinuxVersion(tt.release); got != tt.want {
|
||||
t.Errorf("getAmazonLinuxVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/c-robinson/iplib"
|
||||
"github.com/knqyf263/go-cpe/naming"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
// TOMLLoader loads config
|
||||
@@ -15,7 +19,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
|
||||
@@ -28,13 +32,27 @@ func (c TOMLLoader) Load(pathToToml, _ string) error {
|
||||
&Conf.Exploit,
|
||||
&Conf.Metasploit,
|
||||
&Conf.KEVuln,
|
||||
&Conf.Cti,
|
||||
} {
|
||||
cnf.Init()
|
||||
}
|
||||
|
||||
index := 0
|
||||
servers := map[string]ServerInfo{}
|
||||
for name, server := range Conf.Servers {
|
||||
server.ServerName = name
|
||||
server.BaseName = name
|
||||
|
||||
if server.Type != constant.ServerTypePseudo && server.Host == "" {
|
||||
return xerrors.New("Failed to find hosts. err: server.host is empty")
|
||||
}
|
||||
serverHosts, err := hosts(server.Host, server.IgnoreIPAddresses)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to find hosts. err: %w", err)
|
||||
}
|
||||
if len(serverHosts) == 0 {
|
||||
return xerrors.New("Failed to find hosts. err: zero enumerated hosts")
|
||||
}
|
||||
|
||||
if err := setDefaultIfEmpty(&server); err != nil {
|
||||
return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err)
|
||||
}
|
||||
@@ -93,20 +111,17 @@ func (c TOMLLoader) Load(pathToToml, _ string) error {
|
||||
for _, reg := range cont.IgnorePkgsRegexp {
|
||||
_, err := regexp.Compile(reg)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w",
|
||||
reg, contName, name, err)
|
||||
return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w", reg, contName, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ownerRepo, githubSetting := range server.GitHubRepos {
|
||||
if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
|
||||
ownerRepo, name)
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s", ownerRepo, name)
|
||||
}
|
||||
if githubSetting.Token == "" {
|
||||
return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
|
||||
ownerRepo, name)
|
||||
return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty", ownerRepo, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +134,7 @@ func (c TOMLLoader) Load(pathToToml, _ string) error {
|
||||
case "base", "updates":
|
||||
// nop
|
||||
default:
|
||||
return xerrors.Errorf(
|
||||
"For now, enablerepo have to be base or updates: %s",
|
||||
server.Enablerepo)
|
||||
return xerrors.Errorf("For now, enablerepo have to be base or updates: %s", server.Enablerepo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,37 +143,103 @@ func (c TOMLLoader) Load(pathToToml, _ string) error {
|
||||
server.PortScan.IsUseExternalScanner = true
|
||||
}
|
||||
|
||||
server.LogMsgAnsiColor = Colors[index%len(Colors)]
|
||||
index++
|
||||
|
||||
Conf.Servers[name] = server
|
||||
if !isCIDRNotation(server.Host) {
|
||||
server.ServerName = name
|
||||
servers[server.ServerName] = server
|
||||
continue
|
||||
}
|
||||
for _, host := range serverHosts {
|
||||
server.Host = host
|
||||
server.ServerName = fmt.Sprintf("%s(%s)", name, host)
|
||||
server.LogMsgAnsiColor = Colors[index%len(Colors)]
|
||||
index++
|
||||
servers[server.ServerName] = server
|
||||
}
|
||||
}
|
||||
Conf.Servers = servers
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hosts(host string, ignores []string) ([]string, error) {
|
||||
hostMap := map[string]struct{}{}
|
||||
hosts, err := enumerateHosts(host)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to enumarate hosts. err: %w", err)
|
||||
}
|
||||
for _, host := range hosts {
|
||||
hostMap[host] = struct{}{}
|
||||
}
|
||||
|
||||
for _, ignore := range ignores {
|
||||
hosts, err := enumerateHosts(ignore)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to enumarate hosts. err: %w", err)
|
||||
}
|
||||
if len(hosts) == 1 && net.ParseIP(hosts[0]) == nil {
|
||||
return nil, xerrors.Errorf("Failed to ignore hosts. err: a non-IP address has been entered in ignoreIPAddress")
|
||||
}
|
||||
for _, host := range hosts {
|
||||
delete(hostMap, host)
|
||||
}
|
||||
}
|
||||
|
||||
hosts = []string{}
|
||||
for host := range hostMap {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func enumerateHosts(host string) ([]string, error) {
|
||||
if !isCIDRNotation(host) {
|
||||
return []string{host}, nil
|
||||
}
|
||||
|
||||
ipAddr, ipNet, err := net.ParseCIDR(host)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to parse CIDR. err: %w", err)
|
||||
}
|
||||
maskLen, _ := ipNet.Mask.Size()
|
||||
|
||||
addrs := []string{}
|
||||
if net.ParseIP(ipAddr.String()).To4() != nil {
|
||||
n := iplib.NewNet4(ipAddr, int(maskLen))
|
||||
for _, addr := range n.Enumerate(int(n.Count()), 0) {
|
||||
addrs = append(addrs, addr.String())
|
||||
}
|
||||
} else if net.ParseIP(ipAddr.String()).To16() != nil {
|
||||
n := iplib.NewNet6(ipAddr, int(maskLen), 0)
|
||||
if !n.Count().IsInt64() {
|
||||
return nil, xerrors.Errorf("Failed to enumerate IP address. err: mask bitsize too big")
|
||||
}
|
||||
for _, addr := range n.Enumerate(int(n.Count().Int64()), 0) {
|
||||
addrs = append(addrs, addr.String())
|
||||
}
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
func isCIDRNotation(host string) bool {
|
||||
ss := strings.Split(host, "/")
|
||||
if len(ss) == 1 || net.ParseIP(ss[0]) == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func setDefaultIfEmpty(server *ServerInfo) error {
|
||||
if server.Type != constant.ServerTypePseudo {
|
||||
if len(server.Host) == 0 {
|
||||
return xerrors.Errorf("server.host is empty")
|
||||
}
|
||||
|
||||
if len(server.JumpServer) == 0 {
|
||||
server.JumpServer = Conf.Default.JumpServer
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
@@ -215,6 +294,13 @@ func setDefaultIfEmpty(server *ServerInfo) error {
|
||||
}
|
||||
}
|
||||
|
||||
if server.Windows == nil {
|
||||
server.Windows = Conf.Default.Windows
|
||||
if server.Windows == nil {
|
||||
server.Windows = &WindowsConf{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(server.IgnoredJSONKeys) == 0 {
|
||||
server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
|
||||
}
|
||||
|
||||
@@ -1,9 +1,102 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHosts(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
ignore []string
|
||||
expected []string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
in: "127.0.0.1",
|
||||
expected: []string{"127.0.0.1"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "127.0.0.1",
|
||||
ignore: []string{"127.0.0.1"},
|
||||
expected: []string{},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "ssh/host",
|
||||
expected: []string{"ssh/host"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/30",
|
||||
expected: []string{"192.168.1.1", "192.168.1.2"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/30",
|
||||
ignore: []string{"192.168.1.1"},
|
||||
expected: []string{"192.168.1.2"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/30",
|
||||
ignore: []string{"ignore"},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/30",
|
||||
ignore: []string{"192.168.1.1/30"},
|
||||
expected: []string{},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/31",
|
||||
expected: []string{"192.168.1.0", "192.168.1.1"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "192.168.1.1/32",
|
||||
expected: []string{"192.168.1.1"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "2001:4860:4860::8888/126",
|
||||
expected: []string{"2001:4860:4860::8888", "2001:4860:4860::8889", "2001:4860:4860::888a", "2001:4860:4860::888b"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "2001:4860:4860::8888/127",
|
||||
expected: []string{"2001:4860:4860::8888", "2001:4860:4860::8889"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "2001:4860:4860::8888/128",
|
||||
expected: []string{"2001:4860:4860::8888"},
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
in: "2001:4860:4860::8888/32",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
actual, err := hosts(tt.in, tt.ignore)
|
||||
sort.Slice(actual, func(i, j int) bool { return actual[i] < actual[j] })
|
||||
if err != nil && !tt.err {
|
||||
t.Errorf("[%d] unexpected error occurred, in: %s act: %s, exp: %s",
|
||||
i, tt.in, actual, tt.expected)
|
||||
} else if err == nil && tt.err {
|
||||
t.Errorf("[%d] expected error is not occurred, in: %s act: %s, exp: %s",
|
||||
i, tt.in, actual, tt.expected)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, tt.expected) {
|
||||
t.Errorf("[%d] in: %s, actual: %q, expected: %q", i, tt.in, actual, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToCpeURI(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
|
||||
@@ -301,3 +301,30 @@ func (cnf *KEVulnConf) Init() {
|
||||
cnf.setDefault("go-kev.sqlite3")
|
||||
cnf.DebugSQL = Conf.DebugSQL
|
||||
}
|
||||
|
||||
// CtiConf is go-cti config
|
||||
type CtiConf struct {
|
||||
VulnDict
|
||||
}
|
||||
|
||||
const ctiDBType = "CTI_TYPE"
|
||||
const ctiDBURL = "CTI_URL"
|
||||
const ctiDBPATH = "CTI_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *CtiConf) Init() {
|
||||
cnf.Name = "cti"
|
||||
if os.Getenv(ctiDBType) != "" {
|
||||
cnf.Type = os.Getenv(ctiDBType)
|
||||
}
|
||||
if os.Getenv(ctiDBURL) != "" {
|
||||
cnf.URL = os.Getenv(ctiDBURL)
|
||||
}
|
||||
if os.Getenv(ctiDBPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(ctiDBPATH)
|
||||
}
|
||||
cnf.setDefault("go-cti.sqlite3")
|
||||
cnf.DebugSQL = Conf.DebugSQL
|
||||
}
|
||||
|
||||
27
config/windows.go
Normal file
27
config/windows.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// WindowsConf used for Windows Update Setting
|
||||
type WindowsConf struct {
|
||||
ServerSelection int `toml:"serverSelection,omitempty" json:"serverSelection,omitempty"`
|
||||
CabPath string `toml:"cabPath,omitempty" json:"cabPath,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *WindowsConf) Validate() []error {
|
||||
switch c.ServerSelection {
|
||||
case 0, 1, 2:
|
||||
case 3:
|
||||
if _, err := os.Stat(c.CabPath); err != nil {
|
||||
return []error{xerrors.Errorf("%s does not exist. err: %w", c.CabPath, err)}
|
||||
}
|
||||
default:
|
||||
return []error{xerrors.Errorf("ServerSelection: %d does not support . Reference: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-uamg/07e2bfa4-6795-4189-b007-cc50b476181a", c.ServerSelection)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
34
contrib/Dockerfile
Normal file
34
contrib/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
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 && \
|
||||
make build-snmp2cpe && mv snmp2cpe $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 /go/bin/snmp2cpe /usr/local/bin/
|
||||
COPY --from=aquasec/trivy:latest /usr/local/bin/trivy /usr/local/bin/trivy
|
||||
|
||||
VOLUME ["$WORKDIR", "$LOGDIR"]
|
||||
WORKDIR $WORKDIR
|
||||
ENV PWD $WORKDIR
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
@@ -21,6 +22,7 @@ var (
|
||||
serverUUID string
|
||||
groupID int64
|
||||
token string
|
||||
tags []string
|
||||
url string
|
||||
)
|
||||
|
||||
@@ -47,6 +49,9 @@ func main() {
|
||||
if len(token) == 0 {
|
||||
token = os.Getenv("VULS_TOKEN")
|
||||
}
|
||||
if len(tags) == 0 {
|
||||
tags = strings.Split(os.Getenv("VULS_TAGS"), ",")
|
||||
}
|
||||
|
||||
var scanResultJSON []byte
|
||||
if stdIn {
|
||||
@@ -69,6 +74,12 @@ func main() {
|
||||
return
|
||||
}
|
||||
scanResult.ServerUUID = serverUUID
|
||||
if 0 < len(tags) {
|
||||
if scanResult.Optional == nil {
|
||||
scanResult.Optional = map[string]interface{}{}
|
||||
}
|
||||
scanResult.Optional["VULS_TAGS"] = tags
|
||||
}
|
||||
|
||||
config.Conf.Saas.GroupID = groupID
|
||||
config.Conf.Saas.Token = token
|
||||
|
||||
@@ -2,7 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -41,7 +41,7 @@ func Parse(path string) ([]string, error) {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(file)
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to read OWASP Dependency Check XML: %s", path)
|
||||
return []string{}, nil
|
||||
|
||||
50
contrib/snmp2cpe/README.md
Normal file
50
contrib/snmp2cpe/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# snmp2cpe
|
||||
|
||||
## Main Features
|
||||
|
||||
- Estimate hardware and OS CPE from SNMP reply of network devices
|
||||
|
||||
## Installation
|
||||
|
||||
```console
|
||||
$ git clone https://github.com/future-architect/vuls.git
|
||||
$ make build-snmp2cpe
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
```console
|
||||
$ snmp2cpe help
|
||||
snmp2cpe: SNMP reply To CPE
|
||||
|
||||
Usage:
|
||||
snmp2cpe [command]
|
||||
|
||||
Available Commands:
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
convert snmpget reply to CPE
|
||||
help Help about any command
|
||||
v1 snmpget with SNMPv1
|
||||
v2c snmpget with SNMPv2c
|
||||
v3 snmpget with SNMPv3
|
||||
version Print the version
|
||||
|
||||
Flags:
|
||||
-h, --help help for snmp2cpe
|
||||
|
||||
Use "snmp2cpe [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
$ snmp2cpe v2c --debug 192.168.1.99 public
|
||||
2023/03/28 14:16:54 DEBUG: .1.3.6.1.2.1.1.1.0 ->
|
||||
2023/03/28 14:16:54 DEBUG: .1.3.6.1.2.1.47.1.1.1.1.12.1 -> Fortinet
|
||||
2023/03/28 14:16:54 DEBUG: .1.3.6.1.2.1.47.1.1.1.1.7.1 -> FGT_50E
|
||||
2023/03/28 14:16:54 DEBUG: .1.3.6.1.2.1.47.1.1.1.1.10.1 -> FortiGate-50E v5.4.6,build1165b1165,171018 (GA)
|
||||
{"192.168.1.99":{"entPhysicalTables":{"1":{"entPhysicalMfgName":"Fortinet","entPhysicalName":"FGT_50E","entPhysicalSoftwareRev":"FortiGate-50E v5.4.6,build1165b1165,171018 (GA)"}}}}
|
||||
|
||||
$ snmp2cpe v2c 192.168.1.99 public | snmp2cpe convert
|
||||
{"192.168.1.99":["cpe:2.3:h:fortinet:fortigate-50e:-:*:*:*:*:*:*:*","cpe:2.3:o:fortinet:fortios:5.4.6:*:*:*:*:*:*:*"]}
|
||||
```
|
||||
15
contrib/snmp2cpe/cmd/main.go
Normal file
15
contrib/snmp2cpe/cmd/main.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
rootCmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/root"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.NewCmdRoot().Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to exec snmp2cpe: %s\n", fmt.Sprintf("%+v", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
52
contrib/snmp2cpe/pkg/cmd/convert/convert.go
Normal file
52
contrib/snmp2cpe/pkg/cmd/convert/convert.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cpe"
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
)
|
||||
|
||||
// NewCmdConvert ...
|
||||
func NewCmdConvert() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "snmpget reply to CPE",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Example: `$ snmp2cpe v2c 192.168.11.11 public | snmp2cpe convert
|
||||
$ snmp2cpe v2c 192.168.11.11 public | snmp2cpe convert -
|
||||
$ snmp2cpe v2c 192.168.11.11 public > v2c.json && snmp2cpe convert v2c.json`,
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
r := os.Stdin
|
||||
if len(args) == 1 && args[0] != "-" {
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open %s", args[0])
|
||||
}
|
||||
defer f.Close()
|
||||
r = f
|
||||
}
|
||||
|
||||
var reply map[string]snmp.Result
|
||||
if err := json.NewDecoder(r).Decode(&reply); err != nil {
|
||||
return errors.Wrap(err, "failed to decode")
|
||||
}
|
||||
|
||||
converted := map[string][]string{}
|
||||
for ipaddr, res := range reply {
|
||||
converted[ipaddr] = cpe.Convert(res)
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(os.Stdout).Encode(converted); err != nil {
|
||||
return errors.Wrap(err, "failed to encode")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
30
contrib/snmp2cpe/pkg/cmd/root/root.go
Normal file
30
contrib/snmp2cpe/pkg/cmd/root/root.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
convertCmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/convert"
|
||||
v1Cmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/v1"
|
||||
v2cCmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/v2c"
|
||||
v3Cmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/v3"
|
||||
versionCmd "github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cmd/version"
|
||||
)
|
||||
|
||||
// NewCmdRoot ...
|
||||
func NewCmdRoot() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "snmp2cpe <command>",
|
||||
Short: "snmp2cpe",
|
||||
Long: "snmp2cpe: SNMP reply To CPE",
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
cmd.AddCommand(v1Cmd.NewCmdV1())
|
||||
cmd.AddCommand(v2cCmd.NewCmdV2c())
|
||||
cmd.AddCommand(v3Cmd.NewCmdV3())
|
||||
cmd.AddCommand(convertCmd.NewCmdConvert())
|
||||
cmd.AddCommand(versionCmd.NewCmdVersion())
|
||||
|
||||
return cmd
|
||||
}
|
||||
47
contrib/snmp2cpe/pkg/cmd/v1/v1.go
Normal file
47
contrib/snmp2cpe/pkg/cmd/v1/v1.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
)
|
||||
|
||||
// SNMPv1Options ...
|
||||
type SNMPv1Options struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// NewCmdV1 ...
|
||||
func NewCmdV1() *cobra.Command {
|
||||
opts := &SNMPv1Options{
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "v1 <IP Address> <Community>",
|
||||
Short: "snmpget with SNMPv1",
|
||||
Example: "$ snmp2cpe v1 192.168.100.1 public",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
r, err := snmp.Get(gosnmp.Version1, args[0], snmp.WithCommunity(args[1]), snmp.WithDebug(opts.Debug))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to snmpget")
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(os.Stdout).Encode(map[string]snmp.Result{args[0]: r}); err != nil {
|
||||
return errors.Wrap(err, "failed to encode")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.Debug, "debug", "", false, "debug mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
47
contrib/snmp2cpe/pkg/cmd/v2c/v2c.go
Normal file
47
contrib/snmp2cpe/pkg/cmd/v2c/v2c.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package v2c
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
)
|
||||
|
||||
// SNMPv2cOptions ...
|
||||
type SNMPv2cOptions struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// NewCmdV2c ...
|
||||
func NewCmdV2c() *cobra.Command {
|
||||
opts := &SNMPv2cOptions{
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "v2c <IP Address> <Community>",
|
||||
Short: "snmpget with SNMPv2c",
|
||||
Example: "$ snmp2cpe v2c 192.168.100.1 public",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
r, err := snmp.Get(gosnmp.Version2c, args[0], snmp.WithCommunity(args[1]), snmp.WithDebug(opts.Debug))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to snmpget")
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(os.Stdout).Encode(map[string]snmp.Result{args[0]: r}); err != nil {
|
||||
return errors.Wrap(err, "failed to encode")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.Debug, "debug", "", false, "debug mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
39
contrib/snmp2cpe/pkg/cmd/v3/v3.go
Normal file
39
contrib/snmp2cpe/pkg/cmd/v3/v3.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"github.com/gosnmp/gosnmp"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
)
|
||||
|
||||
// SNMPv3Options ...
|
||||
type SNMPv3Options struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// NewCmdV3 ...
|
||||
func NewCmdV3() *cobra.Command {
|
||||
opts := &SNMPv3Options{
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "v3 <args>",
|
||||
Short: "snmpget with SNMPv3",
|
||||
Example: "$ snmp2cpe v3",
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
_, err := snmp.Get(gosnmp.Version3, "", snmp.WithDebug(opts.Debug))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to snmpget")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&opts.Debug, "debug", "", false, "debug mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
23
contrib/snmp2cpe/pkg/cmd/version/version.go
Normal file
23
contrib/snmp2cpe/pkg/cmd/version/version.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
)
|
||||
|
||||
// NewCmdVersion ...
|
||||
func NewCmdVersion() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
fmt.Fprintf(os.Stdout, "snmp2cpe %s %s\n", config.Version, config.Revision)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
212
contrib/snmp2cpe/pkg/cpe/cpe.go
Normal file
212
contrib/snmp2cpe/pkg/cpe/cpe.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/util"
|
||||
)
|
||||
|
||||
// Convert ...
|
||||
func Convert(result snmp.Result) []string {
|
||||
var cpes []string
|
||||
|
||||
switch detectVendor(result) {
|
||||
case "Cisco":
|
||||
var p, v string
|
||||
lhs, _, _ := strings.Cut(result.SysDescr0, " RELEASE SOFTWARE")
|
||||
for _, s := range strings.Split(lhs, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
switch {
|
||||
case strings.Contains(s, "Cisco NX-OS"):
|
||||
p = "nx-os"
|
||||
case strings.Contains(s, "Cisco IOS Software"), strings.Contains(s, "Cisco Internetwork Operating System Software IOS"):
|
||||
p = "ios"
|
||||
if strings.Contains(lhs, "IOSXE") || strings.Contains(lhs, "IOS-XE") {
|
||||
p = "ios_xe"
|
||||
}
|
||||
case strings.HasPrefix(s, "Version "):
|
||||
v = strings.ToLower(strings.TrimPrefix(s, "Version "))
|
||||
}
|
||||
}
|
||||
if p != "" && v != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:cisco:%s:%s:*:*:*:*:*:*:*", p, v))
|
||||
}
|
||||
|
||||
if t, ok := result.EntPhysicalTables[1]; ok {
|
||||
if t.EntPhysicalName != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:cisco:%s:-:*:*:*:*:*:*:*", strings.ToLower(t.EntPhysicalName)))
|
||||
}
|
||||
if p != "" && t.EntPhysicalSoftwareRev != "" {
|
||||
s, _, _ := strings.Cut(t.EntPhysicalSoftwareRev, " RELEASE SOFTWARE")
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:cisco:%s:%s:*:*:*:*:*:*:*", p, strings.ToLower(strings.TrimSuffix(s, ","))))
|
||||
}
|
||||
}
|
||||
case "Juniper Networks":
|
||||
if strings.HasPrefix(result.SysDescr0, "Juniper Networks, Inc.") {
|
||||
for _, s := range strings.Split(strings.TrimPrefix(result.SysDescr0, "Juniper Networks, Inc. "), ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
switch {
|
||||
case strings.HasPrefix(s, "qfx"), strings.HasPrefix(s, "ex"), strings.HasPrefix(s, "mx"), strings.HasPrefix(s, "ptx"), strings.HasPrefix(s, "acx"), strings.HasPrefix(s, "bti"), strings.HasPrefix(s, "srx"):
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:juniper:%s:-:*:*:*:*:*:*:*", strings.Fields(s)[0]))
|
||||
case strings.HasPrefix(s, "kernel JUNOS "):
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:juniper:junos:%s:*:*:*:*:*:*:*", strings.ToLower(strings.Fields(strings.TrimPrefix(s, "kernel JUNOS "))[0])))
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := result.EntPhysicalTables[1]; ok {
|
||||
if t.EntPhysicalSoftwareRev != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:juniper:junos:%s:*:*:*:*:*:*:*", strings.ToLower(t.EntPhysicalSoftwareRev)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
h, v, ok := strings.Cut(result.SysDescr0, " version ")
|
||||
if ok {
|
||||
cpes = append(cpes,
|
||||
fmt.Sprintf("cpe:2.3:h:juniper:%s:-:*:*:*:*:*:*:*", strings.ToLower(h)),
|
||||
fmt.Sprintf("cpe:2.3:o:juniper:screenos:%s:*:*:*:*:*:*:*", strings.ToLower(strings.Fields(v)[0])),
|
||||
)
|
||||
}
|
||||
}
|
||||
case "Arista Networks":
|
||||
v, h, ok := strings.Cut(result.SysDescr0, " running on an ")
|
||||
if ok {
|
||||
if strings.HasPrefix(v, "Arista Networks EOS version ") {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:arista:eos:%s:*:*:*:*:*:*:*", strings.ToLower(strings.TrimPrefix(v, "Arista Networks EOS version "))))
|
||||
}
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:/h:arista:%s:-:*:*:*:*:*:*:*", strings.ToLower(strings.TrimPrefix(h, "Arista Networks "))))
|
||||
}
|
||||
if t, ok := result.EntPhysicalTables[1]; ok {
|
||||
if t.EntPhysicalSoftwareRev != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:arista:eos:%s:*:*:*:*:*:*:*", strings.ToLower(t.EntPhysicalSoftwareRev)))
|
||||
}
|
||||
}
|
||||
case "Fortinet":
|
||||
if t, ok := result.EntPhysicalTables[1]; ok {
|
||||
if strings.HasPrefix(t.EntPhysicalName, "FGT_") {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:fortinet:fortigate-%s:-:*:*:*:*:*:*:*", strings.ToLower(strings.TrimPrefix(t.EntPhysicalName, "FGT_"))))
|
||||
}
|
||||
for _, s := range strings.Fields(t.EntPhysicalSoftwareRev) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "FortiGate-"):
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:fortinet:%s:-:*:*:*:*:*:*:*", strings.ToLower(s)))
|
||||
case strings.HasPrefix(s, "v") && strings.Contains(s, "build"):
|
||||
if v, _, found := strings.Cut(strings.TrimPrefix(s, "v"), ",build"); found {
|
||||
if _, err := version.NewVersion(v); err == nil {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:fortinet:fortios:%s:*:*:*:*:*:*:*", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "YAMAHA":
|
||||
var h, v string
|
||||
for _, s := range strings.Fields(result.SysDescr0) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "RTX"), strings.HasPrefix(s, "NVR"), strings.HasPrefix(s, "RTV"), strings.HasPrefix(s, "RT"),
|
||||
strings.HasPrefix(s, "SRT"), strings.HasPrefix(s, "FWX"), strings.HasPrefix(s, "YSL-V810"):
|
||||
h = strings.ToLower(s)
|
||||
case strings.HasPrefix(s, "Rev."):
|
||||
if _, err := version.NewVersion(strings.TrimPrefix(s, "Rev.")); err == nil {
|
||||
v = strings.TrimPrefix(s, "Rev.")
|
||||
}
|
||||
}
|
||||
}
|
||||
if h != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:yamaha:%s:-:*:*:*:*:*:*:*", h))
|
||||
if v != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:yamaha:%s:%s:*:*:*:*:*:*:*", h, v))
|
||||
}
|
||||
}
|
||||
case "NEC":
|
||||
var h, v string
|
||||
for _, s := range strings.Split(result.SysDescr0, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
switch {
|
||||
case strings.HasPrefix(s, "IX Series "):
|
||||
h = strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(s, "IX Series "), " (magellan-sec) Software"))
|
||||
case strings.HasPrefix(s, "Version "):
|
||||
if _, err := version.NewVersion(strings.TrimSpace(strings.TrimPrefix(s, "Version "))); err == nil {
|
||||
v = strings.TrimSpace(strings.TrimPrefix(s, "Version "))
|
||||
}
|
||||
}
|
||||
}
|
||||
if h != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:nec:%s:-:*:*:*:*:*:*:*", h))
|
||||
if v != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:nec:%s:%s:*:*:*:*:*:*:*", h, v))
|
||||
}
|
||||
}
|
||||
case "Palo Alto Networks":
|
||||
if t, ok := result.EntPhysicalTables[1]; ok {
|
||||
if t.EntPhysicalName != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:h:paloaltonetworks:%s:-:*:*:*:*:*:*:*", strings.ToLower(t.EntPhysicalName)))
|
||||
}
|
||||
if t.EntPhysicalSoftwareRev != "" {
|
||||
cpes = append(cpes, fmt.Sprintf("cpe:2.3:o:paloaltonetworks:pan-os:%s:*:*:*:*:*:*:*", t.EntPhysicalSoftwareRev))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return util.Unique(cpes)
|
||||
}
|
||||
|
||||
func detectVendor(r snmp.Result) string {
|
||||
if t, ok := r.EntPhysicalTables[1]; ok {
|
||||
switch t.EntPhysicalMfgName {
|
||||
case "Cisco":
|
||||
return "Cisco"
|
||||
case "Juniper Networks":
|
||||
return "Juniper Networks"
|
||||
case "Arista Networks":
|
||||
return "Arista Networks"
|
||||
case "Fortinet":
|
||||
return "Fortinet"
|
||||
case "YAMAHA":
|
||||
return "YAMAHA"
|
||||
case "NEC":
|
||||
return "NEC"
|
||||
case "Palo Alto Networks":
|
||||
return "Palo Alto Networks"
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(r.SysDescr0, "Cisco"):
|
||||
return "Cisco"
|
||||
case strings.Contains(r.SysDescr0, "Juniper Networks"),
|
||||
strings.Contains(r.SysDescr0, "SSG5"), strings.Contains(r.SysDescr0, "SSG20"), strings.Contains(r.SysDescr0, "SSG140"),
|
||||
strings.Contains(r.SysDescr0, "SSG320"), strings.Contains(r.SysDescr0, "SSG350"), strings.Contains(r.SysDescr0, "SSG520"),
|
||||
strings.Contains(r.SysDescr0, "SSG550"):
|
||||
return "Juniper Networks"
|
||||
case strings.Contains(r.SysDescr0, "Arista Networks"):
|
||||
return "Arista Networks"
|
||||
case strings.Contains(r.SysDescr0, "Fortinet"), strings.Contains(r.SysDescr0, "FortiGate"):
|
||||
return "Fortinet"
|
||||
case strings.Contains(r.SysDescr0, "YAMAHA"),
|
||||
strings.Contains(r.SysDescr0, "RTX810"), strings.Contains(r.SysDescr0, "RTX830"),
|
||||
strings.Contains(r.SysDescr0, "RTX1000"), strings.Contains(r.SysDescr0, "RTX1100"),
|
||||
strings.Contains(r.SysDescr0, "RTX1200"), strings.Contains(r.SysDescr0, "RTX1210"), strings.Contains(r.SysDescr0, "RTX1220"),
|
||||
strings.Contains(r.SysDescr0, "RTX1300"), strings.Contains(r.SysDescr0, "RTX1500"), strings.Contains(r.SysDescr0, "RTX2000"),
|
||||
strings.Contains(r.SysDescr0, "RTX3000"), strings.Contains(r.SysDescr0, "RTX3500"), strings.Contains(r.SysDescr0, "RTX5000"),
|
||||
strings.Contains(r.SysDescr0, "NVR500"), strings.Contains(r.SysDescr0, "NVR510"), strings.Contains(r.SysDescr0, "NVR700W"),
|
||||
strings.Contains(r.SysDescr0, "RTV01"), strings.Contains(r.SysDescr0, "RTV700"),
|
||||
strings.Contains(r.SysDescr0, "RT105i"), strings.Contains(r.SysDescr0, "RT105p"), strings.Contains(r.SysDescr0, "RT105e"),
|
||||
strings.Contains(r.SysDescr0, "RT107e"), strings.Contains(r.SysDescr0, "RT250i"), strings.Contains(r.SysDescr0, "RT300i"),
|
||||
strings.Contains(r.SysDescr0, "SRT100"),
|
||||
strings.Contains(r.SysDescr0, "FWX100"),
|
||||
strings.Contains(r.SysDescr0, "YSL-V810"):
|
||||
return "YAMAHA"
|
||||
case strings.Contains(r.SysDescr0, "NEC"):
|
||||
return "NEC"
|
||||
case strings.Contains(r.SysDescr0, "Palo Alto Networks"):
|
||||
return "Palo Alto Networks"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
244
contrib/snmp2cpe/pkg/cpe/cpe_test.go
Normal file
244
contrib/snmp2cpe/pkg/cpe/cpe_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package cpe_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/cpe"
|
||||
"github.com/future-architect/vuls/contrib/snmp2cpe/pkg/snmp"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args snmp.Result
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "Cisco NX-OS Version 7.1(4)N1(1)",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Cisco NX-OS(tm) n6000, Software (n6000-uk9), Version 7.1(4)N1(1), RELEASE SOFTWARE Copyright (c) 2002-2012 by Cisco Systems, Inc. Device Manager Version 6.0(2)N1(1),Compiled 9/2/2016 10:00:00",
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:nx-os:7.1(4)n1(1):*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOS Version 15.1(4)M3",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software, 2800 Software (C2800NM-ADVENTERPRISEK9-M), Version 15.1(4)M3, RELEASE SOFTWARE (fc1)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2011 by Cisco Systems, Inc.
|
||||
Compiled Tue 06-Dec-11 16:21 by prod_rel_team`,
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:ios:15.1(4)m3:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOS Version 15.1(4)M4",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software, C181X Software (C181X-ADVENTERPRISEK9-M), Version 15.1(4)M4, RELEASE SOFTWARE (fc1)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2012 by Cisco Systems, Inc.
|
||||
Compiled Tue 20-Mar-12 23:34 by prod_rel_team`,
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:ios:15.1(4)m4:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOS Vresion 15.5(3)M on Cisco 892J-K9-V02",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software, C890 Software (C890-UNIVERSALK9-M), Version 15.5(3)M, RELEASE SOFTWARE (fc1)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2015 by Cisco Systems, Inc.
|
||||
Compiled Thu 23-Jul-15 03:08 by prod_rel_team`,
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Cisco",
|
||||
EntPhysicalName: "892",
|
||||
EntPhysicalSoftwareRev: "15.5(3)M, RELEASE SOFTWARE (fc1)",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:cisco:892:-:*:*:*:*:*:*:*", "cpe:2.3:o:cisco:ios:15.5(3)m:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOS Version 15.4(3)M5 on Cisco C892FSP-K9-V02",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software, C800 Software (C800-UNIVERSALK9-M), Version 15.4(3)M5, RELEASE SOFTWARE (fc1)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2016 by Cisco Systems, Inc.
|
||||
Compiled Tue 09-Feb-16 06:15 by prod_rel_team`,
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Cisco",
|
||||
EntPhysicalName: "C892FSP-K9",
|
||||
EntPhysicalSoftwareRev: "15.4(3)M5, RELEASE SOFTWARE (fc1)",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:cisco:c892fsp-k9:-:*:*:*:*:*:*:*", "cpe:2.3:o:cisco:ios:15.4(3)m5:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOS Version 12.2(17d)SXB11",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco Internetwork Operating System Software IOS (tm) s72033_rp Software (s72033_rp-JK9SV-M), Version 12.2(17d)SXB11, RELEASE SOFTWARE (fc1)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2005 by cisco Systems, Inc.`,
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:ios:12.2(17d)sxb11:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOX-XE Version 16.12.4",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software [Gibraltar], Catalyst L3 Switch Software (CAT9K_LITE_IOSXE), Version 16.12.4, RELEASE SOFTWARE (fc5)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2020 by Cisco Systems, Inc.
|
||||
Compiled Thu 09-Jul-20 19:31 by m`,
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:ios_xe:16.12.4:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Cisco IOX-XE Version 03.06.07.E",
|
||||
args: snmp.Result{
|
||||
SysDescr0: `Cisco IOS Software, IOS-XE Software, Catalyst 4500 L3 Switch Software (cat4500es8-UNIVERSALK9-M), Version 03.06.07.E RELEASE SOFTWARE (fc3)
|
||||
Technical Support: http://www.cisco.com/techsupport
|
||||
Copyright (c) 1986-2017 by Cisco Systems, Inc.
|
||||
Compiled Wed`,
|
||||
},
|
||||
want: []string{"cpe:2.3:o:cisco:ios_xe:03.06.07.e:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Juniper SSG-5-SH-BT",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "SSG5-ISDN version 6.3.0r14.0 (SN: 0000000000000001, Firewall+VPN)",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:ssg5-isdn:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:screenos:6.3.0r14.0:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "JUNOS 20.4R3-S4.8 on Juniper MX240",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Juniper Networks, Inc. mx240 internet router, kernel JUNOS 20.4R3-S4.8, Build date: 2022-08-16 20:42:11 UTC Copyright (c) 1996-2022 Juniper Networks, Inc.",
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Juniper Networks",
|
||||
EntPhysicalName: "CHAS-BP3-MX240-S",
|
||||
EntPhysicalSoftwareRev: "20.4R3-S4.8",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:mx240:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:junos:20.4r3-s4.8:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "JUNOS 12.1X46-D65.4 on Juniper SRX220H",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Juniper Networks, Inc. srx220h internet router, kernel JUNOS 12.1X46-D65.4 #0: 2016-12-30 01:34:30 UTC builder@quoarth.juniper.net:/volume/build/junos/12.1/service/12.1X46-D65.4/obj-octeon/junos/bsd/kernels/JSRXNLE/kernel Build date: 2016-12-30 02:59",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:srx220h:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:junos:12.1x46-d65.4:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "JUNOS 12.3X48-D30.7 on Juniper SRX220H2",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Juniper Networks, Inc. srx220h2 internet router, kernel JUNOS 12.3X48-D30.7, Build date: 2016-04-29 00:01:04 UTC Copyright (c) 1996-2016 Juniper Networks, Inc.",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:srx220h2:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:junos:12.3x48-d30.7:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "JUNOS 20.4R3-S4.8 on Juniper SRX4600",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Juniper Networks, Inc. srx4600 internet router, kernel JUNOS 20.4R3-S4.8, Build date: 2022-08-16 20:42:11 UTC Copyright (c) 1996-2022 Juniper Networks, Inc.",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:srx4600:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:junos:20.4r3-s4.8:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "cpe:2.3:o:juniper:junos:20.4:r2-s2.2:*:*:*:*:*:*",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Juniper Networks, Inc. ex4300-32f Ethernet Switch, kernel JUNOS 20.4R3-S4.8, Build date: 2022-08-16 21:10:45 UTC Copyright (c) 1996-2022 Juniper Networks, Inc.",
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Juniper Networks",
|
||||
EntPhysicalName: "",
|
||||
EntPhysicalSoftwareRev: "20.4R3-S4.8",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:juniper:ex4300-32f:-:*:*:*:*:*:*:*", "cpe:2.3:o:juniper:junos:20.4r3-s4.8:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Arista Networks EOS version 4.28.4M on DCS-7050TX-64",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Arista Networks EOS version 4.28.4M running on an Arista Networks DCS-7050TX-64",
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Arista Networks",
|
||||
EntPhysicalName: "",
|
||||
EntPhysicalSoftwareRev: "4.28.4M",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:/h:arista:dcs-7050tx-64:-:*:*:*:*:*:*:*", "cpe:2.3:o:arista:eos:4.28.4m:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "FortiGate-50E",
|
||||
args: snmp.Result{
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Fortinet",
|
||||
EntPhysicalName: "FGT_50E",
|
||||
EntPhysicalSoftwareRev: "FortiGate-50E v5.4.6,build1165b1165,171018 (GA)",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:fortinet:fortigate-50e:-:*:*:*:*:*:*:*", "cpe:2.3:o:fortinet:fortios:5.4.6:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "FortiGate-60F",
|
||||
args: snmp.Result{
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Fortinet",
|
||||
EntPhysicalName: "FGT_60F",
|
||||
EntPhysicalSoftwareRev: "FortiGate-60F v6.4.11,build2030,221031 (GA.M)",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:fortinet:fortigate-60f:-:*:*:*:*:*:*:*", "cpe:2.3:o:fortinet:fortios:6.4.11:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "YAMAHA RTX1000",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "RTX1000 Rev.8.01.29 (Fri Apr 15 11:50:44 2011)",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:yamaha:rtx1000:-:*:*:*:*:*:*:*", "cpe:2.3:o:yamaha:rtx1000:8.01.29:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "YAMAHA RTX810",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "RTX810 Rev.11.01.34 (Tue Nov 26 18:39:12 2019)",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:yamaha:rtx810:-:*:*:*:*:*:*:*", "cpe:2.3:o:yamaha:rtx810:11.01.34:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "NEC IX2105",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "NEC Portable Internetwork Core Operating System Software, IX Series IX2105 (magellan-sec) Software, Version 8.8.22, RELEASE SOFTWARE, Compiled Jul 04-Wed-2012 14:18:46 JST #2, IX2105",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:nec:ix2105:-:*:*:*:*:*:*:*", "cpe:2.3:o:nec:ix2105:8.8.22:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "NEC IX2235",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "NEC Portable Internetwork Core Operating System Software, IX Series IX2235 (magellan-sec) Software, Version 10.6.21, RELEASE SOFTWARE, Compiled Dec 15-Fri-YYYY HH:MM:SS JST #2, IX2235",
|
||||
},
|
||||
want: []string{"cpe:2.3:h:nec:ix2235:-:*:*:*:*:*:*:*", "cpe:2.3:o:nec:ix2235:10.6.21:*:*:*:*:*:*:*"},
|
||||
},
|
||||
{
|
||||
name: "Palo Alto Networks PAN-OS 10.0.0 on PA-220",
|
||||
args: snmp.Result{
|
||||
SysDescr0: "Palo Alto Networks PA-220 series firewall",
|
||||
EntPhysicalTables: map[int]snmp.EntPhysicalTable{1: {
|
||||
EntPhysicalMfgName: "Palo Alto Networks",
|
||||
EntPhysicalName: "PA-220",
|
||||
EntPhysicalSoftwareRev: "10.0.0",
|
||||
}},
|
||||
},
|
||||
want: []string{"cpe:2.3:h:paloaltonetworks:pa-220:-:*:*:*:*:*:*:*", "cpe:2.3:o:paloaltonetworks:pan-os:10.0.0:*:*:*:*:*:*:*"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := []cmp.Option{
|
||||
cmpopts.SortSlices(func(i, j string) bool {
|
||||
return i < j
|
||||
}),
|
||||
}
|
||||
if diff := cmp.Diff(cpe.Convert(tt.args), tt.want, opts...); diff != "" {
|
||||
t.Errorf("Convert() value is mismatch (-got +want):%s\n", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
131
contrib/snmp2cpe/pkg/snmp/snmp.go
Normal file
131
contrib/snmp2cpe/pkg/snmp/snmp.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package snmp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosnmp/gosnmp"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
community string
|
||||
debug bool
|
||||
}
|
||||
|
||||
// Option ...
|
||||
type Option interface {
|
||||
apply(*options)
|
||||
}
|
||||
|
||||
type communityOption string
|
||||
|
||||
func (c communityOption) apply(opts *options) {
|
||||
opts.community = string(c)
|
||||
}
|
||||
|
||||
// WithCommunity ...
|
||||
func WithCommunity(c string) Option {
|
||||
return communityOption(c)
|
||||
}
|
||||
|
||||
type debugOption bool
|
||||
|
||||
func (d debugOption) apply(opts *options) {
|
||||
opts.debug = bool(d)
|
||||
}
|
||||
|
||||
// WithDebug ...
|
||||
func WithDebug(d bool) Option {
|
||||
return debugOption(d)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func Get(version gosnmp.SnmpVersion, ipaddr string, opts ...Option) (Result, error) {
|
||||
var options options
|
||||
for _, o := range opts {
|
||||
o.apply(&options)
|
||||
}
|
||||
|
||||
r := Result{SysDescr0: "", EntPhysicalTables: map[int]EntPhysicalTable{}}
|
||||
|
||||
params := &gosnmp.GoSNMP{
|
||||
Target: ipaddr,
|
||||
Port: 161,
|
||||
Version: version,
|
||||
Timeout: time.Duration(2) * time.Second,
|
||||
Retries: 3,
|
||||
ExponentialTimeout: true,
|
||||
MaxOids: gosnmp.MaxOids,
|
||||
}
|
||||
|
||||
switch version {
|
||||
case gosnmp.Version1, gosnmp.Version2c:
|
||||
params.Community = options.community
|
||||
case gosnmp.Version3:
|
||||
return Result{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
if err := params.Connect(); err != nil {
|
||||
return Result{}, errors.Wrap(err, "failed to connect")
|
||||
}
|
||||
defer params.Conn.Close()
|
||||
|
||||
for _, oid := range []string{"1.3.6.1.2.1.1.1.0", "1.3.6.1.2.1.47.1.1.1.1.12.1", "1.3.6.1.2.1.47.1.1.1.1.7.1", "1.3.6.1.2.1.47.1.1.1.1.10.1"} {
|
||||
resp, err := params.Get([]string{oid})
|
||||
if err != nil {
|
||||
return Result{}, errors.Wrap(err, "send SNMP GET request")
|
||||
}
|
||||
for _, v := range resp.Variables {
|
||||
if options.debug {
|
||||
switch v.Type {
|
||||
case gosnmp.OctetString:
|
||||
log.Printf("DEBUG: %s -> %s", v.Name, string(v.Value.([]byte)))
|
||||
default:
|
||||
log.Printf("DEBUG: %s -> %v", v.Name, v.Value)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case v.Name == ".1.3.6.1.2.1.1.1.0":
|
||||
if v.Type == gosnmp.OctetString {
|
||||
r.SysDescr0 = string(v.Value.([]byte))
|
||||
}
|
||||
case strings.HasPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.12."):
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.12."))
|
||||
if err != nil {
|
||||
return Result{}, errors.Wrap(err, "failed to get index")
|
||||
}
|
||||
if v.Type == gosnmp.OctetString {
|
||||
b := r.EntPhysicalTables[i]
|
||||
b.EntPhysicalMfgName = string(v.Value.([]byte))
|
||||
r.EntPhysicalTables[i] = b
|
||||
}
|
||||
case strings.HasPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.7."):
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.7."))
|
||||
if err != nil {
|
||||
return Result{}, errors.Wrap(err, "failed to get index")
|
||||
}
|
||||
if v.Type == gosnmp.OctetString {
|
||||
b := r.EntPhysicalTables[i]
|
||||
b.EntPhysicalName = string(v.Value.([]byte))
|
||||
r.EntPhysicalTables[i] = b
|
||||
}
|
||||
case strings.HasPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.10."):
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(v.Name, ".1.3.6.1.2.1.47.1.1.1.1.10."))
|
||||
if err != nil {
|
||||
return Result{}, errors.Wrap(err, "failed to get index")
|
||||
}
|
||||
if v.Type == gosnmp.OctetString {
|
||||
b := r.EntPhysicalTables[i]
|
||||
b.EntPhysicalSoftwareRev = string(v.Value.([]byte))
|
||||
r.EntPhysicalTables[i] = b
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
14
contrib/snmp2cpe/pkg/snmp/types.go
Normal file
14
contrib/snmp2cpe/pkg/snmp/types.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package snmp
|
||||
|
||||
// Result ...
|
||||
type Result struct {
|
||||
SysDescr0 string `json:"sysDescr0,omitempty"`
|
||||
EntPhysicalTables map[int]EntPhysicalTable `json:"entPhysicalTables,omitempty"`
|
||||
}
|
||||
|
||||
// EntPhysicalTable ...
|
||||
type EntPhysicalTable struct {
|
||||
EntPhysicalMfgName string `json:"entPhysicalMfgName,omitempty"`
|
||||
EntPhysicalName string `json:"entPhysicalName,omitempty"`
|
||||
EntPhysicalSoftwareRev string `json:"entPhysicalSoftwareRev,omitempty"`
|
||||
}
|
||||
12
contrib/snmp2cpe/pkg/util/util.go
Normal file
12
contrib/snmp2cpe/pkg/util/util.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package util
|
||||
|
||||
import "golang.org/x/exp/maps"
|
||||
|
||||
// Unique return unique elements
|
||||
func Unique[T comparable](s []T) []T {
|
||||
m := map[T]struct{}{}
|
||||
for _, v := range s {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return maps.Keys(m)
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -39,7 +38,7 @@ func main() {
|
||||
}
|
||||
trivyJSON = buf.Bytes()
|
||||
} else {
|
||||
if trivyJSON, err = ioutil.ReadFile(jsonFilePath); err != nil {
|
||||
if trivyJSON, err = os.ReadFile(jsonFilePath); err != nil {
|
||||
fmt.Printf("Failed to read file. err: %+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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 +20,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 +30,50 @@ 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" {
|
||||
matches := dockerTagPattern.FindStringSubmatch(report.ArtifactName)
|
||||
var imageName, imageTag string
|
||||
if 2 < len(matches) {
|
||||
// including the image tag
|
||||
imageName = matches[1]
|
||||
imageTag = matches[2]
|
||||
} else {
|
||||
// no image tag
|
||||
imageName = report.ArtifactName
|
||||
imageTag = "latest" // Complement if the tag is omitted
|
||||
}
|
||||
scanResult.ServerName = fmt.Sprintf("%s:%s", imageName, imageTag)
|
||||
if scanResult.Optional == nil {
|
||||
scanResult.Optional = map[string]interface{}{}
|
||||
}
|
||||
scanResult.Optional["TRIVY_IMAGE_NAME"] = imageName
|
||||
scanResult.Optional["TRIVY_IMAGE_TAG"] = imageTag
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
@@ -262,7 +264,8 @@ var redisSR = &models.ScanResult{
|
||||
},
|
||||
},
|
||||
Optional: map[string]interface{}{
|
||||
"trivy-target": "redis (debian 10.10)",
|
||||
"TRIVY_IMAGE_NAME": "redis",
|
||||
"TRIVY_IMAGE_TAG": "latest",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -372,7 +375,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 +461,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 +633,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{
|
||||
@@ -720,6 +722,84 @@ var osAndLibSR = &models.ScanResult{
|
||||
},
|
||||
},
|
||||
Optional: map[string]interface{}{
|
||||
"trivy-target": "quay.io/fluentd_elasticsearch/fluentd:v2.9.0 (debian 10.2)",
|
||||
"TRIVY_IMAGE_NAME": "quay.io/fluentd_elasticsearch/fluentd",
|
||||
"TRIVY_IMAGE_TAG": "v2.9.0",
|
||||
},
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
@@ -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/fanal/analyzer/os"
|
||||
"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
|
||||
}
|
||||
|
||||
4052
cti/cti.go
Normal file
4052
cti/cti.go
Normal file
File diff suppressed because it is too large
Load Diff
104
cwe/cwe.go
104
cwe/cwe.go
@@ -1,7 +1,14 @@
|
||||
package cwe
|
||||
|
||||
// CweTopTwentyfive2019 has CWE-ID in CWE Top 25
|
||||
var CweTopTwentyfive2019 = map[string]string{
|
||||
// CweTopTwentyfives has CWE-ID in CWE Top 25
|
||||
var CweTopTwentyfives = map[string]map[string]string{
|
||||
"2019": cweTopTwentyfive2019,
|
||||
"2020": cweTopTwentyfive2020,
|
||||
"2021": cweTopTwentyfive2021,
|
||||
"2022": cweTopTwentyfive2022,
|
||||
}
|
||||
|
||||
var cweTopTwentyfive2019 = map[string]string{
|
||||
"119": "1",
|
||||
"79": "2",
|
||||
"20": "3",
|
||||
@@ -29,5 +36,94 @@ var CweTopTwentyfive2019 = map[string]string{
|
||||
"295": "25",
|
||||
}
|
||||
|
||||
// CweTopTwentyfive2019URL has CWE Top25 links
|
||||
var CweTopTwentyfive2019URL = "https://cwe.mitre.org/top25/archive/2019/2019_cwe_top25.html"
|
||||
var cweTopTwentyfive2020 = map[string]string{
|
||||
"79": "1",
|
||||
"787": "2",
|
||||
"20": "3",
|
||||
"125": "4",
|
||||
"119": "5",
|
||||
"89": "6",
|
||||
"200": "7",
|
||||
"416": "8",
|
||||
"352": "9",
|
||||
"78": "10",
|
||||
"190": "11",
|
||||
"22": "12",
|
||||
"476": "13",
|
||||
"287": "14",
|
||||
"434": "16",
|
||||
"732": "16",
|
||||
"94": "17",
|
||||
"522": "18",
|
||||
"611": "19",
|
||||
"798": "20",
|
||||
"502": "21",
|
||||
"269": "22",
|
||||
"400": "23",
|
||||
"306": "24",
|
||||
"862": "25",
|
||||
}
|
||||
|
||||
var cweTopTwentyfive2021 = map[string]string{
|
||||
"787": "1",
|
||||
"79": "2",
|
||||
"125": "3",
|
||||
"20": "4",
|
||||
"78": "5",
|
||||
"89": "6",
|
||||
"416": "7",
|
||||
"22": "8",
|
||||
"352": "9",
|
||||
"434": "10",
|
||||
"306": "11",
|
||||
"190": "12",
|
||||
"502": "13",
|
||||
"287": "14",
|
||||
"476": "16",
|
||||
"798": "16",
|
||||
"119": "17",
|
||||
"862": "18",
|
||||
"276": "19",
|
||||
"200": "20",
|
||||
"522": "21",
|
||||
"732": "22",
|
||||
"611": "23",
|
||||
"918": "24",
|
||||
"77": "25",
|
||||
}
|
||||
|
||||
var cweTopTwentyfive2022 = map[string]string{
|
||||
"787": "1",
|
||||
"79": "2",
|
||||
"89": "3",
|
||||
"20": "4",
|
||||
"125": "5",
|
||||
"78": "6",
|
||||
"416": "7",
|
||||
"22": "8",
|
||||
"352": "9",
|
||||
"434": "10",
|
||||
"476": "11",
|
||||
"502": "12",
|
||||
"190": "13",
|
||||
"287": "14",
|
||||
"798": "16",
|
||||
"862": "16",
|
||||
"77": "17",
|
||||
"306": "18",
|
||||
"119": "19",
|
||||
"276": "20",
|
||||
"918": "21",
|
||||
"362": "22",
|
||||
"400": "23",
|
||||
"611": "24",
|
||||
"94": "25",
|
||||
}
|
||||
|
||||
// CweTopTwentyfiveURLs has CWE Top25 links
|
||||
var CweTopTwentyfiveURLs = map[string]string{
|
||||
"2019": "https://cwe.mitre.org/top25/archive/2019/2019_cwe_top25.html",
|
||||
"2020": "https://cwe.mitre.org/top25/archive/2020/2020_cwe_top25.html",
|
||||
"2021": "https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html",
|
||||
"2022": "https://cwe.mitre.org/top25/archive/2022/2022_cwe_top25.html",
|
||||
}
|
||||
|
||||
292
cwe/owasp.go
292
cwe/owasp.go
@@ -1,7 +1,12 @@
|
||||
package cwe
|
||||
|
||||
// OwaspTopTen2017 has CWE-ID in OWSP Top 10
|
||||
var OwaspTopTen2017 = map[string]string{
|
||||
// OwaspTopTens has CWE-ID in OWASP Top 10
|
||||
var OwaspTopTens = map[string]map[string]string{
|
||||
"2017": owaspTopTen2017,
|
||||
"2021": owaspTopTen2021,
|
||||
}
|
||||
|
||||
var owaspTopTen2017 = map[string]string{
|
||||
"77": "1",
|
||||
"89": "1",
|
||||
"564": "1",
|
||||
@@ -36,30 +41,265 @@ var OwaspTopTen2017 = map[string]string{
|
||||
"778": "10",
|
||||
}
|
||||
|
||||
// OwaspTopTen2017GitHubURLEn has GitHub links
|
||||
var OwaspTopTen2017GitHubURLEn = map[string]string{
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa1-injection.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa2-broken-authentication.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa3-sensitive-data-disclosure.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa4-xxe.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa5-broken-access-control.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa6-security-misconfiguration.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa7-xss.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa8-insecure-deserialization.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa9-known-vulns.md<Paste>",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2017/en/0xaa-logging-detection-response.md",
|
||||
var owaspTopTen2021 = map[string]string{
|
||||
"22": "1",
|
||||
"23": "1",
|
||||
"35": "1",
|
||||
"59": "1",
|
||||
"200": "1",
|
||||
"201": "1",
|
||||
"219": "1",
|
||||
"264": "1",
|
||||
"275": "1",
|
||||
"276": "1",
|
||||
"284": "1",
|
||||
"285": "1",
|
||||
"352": "1",
|
||||
"359": "1",
|
||||
"377": "1",
|
||||
"402": "1",
|
||||
"425": "1",
|
||||
"441": "1",
|
||||
"497": "1",
|
||||
"538": "1",
|
||||
"540": "1",
|
||||
"552": "1",
|
||||
"566": "1",
|
||||
"601": "1",
|
||||
"639": "1",
|
||||
"651": "1",
|
||||
"668": "1",
|
||||
"706": "1",
|
||||
"862": "1",
|
||||
"863": "1",
|
||||
"913": "1",
|
||||
"922": "1",
|
||||
"1275": "1",
|
||||
|
||||
"261": "2",
|
||||
"296": "2",
|
||||
"310": "2",
|
||||
"319": "2",
|
||||
"321": "2",
|
||||
"322": "2",
|
||||
"323": "2",
|
||||
"324": "2",
|
||||
"325": "2",
|
||||
"326": "2",
|
||||
"327": "2",
|
||||
"328": "2",
|
||||
"329": "2",
|
||||
"330": "2",
|
||||
"331": "2",
|
||||
"335": "2",
|
||||
"336": "2",
|
||||
"337": "2",
|
||||
"338": "2",
|
||||
"340": "2",
|
||||
"347": "2",
|
||||
"523": "2",
|
||||
"720": "2",
|
||||
"757": "2",
|
||||
"759": "2",
|
||||
"760": "2",
|
||||
"780": "2",
|
||||
"818": "2",
|
||||
"916": "2",
|
||||
|
||||
"20": "3",
|
||||
"74": "3",
|
||||
"75": "3",
|
||||
"77": "3",
|
||||
"78": "3",
|
||||
"79": "3",
|
||||
"80": "3",
|
||||
"83": "3",
|
||||
"87": "3",
|
||||
"88": "3",
|
||||
"89": "3",
|
||||
"90": "3",
|
||||
"91": "3",
|
||||
"93": "3",
|
||||
"94": "3",
|
||||
"95": "3",
|
||||
"96": "3",
|
||||
"97": "3",
|
||||
"98": "3",
|
||||
"99": "3",
|
||||
"100": "3",
|
||||
"113": "3",
|
||||
"116": "3",
|
||||
"138": "3",
|
||||
"184": "3",
|
||||
"470": "3",
|
||||
"471": "3",
|
||||
"564": "3",
|
||||
"610": "3",
|
||||
"643": "3",
|
||||
"644": "3",
|
||||
"652": "3",
|
||||
"917": "3",
|
||||
|
||||
"73": "4",
|
||||
"183": "4",
|
||||
"209": "4",
|
||||
"213": "4",
|
||||
"235": "4",
|
||||
"256": "4",
|
||||
"257": "4",
|
||||
"266": "4",
|
||||
"269": "4",
|
||||
"280": "4",
|
||||
"311": "4",
|
||||
"312": "4",
|
||||
"313": "4",
|
||||
"316": "4",
|
||||
"419": "4",
|
||||
"430": "4",
|
||||
"434": "4",
|
||||
"444": "4",
|
||||
"451": "4",
|
||||
"472": "4",
|
||||
"501": "4",
|
||||
"522": "4",
|
||||
"525": "4",
|
||||
"539": "4",
|
||||
"579": "4",
|
||||
"598": "4",
|
||||
"602": "4",
|
||||
"642": "4",
|
||||
"646": "4",
|
||||
"650": "4",
|
||||
"653": "4",
|
||||
"656": "4",
|
||||
"657": "4",
|
||||
"799": "4",
|
||||
"807": "4",
|
||||
"840": "4",
|
||||
"841": "4",
|
||||
"927": "4",
|
||||
"1021": "4",
|
||||
"1173": "4",
|
||||
|
||||
"2": "5",
|
||||
"11": "5",
|
||||
"13": "5",
|
||||
"15": "5",
|
||||
"16": "5",
|
||||
"260": "5",
|
||||
"315": "5",
|
||||
"520": "5",
|
||||
"526": "5",
|
||||
"537": "5",
|
||||
"541": "5",
|
||||
"547": "5",
|
||||
"611": "5",
|
||||
"614": "5",
|
||||
"756": "5",
|
||||
"776": "5",
|
||||
"942": "5",
|
||||
"1004": "5",
|
||||
"1032": "5",
|
||||
"1174": "5",
|
||||
|
||||
"937": "6",
|
||||
"1035": "6",
|
||||
"1104": "6",
|
||||
|
||||
"255": "7",
|
||||
"259": "7",
|
||||
"287": "7",
|
||||
"288": "7",
|
||||
"290": "7",
|
||||
"294": "7",
|
||||
"295": "7",
|
||||
"297": "7",
|
||||
"300": "7",
|
||||
"302": "7",
|
||||
"304": "7",
|
||||
"306": "7",
|
||||
"307": "7",
|
||||
"346": "7",
|
||||
"384": "7",
|
||||
"521": "7",
|
||||
"613": "7",
|
||||
"620": "7",
|
||||
"640": "7",
|
||||
"798": "7",
|
||||
"940": "7",
|
||||
"1216": "7",
|
||||
|
||||
"345": "8",
|
||||
"353": "8",
|
||||
"426": "8",
|
||||
"494": "8",
|
||||
"502": "8",
|
||||
"565": "8",
|
||||
"784": "8",
|
||||
"829": "8",
|
||||
"830": "8",
|
||||
"915": "8",
|
||||
|
||||
"117": "9",
|
||||
"223": "9",
|
||||
"532": "9",
|
||||
"778": "9",
|
||||
|
||||
"918": "10",
|
||||
}
|
||||
|
||||
// OwaspTopTen2017GitHubURLJa has GitHub links
|
||||
var OwaspTopTen2017GitHubURLJa = map[string]string{
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa1-injection.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa2-broken-authentication.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa3-sensitive-data-disclosure.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa4-xxe.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa5-broken-access-control.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa6-security-misconfiguration.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa7-xss.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa8-insecure-deserialization.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa9-known-vulns.md<Paste>",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xaa-logging-detection-response.md",
|
||||
// OwaspTopTenURLsEn has GitHub links
|
||||
var OwaspTopTenURLsEn = map[string]map[string]string{
|
||||
"2017": {
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa1-injection.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa2-broken-authentication.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa3-sensitive-data-disclosure.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa4-xxe.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa5-broken-access-control.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa6-security-misconfiguration.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa7-xss.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa8-insecure-deserialization.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2017/en/0xa9-known-vulns.md",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2017/en/0xaa-logging-detection-response.md",
|
||||
},
|
||||
"2021": {
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2021/docs/A01_2021-Broken_Access_Control.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2021/docs/A02_2021-Cryptographic_Failures.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2021/docs/A03_2021-Injection.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2021/docs/A04_2021-Insecure_Design.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2021/docs/A05_2021-Security_Misconfiguration.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2021/docs/A06_2021-Vulnerable_and_Outdated_Components.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2021/docs/A07_2021-Identification_and_Authentication_Failures.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2021/docs/A08_2021-Software_and_Data_Integrity_Failures.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2021/docs/A09_2021-Security_Logging_and_Monitoring_Failures.md",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2021/docs/A10_2021-Server-Side_Request_Forgery_(SSRF).md",
|
||||
},
|
||||
}
|
||||
|
||||
// OwaspTopTenURLsJa has GitHub links
|
||||
var OwaspTopTenURLsJa = map[string]map[string]string{
|
||||
"2017": {
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa1-injection.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa2-broken-authentication.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa3-sensitive-data-disclosure.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa4-xxe.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa5-broken-access-control.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa6-security-misconfiguration.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa7-xss.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa8-insecure-deserialization.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xa9-known-vulns.md",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2017/ja/0xaa-logging-detection-response.md",
|
||||
},
|
||||
"2021": {
|
||||
"1": "https://github.com/OWASP/Top10/blob/master/2021/docs/A01_2021-Broken_Access_Control.ja.md",
|
||||
"2": "https://github.com/OWASP/Top10/blob/master/2021/docs/A02_2021-Cryptographic_Failures.ja.md",
|
||||
"3": "https://github.com/OWASP/Top10/blob/master/2021/docs/A03_2021-Injection.ja.md",
|
||||
"4": "https://github.com/OWASP/Top10/blob/master/2021/docs/A04_2021-Insecure_Design.ja.md",
|
||||
"5": "https://github.com/OWASP/Top10/blob/master/2021/docs/A05_2021-Security_Misconfiguration.ja.md",
|
||||
"6": "https://github.com/OWASP/Top10/blob/master/2021/docs/A06_2021-Vulnerable_and_Outdated_Components.ja.md",
|
||||
"7": "https://github.com/OWASP/Top10/blob/master/2021/docs/A07_2021-Identification_and_Authentication_Failures.ja.md",
|
||||
"8": "https://github.com/OWASP/Top10/blob/master/2021/docs/A08_2021-Software_and_Data_Integrity_Failures.ja.md",
|
||||
"9": "https://github.com/OWASP/Top10/blob/master/2021/docs/A09_2021-Security_Logging_and_Monitoring_Failures.ja.md",
|
||||
"10": "https://github.com/OWASP/Top10/blob/master/2021/docs/A10_2021-Server-Side_Request_Forgery_(SSRF).ja.md",
|
||||
},
|
||||
}
|
||||
|
||||
74
cwe/sans.go
74
cwe/sans.go
@@ -1,7 +1,41 @@
|
||||
package cwe
|
||||
|
||||
// SansTopTwentyfive has CWE-ID in CWE/SANS Top 25
|
||||
var SansTopTwentyfive = map[string]string{
|
||||
// SansTopTwentyfives has CWE-ID in CWE/SANS Top 25
|
||||
var SansTopTwentyfives = map[string]map[string]string{
|
||||
"2010": sansTopTwentyfive2010,
|
||||
"2011": sansTopTwentyfive2011,
|
||||
"latest": sansTopTwentyfiveLatest,
|
||||
}
|
||||
|
||||
var sansTopTwentyfive2010 = map[string]string{
|
||||
"79": "1",
|
||||
"89": "2",
|
||||
"120": "3",
|
||||
"352": "4",
|
||||
"285": "5",
|
||||
"807": "6",
|
||||
"22": "7",
|
||||
"434": "8",
|
||||
"78": "9",
|
||||
"311": "10",
|
||||
"798": "11",
|
||||
"805": "12",
|
||||
"98": "13",
|
||||
"129": "14",
|
||||
"754": "15",
|
||||
"209": "16",
|
||||
"190": "17",
|
||||
"131": "18",
|
||||
"306": "19",
|
||||
"494": "20",
|
||||
"732": "21",
|
||||
"770": "22",
|
||||
"601": "23",
|
||||
"327": "24",
|
||||
"362": "25",
|
||||
}
|
||||
|
||||
var sansTopTwentyfive2011 = map[string]string{
|
||||
"89": "1",
|
||||
"78": "2",
|
||||
"120": "3",
|
||||
@@ -29,5 +63,37 @@ var SansTopTwentyfive = map[string]string{
|
||||
"759": "25",
|
||||
}
|
||||
|
||||
// SansTopTwentyfiveURL is a URL of sans 25
|
||||
var SansTopTwentyfiveURL = "https://www.sans.org/top25-software-errors/"
|
||||
var sansTopTwentyfiveLatest = map[string]string{
|
||||
"119": "1",
|
||||
"79": "2",
|
||||
"20": "3",
|
||||
"200": "4",
|
||||
"125": "5",
|
||||
"89": "6",
|
||||
"416": "7",
|
||||
"190": "8",
|
||||
"352": "9",
|
||||
"22": "10",
|
||||
"78": "11",
|
||||
"787": "12",
|
||||
"287": "13",
|
||||
"476": "14",
|
||||
"732": "15",
|
||||
"434": "16",
|
||||
"611": "17",
|
||||
"94": "18",
|
||||
"798": "19",
|
||||
"400": "20",
|
||||
"772": "21",
|
||||
"426": "22",
|
||||
"502": "23",
|
||||
"269": "24",
|
||||
"295": "25",
|
||||
}
|
||||
|
||||
// SansTopTwentyfiveURLs has CWE/SANS Top25 links
|
||||
var SansTopTwentyfiveURLs = map[string]string{
|
||||
"2010": "https://cwe.mitre.org/top25/archive/2010/2010_cwe_sans_top25.html",
|
||||
"2011": "https://cwe.mitre.org/top25/archive/2011/2011_cwe_sans_top25.html",
|
||||
"latest": "https://www.sans.org/top25-software-errors/",
|
||||
}
|
||||
|
||||
222
detector/cti.go
Normal file
222
detector/cti.go
Normal file
@@ -0,0 +1,222 @@
|
||||
//go:build !scanner
|
||||
// +build !scanner
|
||||
|
||||
package detector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"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"
|
||||
ctidb "github.com/vulsio/go-cti/db"
|
||||
ctilog "github.com/vulsio/go-cti/utils"
|
||||
)
|
||||
|
||||
// goCTIDBClient is a DB Driver
|
||||
type goCTIDBClient struct {
|
||||
driver ctidb.DB
|
||||
baseURL string
|
||||
}
|
||||
|
||||
// closeDB close a DB connection
|
||||
func (client goCTIDBClient) closeDB() error {
|
||||
if client.driver == nil {
|
||||
return nil
|
||||
}
|
||||
return client.driver.CloseDB()
|
||||
}
|
||||
|
||||
func newGoCTIDBClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goCTIDBClient, error) {
|
||||
if err := ctilog.SetLogger(o.LogToFile, o.LogDir, o.Debug, o.LogJSON); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to set go-cti logger. err: %w", err)
|
||||
}
|
||||
|
||||
db, err := newCTIDB(cnf)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to newCTIDB. err: %w", err)
|
||||
}
|
||||
return &goCTIDBClient{driver: db, baseURL: cnf.GetURL()}, nil
|
||||
}
|
||||
|
||||
// FillWithCTI :
|
||||
func FillWithCTI(r *models.ScanResult, cnf config.CtiConf, logOpts logging.LogOpts) error {
|
||||
client, err := newGoCTIDBClient(&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)
|
||||
}
|
||||
}()
|
||||
|
||||
nCti := 0
|
||||
if client.driver == nil {
|
||||
var cveIDs []string
|
||||
for cveID := range r.ScannedCves {
|
||||
cveIDs = append(cveIDs, cveID)
|
||||
}
|
||||
prefix, err := util.URLPathJoin(client.baseURL, "cves")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
responses, err := getCTIsViaHTTP(cveIDs, prefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, res := range responses {
|
||||
var techniqueIDs []string
|
||||
if err := json.Unmarshal([]byte(res.json), &techniqueIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
v, ok := r.ScannedCves[res.request.cveID]
|
||||
if ok {
|
||||
v.Ctis = techniqueIDs
|
||||
nCti++
|
||||
}
|
||||
r.ScannedCves[res.request.cveID] = v
|
||||
}
|
||||
} else {
|
||||
for cveID, vuln := range r.ScannedCves {
|
||||
if cveID == "" {
|
||||
continue
|
||||
}
|
||||
techniqueIDs, err := client.driver.GetTechniqueIDsByCveID(cveID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to get CTIs by CVE-ID. err: %w", err)
|
||||
}
|
||||
if len(techniqueIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
vuln.Ctis = techniqueIDs
|
||||
nCti++
|
||||
r.ScannedCves[cveID] = vuln
|
||||
}
|
||||
}
|
||||
|
||||
logging.Log.Infof("%s: Cyber Threat Intelligences are detected for %d CVEs", r.FormatServerName(), nCti)
|
||||
return nil
|
||||
}
|
||||
|
||||
type ctiResponse struct {
|
||||
request ctiRequest
|
||||
json string
|
||||
}
|
||||
|
||||
func getCTIsViaHTTP(cveIDs []string, urlPrefix string) (responses []ctiResponse, err error) {
|
||||
nReq := len(cveIDs)
|
||||
reqChan := make(chan ctiRequest, nReq)
|
||||
resChan := make(chan ctiResponse, nReq)
|
||||
errChan := make(chan error, nReq)
|
||||
defer close(reqChan)
|
||||
defer close(resChan)
|
||||
defer close(errChan)
|
||||
|
||||
go func() {
|
||||
for _, cveID := range cveIDs {
|
||||
reqChan <- ctiRequest{
|
||||
cveID: cveID,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for i := 0; i < nReq; i++ {
|
||||
tasks <- func() {
|
||||
req := <-reqChan
|
||||
url, err := util.URLPathJoin(
|
||||
urlPrefix,
|
||||
req.cveID,
|
||||
)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
logging.Log.Debugf("HTTP Request to %s", url)
|
||||
httpGetCTI(url, req, resChan, errChan)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.After(2 * 60 * time.Second)
|
||||
var errs []error
|
||||
for i := 0; i < nReq; i++ {
|
||||
select {
|
||||
case res := <-resChan:
|
||||
responses = append(responses, res)
|
||||
case err := <-errChan:
|
||||
errs = append(errs, err)
|
||||
case <-timeout:
|
||||
return nil, xerrors.New("Timeout Fetching CTI")
|
||||
}
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
return nil, xerrors.Errorf("Failed to fetch CTI. err: %w", errs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ctiRequest struct {
|
||||
cveID string
|
||||
}
|
||||
|
||||
func httpGetCTI(url string, req ctiRequest, resChan chan<- ctiResponse, errChan chan<- error) {
|
||||
var body string
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
count, retryMax := 0, 3
|
||||
f := func() (err error) {
|
||||
// resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
|
||||
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
|
||||
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
count++
|
||||
if count == retryMax {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
errChan <- xerrors.Errorf("HTTP Error %w", err)
|
||||
return
|
||||
}
|
||||
if count == retryMax {
|
||||
errChan <- xerrors.New("Retry count exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
resChan <- ctiResponse{
|
||||
request: req,
|
||||
json: body,
|
||||
}
|
||||
}
|
||||
|
||||
func newCTIDB(cnf config.VulnDictInterface) (ctidb.DB, error) {
|
||||
if cnf.IsFetchViaHTTP() {
|
||||
return nil, nil
|
||||
}
|
||||
path := cnf.GetURL()
|
||||
if cnf.GetType() == "sqlite3" {
|
||||
path = cnf.GetSQLite3Path()
|
||||
}
|
||||
driver, locked, err := ctidb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), ctidb.Option{})
|
||||
if err != nil {
|
||||
if locked {
|
||||
return nil, xerrors.Errorf("Failed to init cti DB. SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
|
||||
}
|
||||
return nil, xerrors.Errorf("Failed to init cti DB. DB Path: %s, err: %w", path, err)
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,22 +100,26 @@ 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)
|
||||
}
|
||||
|
||||
if err := FillWithCTI(&r, config.Conf.Cti, config.Conf.LogOpts); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to fill with Cyber Threat Intelligences: %w", err)
|
||||
}
|
||||
|
||||
FillCweDict(&r)
|
||||
|
||||
r.ReportedBy, _ = os.Hostname()
|
||||
@@ -143,7 +148,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 +210,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,11 +259,40 @@ 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 {
|
||||
switch r.Family {
|
||||
case constant.FreeBSD, constant.ServerTypePseudo:
|
||||
logging.Log.Infof("%s type. Skip OVAL and gost detection", r.Family)
|
||||
return false
|
||||
case constant.Windows:
|
||||
return true
|
||||
default:
|
||||
if r.ScannedVia == "trivy" {
|
||||
logging.Log.Infof("r.ScannedVia is trivy. Skip OVAL and gost detection")
|
||||
return false
|
||||
}
|
||||
|
||||
if r.Release == "" {
|
||||
logging.Log.Infof("r.Release is empty. Skip OVAL and gost detection")
|
||||
return false
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.GitHubManifests = models.DependencyGraphManifests{}
|
||||
for ownerRepo, setting := range githubConfs {
|
||||
ss := strings.Split(ownerRepo, "/")
|
||||
if len(ss) != 2 {
|
||||
@@ -281,6 +305,10 @@ func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHub
|
||||
}
|
||||
logging.Log.Infof("%s: %d CVEs detected with GHSA %s/%s",
|
||||
r.FormatServerName(), n, owner, repo)
|
||||
|
||||
if err = DetectGitHubDependencyGraph(r, owner, repo, setting.Token); err != nil {
|
||||
return xerrors.Errorf("Failed to access GitHub Dependency graph: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -308,7 +336,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 +344,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 +414,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, constant.Ubuntu:
|
||||
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 +459,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)
|
||||
@@ -444,19 +472,21 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
|
||||
|
||||
nCVEs, err := client.DetectCVEs(r, true)
|
||||
if err != nil {
|
||||
if r.Family == constant.Debian {
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Ubuntu, constant.Windows:
|
||||
return xerrors.Errorf("Failed to detect CVEs with gost: %w", err)
|
||||
default:
|
||||
return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
|
||||
}
|
||||
return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
|
||||
}
|
||||
|
||||
if r.Family == constant.Debian {
|
||||
logging.Log.Infof("%s: %d CVEs are detected with gost",
|
||||
r.FormatServerName(), nCVEs)
|
||||
} else {
|
||||
logging.Log.Infof("%s: %d unfixed CVEs are detected with gost",
|
||||
r.FormatServerName(), nCVEs)
|
||||
switch r.Family {
|
||||
case constant.Debian, constant.Ubuntu, constant.Windows:
|
||||
logging.Log.Infof("%s: %d CVEs are detected with gost", r.FormatServerName(), nCVEs)
|
||||
default:
|
||||
logging.Log.Infof("%s: %d unfixed CVEs are detected with gost", r.FormatServerName(), nCVEs)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -464,7 +494,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 +506,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 {
|
||||
@@ -551,17 +581,13 @@ func FillCweDict(r *models.ScanResult) {
|
||||
|
||||
dict := map[string]models.CweDictEntry{}
|
||||
for id := range uniqCweIDMap {
|
||||
entry := models.CweDictEntry{}
|
||||
entry := models.CweDictEntry{
|
||||
OwaspTopTens: map[string]string{},
|
||||
CweTopTwentyfives: map[string]string{},
|
||||
SansTopTwentyfives: map[string]string{},
|
||||
}
|
||||
if e, ok := cwe.CweDictEn[id]; ok {
|
||||
if rank, ok := cwe.OwaspTopTen2017[id]; ok {
|
||||
entry.OwaspTopTen2017 = rank
|
||||
}
|
||||
if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
|
||||
entry.CweTopTwentyfive2019 = rank
|
||||
}
|
||||
if rank, ok := cwe.SansTopTwentyfive[id]; ok {
|
||||
entry.SansTopTwentyfive = rank
|
||||
}
|
||||
fillCweRank(&entry, id)
|
||||
entry.En = &e
|
||||
} else {
|
||||
logging.Log.Debugf("CWE-ID %s is not found in English CWE Dict", id)
|
||||
@@ -570,23 +596,34 @@ func FillCweDict(r *models.ScanResult) {
|
||||
|
||||
if r.Lang == "ja" {
|
||||
if e, ok := cwe.CweDictJa[id]; ok {
|
||||
if rank, ok := cwe.OwaspTopTen2017[id]; ok {
|
||||
entry.OwaspTopTen2017 = rank
|
||||
}
|
||||
if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
|
||||
entry.CweTopTwentyfive2019 = rank
|
||||
}
|
||||
if rank, ok := cwe.SansTopTwentyfive[id]; ok {
|
||||
entry.SansTopTwentyfive = rank
|
||||
}
|
||||
fillCweRank(&entry, id)
|
||||
entry.Ja = &e
|
||||
} else {
|
||||
logging.Log.Debugf("CWE-ID %s is not found in Japanese CWE Dict", id)
|
||||
entry.Ja = &cwe.Cwe{CweID: id}
|
||||
}
|
||||
}
|
||||
|
||||
dict[id] = entry
|
||||
}
|
||||
r.CweDict = dict
|
||||
return
|
||||
}
|
||||
|
||||
func fillCweRank(entry *models.CweDictEntry, id string) {
|
||||
for year, ranks := range cwe.OwaspTopTens {
|
||||
if rank, ok := ranks[id]; ok {
|
||||
entry.OwaspTopTens[year] = rank
|
||||
}
|
||||
}
|
||||
for year, ranks := range cwe.CweTopTwentyfives {
|
||||
if rank, ok := ranks[id]; ok {
|
||||
entry.CweTopTwentyfives[year] = rank
|
||||
}
|
||||
}
|
||||
for year, ranks := range cwe.SansTopTwentyfives {
|
||||
if rank, ok := ranks[id]; ok {
|
||||
entry.SansTopTwentyfives[year] = rank
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -81,14 +109,20 @@ func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf) (nExploitCve
|
||||
// ConvertToModelsExploit converts exploit model to vuls model
|
||||
func ConvertToModelsExploit(es []exploitmodels.Exploit) (exploits []models.Exploit) {
|
||||
for _, e := range es {
|
||||
var documentURL, shellURL *string
|
||||
var documentURL, shellURL, paperURL, ghdbURL *string
|
||||
if e.OffensiveSecurity != nil {
|
||||
os := e.OffensiveSecurity
|
||||
if os.Document != nil {
|
||||
documentURL = &os.Document.DocumentURL
|
||||
documentURL = &os.Document.FileURL
|
||||
}
|
||||
if os.ShellCode != nil {
|
||||
shellURL = &os.ShellCode.ShellCodeURL
|
||||
shellURL = &os.ShellCode.FileURL
|
||||
}
|
||||
if os.Paper != nil {
|
||||
paperURL = &os.Paper.FileURL
|
||||
}
|
||||
if os.GHDB != nil {
|
||||
ghdbURL = &os.GHDB.Link
|
||||
}
|
||||
}
|
||||
exploit := models.Exploit{
|
||||
@@ -98,6 +132,8 @@ func ConvertToModelsExploit(es []exploitmodels.Exploit) (exploits []models.Explo
|
||||
Description: e.Description,
|
||||
DocumentURL: documentURL,
|
||||
ShellCodeURL: shellURL,
|
||||
PaperURL: paperURL,
|
||||
GHDBURL: ghdbURL,
|
||||
}
|
||||
exploits = append(exploits, exploit)
|
||||
}
|
||||
@@ -203,19 +239,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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -29,7 +29,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string,
|
||||
// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
|
||||
// Memo : https://developer.github.com/v4/explorer/
|
||||
const jsonfmt = `{"query":
|
||||
"query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}`
|
||||
"query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, states:[OPEN], %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } vulnerableManifestFilename vulnerableManifestPath vulnerableRequirements securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}`
|
||||
after := ""
|
||||
|
||||
for {
|
||||
@@ -57,7 +57,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string,
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -79,11 +79,15 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string,
|
||||
continue
|
||||
}
|
||||
|
||||
pkgName := fmt.Sprintf("%s %s",
|
||||
alerts.Data.Repository.URL, v.Node.SecurityVulnerability.Package.Name)
|
||||
|
||||
m := models.GitHubSecurityAlert{
|
||||
PackageName: pkgName,
|
||||
Repository: alerts.Data.Repository.URL,
|
||||
Package: models.GSAVulnerablePackage{
|
||||
Name: v.Node.SecurityVulnerability.Package.Name,
|
||||
Ecosystem: v.Node.SecurityVulnerability.Package.Ecosystem,
|
||||
ManifestFilename: v.Node.VulnerableManifestFilename,
|
||||
ManifestPath: v.Node.VulnerableManifestPath,
|
||||
Requirements: v.Node.VulnerableRequirements,
|
||||
},
|
||||
FixedIn: v.Node.SecurityVulnerability.FirstPatchedVersion.Identifier,
|
||||
AffectedRange: v.Node.SecurityVulnerability.VulnerableVersionRange,
|
||||
Dismissed: len(v.Node.DismissReason) != 0,
|
||||
@@ -148,7 +152,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string,
|
||||
return nCVEs, err
|
||||
}
|
||||
|
||||
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
|
||||
// SecurityAlerts has detected CVE-IDs, PackageNames, Refs
|
||||
type SecurityAlerts struct {
|
||||
Data struct {
|
||||
Repository struct {
|
||||
@@ -175,7 +179,10 @@ type SecurityAlerts struct {
|
||||
Identifier string `json:"identifier"`
|
||||
} `json:"firstPatchedVersion"`
|
||||
} `json:"securityVulnerability"`
|
||||
SecurityAdvisory struct {
|
||||
VulnerableManifestFilename string `json:"vulnerableManifestFilename"`
|
||||
VulnerableManifestPath string `json:"vulnerableManifestPath"`
|
||||
VulnerableRequirements string `json:"vulnerableRequirements"`
|
||||
SecurityAdvisory struct {
|
||||
Description string `json:"description"`
|
||||
GhsaID string `json:"ghsaId"`
|
||||
Permalink string `json:"permalink"`
|
||||
@@ -199,3 +206,138 @@ type SecurityAlerts struct {
|
||||
} `json:"repository"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// DetectGitHubDependencyGraph access to owner/repo on GitHub and fetch dependency graph of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
|
||||
// https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph
|
||||
func DetectGitHubDependencyGraph(r *models.ScanResult, owner, repo, token string) (err error) {
|
||||
src := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
//TODO Proxy
|
||||
httpClient := oauth2.NewClient(context.Background(), src)
|
||||
|
||||
return fetchDependencyGraph(r, httpClient, owner, repo, "", "")
|
||||
}
|
||||
|
||||
// recursive function
|
||||
func fetchDependencyGraph(r *models.ScanResult, httpClient *http.Client, owner, repo, after, dependenciesAfter string) (err error) {
|
||||
const queryFmt = `{"query":
|
||||
"query { repository(owner:\"%s\", name:\"%s\") { url dependencyGraphManifests(first: %d, withDependencies: true%s) { pageInfo { endCursor hasNextPage } edges { node { blobPath filename repository { url } parseable exceedsMaxSize dependenciesCount dependencies%s { pageInfo { endCursor hasNextPage } edges { node { packageName packageManager repository { url } requirements hasDependencies } } } } } } } }"}`
|
||||
|
||||
queryStr := fmt.Sprintf(queryFmt, owner, repo, 100, after, dependenciesAfter)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
"https://api.github.com/graphql",
|
||||
bytes.NewBuffer([]byte(queryStr)),
|
||||
)
|
||||
defer cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/graphql/overview/schema-previews#access-to-a-repository-s-dependency-graph-preview
|
||||
// TODO remove this header if it is no longer preview status in the future.
|
||||
req.Header.Set("Accept", "application/vnd.github.hawkgirl-preview+json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
graph := DependencyGraph{}
|
||||
if err := json.Unmarshal(body, &graph); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if graph.Data.Repository.URL == "" {
|
||||
return errof.New(errof.ErrFailedToAccessGithubAPI,
|
||||
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)))
|
||||
}
|
||||
|
||||
dependenciesAfter = ""
|
||||
for _, m := range graph.Data.Repository.DependencyGraphManifests.Edges {
|
||||
manifest, ok := r.GitHubManifests[m.Node.BlobPath]
|
||||
if !ok {
|
||||
manifest = models.DependencyGraphManifest{
|
||||
BlobPath: m.Node.BlobPath,
|
||||
Filename: m.Node.Filename,
|
||||
Repository: m.Node.Repository.URL,
|
||||
Dependencies: []models.Dependency{},
|
||||
}
|
||||
}
|
||||
for _, d := range m.Node.Dependencies.Edges {
|
||||
manifest.Dependencies = append(manifest.Dependencies, models.Dependency{
|
||||
PackageName: d.Node.PackageName,
|
||||
PackageManager: d.Node.PackageManager,
|
||||
Repository: d.Node.Repository.URL,
|
||||
Requirements: d.Node.Requirements,
|
||||
})
|
||||
}
|
||||
r.GitHubManifests[m.Node.BlobPath] = manifest
|
||||
|
||||
if m.Node.Dependencies.PageInfo.HasNextPage {
|
||||
dependenciesAfter = fmt.Sprintf(`(after: \"%s\")`, m.Node.Dependencies.PageInfo.EndCursor)
|
||||
}
|
||||
}
|
||||
if dependenciesAfter != "" {
|
||||
return fetchDependencyGraph(r, httpClient, owner, repo, after, dependenciesAfter)
|
||||
}
|
||||
|
||||
if graph.Data.Repository.DependencyGraphManifests.PageInfo.HasNextPage {
|
||||
after = fmt.Sprintf(`, after: \"%s\"`, graph.Data.Repository.DependencyGraphManifests.PageInfo.EndCursor)
|
||||
return fetchDependencyGraph(r, httpClient, owner, repo, after, dependenciesAfter)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DependencyGraph is a GitHub API response
|
||||
type DependencyGraph struct {
|
||||
Data struct {
|
||||
Repository struct {
|
||||
URL string `json:"url"`
|
||||
DependencyGraphManifests struct {
|
||||
PageInfo struct {
|
||||
EndCursor string `json:"endCursor"`
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
} `json:"pageInfo"`
|
||||
Edges []struct {
|
||||
Node struct {
|
||||
BlobPath string `json:"blobPath"`
|
||||
Filename string `json:"filename"`
|
||||
Repository struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
Parseable bool `json:"parseable"`
|
||||
ExceedsMaxSize bool `json:"exceedsMaxSize"`
|
||||
DependenciesCount int `json:"dependenciesCount"`
|
||||
Dependencies struct {
|
||||
PageInfo struct {
|
||||
EndCursor string `json:"endCursor"`
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
} `json:"pageInfo"`
|
||||
Edges []struct {
|
||||
Node struct {
|
||||
PackageName string `json:"packageName"`
|
||||
PackageManager string `json:"packageManager"`
|
||||
Repository struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
Requirements string `json:"requirements"`
|
||||
HasDependencies bool `json:"hasDependencies"`
|
||||
} `json:"node"`
|
||||
} `json:"edges"`
|
||||
} `json:"dependencies"`
|
||||
} `json:"node"`
|
||||
} `json:"edges"`
|
||||
} `json:"dependencyGraphManifests"`
|
||||
} `json:"repository"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
@@ -9,25 +9,63 @@ 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)
|
||||
}
|
||||
}()
|
||||
|
||||
nKEV := 0
|
||||
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
|
||||
}
|
||||
@@ -53,27 +91,16 @@ func FillWithKEVuln(r *models.ScanResult, cnf config.KEVulnConf) error {
|
||||
v, ok := r.ScannedCves[res.request.cveID]
|
||||
if ok {
|
||||
v.AlertDict.CISA = alerts
|
||||
nKEV++
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -91,9 +118,12 @@ func FillWithKEVuln(r *models.ScanResult, cnf config.KEVulnConf) error {
|
||||
}
|
||||
|
||||
vuln.AlertDict.CISA = alerts
|
||||
nKEV++
|
||||
r.ScannedCves[cveID] = vuln
|
||||
}
|
||||
}
|
||||
|
||||
logging.Log.Infof("%s: Known Exploited Vulnerabilities are detected for %d CVEs", r.FormatServerName(), nKEV)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -196,19 +226,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
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func DetectLibsCves(r *models.ScanResult, cacheDir string, noProgress bool) (err
|
||||
}
|
||||
|
||||
func downloadDB(appVersion, cacheDir string, quiet, skipUpdate bool) error {
|
||||
client := db.NewClient(cacheDir, quiet)
|
||||
client := db.NewClient(cacheDir, quiet, false)
|
||||
ctx := context.Background()
|
||||
needsUpdate, err := client.NeedsUpdate(appVersion, skipUpdate)
|
||||
if err != nil {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,11 +6,9 @@ package detector
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -26,12 +24,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 +123,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 +143,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
|
||||
@@ -188,11 +181,7 @@ func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
}
|
||||
|
||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
cTypes := []models.CveContentType{
|
||||
models.Nvd,
|
||||
models.Jvn,
|
||||
models.NewCveContentType(current.Family),
|
||||
}
|
||||
cTypes := append([]models.CveContentType{models.Nvd, models.Jvn}, models.GetCveContentTypes(current.Family)...)
|
||||
|
||||
prevLastModified := map[models.CveContentType][]time.Time{}
|
||||
preVinfo, ok := previous.ScannedCves[cveID]
|
||||
@@ -230,25 +219,23 @@ func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// jsonDirPattern is file name pattern of JSON directory
|
||||
// 2016-11-16T10:43:28+09:00
|
||||
// 2016-11-16T10:43:28Z
|
||||
var jsonDirPattern = regexp.MustCompile(
|
||||
`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
|
||||
|
||||
// ListValidJSONDirs returns valid json directory as array
|
||||
// Returned array is sorted so that recent directories are at the head
|
||||
func ListValidJSONDirs(resultsDir string) (dirs []string, err error) {
|
||||
var dirInfo []os.FileInfo
|
||||
if dirInfo, err = ioutil.ReadDir(resultsDir); err != nil {
|
||||
err = xerrors.Errorf("Failed to read %s: %w",
|
||||
config.Conf.ResultsDir, err)
|
||||
return
|
||||
dirInfo, err := os.ReadDir(resultsDir)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", config.Conf.ResultsDir, err)
|
||||
}
|
||||
for _, d := range dirInfo {
|
||||
if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
|
||||
jsonDir := filepath.Join(resultsDir, d.Name())
|
||||
dirs = append(dirs, jsonDir)
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, layout := range []string{"2006-01-02T15:04:05Z", "2006-01-02T15:04:05-07:00", "2006-01-02T15-04-05-0700"} {
|
||||
if _, err := time.Parse(layout, d.Name()); err == nil {
|
||||
dirs = append(dirs, filepath.Join(resultsDir, d.Name()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(dirs, func(i, j int) bool {
|
||||
@@ -263,7 +250,7 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
if data, err = ioutil.ReadFile(jsonFile); err != nil {
|
||||
if data, err = os.ReadFile(jsonFile); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", jsonFile, err)
|
||||
}
|
||||
result := &models.ScanResult{}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
//WpCveInfos is for wpscan json
|
||||
// WpCveInfos is for wpscan json
|
||||
type WpCveInfos struct {
|
||||
ReleaseDate string `json:"release_date"`
|
||||
ChangelogURL string `json:"changelog_url"`
|
||||
@@ -33,7 +33,7 @@ type WpCveInfos struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
//WpCveInfo is for wpscan json
|
||||
// WpCveInfo is for wpscan json
|
||||
type WpCveInfo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
@@ -44,7 +44,7 @@ type WpCveInfo struct {
|
||||
FixedIn string `json:"fixed_in"`
|
||||
}
|
||||
|
||||
//References is for wpscan json
|
||||
// References is for wpscan json
|
||||
type References struct {
|
||||
URL []string `json:"url"`
|
||||
Cve []string `json:"cve"`
|
||||
@@ -242,7 +242,7 @@ func httpRequest(url, token string) (string, error) {
|
||||
return "", errof.New(errof.ErrFailedToAccessWpScan,
|
||||
fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", errof.New(errof.ErrFailedToAccessWpScan,
|
||||
fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
|
||||
|
||||
240
go.mod
240
go.mod
@@ -1,172 +1,186 @@
|
||||
module github.com/future-architect/vuls
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
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 v66.0.0+incompatible
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.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/go-dep-parser v0.0.0-20221114145626-35ef808901e8
|
||||
github.com/aquasecurity/trivy v0.35.0
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20220627104749-930461748b63
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/aws/aws-sdk-go v1.42.30
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/briandowns/spinner v1.16.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.136
|
||||
github.com/c-robinson/iplib v1.0.3
|
||||
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/google/uuid v1.3.0
|
||||
github.com/gosnmp/gosnmp v1.35.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-uuid v1.0.3
|
||||
github.com/hashicorp/go-version v1.6.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
|
||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
|
||||
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
|
||||
github.com/nlopes/slack v0.6.0
|
||||
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
|
||||
github.com/parnurzeal/gorequest v0.2.16
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
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/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-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
|
||||
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
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/vulsio/go-cti v0.0.2
|
||||
github.com/vulsio/go-cve-dictionary v0.8.3
|
||||
github.com/vulsio/go-exploitdb v0.4.4
|
||||
github.com/vulsio/go-kev v0.1.1
|
||||
github.com/vulsio/go-msfdb v0.2.1
|
||||
github.com/vulsio/gost v0.4.2
|
||||
github.com/vulsio/goval-dictionary v0.8.2
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
|
||||
golang.org/x/oauth2 v0.1.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/text v0.7.0
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.99.0 // indirect
|
||||
cloud.google.com/go/storage v1.14.0 // indirect
|
||||
cloud.google.com/go v0.105.0 // indirect
|
||||
cloud.google.com/go/compute v1.14.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v0.8.0 // indirect
|
||||
cloud.google.com/go/storage v1.27.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/adal v0.9.18 // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.21 // 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/PuerkitoBio/goquery v1.8.1 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce // indirect
|
||||
github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 // indirect
|
||||
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/briandowns/spinner v1.21.0 // indirect
|
||||
github.com/caarlos0/env/v6 v6.10.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cheggaaa/pb/v3 v3.1.0 // 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/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.12+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/dnaeon/go-vcr v1.2.0 // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v20.10.20+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.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-cmp v0.5.9 // indirect
|
||||
github.com/google/go-containerregistry v0.12.0 // indirect
|
||||
github.com/google/licenseclassifier/v2 v2.0.0-pre6 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // 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.7.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 // 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/pgio v1.0.0 // indirect
|
||||
github.com/inconshreveable/log15 v2.16.0+incompatible // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.2.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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.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/jtolds/gls v4.20.0+incompatible // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/liamg/jfather v0.0.7 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // 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/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/masahiro331/go-xfs-filesystem v0.0.0-20221127135739-051c25f1becd // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/nsf/termbox-go v1.1.1 // 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/pkg/errors v0.9.1 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // 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/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
github.com/samber/lo v1.33.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/smartystreets/assertions v1.13.0 // indirect
|
||||
github.com/spdx/tools-golang v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.9.4 // indirect
|
||||
github.com/spf13/cast v1.5.0 // 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/stretchr/objx v0.3.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // 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/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
|
||||
github.com/spf13/viper v1.15.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/goleak v1.1.12 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/term v0.5.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/api v0.107.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
|
||||
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
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.52.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/mysql v1.4.7 // indirect
|
||||
gorm.io/driver/postgres v1.4.8 // indirect
|
||||
gorm.io/driver/sqlite v1.4.4 // indirect
|
||||
gorm.io/gorm v1.24.5 // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
|
||||
// See https://github.com/moby/moby/issues/42939#issuecomment-1114255529
|
||||
replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible
|
||||
|
||||
@@ -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{}
|
||||
|
||||
94
gost/gost.go
94
gost/gost.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -4,10 +4,23 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
gostmodels "github.com/vulsio/gost/models"
|
||||
)
|
||||
|
||||
@@ -18,62 +31,297 @@ 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 {
|
||||
return 0, nil
|
||||
var applied, unapplied []string
|
||||
if r.WindowsKB != nil {
|
||||
applied = r.WindowsKB.Applied
|
||||
unapplied = r.WindowsKB.Unapplied
|
||||
}
|
||||
cveIDs := []string{}
|
||||
for cveID := range r.ScannedCves {
|
||||
cveIDs = append(cveIDs, cveID)
|
||||
if ms.driver == nil {
|
||||
u, err := util.URLPathJoin(ms.baseURL, "microsoft", "kbs")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
|
||||
content := map[string]interface{}{"applied": applied, "unapplied": unapplied}
|
||||
var body []byte
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
f := func() error {
|
||||
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
|
||||
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return 0, xerrors.Errorf("HTTP Error: %w", err)
|
||||
}
|
||||
|
||||
var r struct {
|
||||
Applied []string `json:"applied"`
|
||||
Unapplied []string `json:"unapplied"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &r); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
applied = r.Applied
|
||||
unapplied = r.Unapplied
|
||||
} else {
|
||||
applied, unapplied, err = ms.driver.GetExpandKB(applied, unapplied)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
|
||||
}
|
||||
}
|
||||
msCves, err := ms.DBDriver.DB.GetMicrosoftMulti(cveIDs)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
|
||||
var products []string
|
||||
if ms.driver == nil {
|
||||
u, err := util.URLPathJoin(ms.baseURL, "microsoft", "products")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
|
||||
content := map[string]interface{}{"release": r.Release, "kbs": append(applied, unapplied...)}
|
||||
var body []byte
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
f := func() error {
|
||||
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
|
||||
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return 0, xerrors.Errorf("HTTP Error: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &products); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
} else {
|
||||
ps, err := ms.driver.GetRelatedProducts(r.Release, append(applied, unapplied...))
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
|
||||
}
|
||||
products = ps
|
||||
}
|
||||
for cveID, msCve := range msCves {
|
||||
if _, ok := r.ScannedCves[cveID]; !ok {
|
||||
|
||||
m := map[string]struct{}{}
|
||||
for _, p := range products {
|
||||
m[p] = struct{}{}
|
||||
}
|
||||
for _, n := range []string{"Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release)} {
|
||||
delete(m, n)
|
||||
}
|
||||
filtered := []string{r.Release}
|
||||
for _, p := range r.Packages {
|
||||
switch p.Name {
|
||||
case "Microsoft Edge":
|
||||
if ss := strings.Split(p.Version, "."); len(ss) > 0 {
|
||||
v, err := strconv.ParseInt(ss[0], 10, 8)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if v > 44 {
|
||||
filtered = append(filtered, "Microsoft Edge (Chromium-based)", fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (Chromium-based) in IE Mode on %s", r.Release))
|
||||
} else {
|
||||
filtered = append(filtered, fmt.Sprintf("Microsoft Edge on %s", r.Release), fmt.Sprintf("Microsoft Edge (EdgeHTML-based) on %s", r.Release))
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
filtered = unique(append(filtered, maps.Keys(m)...))
|
||||
|
||||
var cves map[string]gostmodels.MicrosoftCVE
|
||||
if ms.driver == nil {
|
||||
u, err := util.URLPathJoin(ms.baseURL, "microsoft", "filtered-cves")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
|
||||
content := map[string]interface{}{"products": filtered, "kbs": append(applied, unapplied...)}
|
||||
var body []byte
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
f := func() error {
|
||||
resp, body, errs = gorequest.New().Timeout(10 * time.Second).Post(u).SendStruct(content).Type("json").EndBytes()
|
||||
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", u, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return 0, xerrors.Errorf("HTTP Error: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &cves); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
} else {
|
||||
cves, err = ms.driver.GetFilteredCvesMicrosoft(filtered, append(applied, unapplied...))
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to detect CVEs. err: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for cveID, cve := range cves {
|
||||
var ps []gostmodels.MicrosoftProduct
|
||||
for _, p := range cve.Products {
|
||||
if len(p.KBs) == 0 {
|
||||
ps = append(ps, p)
|
||||
continue
|
||||
}
|
||||
|
||||
var kbs []gostmodels.MicrosoftKB
|
||||
for _, kb := range p.KBs {
|
||||
if _, err := strconv.Atoi(kb.Article); err != nil {
|
||||
switch {
|
||||
case strings.HasPrefix(p.Name, "Microsoft Edge"):
|
||||
p, ok := r.Packages["Microsoft Edge"]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
if kb.FixedBuild == "" {
|
||||
kbs = append(kbs, kb)
|
||||
break
|
||||
}
|
||||
|
||||
vera, err := version.NewVersion(p.Version)
|
||||
if err != nil {
|
||||
kbs = append(kbs, kb)
|
||||
break
|
||||
}
|
||||
verb, err := version.NewVersion(kb.FixedBuild)
|
||||
if err != nil {
|
||||
kbs = append(kbs, kb)
|
||||
break
|
||||
}
|
||||
if vera.LessThan(verb) {
|
||||
kbs = append(kbs, kb)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if slices.Contains(applied, kb.Article) {
|
||||
kbs = []gostmodels.MicrosoftKB{}
|
||||
break
|
||||
}
|
||||
if slices.Contains(unapplied, kb.Article) {
|
||||
kbs = append(kbs, kb)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(kbs) > 0 {
|
||||
p.KBs = kbs
|
||||
ps = append(ps, p)
|
||||
}
|
||||
}
|
||||
cve.Products = ps
|
||||
if len(cve.Products) == 0 {
|
||||
continue
|
||||
}
|
||||
cveCont, mitigations := ms.ConvertToModel(&msCve)
|
||||
v, _ := r.ScannedCves[cveID]
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.CveContents{}
|
||||
nCVEs++
|
||||
|
||||
cveCont, mitigations := ms.ConvertToModel(&cve)
|
||||
uniqKB := map[string]struct{}{}
|
||||
var stats models.PackageFixStatuses
|
||||
for _, p := range cve.Products {
|
||||
for _, kb := range p.KBs {
|
||||
if _, err := strconv.Atoi(kb.Article); err != nil {
|
||||
switch {
|
||||
case strings.HasPrefix(p.Name, "Microsoft Edge"):
|
||||
s := models.PackageFixStatus{
|
||||
Name: "Microsoft Edge",
|
||||
FixState: "fixed",
|
||||
FixedIn: kb.FixedBuild,
|
||||
}
|
||||
if kb.FixedBuild == "" {
|
||||
s.FixState = "unknown"
|
||||
}
|
||||
stats = append(stats, s)
|
||||
default:
|
||||
stats = append(stats, models.PackageFixStatus{
|
||||
Name: p.Name,
|
||||
FixState: "unknown",
|
||||
FixedIn: kb.FixedBuild,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uniqKB[fmt.Sprintf("KB%s", kb.Article)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(uniqKB) == 0 && len(stats) == 0 {
|
||||
for _, p := range cve.Products {
|
||||
switch {
|
||||
case strings.HasPrefix(p.Name, "Microsoft Edge"):
|
||||
stats = append(stats, models.PackageFixStatus{
|
||||
Name: "Microsoft Edge",
|
||||
FixState: "unknown",
|
||||
})
|
||||
default:
|
||||
stats = append(stats, models.PackageFixStatus{
|
||||
Name: p.Name,
|
||||
FixState: "unknown",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
advisories := []models.DistroAdvisory{}
|
||||
for kb := range uniqKB {
|
||||
advisories = append(advisories, models.DistroAdvisory{
|
||||
AdvisoryID: kb,
|
||||
Description: "Microsoft Knowledge Base",
|
||||
})
|
||||
}
|
||||
|
||||
r.ScannedCves[cveID] = models.VulnInfo{
|
||||
CveID: cveID,
|
||||
Confidences: models.Confidences{models.WindowsUpdateSearch},
|
||||
DistroAdvisories: advisories,
|
||||
CveContents: models.NewCveContents(*cveCont),
|
||||
Mitigations: mitigations,
|
||||
AffectedPackages: stats,
|
||||
WindowsKBFixedIns: maps.Keys(uniqKB),
|
||||
}
|
||||
v.CveContents[models.Microsoft] = []models.CveContent{*cveCont}
|
||||
v.Mitigations = append(v.Mitigations, mitigations...)
|
||||
r.ScannedCves[cveID] = v
|
||||
}
|
||||
return len(cveIDs), nil
|
||||
return nCVEs, nil
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) {
|
||||
sort.Slice(cve.ScoreSets, func(i, j int) bool {
|
||||
return cve.ScoreSets[i].Vector < cve.ScoreSets[j].Vector
|
||||
slices.SortFunc(cve.Products, func(i, j gostmodels.MicrosoftProduct) bool {
|
||||
return i.ScoreSet.Vector < j.ScoreSet.Vector
|
||||
})
|
||||
|
||||
v3score := 0.0
|
||||
var v3Vector string
|
||||
for _, scoreSet := range cve.ScoreSets {
|
||||
if v3score < scoreSet.BaseScore {
|
||||
v3score = scoreSet.BaseScore
|
||||
v3Vector = scoreSet.Vector
|
||||
for _, p := range cve.Products {
|
||||
v, err := strconv.ParseFloat(p.ScoreSet.BaseScore, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if v3score < v {
|
||||
v3score = v
|
||||
v3Vector = p.ScoreSet.Vector
|
||||
}
|
||||
}
|
||||
|
||||
var v3Severity string
|
||||
for _, s := range cve.Severity {
|
||||
v3Severity = s.Description
|
||||
}
|
||||
|
||||
var refs []models.Reference
|
||||
for _, r := range cve.References {
|
||||
if r.AttrType == "External" {
|
||||
refs = append(refs, models.Reference{Link: r.URL})
|
||||
}
|
||||
}
|
||||
|
||||
var cwe []string
|
||||
if 0 < len(cve.CWE) {
|
||||
cwe = []string{cve.CWE}
|
||||
for _, p := range cve.Products {
|
||||
v3Severity = p.Severity
|
||||
}
|
||||
|
||||
option := map[string]string{}
|
||||
@@ -82,28 +330,20 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
|
||||
// "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A",
|
||||
option["exploit"] = cve.ExploitStatus
|
||||
}
|
||||
kbids := []string{}
|
||||
for _, kbid := range cve.KBIDs {
|
||||
kbids = append(kbids, kbid.KBID)
|
||||
}
|
||||
if 0 < len(kbids) {
|
||||
option["kbids"] = strings.Join(kbids, ",")
|
||||
}
|
||||
|
||||
vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
|
||||
mitigations := []models.Mitigation{}
|
||||
if cve.Mitigation != "" {
|
||||
mitigations = append(mitigations, models.Mitigation{
|
||||
CveContentType: models.Microsoft,
|
||||
Mitigation: cve.Mitigation,
|
||||
URL: vendorURL,
|
||||
URL: cve.URL,
|
||||
})
|
||||
}
|
||||
if cve.Workaround != "" {
|
||||
mitigations = append(mitigations, models.Mitigation{
|
||||
CveContentType: models.Microsoft,
|
||||
Mitigation: cve.Workaround,
|
||||
URL: vendorURL,
|
||||
URL: cve.URL,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -115,11 +355,9 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
|
||||
Cvss3Score: v3score,
|
||||
Cvss3Vector: v3Vector,
|
||||
Cvss3Severity: v3Severity,
|
||||
References: refs,
|
||||
CweIDs: cwe,
|
||||
Published: cve.PublishDate,
|
||||
LastModified: cve.LastUpdateDate,
|
||||
SourceLink: vendorURL,
|
||||
SourceLink: cve.URL,
|
||||
Optional: option,
|
||||
}, mitigations
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
|
||||
if red.driver == nil {
|
||||
prefix, err := util.URLPathJoin(red.baseURL, "redhat", major(gostRelease), "pkgs")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, prefix, "unfixed-cves")
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
319
gost/ubuntu.go
319
gost/ubuntu.go
@@ -5,8 +5,12 @@ package gost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
@@ -20,16 +24,52 @@ type Ubuntu struct {
|
||||
|
||||
func (ubu Ubuntu) supported(version string) bool {
|
||||
_, ok := map[string]string{
|
||||
"606": "dapper",
|
||||
"610": "edgy",
|
||||
"704": "feisty",
|
||||
"710": "gutsy",
|
||||
"804": "hardy",
|
||||
"810": "intrepid",
|
||||
"904": "jaunty",
|
||||
"910": "karmic",
|
||||
"1004": "lucid",
|
||||
"1010": "maverick",
|
||||
"1104": "natty",
|
||||
"1110": "oneiric",
|
||||
"1204": "precise",
|
||||
"1210": "quantal",
|
||||
"1304": "raring",
|
||||
"1310": "saucy",
|
||||
"1404": "trusty",
|
||||
"1410": "utopic",
|
||||
"1504": "vivid",
|
||||
"1510": "wily",
|
||||
"1604": "xenial",
|
||||
"1610": "yakkety",
|
||||
"1704": "zesty",
|
||||
"1710": "artful",
|
||||
"1804": "bionic",
|
||||
"1810": "cosmic",
|
||||
"1904": "disco",
|
||||
"1910": "eoan",
|
||||
"2004": "focal",
|
||||
"2010": "groovy",
|
||||
"2104": "hirsute",
|
||||
"2110": "impish",
|
||||
"2204": "jammy",
|
||||
"2210": "kinetic",
|
||||
// "2304": "lunar",
|
||||
}[version]
|
||||
return ok
|
||||
}
|
||||
|
||||
type cveContent struct {
|
||||
cveContent models.CveContent
|
||||
fixStatuses models.PackageFixStatuses
|
||||
}
|
||||
|
||||
var kernelSourceNamePattern = regexp.MustCompile(`^linux((-(ti-omap4|armadaxp|mako|manta|flo|goldfish|joule|raspi2?|snapdragon|aws|azure|bluefield|dell300x|gcp|gke(op)?|ibm|intel|lowlatency|kvm|oem|oracle|euclid|lts-xenial|hwe|riscv))?(-(edge|fde|iotg|hwe|osp1))?(-[\d\.]+)?)?$`)
|
||||
|
||||
// DetectCVEs fills cve information that has in Gost
|
||||
func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
|
||||
ubuReleaseVer := strings.Replace(r.Release, ".", "", 1)
|
||||
@@ -38,129 +78,222 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
linuxImage := "linux-image-" + r.RunningKernel.Release
|
||||
// Add linux and set the version of running kernel to search Gost.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
packCvesList := []packCves{}
|
||||
if ubu.DBDriver.Cnf.IsFetchViaHTTP() {
|
||||
url, _ := util.URLPathJoin(ubu.DBDriver.Cnf.GetURL(), "ubuntu", ubuReleaseVer, "pkgs")
|
||||
responses, err := getAllUnfixedCvesViaHTTP(r, url)
|
||||
detects := map[string]cveContent{}
|
||||
if ubu.driver == nil {
|
||||
urlPrefix, err := util.URLPathJoin(ubu.baseURL, "ubuntu", ubuReleaseVer, "pkgs")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, xerrors.Errorf("Failed to join URLPath. err: %w", err)
|
||||
}
|
||||
responses, err := getCvesWithFixStateViaHTTP(r, urlPrefix, "fixed-cves")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get fixed 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
|
||||
if !res.request.isSrcPack {
|
||||
continue
|
||||
}
|
||||
cves := []models.CveContent{}
|
||||
for _, ubucve := range ubuCves {
|
||||
cves = append(cves, *ubu.ConvertToModel(&ubucve))
|
||||
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(res.request.packName)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
continue
|
||||
}
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: res.request.packName,
|
||||
isSrcPack: res.request.isSrcPack,
|
||||
cves: cves,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if ubu.DBDriver.DB == nil {
|
||||
return 0, nil
|
||||
}
|
||||
for _, pack := range r.Packages {
|
||||
ubuCves, err := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
|
||||
fixeds := map[string]gostmodels.UbuntuCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &fixeds); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
}
|
||||
cves := []models.CveContent{}
|
||||
for _, ubucve := range ubuCves {
|
||||
cves = append(cves, *ubu.ConvertToModel(&ubucve))
|
||||
for _, content := range detect(fixeds, true, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: pack.Name,
|
||||
isSrcPack: false,
|
||||
cves: cves,
|
||||
})
|
||||
}
|
||||
|
||||
// SrcPack
|
||||
responses, err = getCvesWithFixStateViaHTTP(r, urlPrefix, "unfixed-cves")
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get unfixed CVEs via HTTP. err: %w", err)
|
||||
}
|
||||
for _, res := range responses {
|
||||
if !res.request.isSrcPack {
|
||||
continue
|
||||
}
|
||||
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(res.request.packName)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
for _, bn := range r.SrcPackages[res.request.packName].BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
unfixeds := map[string]gostmodels.UbuntuCVE{}
|
||||
if err := json.Unmarshal([]byte(res.json), &unfixeds); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to unmarshal json. err: %w", err)
|
||||
}
|
||||
for _, content := range detect(unfixeds, false, models.SrcPackage{Name: res.request.packName, Version: r.SrcPackages[res.request.packName].Version, BinaryNames: r.SrcPackages[res.request.packName].BinaryNames}, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, pack := range r.SrcPackages {
|
||||
ubuCves, err := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(pack.Name)
|
||||
|
||||
if kernelSourceNamePattern.MatchString(n) {
|
||||
isDetect := false
|
||||
for _, bn := range pack.BinaryNames {
|
||||
if bn == fmt.Sprintf("linux-image-%s", r.RunningKernel.Release) {
|
||||
isDetect = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isDetect {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fixeds, err := ubu.driver.GetFixedCvesUbuntu(ubuReleaseVer, n)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
return 0, xerrors.Errorf("Failed to get fixed CVEs for SrcPackage. err: %w", err)
|
||||
}
|
||||
cves := []models.CveContent{}
|
||||
for _, ubucve := range ubuCves {
|
||||
cves = append(cves, *ubu.ConvertToModel(&ubucve))
|
||||
for _, content := range detect(fixeds, true, pack, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
|
||||
unfixeds, err := ubu.driver.GetUnfixedCvesUbuntu(ubuReleaseVer, n)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("Failed to get unfixed CVEs for SrcPackage. err: %w", err)
|
||||
}
|
||||
for _, content := range detect(unfixeds, false, pack, fmt.Sprintf("linux-image-%s", r.RunningKernel.Release)) {
|
||||
c, ok := detects[content.cveContent.CveID]
|
||||
if ok {
|
||||
content.fixStatuses = append(content.fixStatuses, c.fixStatuses...)
|
||||
}
|
||||
detects[content.cveContent.CveID] = content
|
||||
}
|
||||
packCvesList = append(packCvesList, packCves{
|
||||
packName: pack.Name,
|
||||
isSrcPack: true,
|
||||
cves: cves,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delete(r.Packages, "linux")
|
||||
|
||||
for _, p := range packCvesList {
|
||||
for _, cve := range p.cves {
|
||||
v, ok := r.ScannedCves[cve.CveID]
|
||||
if ok {
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.NewCveContents(cve)
|
||||
} else {
|
||||
v.CveContents[models.UbuntuAPI] = []models.CveContent{cve}
|
||||
}
|
||||
for _, content := range detects {
|
||||
v, ok := r.ScannedCves[content.cveContent.CveID]
|
||||
if ok {
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.NewCveContents(content.cveContent)
|
||||
} else {
|
||||
v = models.VulnInfo{
|
||||
CveID: cve.CveID,
|
||||
CveContents: models.NewCveContents(cve),
|
||||
Confidences: models.Confidences{models.UbuntuAPIMatch},
|
||||
}
|
||||
nCVEs++
|
||||
v.CveContents[models.UbuntuAPI] = []models.CveContent{content.cveContent}
|
||||
v.Confidences = models.Confidences{models.UbuntuAPIMatch}
|
||||
}
|
||||
} else {
|
||||
v = models.VulnInfo{
|
||||
CveID: content.cveContent.CveID,
|
||||
CveContents: models.NewCveContents(content.cveContent),
|
||||
Confidences: models.Confidences{models.UbuntuAPIMatch},
|
||||
}
|
||||
}
|
||||
|
||||
names := []string{}
|
||||
if p.isSrcPack {
|
||||
if srcPack, ok := r.SrcPackages[p.packName]; ok {
|
||||
for _, binName := range srcPack.BinaryNames {
|
||||
if _, ok := r.Packages[binName]; ok {
|
||||
names = append(names, binName)
|
||||
for _, s := range content.fixStatuses {
|
||||
v.AffectedPackages = v.AffectedPackages.Store(s)
|
||||
}
|
||||
r.ScannedCves[content.cveContent.CveID] = v
|
||||
}
|
||||
|
||||
return len(detects), nil
|
||||
}
|
||||
|
||||
func detect(cves map[string]gostmodels.UbuntuCVE, fixed bool, srcPkg models.SrcPackage, runningKernelBinaryPkgName string) []cveContent {
|
||||
n := strings.NewReplacer("linux-signed", "linux", "linux-meta", "linux").Replace(srcPkg.Name)
|
||||
|
||||
var contents []cveContent
|
||||
for _, cve := range cves {
|
||||
c := cveContent{
|
||||
cveContent: *(Ubuntu{}).ConvertToModel(&cve),
|
||||
}
|
||||
|
||||
if fixed {
|
||||
for _, p := range cve.Patches {
|
||||
for _, rp := range p.ReleasePatches {
|
||||
installedVersion := srcPkg.Version
|
||||
patchedVersion := rp.Note
|
||||
|
||||
// https://git.launchpad.net/ubuntu-cve-tracker/tree/scripts/generate-oval#n384
|
||||
if kernelSourceNamePattern.MatchString(n) && strings.HasPrefix(srcPkg.Name, "linux-meta") {
|
||||
// 5.15.0.1026.30~20.04.16 -> 5.15.0.1026
|
||||
ss := strings.Split(installedVersion, ".")
|
||||
if len(ss) >= 4 {
|
||||
installedVersion = strings.Join(ss[:4], ".")
|
||||
}
|
||||
|
||||
// 5.15.0-1026.30~20.04.16 -> 5.15.0.1026
|
||||
lhs, rhs, ok := strings.Cut(patchedVersion, "-")
|
||||
if ok {
|
||||
patchedVersion = fmt.Sprintf("%s.%s", lhs, strings.Split(rhs, ".")[0])
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := isGostDefAffected(installedVersion, patchedVersion)
|
||||
if err != nil {
|
||||
logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s", err, installedVersion, patchedVersion)
|
||||
continue
|
||||
}
|
||||
|
||||
if affected {
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if kernelSourceNamePattern.MatchString(n) && bn != runningKernelBinaryPkgName {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
Name: bn,
|
||||
FixedIn: patchedVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if p.packName == "linux" {
|
||||
names = append(names, linuxImage)
|
||||
} else {
|
||||
names = append(names, p.packName)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
|
||||
Name: name,
|
||||
} else {
|
||||
for _, bn := range srcPkg.BinaryNames {
|
||||
if kernelSourceNamePattern.MatchString(n) && bn != runningKernelBinaryPkgName {
|
||||
continue
|
||||
}
|
||||
c.fixStatuses = append(c.fixStatuses, models.PackageFixStatus{
|
||||
Name: bn,
|
||||
FixState: "open",
|
||||
NotFixedYet: true,
|
||||
})
|
||||
}
|
||||
r.ScannedCves[cve.CveID] = v
|
||||
}
|
||||
|
||||
if len(c.fixStatuses) > 0 {
|
||||
contents = append(contents, c)
|
||||
}
|
||||
}
|
||||
return nCVEs, nil
|
||||
return contents
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
|
||||
@@ -127,11 +127,171 @@ func TestUbuntuConvertToModel(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ubu := Ubuntu{}
|
||||
got := ubu.ConvertToModel(&tt.input)
|
||||
if !reflect.DeepEqual(got, &tt.expected) {
|
||||
if got := (Ubuntu{}).ConvertToModel(&tt.input); !reflect.DeepEqual(got, &tt.expected) {
|
||||
t.Errorf("Ubuntu.ConvertToModel() = %#v, want %#v", got, &tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_detect(t *testing.T) {
|
||||
type args struct {
|
||||
cves map[string]gostmodels.UbuntuCVE
|
||||
fixed bool
|
||||
srcPkg models.SrcPackage
|
||||
runningKernelBinaryPkgName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []cveContent
|
||||
}{
|
||||
{
|
||||
name: "fixed",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.UbuntuCVE{
|
||||
"CVE-0000-0000": {
|
||||
Candidate: "CVE-0000-0000",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-0"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
Candidate: "CVE-0000-0001",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fixed: true,
|
||||
srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}},
|
||||
runningKernelBinaryPkgName: "",
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0001", SourceLink: "https://ubuntu.com/security/CVE-0000-0001", References: []models.Reference{}},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "pkg",
|
||||
FixedIn: "0.0.0-2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unfixed",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.UbuntuCVE{
|
||||
"CVE-0000-0000": {
|
||||
Candidate: "CVE-0000-0000",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "pkg",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "open"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fixed: false,
|
||||
srcPkg: models.SrcPackage{Name: "pkg", Version: "0.0.0-1", BinaryNames: []string{"pkg"}},
|
||||
runningKernelBinaryPkgName: "",
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0000", SourceLink: "https://ubuntu.com/security/CVE-0000-0000", References: []models.Reference{}},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "pkg",
|
||||
FixState: "open",
|
||||
NotFixedYet: true,
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "linux-signed",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.UbuntuCVE{
|
||||
"CVE-0000-0000": {
|
||||
Candidate: "CVE-0000-0000",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "linux",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-0"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
Candidate: "CVE-0000-0001",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "linux",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fixed: true,
|
||||
srcPkg: models.SrcPackage{Name: "linux-signed", Version: "0.0.0-1", BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}},
|
||||
runningKernelBinaryPkgName: "linux-image-generic",
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0001", SourceLink: "https://ubuntu.com/security/CVE-0000-0001", References: []models.Reference{}},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "linux-image-generic",
|
||||
FixedIn: "0.0.0-2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "linux-meta",
|
||||
args: args{
|
||||
cves: map[string]gostmodels.UbuntuCVE{
|
||||
"CVE-0000-0000": {
|
||||
Candidate: "CVE-0000-0000",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "linux",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-0"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-0000-0001": {
|
||||
Candidate: "CVE-0000-0001",
|
||||
Patches: []gostmodels.UbuntuPatch{
|
||||
{
|
||||
PackageName: "linux",
|
||||
ReleasePatches: []gostmodels.UbuntuReleasePatch{{ReleaseName: "jammy", Status: "released", Note: "0.0.0-2"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fixed: true,
|
||||
srcPkg: models.SrcPackage{Name: "linux-meta", Version: "0.0.0.1", BinaryNames: []string{"linux-image-generic", "linux-headers-generic"}},
|
||||
runningKernelBinaryPkgName: "linux-image-generic",
|
||||
},
|
||||
want: []cveContent{
|
||||
{
|
||||
cveContent: models.CveContent{Type: models.UbuntuAPI, CveID: "CVE-0000-0001", SourceLink: "https://ubuntu.com/security/CVE-0000-0001", References: []models.Reference{}},
|
||||
fixStatuses: models.PackageFixStatuses{{
|
||||
Name: "linux-image-generic",
|
||||
FixedIn: "0.0.0.2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := detect(tt.args.cves, tt.args.fixed, tt.args.srcPkg, tt.args.runningKernelBinaryPkgName); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("detect() = %#v, want %#v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
19
gost/util.go
19
gost/util.go
@@ -9,11 +9,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
@@ -84,11 +86,6 @@ type request struct {
|
||||
cveID string
|
||||
}
|
||||
|
||||
func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) (
|
||||
responses []response, err error) {
|
||||
return getCvesWithFixStateViaHTTP(r, urlPrefix, "unfixed-cves")
|
||||
}
|
||||
|
||||
func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string) (responses []response, err error) {
|
||||
nReq := len(r.Packages) + len(r.SrcPackages)
|
||||
reqChan := make(chan request, nReq)
|
||||
@@ -193,3 +190,11 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
func major(osVer string) (majorVersion string) {
|
||||
return strings.Split(osVer, ".")[0]
|
||||
}
|
||||
|
||||
func unique[T comparable](s []T) []T {
|
||||
m := map[T]struct{}{}
|
||||
for _, v := range s {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return maps.Keys(m)
|
||||
}
|
||||
|
||||
Submodule integration updated: 75327e7431...fbd9e815f6
@@ -4,7 +4,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -16,7 +15,7 @@ import (
|
||||
formatter "github.com/kotakanbe/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
//LogOpts has options for logging
|
||||
// LogOpts has options for logging
|
||||
type LogOpts struct {
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
DebugSQL bool `json:"debugSQL,omitempty"`
|
||||
@@ -36,7 +35,7 @@ type Logger struct {
|
||||
|
||||
func init() {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
log.Out = io.Discard
|
||||
fields := logrus.Fields{"prefix": ""}
|
||||
Log = Logger{Entry: *log.WithFields(fields)}
|
||||
}
|
||||
@@ -46,6 +45,13 @@ func NewNormalLogger() Logger {
|
||||
return Logger{Entry: logrus.Entry{Logger: logrus.New()}}
|
||||
}
|
||||
|
||||
// NewIODiscardLogger creates discard logger
|
||||
func NewIODiscardLogger() Logger {
|
||||
l := logrus.New()
|
||||
l.Out = io.Discard
|
||||
return Logger{Entry: logrus.Entry{Logger: l}}
|
||||
}
|
||||
|
||||
// NewCustomLogger creates logrus
|
||||
func NewCustomLogger(debug, quiet, logToFile bool, logDir, logMsgAnsiColor, serverName string) Logger {
|
||||
log := logrus.New()
|
||||
@@ -101,7 +107,7 @@ func NewCustomLogger(debug, quiet, logToFile bool, logDir, logMsgAnsiColor, serv
|
||||
}
|
||||
}
|
||||
} else if quiet {
|
||||
log.Out = ioutil.Discard
|
||||
log.Out = io.Discard
|
||||
} else {
|
||||
log.Out = os.Stderr
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
// CveContents has CveContent
|
||||
@@ -73,7 +75,7 @@ func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string, confidences Co
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{Nvd, NewCveContentType(myFamily), GitHub}
|
||||
order := append(append(CveContentTypes{Nvd}, GetCveContentTypes(myFamily)...), GitHub)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
@@ -131,24 +133,6 @@ func (v CveContents) PatchURLs() (urls []string) {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
// Severities returns Severities
|
||||
func (v CveContents) Severities(myFamily string) (values []CveContentStr) {
|
||||
order := CveContentTypes{NVD, NewCveContentType(myFamily)}
|
||||
order = append(order, AllCveContetTypes.Except(append(order)...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
if cont, found := v[ctype]; found && 0 < len(cont.Severity) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cont.Severity,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
// CveContentCpes has CveContentType and Value
|
||||
type CveContentCpes struct {
|
||||
Type CveContentType
|
||||
@@ -157,7 +141,7 @@ type CveContentCpes struct {
|
||||
|
||||
// Cpes returns affected CPEs of this Vulnerability
|
||||
func (v CveContents) Cpes(myFamily string) (values []CveContentCpes) {
|
||||
order := CveContentTypes{NewCveContentType(myFamily)}
|
||||
order := GetCveContentTypes(myFamily)
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
@@ -183,7 +167,7 @@ type CveContentRefs struct {
|
||||
|
||||
// References returns References
|
||||
func (v CveContents) References(myFamily string) (values []CveContentRefs) {
|
||||
order := CveContentTypes{NewCveContentType(myFamily)}
|
||||
order := GetCveContentTypes(myFamily)
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
@@ -204,7 +188,7 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) {
|
||||
|
||||
// CweIDs returns related CweIDs of the vulnerability
|
||||
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
|
||||
order := CveContentTypes{NewCveContentType(myFamily)}
|
||||
order := GetCveContentTypes(myFamily)
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
@@ -333,6 +317,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":
|
||||
@@ -348,6 +334,30 @@ func NewCveContentType(name string) CveContentType {
|
||||
}
|
||||
}
|
||||
|
||||
// GetCveContentTypes return CveContentTypes
|
||||
func GetCveContentTypes(family string) []CveContentType {
|
||||
switch family {
|
||||
case constant.RedHat, constant.CentOS, constant.Alma, constant.Rocky:
|
||||
return []CveContentType{RedHat, RedHatAPI}
|
||||
case constant.Fedora:
|
||||
return []CveContentType{Fedora}
|
||||
case constant.Oracle:
|
||||
return []CveContentType{Oracle}
|
||||
case constant.Amazon:
|
||||
return []CveContentType{Amazon}
|
||||
case constant.Debian, constant.Raspbian:
|
||||
return []CveContentType{Debian, DebianSecurityTracker}
|
||||
case constant.Ubuntu:
|
||||
return []CveContentType{Ubuntu, UbuntuAPI}
|
||||
case constant.OpenSUSE, constant.OpenSUSELeap, constant.SUSEEnterpriseServer, constant.SUSEEnterpriseDesktop:
|
||||
return []CveContentType{SUSE}
|
||||
case constant.Windows:
|
||||
return []CveContentType{Microsoft}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// Nvd is Nvd JSON
|
||||
Nvd CveContentType = "nvd"
|
||||
|
||||
@@ -3,6 +3,8 @@ package models
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
func TestExcept(t *testing.T) {
|
||||
@@ -249,3 +251,61 @@ func TestCveContents_Sort(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCveContentType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want CveContentType
|
||||
}{
|
||||
{
|
||||
name: "redhat",
|
||||
want: RedHat,
|
||||
},
|
||||
{
|
||||
name: "centos",
|
||||
want: RedHat,
|
||||
},
|
||||
{
|
||||
name: "unknown",
|
||||
want: Unknown,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewCveContentType(tt.name); got != tt.want {
|
||||
t.Errorf("NewCveContentType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCveContentTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
family string
|
||||
want []CveContentType
|
||||
}{
|
||||
{
|
||||
family: constant.RedHat,
|
||||
want: []CveContentType{RedHat, RedHatAPI},
|
||||
},
|
||||
{
|
||||
family: constant.Debian,
|
||||
want: []CveContentType{Debian, DebianSecurityTracker},
|
||||
},
|
||||
{
|
||||
family: constant.Ubuntu,
|
||||
want: []CveContentType{Ubuntu, UbuntuAPI},
|
||||
},
|
||||
{
|
||||
family: constant.FreeBSD,
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.family, func(t *testing.T) {
|
||||
if got := GetCveContentTypes(tt.family); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetCveContentTypes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
96
models/github.go
Normal file
96
models/github.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
// DependencyGraphManifests has a map of DependencyGraphManifest
|
||||
// key: BlobPath
|
||||
type DependencyGraphManifests map[string]DependencyGraphManifest
|
||||
|
||||
// DependencyGraphManifest has filename, repository, dependencies
|
||||
type DependencyGraphManifest struct {
|
||||
BlobPath string `json:"blobPath"`
|
||||
Filename string `json:"filename"`
|
||||
Repository string `json:"repository"`
|
||||
Dependencies []Dependency `json:"dependencies"`
|
||||
}
|
||||
|
||||
// RepoURLFilename should be same format with GitHubSecurityAlert.RepoURLManifestPath()
|
||||
func (m DependencyGraphManifest) RepoURLFilename() string {
|
||||
return fmt.Sprintf("%s/%s", m.Repository, m.Filename)
|
||||
}
|
||||
|
||||
// Ecosystem returns a name of ecosystem(or package manager) of manifest(lock) file in trivy way
|
||||
// https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-the-dependency-graph#supported-package-ecosystems
|
||||
func (m DependencyGraphManifest) Ecosystem() string {
|
||||
switch {
|
||||
case strings.HasSuffix(m.Filename, "Cargo.lock"),
|
||||
strings.HasSuffix(m.Filename, "Cargo.toml"):
|
||||
return ftypes.Cargo // Rust
|
||||
case strings.HasSuffix(m.Filename, "composer.lock"),
|
||||
strings.HasSuffix(m.Filename, "composer.json"):
|
||||
return ftypes.Composer // PHP
|
||||
case strings.HasSuffix(m.Filename, ".csproj"),
|
||||
strings.HasSuffix(m.Filename, ".vbproj"),
|
||||
strings.HasSuffix(m.Filename, ".nuspec"),
|
||||
strings.HasSuffix(m.Filename, ".vcxproj"),
|
||||
strings.HasSuffix(m.Filename, ".fsproj"),
|
||||
strings.HasSuffix(m.Filename, "packages.config"):
|
||||
return ftypes.NuGet // .NET languages (C#, F#, VB), C++
|
||||
case strings.HasSuffix(m.Filename, "go.sum"),
|
||||
strings.HasSuffix(m.Filename, "go.mod"):
|
||||
return ftypes.GoModule // Go
|
||||
case strings.HasSuffix(m.Filename, "pom.xml"):
|
||||
return ftypes.Pom // Java, Scala
|
||||
case strings.HasSuffix(m.Filename, "package-lock.json"),
|
||||
strings.HasSuffix(m.Filename, "package.json"):
|
||||
return ftypes.Npm // JavaScript
|
||||
case strings.HasSuffix(m.Filename, "yarn.lock"):
|
||||
return ftypes.Yarn // JavaScript
|
||||
case strings.HasSuffix(m.Filename, "requirements.txt"),
|
||||
strings.HasSuffix(m.Filename, "requirements-dev.txt"),
|
||||
strings.HasSuffix(m.Filename, "setup.py"):
|
||||
return ftypes.Pip // Python
|
||||
case strings.HasSuffix(m.Filename, "Pipfile.lock"),
|
||||
strings.HasSuffix(m.Filename, "Pipfile"):
|
||||
return ftypes.Pipenv // Python
|
||||
case strings.HasSuffix(m.Filename, "poetry.lock"),
|
||||
strings.HasSuffix(m.Filename, "pyproject.toml"):
|
||||
return ftypes.Poetry // Python
|
||||
case strings.HasSuffix(m.Filename, "Gemfile.lock"),
|
||||
strings.HasSuffix(m.Filename, "Gemfile"):
|
||||
return ftypes.Bundler // Ruby
|
||||
case strings.HasSuffix(m.Filename, ".gemspec"):
|
||||
return ftypes.GemSpec // Ruby
|
||||
case strings.HasSuffix(m.Filename, "pubspec.lock"),
|
||||
strings.HasSuffix(m.Filename, "pubspec.yaml"):
|
||||
return "pub" // Dart
|
||||
case strings.HasSuffix(m.Filename, ".yml"),
|
||||
strings.HasSuffix(m.Filename, ".yaml"):
|
||||
return "actions" // GitHub Actions workflows
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Dependency has dependency package information
|
||||
type Dependency struct {
|
||||
PackageName string `json:"packageName"`
|
||||
PackageManager string `json:"packageManager"`
|
||||
Repository string `json:"repository"`
|
||||
Requirements string `json:"requirements"`
|
||||
}
|
||||
|
||||
// Version returns version
|
||||
func (d Dependency) Version() string {
|
||||
s := strings.Split(d.Requirements, " ")
|
||||
if len(s) == 2 && s[0] == "=" {
|
||||
return s[1]
|
||||
}
|
||||
// in case of ranged version
|
||||
return ""
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"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"
|
||||
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/logging"
|
||||
)
|
||||
|
||||
// LibraryScanners is an array of LibraryScanner
|
||||
@@ -60,11 +59,11 @@ type Library struct {
|
||||
func (s LibraryScanner) Scan() ([]VulnInfo, error) {
|
||||
scanner, err := library.NewDriver(s.Type)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to new a library driver: %w", err)
|
||||
return nil, xerrors.Errorf("Failed to new a library driver %s: %w", s.Type, err)
|
||||
}
|
||||
var vulnerabilities = []VulnInfo{}
|
||||
for _, pkg := range s.Libs {
|
||||
tvulns, err := scanner.DetectVulnerabilities(pkg.Name, pkg.Version)
|
||||
tvulns, err := scanner.DetectVulnerabilities("", pkg.Name, pkg.Version)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", scanner.Type(), err)
|
||||
}
|
||||
@@ -130,27 +129,52 @@ func getCveContents(cveID string, vul trivyDBTypes.Vulnerability) (contents map[
|
||||
return contents
|
||||
}
|
||||
|
||||
// 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",
|
||||
// FindLockFiles is a list of filenames that is the target of findLock
|
||||
var FindLockFiles = []string{
|
||||
// node
|
||||
ftypes.NpmPkgLock, ftypes.YarnLock, ftypes.PnpmLock,
|
||||
// ruby
|
||||
ftypes.GemfileLock,
|
||||
// rust
|
||||
ftypes.CargoLock,
|
||||
// php
|
||||
ftypes.ComposerLock,
|
||||
// python
|
||||
ftypes.PipRequirements, ftypes.PipfileLock, ftypes.PoetryLock,
|
||||
// .net
|
||||
ftypes.NuGetPkgsLock, ftypes.NuGetPkgsConfig, "*.deps.json",
|
||||
// gomod
|
||||
ftypes.GoMod, ftypes.GoSum,
|
||||
// java
|
||||
ftypes.MavenPom, "*.jar", "*.war", "*.ear", "*.par", "*gradle.lockfile",
|
||||
// C / C++
|
||||
ftypes.ConanLock,
|
||||
}
|
||||
|
||||
// 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, ftypes.Gradle:
|
||||
return "java"
|
||||
case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript:
|
||||
return "node"
|
||||
case ftypes.NuGet, ftypes.DotNetCore:
|
||||
return ".net"
|
||||
case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg:
|
||||
return "python"
|
||||
case ftypes.ConanLock:
|
||||
return "c"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return LibraryMap[fileName]
|
||||
}
|
||||
|
||||
// LibraryFixedIn has library fixed information
|
||||
|
||||
@@ -45,15 +45,17 @@ type ScanResult struct {
|
||||
Errors []string `json:"errors"`
|
||||
Warnings []string `json:"warnings"`
|
||||
|
||||
ScannedCves VulnInfos `json:"scannedCves"`
|
||||
RunningKernel Kernel `json:"runningKernel"`
|
||||
Packages Packages `json:"packages"`
|
||||
SrcPackages SrcPackages `json:",omitempty"`
|
||||
EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules
|
||||
WordPressPackages WordPressPackages `json:",omitempty"`
|
||||
LibraryScanners LibraryScanners `json:"libraries,omitempty"`
|
||||
CweDict CweDict `json:"cweDict,omitempty"`
|
||||
Optional map[string]interface{} `json:",omitempty"`
|
||||
ScannedCves VulnInfos `json:"scannedCves"`
|
||||
RunningKernel Kernel `json:"runningKernel"`
|
||||
Packages Packages `json:"packages"`
|
||||
SrcPackages SrcPackages `json:",omitempty"`
|
||||
EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules
|
||||
WordPressPackages WordPressPackages `json:",omitempty"`
|
||||
GitHubManifests DependencyGraphManifests `json:"gitHubManifests,omitempty"`
|
||||
LibraryScanners LibraryScanners `json:"libraries,omitempty"`
|
||||
WindowsKB *WindowsKB `json:"windowsKB,omitempty"`
|
||||
CweDict CweDict `json:"cweDict,omitempty"`
|
||||
Optional map[string]interface{} `json:",omitempty"`
|
||||
Config struct {
|
||||
Scan config.Config `json:"scan"`
|
||||
Report config.Config `json:"report"`
|
||||
@@ -82,6 +84,12 @@ type Kernel struct {
|
||||
RebootRequired bool `json:"rebootRequired"`
|
||||
}
|
||||
|
||||
// WindowsKB has applied and unapplied KBs
|
||||
type WindowsKB struct {
|
||||
Applied []string `json:"applied,omitempty"`
|
||||
Unapplied []string `json:"unapplied,omitempty"`
|
||||
}
|
||||
|
||||
// FilterInactiveWordPressLibs is filter function.
|
||||
func (r *ScanResult) FilterInactiveWordPressLibs(detectInactive bool) {
|
||||
if detectInactive {
|
||||
@@ -436,23 +444,23 @@ func (r *ScanResult) SortForJSONOutput() {
|
||||
// CweDict is a dictionary for CWE
|
||||
type CweDict map[string]CweDictEntry
|
||||
|
||||
// AttentionCWE has OWASP TOP10, CWE TOP25, CWE/SANS TOP25 rank and url
|
||||
type AttentionCWE struct {
|
||||
Rank string
|
||||
URL string
|
||||
}
|
||||
|
||||
// Get the name, url, top10URL for the specified cweID, lang
|
||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL string) {
|
||||
func (c CweDict) Get(cweID, lang string) (name, url string, owasp, cwe25, sans map[string]AttentionCWE) {
|
||||
cweNum := strings.TrimPrefix(cweID, "CWE-")
|
||||
dict, ok := c[cweNum]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
owasp, cwe25, sans = fillAttentionCwe(dict, lang)
|
||||
switch lang {
|
||||
case "ja":
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
if dict, ok := cwe.CweDictJa[cweNum]; ok {
|
||||
name = dict.Name
|
||||
url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
|
||||
@@ -463,18 +471,6 @@ func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
}
|
||||
default:
|
||||
if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
|
||||
top10Rank = dict.OwaspTopTen2017
|
||||
top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
|
||||
cweTop25Rank = dict.CweTopTwentyfive2019
|
||||
cweTop25URL = cwe.CweTopTwentyfive2019URL
|
||||
}
|
||||
if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
|
||||
sansTop25Rank = dict.SansTopTwentyfive
|
||||
sansTop25URL = cwe.SansTopTwentyfiveURL
|
||||
}
|
||||
url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
|
||||
if dict, ok := cwe.CweDictEn[cweNum]; ok {
|
||||
name = dict.Name
|
||||
@@ -483,11 +479,47 @@ func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop
|
||||
return
|
||||
}
|
||||
|
||||
func fillAttentionCwe(dict CweDictEntry, lang string) (owasp, cwe25, sans map[string]AttentionCWE) {
|
||||
owasp, cwe25, sans = map[string]AttentionCWE{}, map[string]AttentionCWE{}, map[string]AttentionCWE{}
|
||||
switch lang {
|
||||
case "ja":
|
||||
for year, rank := range dict.OwaspTopTens {
|
||||
owasp[year] = AttentionCWE{
|
||||
Rank: rank,
|
||||
URL: cwe.OwaspTopTenURLsJa[year][rank],
|
||||
}
|
||||
}
|
||||
default:
|
||||
for year, rank := range dict.OwaspTopTens {
|
||||
owasp[year] = AttentionCWE{
|
||||
Rank: rank,
|
||||
URL: cwe.OwaspTopTenURLsEn[year][rank],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for year, rank := range dict.CweTopTwentyfives {
|
||||
cwe25[year] = AttentionCWE{
|
||||
Rank: rank,
|
||||
URL: cwe.CweTopTwentyfiveURLs[year],
|
||||
}
|
||||
}
|
||||
|
||||
for year, rank := range dict.SansTopTwentyfives {
|
||||
sans[year] = AttentionCWE{
|
||||
Rank: rank,
|
||||
URL: cwe.SansTopTwentyfiveURLs[year],
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CweDictEntry is a entry of CWE
|
||||
type CweDictEntry struct {
|
||||
En *cwe.Cwe `json:"en,omitempty"`
|
||||
Ja *cwe.Cwe `json:"ja,omitempty"`
|
||||
OwaspTopTen2017 string `json:"owaspTopTen2017"`
|
||||
CweTopTwentyfive2019 string `json:"cweTopTwentyfive2019"`
|
||||
SansTopTwentyfive string `json:"sansTopTwentyfive"`
|
||||
En *cwe.Cwe `json:"en,omitempty"`
|
||||
Ja *cwe.Cwe `json:"ja,omitempty"`
|
||||
OwaspTopTens map[string]string `json:"owaspTopTens"`
|
||||
CweTopTwentyfives map[string]string `json:"cweTopTwentyfives"`
|
||||
SansTopTwentyfives map[string]string `json:"sansTopTwentyfives"`
|
||||
}
|
||||
|
||||
@@ -256,16 +256,18 @@ type VulnInfo struct {
|
||||
CveID string `json:"cveID,omitempty"`
|
||||
Confidences Confidences `json:"confidences,omitempty"`
|
||||
AffectedPackages PackageFixStatuses `json:"affectedPackages,omitempty"`
|
||||
DistroAdvisories DistroAdvisories `json:"distroAdvisories,omitempty"` // for Amazon, RHEL, Fedora, FreeBSD
|
||||
DistroAdvisories DistroAdvisories `json:"distroAdvisories,omitempty"` // for Amazon, RHEL, Fedora, FreeBSD, Microsoft
|
||||
CveContents CveContents `json:"cveContents,omitempty"`
|
||||
Exploits []Exploit `json:"exploits,omitempty"`
|
||||
Metasploits []Metasploit `json:"metasploits,omitempty"`
|
||||
Mitigations []Mitigation `json:"mitigations,omitempty"`
|
||||
Ctis []string `json:"ctis,omitempty"`
|
||||
AlertDict AlertDict `json:"alertDict,omitempty"`
|
||||
CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml
|
||||
GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
|
||||
WpPackageFixStats WpPackageFixStats `json:"wpPackageFixStats,omitempty"`
|
||||
LibraryFixedIns LibraryFixedIns `json:"libraryFixedIns,omitempty"`
|
||||
WindowsKBFixedIns []string `json:"windowsKBFixedIns,omitempty"`
|
||||
VulnType string `json:"vulnType,omitempty"`
|
||||
DiffStatus DiffStatus `json:"diffStatus,omitempty"`
|
||||
}
|
||||
@@ -283,7 +285,7 @@ type GitHubSecurityAlerts []GitHubSecurityAlert
|
||||
// Add adds given arg to the slice and return the slice (immutable)
|
||||
func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts {
|
||||
for _, a := range g {
|
||||
if a.PackageName == alert.PackageName {
|
||||
if a.RepoURLPackageName() == alert.RepoURLPackageName() {
|
||||
return g
|
||||
}
|
||||
}
|
||||
@@ -293,19 +295,39 @@ func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlert
|
||||
// Names return a slice of lib names
|
||||
func (g GitHubSecurityAlerts) Names() (names []string) {
|
||||
for _, a := range g {
|
||||
names = append(names, a.PackageName)
|
||||
names = append(names, a.RepoURLPackageName())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GitHubSecurityAlert has detected CVE-ID, PackageName, Status fetched via GitHub API
|
||||
// GitHubSecurityAlert has detected CVE-ID, GSAVulnerablePackage, Status fetched via GitHub API
|
||||
type GitHubSecurityAlert struct {
|
||||
PackageName string `json:"packageName"`
|
||||
FixedIn string `json:"fixedIn"`
|
||||
AffectedRange string `json:"affectedRange"`
|
||||
Dismissed bool `json:"dismissed"`
|
||||
DismissedAt time.Time `json:"dismissedAt"`
|
||||
DismissReason string `json:"dismissReason"`
|
||||
Repository string `json:"repository"`
|
||||
Package GSAVulnerablePackage `json:"package,omitempty"`
|
||||
FixedIn string `json:"fixedIn"`
|
||||
AffectedRange string `json:"affectedRange"`
|
||||
Dismissed bool `json:"dismissed"`
|
||||
DismissedAt time.Time `json:"dismissedAt"`
|
||||
DismissReason string `json:"dismissReason"`
|
||||
}
|
||||
|
||||
// RepoURLPackageName returns a string connecting the repository and package name
|
||||
func (a GitHubSecurityAlert) RepoURLPackageName() string {
|
||||
return fmt.Sprintf("%s %s", a.Repository, a.Package.Name)
|
||||
}
|
||||
|
||||
// RepoURLManifestPath should be same format with DependencyGraphManifest.RepoURLFilename()
|
||||
func (a GitHubSecurityAlert) RepoURLManifestPath() string {
|
||||
return fmt.Sprintf("%s/%s", a.Repository, a.Package.ManifestPath)
|
||||
}
|
||||
|
||||
// GSAVulnerablePackage has vulnerable package information
|
||||
type GSAVulnerablePackage struct {
|
||||
Name string `json:"name"`
|
||||
Ecosystem string `json:"ecosystem"`
|
||||
ManifestFilename string `json:"manifestFilename"`
|
||||
ManifestPath string `json:"manifestPath"`
|
||||
Requirements string `json:"requirements"`
|
||||
}
|
||||
|
||||
// LibraryFixedIns is a list of Library's FixedIn
|
||||
@@ -392,7 +414,7 @@ func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{Trivy, Nvd, NewCveContentType(myFamily)}
|
||||
order := append(CveContentTypes{Trivy, Nvd}, GetCveContentTypes(myFamily)...)
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
@@ -439,7 +461,7 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{Trivy, NewCveContentType(myFamily), Nvd, GitHub}
|
||||
order := append(append(CveContentTypes{Trivy}, GetCveContentTypes(myFamily)...), Nvd, GitHub)
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
@@ -510,7 +532,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, Microsoft, Nvd, Jvn}
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
@@ -531,7 +553,7 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, ctype := range []CveContentType{Debian, DebianSecurityTracker, Ubuntu, Amazon, Trivy, GitHub, WpScan} {
|
||||
for _, ctype := range []CveContentType{Debian, DebianSecurityTracker, Ubuntu, UbuntuAPI, Amazon, Trivy, GitHub, WpScan} {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if cont.Cvss3Severity != "" {
|
||||
@@ -549,7 +571,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)
|
||||
@@ -640,6 +662,7 @@ func (v VulnInfo) PatchStatus(packs Packages) string {
|
||||
if len(v.CpeURIs) != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, p := range v.AffectedPackages {
|
||||
if p.NotFixedYet {
|
||||
return "unfixed"
|
||||
@@ -659,6 +682,13 @@ func (v VulnInfo) PatchStatus(packs Packages) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range v.Confidences {
|
||||
if c == WindowsUpdateSearch && len(v.WindowsKBFixedIns) == 0 {
|
||||
return "unfixed"
|
||||
}
|
||||
}
|
||||
|
||||
return "fixed"
|
||||
}
|
||||
|
||||
@@ -709,7 +739,7 @@ func severityToCvssScoreRange(severity string) string {
|
||||
return "7.0-8.9"
|
||||
case "MODERATE", "MEDIUM":
|
||||
return "4.0-6.9"
|
||||
case "LOW":
|
||||
case "LOW", "NEGLIGIBLE":
|
||||
return "0.1-3.9"
|
||||
}
|
||||
return "None"
|
||||
@@ -727,6 +757,10 @@ func severityToCvssScoreRange(severity string) string {
|
||||
// Critical, High, Medium, Low
|
||||
// https://wiki.ubuntu.com/Bugs/Importance
|
||||
// https://people.canonical.com/~ubuntu-security/cve/priority.html
|
||||
//
|
||||
// Ubuntu CVE Tracker
|
||||
// Critical, High, Medium, Low, Negligible
|
||||
// https://people.canonical.com/~ubuntu-security/priority.html
|
||||
func severityToCvssScoreRoughly(severity string) float64 {
|
||||
switch strings.ToUpper(severity) {
|
||||
case "CRITICAL":
|
||||
@@ -735,7 +769,7 @@ func severityToCvssScoreRoughly(severity string) float64 {
|
||||
return 8.9
|
||||
case "MODERATE", "MEDIUM":
|
||||
return 6.9
|
||||
case "LOW":
|
||||
case "LOW", "NEGLIGIBLE":
|
||||
return 3.9
|
||||
}
|
||||
return 0
|
||||
@@ -796,6 +830,8 @@ type Exploit struct {
|
||||
DocumentURL *string `json:"documentURL,omitempty"`
|
||||
ShellCodeURL *string `json:"shellCodeURL,omitempty"`
|
||||
BinaryURL *string `json:"binaryURL,omitempty"`
|
||||
PaperURL *string `json:"paperURL,omitempty"`
|
||||
GHDBURL *string `json:"ghdbURL,omitempty"`
|
||||
}
|
||||
|
||||
// Metasploit :
|
||||
@@ -903,6 +939,9 @@ const (
|
||||
// UbuntuAPIMatchStr :
|
||||
UbuntuAPIMatchStr = "UbuntuAPIMatch"
|
||||
|
||||
// WindowsUpdateSearchStr :
|
||||
WindowsUpdateSearchStr = "WindowsUpdateSearch"
|
||||
|
||||
// TrivyMatchStr :
|
||||
TrivyMatchStr = "TrivyMatch"
|
||||
|
||||
@@ -941,6 +980,9 @@ var (
|
||||
// UbuntuAPIMatch ranking how confident the CVE-ID was detected correctly
|
||||
UbuntuAPIMatch = Confidence{100, UbuntuAPIMatchStr, 0}
|
||||
|
||||
// WindowsUpdateSearch ranking how confident the CVE-ID was detected correctly
|
||||
WindowsUpdateSearch = Confidence{100, WindowsUpdateSearchStr, 0}
|
||||
|
||||
// TrivyMatch ranking how confident the CVE-ID was detected correctly
|
||||
TrivyMatch = Confidence{100, TrivyMatchStr, 0}
|
||||
|
||||
|
||||
@@ -1717,3 +1717,103 @@ func TestVulnInfos_FilterByConfidenceOver(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVulnInfo_PatchStatus(t *testing.T) {
|
||||
type fields struct {
|
||||
Confidences Confidences
|
||||
AffectedPackages PackageFixStatuses
|
||||
CpeURIs []string
|
||||
WindowsKBFixedIns []string
|
||||
}
|
||||
type args struct {
|
||||
packs Packages
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "cpe",
|
||||
fields: fields{
|
||||
CpeURIs: []string{"cpe:/a:microsoft:internet_explorer:10"},
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "package unfixed",
|
||||
fields: fields{
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{
|
||||
Name: "bash",
|
||||
NotFixedYet: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "unfixed",
|
||||
},
|
||||
{
|
||||
name: "package unknown",
|
||||
fields: fields{
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{
|
||||
Name: "bash",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
packs: Packages{"bash": {
|
||||
Name: "bash",
|
||||
}},
|
||||
},
|
||||
want: "unknown",
|
||||
},
|
||||
{
|
||||
name: "package fixed",
|
||||
fields: fields{
|
||||
AffectedPackages: PackageFixStatuses{
|
||||
{
|
||||
Name: "bash",
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
packs: Packages{"bash": {
|
||||
Name: "bash",
|
||||
Version: "4.3-9.1",
|
||||
NewVersion: "5.0-4",
|
||||
}},
|
||||
},
|
||||
want: "fixed",
|
||||
},
|
||||
{
|
||||
name: "windows unfixed",
|
||||
fields: fields{
|
||||
Confidences: Confidences{WindowsUpdateSearch},
|
||||
},
|
||||
want: "unfixed",
|
||||
},
|
||||
{
|
||||
name: "windows fixed",
|
||||
fields: fields{
|
||||
Confidences: Confidences{WindowsUpdateSearch},
|
||||
WindowsKBFixedIns: []string{"000000"},
|
||||
},
|
||||
want: "fixed",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := VulnInfo{
|
||||
Confidences: tt.fields.Confidences,
|
||||
AffectedPackages: tt.fields.AffectedPackages,
|
||||
CpeURIs: tt.fields.CpeURIs,
|
||||
WindowsKBFixedIns: tt.fields.WindowsKBFixedIns,
|
||||
}
|
||||
if got := v.PatchStatus(tt.args.packs); got != tt.want {
|
||||
t.Errorf("VulnInfo.PatchStatus() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
361
oval/debian.go
361
oval/debian.go
@@ -4,14 +4,12 @@
|
||||
package oval
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"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"
|
||||
ovaldb "github.com/vulsio/goval-dictionary/db"
|
||||
ovalmodels "github.com/vulsio/goval-dictionary/models"
|
||||
)
|
||||
|
||||
@@ -122,12 +120,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 +140,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,315 +202,19 @@ 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FillWithOval returns scan result after updating CVE info by OVAL
|
||||
func (o Ubuntu) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
|
||||
switch util.Major(r.Release) {
|
||||
case "14":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
"linux-azure",
|
||||
"linux-lts-xenial",
|
||||
"linux-meta",
|
||||
"linux-meta-aws",
|
||||
"linux-meta-azure",
|
||||
"linux-meta-lts-xenial",
|
||||
"linux-signed",
|
||||
"linux-signed-azure",
|
||||
"linux-signed-lts-xenial",
|
||||
"linux",
|
||||
}
|
||||
return o.fillWithOval(r, kernelNamesInOval)
|
||||
case "16":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
"linux-aws-hwe",
|
||||
"linux-azure",
|
||||
"linux-euclid",
|
||||
"linux-flo",
|
||||
"linux-gcp",
|
||||
"linux-gke",
|
||||
"linux-goldfish",
|
||||
"linux-hwe",
|
||||
"linux-kvm",
|
||||
"linux-mako",
|
||||
"linux-meta",
|
||||
"linux-meta-aws",
|
||||
"linux-meta-aws-hwe",
|
||||
"linux-meta-azure",
|
||||
"linux-meta-gcp",
|
||||
"linux-meta-hwe",
|
||||
"linux-meta-kvm",
|
||||
"linux-meta-oracle",
|
||||
"linux-meta-raspi2",
|
||||
"linux-meta-snapdragon",
|
||||
"linux-oem",
|
||||
"linux-oracle",
|
||||
"linux-raspi2",
|
||||
"linux-signed",
|
||||
"linux-signed-azure",
|
||||
"linux-signed-gcp",
|
||||
"linux-signed-hwe",
|
||||
"linux-signed-oracle",
|
||||
"linux-snapdragon",
|
||||
"linux",
|
||||
}
|
||||
return o.fillWithOval(r, kernelNamesInOval)
|
||||
case "18":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
"linux-aws-5.0",
|
||||
"linux-azure",
|
||||
"linux-gcp",
|
||||
"linux-gcp-5.3",
|
||||
"linux-gke-4.15",
|
||||
"linux-gke-5.0",
|
||||
"linux-gke-5.3",
|
||||
"linux-hwe",
|
||||
"linux-kvm",
|
||||
"linux-meta",
|
||||
"linux-meta-aws",
|
||||
"linux-meta-aws-5.0",
|
||||
"linux-meta-azure",
|
||||
"linux-meta-gcp",
|
||||
"linux-meta-gcp-5.3",
|
||||
"linux-meta-gke-4.15",
|
||||
"linux-meta-gke-5.0",
|
||||
"linux-meta-gke-5.3",
|
||||
"linux-meta-hwe",
|
||||
"linux-meta-kvm",
|
||||
"linux-meta-oem",
|
||||
"linux-meta-oem-osp1",
|
||||
"linux-meta-oracle",
|
||||
"linux-meta-oracle-5.0",
|
||||
"linux-meta-oracle-5.3",
|
||||
"linux-meta-raspi2",
|
||||
"linux-meta-raspi2-5.3",
|
||||
"linux-meta-snapdragon",
|
||||
"linux-oem",
|
||||
"linux-oem-osp1",
|
||||
"linux-oracle",
|
||||
"linux-oracle-5.0",
|
||||
"linux-oracle-5.3",
|
||||
"linux-raspi2",
|
||||
"linux-raspi2-5.3",
|
||||
"linux-signed",
|
||||
"linux-signed-azure",
|
||||
"linux-signed-gcp",
|
||||
"linux-signed-gcp-5.3",
|
||||
"linux-signed-gke-4.15",
|
||||
"linux-signed-gke-5.0",
|
||||
"linux-signed-gke-5.3",
|
||||
"linux-signed-hwe",
|
||||
"linux-signed-oem",
|
||||
"linux-signed-oem-osp1",
|
||||
"linux-signed-oracle",
|
||||
"linux-signed-oracle-5.0",
|
||||
"linux-signed-oracle-5.3",
|
||||
"linux-snapdragon",
|
||||
"linux",
|
||||
}
|
||||
return o.fillWithOval(r, kernelNamesInOval)
|
||||
case "20":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
"linux-azure",
|
||||
"linux-gcp",
|
||||
"linux-kvm",
|
||||
"linux-meta",
|
||||
"linux-meta-aws",
|
||||
"linux-meta-azure",
|
||||
"linux-meta-gcp",
|
||||
"linux-meta-kvm",
|
||||
"linux-meta-oem-5.6",
|
||||
"linux-meta-oracle",
|
||||
"linux-meta-raspi",
|
||||
"linux-meta-riscv",
|
||||
"linux-oem-5.6",
|
||||
"linux-oracle",
|
||||
"linux-raspi",
|
||||
"linux-raspi2",
|
||||
"linux-riscv",
|
||||
"linux-signed",
|
||||
"linux-signed-azure",
|
||||
"linux-signed-gcp",
|
||||
"linux-signed-oem-5.6",
|
||||
"linux-signed-oracle",
|
||||
"linux",
|
||||
}
|
||||
return o.fillWithOval(r, kernelNamesInOval)
|
||||
case "21":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
"linux-base-sgx",
|
||||
"linux-base",
|
||||
"linux-cloud-tools-common",
|
||||
"linux-cloud-tools-generic",
|
||||
"linux-cloud-tools-lowlatency",
|
||||
"linux-cloud-tools-virtual",
|
||||
"linux-gcp",
|
||||
"linux-generic",
|
||||
"linux-gke",
|
||||
"linux-headers-aws",
|
||||
"linux-headers-gcp",
|
||||
"linux-headers-gke",
|
||||
"linux-headers-oracle",
|
||||
"linux-image-aws",
|
||||
"linux-image-extra-virtual",
|
||||
"linux-image-gcp",
|
||||
"linux-image-generic",
|
||||
"linux-image-gke",
|
||||
"linux-image-lowlatency",
|
||||
"linux-image-oracle",
|
||||
"linux-image-virtual",
|
||||
"linux-lowlatency",
|
||||
"linux-modules-extra-aws",
|
||||
"linux-modules-extra-gcp",
|
||||
"linux-modules-extra-gke",
|
||||
"linux-oracle",
|
||||
"linux-tools-aws",
|
||||
"linux-tools-common",
|
||||
"linux-tools-gcp",
|
||||
"linux-tools-generic",
|
||||
"linux-tools-gke",
|
||||
"linux-tools-host",
|
||||
"linux-tools-lowlatency",
|
||||
"linux-tools-oracle",
|
||||
"linux-tools-virtual",
|
||||
"linux-virtual",
|
||||
}
|
||||
return o.fillWithOval(r, kernelNamesInOval)
|
||||
}
|
||||
return 0, fmt.Errorf("Ubuntu %s is not support for now", r.Release)
|
||||
}
|
||||
|
||||
func (o Ubuntu) fillWithOval(r *models.ScanResult, kernelNamesInOval []string) (nCVEs int, err error) {
|
||||
linuxImage := "linux-image-" + r.RunningKernel.Release
|
||||
runningKernelVersion := ""
|
||||
kernelPkgInOVAL := ""
|
||||
isOVALKernelPkgAdded := false
|
||||
unusedKernels := []models.Package{}
|
||||
copiedSourcePkgs := models.SrcPackages{}
|
||||
|
||||
if r.Container.ContainerID == "" {
|
||||
if v, ok := r.Packages[linuxImage]; ok {
|
||||
runningKernelVersion = v.Version
|
||||
} else {
|
||||
logging.Log.Warnf("Unable to detect vulns of running kernel because the version of the running kernel is unknown. server: %s",
|
||||
r.ServerName)
|
||||
}
|
||||
|
||||
for _, n := range kernelNamesInOval {
|
||||
if p, ok := r.Packages[n]; ok {
|
||||
kernelPkgInOVAL = p.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// remove unused kernels from packages to prevent detecting vulns of unused kernel
|
||||
for _, n := range kernelNamesInOval {
|
||||
if v, ok := r.Packages[n]; ok {
|
||||
unusedKernels = append(unusedKernels, v)
|
||||
delete(r.Packages, n)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove linux-* in order to detect only vulnerabilities in the running kernel.
|
||||
for n := range r.Packages {
|
||||
if n != kernelPkgInOVAL && strings.HasPrefix(n, "linux-") {
|
||||
unusedKernels = append(unusedKernels, r.Packages[n])
|
||||
delete(r.Packages, n)
|
||||
}
|
||||
}
|
||||
for srcPackName, srcPack := range r.SrcPackages {
|
||||
copiedSourcePkgs[srcPackName] = srcPack
|
||||
targetBinaryNames := []string{}
|
||||
for _, n := range srcPack.BinaryNames {
|
||||
if n == kernelPkgInOVAL || !strings.HasPrefix(n, "linux-") {
|
||||
targetBinaryNames = append(targetBinaryNames, n)
|
||||
}
|
||||
}
|
||||
srcPack.BinaryNames = targetBinaryNames
|
||||
r.SrcPackages[srcPackName] = srcPack
|
||||
}
|
||||
|
||||
if kernelPkgInOVAL == "" {
|
||||
logging.Log.Warnf("The OVAL name of the running kernel image %+v is not found. So vulns of `linux` wll be detected. server: %s",
|
||||
r.RunningKernel, r.ServerName)
|
||||
kernelPkgInOVAL = "linux"
|
||||
isOVALKernelPkgAdded = true
|
||||
}
|
||||
|
||||
if runningKernelVersion != "" {
|
||||
r.Packages[kernelPkgInOVAL] = models.Package{
|
||||
Name: kernelPkgInOVAL,
|
||||
Version: runningKernelVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var relatedDefs ovalResult
|
||||
if o.Cnf.IsFetchViaHTTP() {
|
||||
if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
|
||||
return 0, 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 isOVALKernelPkgAdded {
|
||||
delete(r.Packages, kernelPkgInOVAL)
|
||||
}
|
||||
for _, p := range unusedKernels {
|
||||
r.Packages[p.Name] = p
|
||||
}
|
||||
r.SrcPackages = copiedSourcePkgs
|
||||
|
||||
for _, defPacks := range relatedDefs.entries {
|
||||
// Remove "linux" added above for searching oval
|
||||
// "linux" is not a real package name (key of affected packages in OVAL)
|
||||
if nfy, ok := defPacks.binpkgFixstat[kernelPkgInOVAL]; isOVALKernelPkgAdded && ok {
|
||||
defPacks.binpkgFixstat[linuxImage] = nfy
|
||||
delete(defPacks.binpkgFixstat, kernelPkgInOVAL)
|
||||
for i, p := range defPacks.def.AffectedPacks {
|
||||
if p.Name == kernelPkgInOVAL {
|
||||
p.Name = linuxImage
|
||||
defPacks.def.AffectedPacks[i] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
o.update(r, defPacks)
|
||||
}
|
||||
|
||||
for _, vuln := range r.ScannedCves {
|
||||
if conts, ok := vuln.CveContents[models.Ubuntu]; ok {
|
||||
for i, cont := range conts {
|
||||
cont.SourceLink = "http://people.ubuntu.com/~ubuntu-security/cve/" + cont.CveID
|
||||
vuln.CveContents[models.Ubuntu][i] = cont
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(relatedDefs.entries), nil
|
||||
func (o Ubuntu) FillWithOval(_ *models.ScanResult) (nCVEs int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
102
oval/oval.go
102
oval/oval.go
@@ -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
24
oval/pseudo.go
Normal 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
|
||||
}
|
||||
124
oval/redhat.go
124
oval/redhat.go
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,12 +68,15 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
|
||||
for _, d := range vuln.DistroAdvisories {
|
||||
if conts, ok := vuln.CveContents[models.Amazon]; ok {
|
||||
for i, cont := range conts {
|
||||
if strings.HasPrefix(d.AdvisoryID, "ALAS2022-") {
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/AL2022/%s.html", strings.ReplaceAll(d.AdvisoryID, "ALAS2022", "ALAS"))
|
||||
} else if strings.HasPrefix(d.AdvisoryID, "ALAS2-") {
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/AL2/%s.html", strings.ReplaceAll(d.AdvisoryID, "ALAS2", "ALAS"))
|
||||
} else if strings.HasPrefix(d.AdvisoryID, "ALAS-") {
|
||||
switch {
|
||||
case strings.HasPrefix(d.AdvisoryID, "ALAS-"):
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/%s.html", d.AdvisoryID)
|
||||
case strings.HasPrefix(d.AdvisoryID, "ALAS2-"):
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/AL2/%s.html", strings.ReplaceAll(d.AdvisoryID, "ALAS2", "ALAS"))
|
||||
case strings.HasPrefix(d.AdvisoryID, "ALAS2022-"):
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/AL2022/%s.html", strings.ReplaceAll(d.AdvisoryID, "ALAS2022", "ALAS"))
|
||||
case strings.HasPrefix(d.AdvisoryID, "ALAS2023-"):
|
||||
cont.SourceLink = fmt.Sprintf("https://alas.aws.amazon.com/AL2023/%s.html", strings.ReplaceAll(d.AdvisoryID, "ALAS2023", "ALAS"))
|
||||
}
|
||||
vuln.CveContents[models.Amazon][i] = cont
|
||||
}
|
||||
@@ -225,8 +219,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 +256,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 +280,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 +298,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 +317,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 +336,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 +355,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 +374,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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
76
oval/suse.go
76
oval/suse.go
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
228
oval/util.go
228
oval/util.go
@@ -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 {
|
||||
@@ -90,6 +93,7 @@ type request struct {
|
||||
binaryPackNames []string
|
||||
isSrcPack bool
|
||||
modularityLabel string // RHEL 8 or later only
|
||||
repository string // Amazon Linux 2 Only
|
||||
}
|
||||
|
||||
type response struct {
|
||||
@@ -99,6 +103,37 @@ type response struct {
|
||||
|
||||
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
|
||||
func getDefsByPackNameViaHTTP(r *models.ScanResult, url string) (relatedDefs ovalResult, err error) {
|
||||
ovalFamily, err := GetFamilyInOval(r.Family)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
|
||||
}
|
||||
ovalRelease := r.Release
|
||||
switch r.Family {
|
||||
case constant.CentOS:
|
||||
ovalRelease = strings.TrimPrefix(r.Release, "stream")
|
||||
case constant.Amazon:
|
||||
switch s := strings.Fields(r.Release)[0]; s {
|
||||
case "1":
|
||||
ovalRelease = "1"
|
||||
case "2":
|
||||
ovalRelease = "2"
|
||||
case "2022":
|
||||
ovalRelease = "2022"
|
||||
case "2023":
|
||||
ovalRelease = "2023"
|
||||
case "2025":
|
||||
ovalRelease = "2025"
|
||||
case "2027":
|
||||
ovalRelease = "2027"
|
||||
case "2029":
|
||||
ovalRelease = "2029"
|
||||
default:
|
||||
if _, err := time.Parse("2006.01", s); err == nil {
|
||||
ovalRelease = "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nReq := len(r.Packages) + len(r.SrcPackages)
|
||||
reqChan := make(chan request, nReq)
|
||||
resChan := make(chan response, nReq)
|
||||
@@ -109,13 +144,18 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult, url string) (relatedDefs ova
|
||||
|
||||
go func() {
|
||||
for _, pack := range r.Packages {
|
||||
reqChan <- request{
|
||||
req := request{
|
||||
packName: pack.Name,
|
||||
versionRelease: pack.FormatVer(),
|
||||
newVersionRelease: pack.FormatVer(),
|
||||
newVersionRelease: pack.FormatNewVer(),
|
||||
isSrcPack: false,
|
||||
arch: pack.Arch,
|
||||
repository: pack.Repository,
|
||||
}
|
||||
if ovalFamily == constant.Amazon && ovalRelease == "2" && req.repository == "" {
|
||||
req.repository = "amzn2-core"
|
||||
}
|
||||
reqChan <- req
|
||||
}
|
||||
for _, pack := range r.SrcPackages {
|
||||
reqChan <- request{
|
||||
@@ -128,14 +168,6 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult, url string) (relatedDefs ova
|
||||
}
|
||||
}()
|
||||
|
||||
ovalFamily, err := GetFamilyInOval(r.Family)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
|
||||
}
|
||||
ovalRelease := r.Release
|
||||
if r.Family == constant.CentOS {
|
||||
ovalRelease = strings.TrimPrefix(r.Release, "stream")
|
||||
}
|
||||
concurrency := 10
|
||||
tasks := util.GenWorkers(concurrency)
|
||||
for i := 0; i < nReq; i++ {
|
||||
@@ -165,7 +197,7 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult, url string) (relatedDefs ova
|
||||
select {
|
||||
case res := <-resChan:
|
||||
for _, def := range res.defs {
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(def, res.request, ovalFamily, r.RunningKernel, r.EnabledDnfModules)
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(def, res.request, ovalFamily, ovalRelease, r.RunningKernel, r.EnabledDnfModules)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
@@ -244,16 +276,52 @@ 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) {
|
||||
ovalFamily, err := GetFamilyInOval(r.Family)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
|
||||
}
|
||||
ovalRelease := r.Release
|
||||
switch r.Family {
|
||||
case constant.CentOS:
|
||||
ovalRelease = strings.TrimPrefix(r.Release, "stream")
|
||||
case constant.Amazon:
|
||||
switch s := strings.Fields(r.Release)[0]; s {
|
||||
case "1":
|
||||
ovalRelease = "1"
|
||||
case "2":
|
||||
ovalRelease = "2"
|
||||
case "2022":
|
||||
ovalRelease = "2022"
|
||||
case "2023":
|
||||
ovalRelease = "2023"
|
||||
case "2025":
|
||||
ovalRelease = "2025"
|
||||
case "2027":
|
||||
ovalRelease = "2027"
|
||||
case "2029":
|
||||
ovalRelease = "2029"
|
||||
default:
|
||||
if _, err := time.Parse("2006.01", s); err == nil {
|
||||
ovalRelease = "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requests := []request{}
|
||||
for _, pack := range r.Packages {
|
||||
requests = append(requests, request{
|
||||
req := request{
|
||||
packName: pack.Name,
|
||||
versionRelease: pack.FormatVer(),
|
||||
newVersionRelease: pack.FormatNewVer(),
|
||||
arch: pack.Arch,
|
||||
repository: pack.Repository,
|
||||
isSrcPack: false,
|
||||
})
|
||||
}
|
||||
if ovalFamily == constant.Amazon && ovalRelease == "2" && req.repository == "" {
|
||||
req.repository = "amzn2-core"
|
||||
}
|
||||
requests = append(requests, req)
|
||||
}
|
||||
for _, pack := range r.SrcPackages {
|
||||
requests = append(requests, request{
|
||||
@@ -264,22 +332,13 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef
|
||||
isSrcPack: true,
|
||||
})
|
||||
}
|
||||
|
||||
ovalFamily, err := GetFamilyInOval(r.Family)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to GetFamilyInOval. err: %w", err)
|
||||
}
|
||||
ovalRelease := r.Release
|
||||
if r.Family == constant.CentOS {
|
||||
ovalRelease = strings.TrimPrefix(r.Release, "stream")
|
||||
}
|
||||
for _, req := range requests {
|
||||
definitions, err := driver.GetByPackName(ovalFamily, ovalRelease, req.packName, req.arch)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to get %s OVAL info by package: %#v, err: %w", r.Family, req, err)
|
||||
}
|
||||
for _, def := range definitions {
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(def, req, ovalFamily, r.RunningKernel, r.EnabledDnfModules)
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(def, req, ovalFamily, ovalRelease, r.RunningKernel, r.EnabledDnfModules)
|
||||
if err != nil {
|
||||
return relatedDefs, xerrors.Errorf("Failed to exec isOvalAffected. err: %w", err)
|
||||
}
|
||||
@@ -311,7 +370,16 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef
|
||||
|
||||
var modularVersionPattern = regexp.MustCompile(`.+\.module(?:\+el|_f)\d{1,2}.*`)
|
||||
|
||||
func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel, enabledMods []string) (affected, notFixedYet bool, fixedIn string, err error) {
|
||||
func isOvalDefAffected(def ovalmodels.Definition, req request, family, release string, running models.Kernel, enabledMods []string) (affected, notFixedYet bool, fixedIn string, err error) {
|
||||
if family == constant.Amazon && release == "2" {
|
||||
if def.Advisory.AffectedRepository == "" {
|
||||
def.Advisory.AffectedRepository = "amzn2-core"
|
||||
}
|
||||
if req.repository != def.Advisory.AffectedRepository {
|
||||
return false, false, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, ovalPack := range def.AffectedPacks {
|
||||
if req.packName != ovalPack.Name {
|
||||
continue
|
||||
@@ -398,7 +466,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 +508,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 +558,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 +623,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 +647,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, ""
|
||||
}
|
||||
|
||||
@@ -199,11 +199,12 @@ func TestDefpacksToPackStatuses(t *testing.T) {
|
||||
|
||||
func TestIsOvalDefAffected(t *testing.T) {
|
||||
type in struct {
|
||||
def ovalmodels.Definition
|
||||
req request
|
||||
family string
|
||||
kernel models.Kernel
|
||||
mods []string
|
||||
def ovalmodels.Definition
|
||||
req request
|
||||
family string
|
||||
release string
|
||||
kernel models.Kernel
|
||||
mods []string
|
||||
}
|
||||
var tests = []struct {
|
||||
in in
|
||||
@@ -1856,10 +1857,63 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
wantErr: false,
|
||||
fixedIn: "",
|
||||
},
|
||||
// amazon linux 2 repository
|
||||
{
|
||||
in: in{
|
||||
family: constant.Amazon,
|
||||
release: "2",
|
||||
def: ovalmodels.Definition{
|
||||
Advisory: ovalmodels.Advisory{
|
||||
AffectedRepository: "amzn2-core",
|
||||
},
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
Name: "nginx",
|
||||
Version: "2.17-106.0.1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
},
|
||||
},
|
||||
req: request{
|
||||
packName: "nginx",
|
||||
versionRelease: "2.17-105.0.1",
|
||||
arch: "x86_64",
|
||||
repository: "amzn2-core",
|
||||
},
|
||||
},
|
||||
affected: true,
|
||||
fixedIn: "2.17-106.0.1",
|
||||
},
|
||||
{
|
||||
in: in{
|
||||
family: constant.Amazon,
|
||||
release: "2",
|
||||
def: ovalmodels.Definition{
|
||||
Advisory: ovalmodels.Advisory{
|
||||
AffectedRepository: "amzn2-core",
|
||||
},
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
Name: "nginx",
|
||||
Version: "2.17-106.0.1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
},
|
||||
},
|
||||
req: request{
|
||||
packName: "nginx",
|
||||
versionRelease: "2.17-105.0.1",
|
||||
arch: "x86_64",
|
||||
repository: "amzn2extra-nginx",
|
||||
},
|
||||
},
|
||||
affected: false,
|
||||
fixedIn: "",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.kernel, tt.in.mods)
|
||||
affected, notFixedYet, fixedIn, err := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.release, tt.in.kernel, tt.in.mods)
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Errorf("[%d] err\nexpected: %t\n actual: %s\n", i, tt.wantErr, err)
|
||||
}
|
||||
@@ -2049,3 +2103,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type ChatWorkWriter struct {
|
||||
Proxy string
|
||||
}
|
||||
|
||||
// Write results to ChatWork
|
||||
func (w ChatWorkWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
|
||||
for _, r := range rs {
|
||||
|
||||
@@ -23,6 +23,7 @@ type EMailWriter struct {
|
||||
Cnf config.SMTPConf
|
||||
}
|
||||
|
||||
// Write results to Email
|
||||
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
var message string
|
||||
sender := NewEMailSender(w.Cnf)
|
||||
@@ -31,7 +32,7 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
if w.FormatOneEMail {
|
||||
message += formatFullPlainText(r) + "\r\n\r\n"
|
||||
mm := r.ScannedCves.CountGroupBySeverity()
|
||||
keys := []string{"High", "Medium", "Low", "Unknown"}
|
||||
keys := []string{"Critical", "High", "Medium", "Low", "Unknown"}
|
||||
for _, k := range keys {
|
||||
m[k] += mm[k]
|
||||
}
|
||||
@@ -60,9 +61,9 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
summary := fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
summary := fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["Critical"]+m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
|
||||
origmessage := message
|
||||
if w.FormatOneEMail {
|
||||
|
||||
@@ -21,6 +21,7 @@ type GoogleChatWriter struct {
|
||||
Proxy string
|
||||
}
|
||||
|
||||
// Write results to Google Chat
|
||||
func (w GoogleChatWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
re := regexp.MustCompile(w.Cnf.ServerNameRegexp)
|
||||
|
||||
|
||||
@@ -2,27 +2,33 @@ package reporter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/reporter/sbom"
|
||||
)
|
||||
|
||||
// LocalFileWriter writes results to a local file.
|
||||
type LocalFileWriter struct {
|
||||
CurrentDir string
|
||||
DiffPlus bool
|
||||
DiffMinus bool
|
||||
FormatJSON bool
|
||||
FormatCsv bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
Gzip bool
|
||||
CurrentDir string
|
||||
DiffPlus bool
|
||||
DiffMinus bool
|
||||
FormatJSON bool
|
||||
FormatCsv bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
FormatCycloneDXJSON bool
|
||||
FormatCycloneDXXML bool
|
||||
Gzip bool
|
||||
}
|
||||
|
||||
// Write results to Local File
|
||||
func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
if w.FormatOneLineText {
|
||||
path := filepath.Join(w.CurrentDir, "summary.txt")
|
||||
@@ -87,6 +93,28 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if w.FormatCycloneDXJSON {
|
||||
bs, err := sbom.GenerateCycloneDX(cyclonedx.BOMFileFormatJSON, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to generate CycloneDX JSON. err: %w", err)
|
||||
}
|
||||
p := fmt.Sprintf("%s_cyclonedx.json", path)
|
||||
if err := w.writeFile(p, bs, 0600); err != nil {
|
||||
return xerrors.Errorf("Failed to write CycloneDX JSON. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if w.FormatCycloneDXXML {
|
||||
bs, err := sbom.GenerateCycloneDX(cyclonedx.BOMFileFormatXML, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to generate CycloneDX XML. err: %w", err)
|
||||
}
|
||||
p := fmt.Sprintf("%s_cyclonedx.xml", path)
|
||||
if err := w.writeFile(p, bs, 0600); err != nil {
|
||||
return xerrors.Errorf("Failed to write CycloneDX XML. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -99,5 +127,5 @@ func (w LocalFileWriter) writeFile(path string, data []byte, perm os.FileMode) (
|
||||
}
|
||||
path += ".gz"
|
||||
}
|
||||
return ioutil.WriteFile(path, []byte(data), perm)
|
||||
return os.WriteFile(path, []byte(data), perm)
|
||||
}
|
||||
|
||||
561
reporter/sbom/cyclonedx.go
Normal file
561
reporter/sbom/cyclonedx.go
Normal file
@@ -0,0 +1,561 @@
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/uuid"
|
||||
"github.com/package-url/packageurl-go"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// GenerateCycloneDX generates a string in CycloneDX format
|
||||
func GenerateCycloneDX(format cdx.BOMFileFormat, r models.ScanResult) ([]byte, error) {
|
||||
bom := cdx.NewBOM()
|
||||
bom.SerialNumber = uuid.New().URN()
|
||||
bom.Metadata = cdxMetadata(r)
|
||||
bom.Components, bom.Dependencies, bom.Vulnerabilities = cdxComponents(r, bom.Metadata.Component.BOMRef)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
enc := cdx.NewBOMEncoder(buf, format)
|
||||
enc.SetPretty(true)
|
||||
if err := enc.Encode(bom); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to encode CycloneDX. err: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func cdxMetadata(result models.ScanResult) *cdx.Metadata {
|
||||
metadata := cdx.Metadata{
|
||||
Timestamp: result.ReportedAt.Format(time.RFC3339),
|
||||
Tools: &[]cdx.Tool{
|
||||
{
|
||||
Vendor: "future-architect",
|
||||
Name: "vuls",
|
||||
Version: fmt.Sprintf("%s-%s", result.ReportedVersion, result.ReportedRevision),
|
||||
},
|
||||
},
|
||||
Component: &cdx.Component{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeOS,
|
||||
Name: result.ServerName,
|
||||
},
|
||||
}
|
||||
return &metadata
|
||||
}
|
||||
|
||||
func cdxComponents(result models.ScanResult, metaBomRef string) (*[]cdx.Component, *[]cdx.Dependency, *[]cdx.Vulnerability) {
|
||||
var components []cdx.Component
|
||||
bomRefs := map[string][]string{}
|
||||
|
||||
ospkgToPURL := map[string]string{}
|
||||
if ospkgComps := ospkgToCdxComponents(result.Family, result.Release, result.RunningKernel, result.Packages, result.SrcPackages, ospkgToPURL); ospkgComps != nil {
|
||||
bomRefs[metaBomRef] = append(bomRefs[metaBomRef], ospkgComps[0].BOMRef)
|
||||
for _, comp := range ospkgComps[1:] {
|
||||
bomRefs[ospkgComps[0].BOMRef] = append(bomRefs[ospkgComps[0].BOMRef], comp.BOMRef)
|
||||
}
|
||||
components = append(components, ospkgComps...)
|
||||
}
|
||||
|
||||
if cpeComps := cpeToCdxComponents(result.ScannedCves); cpeComps != nil {
|
||||
bomRefs[metaBomRef] = append(bomRefs[metaBomRef], cpeComps[0].BOMRef)
|
||||
for _, comp := range cpeComps[1:] {
|
||||
bomRefs[cpeComps[0].BOMRef] = append(bomRefs[cpeComps[0].BOMRef], comp.BOMRef)
|
||||
}
|
||||
components = append(components, cpeComps...)
|
||||
}
|
||||
|
||||
libpkgToPURL := map[string]map[string]string{}
|
||||
for _, libscanner := range result.LibraryScanners {
|
||||
libpkgToPURL[libscanner.LockfilePath] = map[string]string{}
|
||||
|
||||
libpkgComps := libpkgToCdxComponents(libscanner, libpkgToPURL)
|
||||
bomRefs[metaBomRef] = append(bomRefs[metaBomRef], libpkgComps[0].BOMRef)
|
||||
for _, comp := range libpkgComps[1:] {
|
||||
bomRefs[libpkgComps[0].BOMRef] = append(bomRefs[libpkgComps[0].BOMRef], comp.BOMRef)
|
||||
}
|
||||
components = append(components, libpkgComps...)
|
||||
}
|
||||
|
||||
ghpkgToPURL := map[string]map[string]string{}
|
||||
for _, ghm := range result.GitHubManifests {
|
||||
ghpkgToPURL[ghm.RepoURLFilename()] = map[string]string{}
|
||||
|
||||
ghpkgComps := ghpkgToCdxComponents(ghm, ghpkgToPURL)
|
||||
bomRefs[metaBomRef] = append(bomRefs[metaBomRef], ghpkgComps[0].BOMRef)
|
||||
for _, comp := range ghpkgComps[1:] {
|
||||
bomRefs[ghpkgComps[0].BOMRef] = append(bomRefs[ghpkgComps[0].BOMRef], comp.BOMRef)
|
||||
}
|
||||
components = append(components, ghpkgComps...)
|
||||
}
|
||||
|
||||
wppkgToPURL := map[string]string{}
|
||||
if wppkgComps := wppkgToCdxComponents(result.WordPressPackages, wppkgToPURL); wppkgComps != nil {
|
||||
bomRefs[metaBomRef] = append(bomRefs[metaBomRef], wppkgComps[0].BOMRef)
|
||||
for _, comp := range wppkgComps[1:] {
|
||||
bomRefs[wppkgComps[0].BOMRef] = append(bomRefs[wppkgComps[0].BOMRef], comp.BOMRef)
|
||||
}
|
||||
components = append(components, wppkgComps...)
|
||||
}
|
||||
|
||||
return &components, cdxDependencies(bomRefs), cdxVulnerabilities(result, ospkgToPURL, libpkgToPURL, ghpkgToPURL, wppkgToPURL)
|
||||
}
|
||||
|
||||
func osToCdxComponent(family, release, runningKernelRelease, runningKernelVersion string) cdx.Component {
|
||||
props := []cdx.Property{
|
||||
{
|
||||
Name: "future-architect:vuls:Type",
|
||||
Value: "Package",
|
||||
},
|
||||
}
|
||||
if runningKernelRelease != "" {
|
||||
props = append(props, cdx.Property{
|
||||
Name: "RunningKernelRelease",
|
||||
Value: runningKernelRelease,
|
||||
})
|
||||
}
|
||||
if runningKernelVersion != "" {
|
||||
props = append(props, cdx.Property{
|
||||
Name: "RunningKernelVersion",
|
||||
Value: runningKernelVersion,
|
||||
})
|
||||
}
|
||||
return cdx.Component{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeOS,
|
||||
Name: family,
|
||||
Version: release,
|
||||
Properties: &props,
|
||||
}
|
||||
}
|
||||
|
||||
func ospkgToCdxComponents(family, release string, runningKernel models.Kernel, binpkgs models.Packages, srcpkgs models.SrcPackages, ospkgToPURL map[string]string) []cdx.Component {
|
||||
if family == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
components := []cdx.Component{
|
||||
osToCdxComponent(family, release, runningKernel.Release, runningKernel.Version),
|
||||
}
|
||||
|
||||
if len(binpkgs) == 0 {
|
||||
return components
|
||||
}
|
||||
|
||||
type srcpkg struct {
|
||||
name string
|
||||
version string
|
||||
arch string
|
||||
}
|
||||
binToSrc := map[string]srcpkg{}
|
||||
for _, pack := range srcpkgs {
|
||||
for _, binpkg := range pack.BinaryNames {
|
||||
binToSrc[binpkg] = srcpkg{
|
||||
name: pack.Name,
|
||||
version: pack.Version,
|
||||
arch: pack.Arch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pack := range binpkgs {
|
||||
var props []cdx.Property
|
||||
if p, ok := binToSrc[pack.Name]; ok {
|
||||
if p.name != "" {
|
||||
props = append(props, cdx.Property{
|
||||
Name: "future-architect:vuls:SrcName",
|
||||
Value: p.name,
|
||||
})
|
||||
}
|
||||
if p.version != "" {
|
||||
props = append(props, cdx.Property{
|
||||
Name: "future-architect:vuls:SrcVersion",
|
||||
Value: p.version,
|
||||
})
|
||||
}
|
||||
if p.arch != "" {
|
||||
props = append(props, cdx.Property{
|
||||
Name: "future-architect:vuls:SrcArch",
|
||||
Value: p.arch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
purl := toPkgPURL(family, release, pack.Name, pack.Version, pack.Release, pack.Arch, pack.Repository)
|
||||
components = append(components, cdx.Component{
|
||||
BOMRef: purl,
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Name: pack.Name,
|
||||
Version: pack.Version,
|
||||
PackageURL: purl,
|
||||
Properties: &props,
|
||||
})
|
||||
|
||||
ospkgToPURL[pack.Name] = purl
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
func cpeToCdxComponents(scannedCves models.VulnInfos) []cdx.Component {
|
||||
cpes := map[string]struct{}{}
|
||||
for _, cve := range scannedCves {
|
||||
for _, cpe := range cve.CpeURIs {
|
||||
cpes[cpe] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(cpes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
components := []cdx.Component{
|
||||
{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeApplication,
|
||||
Name: "CPEs",
|
||||
Properties: &[]cdx.Property{
|
||||
{
|
||||
Name: "future-architect:vuls:Type",
|
||||
Value: "CPE",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for cpe := range cpes {
|
||||
components = append(components, cdx.Component{
|
||||
BOMRef: cpe,
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Name: cpe,
|
||||
CPE: cpe,
|
||||
})
|
||||
}
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func libpkgToCdxComponents(libscanner models.LibraryScanner, libpkgToPURL map[string]map[string]string) []cdx.Component {
|
||||
components := []cdx.Component{
|
||||
{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeApplication,
|
||||
Name: libscanner.LockfilePath,
|
||||
Properties: &[]cdx.Property{
|
||||
{
|
||||
Name: "future-architect:vuls:Type",
|
||||
Value: libscanner.Type,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, lib := range libscanner.Libs {
|
||||
purl := packageurl.NewPackageURL(libscanner.Type, "", lib.Name, lib.Version, packageurl.Qualifiers{{Key: "file_path", Value: libscanner.LockfilePath}}, "").ToString()
|
||||
components = append(components, cdx.Component{
|
||||
BOMRef: purl,
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Name: lib.Name,
|
||||
Version: lib.Version,
|
||||
PackageURL: purl,
|
||||
})
|
||||
|
||||
libpkgToPURL[libscanner.LockfilePath][lib.Name] = purl
|
||||
}
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func ghpkgToCdxComponents(m models.DependencyGraphManifest, ghpkgToPURL map[string]map[string]string) []cdx.Component {
|
||||
components := []cdx.Component{
|
||||
{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeApplication,
|
||||
Name: m.BlobPath,
|
||||
Properties: &[]cdx.Property{
|
||||
{
|
||||
Name: "future-architect:vuls:Type",
|
||||
Value: m.Ecosystem(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, dep := range m.Dependencies {
|
||||
purl := packageurl.NewPackageURL(m.Ecosystem(), "", dep.PackageName, dep.Version(), packageurl.Qualifiers{{Key: "repo_url", Value: m.Repository}, {Key: "file_path", Value: m.Filename}}, "").ToString()
|
||||
components = append(components, cdx.Component{
|
||||
BOMRef: purl,
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Name: dep.PackageName,
|
||||
Version: dep.Version(),
|
||||
PackageURL: purl,
|
||||
})
|
||||
|
||||
ghpkgToPURL[m.RepoURLFilename()][dep.PackageName] = purl
|
||||
}
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func wppkgToCdxComponents(wppkgs models.WordPressPackages, wppkgToPURL map[string]string) []cdx.Component {
|
||||
if len(wppkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
components := []cdx.Component{
|
||||
{
|
||||
BOMRef: uuid.NewString(),
|
||||
Type: cdx.ComponentTypeApplication,
|
||||
Name: "wordpress",
|
||||
Properties: &[]cdx.Property{
|
||||
{
|
||||
Name: "future-architect:vuls:Type",
|
||||
Value: "WordPress",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, wppkg := range wppkgs {
|
||||
purl := packageurl.NewPackageURL("wordpress", wppkg.Type, wppkg.Name, wppkg.Version, packageurl.Qualifiers{{Key: "status", Value: wppkg.Status}}, "").ToString()
|
||||
components = append(components, cdx.Component{
|
||||
BOMRef: purl,
|
||||
Type: cdx.ComponentTypeLibrary,
|
||||
Name: wppkg.Name,
|
||||
Version: wppkg.Version,
|
||||
PackageURL: purl,
|
||||
})
|
||||
|
||||
wppkgToPURL[wppkg.Name] = purl
|
||||
}
|
||||
|
||||
return components
|
||||
}
|
||||
|
||||
func cdxDependencies(bomRefs map[string][]string) *[]cdx.Dependency {
|
||||
dependencies := make([]cdx.Dependency, 0, len(bomRefs))
|
||||
for ref, depRefs := range bomRefs {
|
||||
ds := depRefs
|
||||
dependencies = append(dependencies, cdx.Dependency{
|
||||
Ref: ref,
|
||||
Dependencies: &ds,
|
||||
})
|
||||
}
|
||||
return &dependencies
|
||||
}
|
||||
|
||||
func toPkgPURL(osFamily, osVersion, packName, packVersion, packRelease, packArch, packRepository string) string {
|
||||
var purlType string
|
||||
switch osFamily {
|
||||
case constant.Alma, constant.Amazon, constant.CentOS, constant.Fedora, constant.OpenSUSE, constant.OpenSUSELeap, constant.Oracle, constant.RedHat, constant.Rocky, constant.SUSEEnterpriseDesktop, constant.SUSEEnterpriseServer:
|
||||
purlType = "rpm"
|
||||
case constant.Alpine:
|
||||
purlType = "apk"
|
||||
case constant.Debian, constant.Raspbian, constant.Ubuntu:
|
||||
purlType = "deb"
|
||||
case constant.FreeBSD:
|
||||
purlType = "pkg"
|
||||
case constant.Windows:
|
||||
purlType = "win"
|
||||
case constant.ServerTypePseudo:
|
||||
purlType = "pseudo"
|
||||
default:
|
||||
purlType = "unknown"
|
||||
}
|
||||
|
||||
version := packVersion
|
||||
if packRelease != "" {
|
||||
version = fmt.Sprintf("%s-%s", packVersion, packRelease)
|
||||
}
|
||||
|
||||
var qualifiers packageurl.Qualifiers
|
||||
if osVersion != "" {
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "distro",
|
||||
Value: osVersion,
|
||||
})
|
||||
}
|
||||
if packArch != "" {
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "arch",
|
||||
Value: packArch,
|
||||
})
|
||||
}
|
||||
if packRepository != "" {
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "repo",
|
||||
Value: packRepository,
|
||||
})
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(purlType, osFamily, packName, version, qualifiers, "").ToString()
|
||||
}
|
||||
|
||||
func cdxVulnerabilities(result models.ScanResult, ospkgToPURL map[string]string, libpkgToPURL, ghpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Vulnerability {
|
||||
vulnerabilities := make([]cdx.Vulnerability, 0, len(result.ScannedCves))
|
||||
for _, cve := range result.ScannedCves {
|
||||
vulnerabilities = append(vulnerabilities, cdx.Vulnerability{
|
||||
ID: cve.CveID,
|
||||
Ratings: cdxRatings(cve.CveContents),
|
||||
CWEs: cdxCWEs(cve.CveContents),
|
||||
Description: cdxDescription(cve.CveContents),
|
||||
Advisories: cdxAdvisories(cve.CveContents),
|
||||
Affects: cdxAffects(cve, ospkgToPURL, libpkgToPURL, ghpkgToPURL, wppkgToPURL),
|
||||
})
|
||||
}
|
||||
return &vulnerabilities
|
||||
}
|
||||
|
||||
func cdxRatings(cveContents models.CveContents) *[]cdx.VulnerabilityRating {
|
||||
var ratings []cdx.VulnerabilityRating
|
||||
for _, contents := range cveContents {
|
||||
for _, content := range contents {
|
||||
if content.Cvss2Score != 0 || content.Cvss2Vector != "" || content.Cvss2Severity != "" {
|
||||
ratings = append(ratings, cdxCVSS2Rating(string(content.Type), content.Cvss2Vector, content.Cvss2Score, content.Cvss2Severity))
|
||||
}
|
||||
if content.Cvss3Score != 0 || content.Cvss3Vector != "" || content.Cvss3Severity != "" {
|
||||
ratings = append(ratings, cdxCVSS3Rating(string(content.Type), content.Cvss3Vector, content.Cvss3Score, content.Cvss3Severity))
|
||||
}
|
||||
}
|
||||
}
|
||||
return &ratings
|
||||
}
|
||||
|
||||
func cdxCVSS2Rating(source, vector string, score float64, severity string) cdx.VulnerabilityRating {
|
||||
r := cdx.VulnerabilityRating{
|
||||
Source: &cdx.Source{Name: source},
|
||||
Method: cdx.ScoringMethodCVSSv2,
|
||||
Vector: vector,
|
||||
}
|
||||
if score != 0 {
|
||||
r.Score = &score
|
||||
}
|
||||
switch strings.ToLower(severity) {
|
||||
case "high":
|
||||
r.Severity = cdx.SeverityHigh
|
||||
case "medium":
|
||||
r.Severity = cdx.SeverityMedium
|
||||
case "low":
|
||||
r.Severity = cdx.SeverityLow
|
||||
default:
|
||||
r.Severity = cdx.SeverityUnknown
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func cdxCVSS3Rating(source, vector string, score float64, severity string) cdx.VulnerabilityRating {
|
||||
r := cdx.VulnerabilityRating{
|
||||
Source: &cdx.Source{Name: source},
|
||||
Method: cdx.ScoringMethodCVSSv3,
|
||||
Vector: vector,
|
||||
}
|
||||
if strings.HasPrefix(vector, "CVSS:3.1") {
|
||||
r.Method = cdx.ScoringMethodCVSSv31
|
||||
}
|
||||
if score != 0 {
|
||||
r.Score = &score
|
||||
}
|
||||
switch strings.ToLower(severity) {
|
||||
case "critical":
|
||||
r.Severity = cdx.SeverityCritical
|
||||
case "high":
|
||||
r.Severity = cdx.SeverityHigh
|
||||
case "medium":
|
||||
r.Severity = cdx.SeverityMedium
|
||||
case "low":
|
||||
r.Severity = cdx.SeverityLow
|
||||
case "none":
|
||||
r.Severity = cdx.SeverityNone
|
||||
default:
|
||||
r.Severity = cdx.SeverityUnknown
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func cdxAffects(cve models.VulnInfo, ospkgToPURL map[string]string, libpkgToPURL, ghpkgToPURL map[string]map[string]string, wppkgToPURL map[string]string) *[]cdx.Affects {
|
||||
affects := make([]cdx.Affects, 0, len(cve.AffectedPackages)+len(cve.CpeURIs)+len(cve.LibraryFixedIns)+len(cve.WpPackageFixStats))
|
||||
|
||||
for _, p := range cve.AffectedPackages {
|
||||
affects = append(affects, cdx.Affects{
|
||||
Ref: ospkgToPURL[p.Name],
|
||||
})
|
||||
}
|
||||
for _, cpe := range cve.CpeURIs {
|
||||
affects = append(affects, cdx.Affects{
|
||||
Ref: cpe,
|
||||
})
|
||||
}
|
||||
for _, lib := range cve.LibraryFixedIns {
|
||||
affects = append(affects, cdx.Affects{
|
||||
Ref: libpkgToPURL[lib.Path][lib.Name],
|
||||
})
|
||||
}
|
||||
for _, alert := range cve.GitHubSecurityAlerts {
|
||||
// TODO: not in dependency graph
|
||||
if purl, ok := ghpkgToPURL[alert.RepoURLManifestPath()][alert.Package.Name]; ok {
|
||||
affects = append(affects, cdx.Affects{
|
||||
Ref: purl,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, wppack := range cve.WpPackageFixStats {
|
||||
affects = append(affects, cdx.Affects{
|
||||
Ref: wppkgToPURL[wppack.Name],
|
||||
})
|
||||
}
|
||||
|
||||
return &affects
|
||||
}
|
||||
|
||||
func cdxCWEs(cveContents models.CveContents) *[]int {
|
||||
m := map[int]struct{}{}
|
||||
for _, contents := range cveContents {
|
||||
for _, content := range contents {
|
||||
for _, cweID := range content.CweIDs {
|
||||
if !strings.HasPrefix(cweID, "CWE-") {
|
||||
continue
|
||||
}
|
||||
i, err := strconv.Atoi(strings.TrimPrefix(cweID, "CWE-"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
cweIDs := maps.Keys(m)
|
||||
return &cweIDs
|
||||
}
|
||||
|
||||
func cdxDescription(cveContents models.CveContents) string {
|
||||
if contents, ok := cveContents[models.Nvd]; ok {
|
||||
return contents[0].Summary
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func cdxAdvisories(cveContents models.CveContents) *[]cdx.Advisory {
|
||||
urls := map[string]struct{}{}
|
||||
for _, contents := range cveContents {
|
||||
for _, content := range contents {
|
||||
if content.SourceLink != "" {
|
||||
urls[content.SourceLink] = struct{}{}
|
||||
}
|
||||
for _, r := range content.References {
|
||||
urls[r.Link] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
advisories := make([]cdx.Advisory, 0, len(urls))
|
||||
for u := range urls {
|
||||
advisories = append(advisories, cdx.Advisory{
|
||||
URL: u,
|
||||
})
|
||||
}
|
||||
return &advisories
|
||||
}
|
||||
@@ -33,12 +33,13 @@ type message struct {
|
||||
Attachments []slack.Attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// Write results to Slack
|
||||
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
|
||||
channel := w.Cnf.Channel
|
||||
for _, r := range rs {
|
||||
w.lang, w.osFamily = r.Lang, r.Family
|
||||
if channel == "${servername}" {
|
||||
channel := w.Cnf.Channel
|
||||
if w.Cnf.Channel == "${servername}" {
|
||||
channel = fmt.Sprintf("#%s", r.ServerName)
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ func (w SlackWriter) toSlackAttachments(r models.ScanResult) (attaches []slack.A
|
||||
candidate = append(candidate, "?")
|
||||
}
|
||||
for _, n := range vinfo.GitHubSecurityAlerts {
|
||||
installed = append(installed, n.PackageName)
|
||||
installed = append(installed, n.RepoURLPackageName())
|
||||
candidate = append(candidate, "?")
|
||||
}
|
||||
|
||||
@@ -326,23 +327,19 @@ func (w SlackWriter) attachmentText(vinfo models.VulnInfo, cweDict map[string]mo
|
||||
func (w SlackWriter) cweIDs(vinfo models.VulnInfo, osFamily string, cweDict models.CweDict) string {
|
||||
links := []string{}
|
||||
for _, c := range vinfo.CveContents.UniqCweIDs(osFamily) {
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := cweDict.Get(c.Value, w.lang)
|
||||
line := ""
|
||||
if top10Rank != "" {
|
||||
line = fmt.Sprintf("<%s|[OWASP Top %s]>",
|
||||
top10URL, top10Rank)
|
||||
name, url, owasp, cwe25, sans := cweDict.Get(c.Value, w.lang)
|
||||
line := fmt.Sprintf("<%s|%s>: %s", url, c.Value, name)
|
||||
for year, info := range owasp {
|
||||
links = append(links, fmt.Sprintf("<%s|[OWASP(%s) Top %s]> %s", info.URL, year, info.Rank, line))
|
||||
}
|
||||
if cweTop25Rank != "" {
|
||||
line = fmt.Sprintf("<%s|[CWE Top %s]>",
|
||||
cweTop25URL, cweTop25Rank)
|
||||
for year, info := range cwe25 {
|
||||
links = append(links, fmt.Sprintf("<%s|[CWE(%s) Top %s]> %s", info.URL, year, info.Rank, line))
|
||||
}
|
||||
if sansTop25Rank != "" {
|
||||
line = fmt.Sprintf("<%s|[CWE/SANS Top %s]>",
|
||||
sansTop25URL, sansTop25Rank)
|
||||
for year, info := range sans {
|
||||
links = append(links, fmt.Sprintf("<%s|[CWE/SANS(%s) Top %s]> %s", info.URL, year, info.Rank, line))
|
||||
}
|
||||
if top10Rank == "" && cweTop25Rank == "" && sansTop25Rank == "" {
|
||||
links = append(links, fmt.Sprintf("%s <%s|%s>: %s",
|
||||
line, url, c.Value, name))
|
||||
if len(owasp) == 0 && len(cwe25) == 0 && len(sans) == 0 {
|
||||
links = append(links, line)
|
||||
}
|
||||
}
|
||||
return strings.Join(links, "\n")
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
// StdoutWriter write to stdout
|
||||
type StdoutWriter struct {
|
||||
FormatCsv bool
|
||||
FormatFullText bool
|
||||
FormatOneLineText bool
|
||||
FormatList bool
|
||||
@@ -24,6 +23,7 @@ func (w StdoutWriter) WriteScanSummary(rs ...models.ScanResult) {
|
||||
fmt.Printf("%s\n", formatScanSummary(rs...))
|
||||
}
|
||||
|
||||
// Write results to stdout
|
||||
func (w StdoutWriter) Write(rs ...models.ScanResult) error {
|
||||
if w.FormatOneLineText {
|
||||
fmt.Print("\n\n")
|
||||
@@ -33,7 +33,7 @@ func (w StdoutWriter) Write(rs ...models.ScanResult) error {
|
||||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
if w.FormatList || w.FormatCsv {
|
||||
if w.FormatList {
|
||||
for _, r := range rs {
|
||||
fmt.Println(formatList(r))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package reporter
|
||||
|
||||
import (
|
||||
@@ -16,6 +18,7 @@ type SyslogWriter struct {
|
||||
Cnf config.SyslogConf
|
||||
}
|
||||
|
||||
// Write results to syslog
|
||||
func (w SyslogWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
facility, _ := w.Cnf.GetFacility()
|
||||
severity, _ := w.Cnf.GetSeverity()
|
||||
|
||||
@@ -21,6 +21,7 @@ type TelegramWriter struct {
|
||||
Proxy string
|
||||
}
|
||||
|
||||
// Write results to Telegram
|
||||
func (w TelegramWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
for _, r := range rs {
|
||||
msgs := []string{fmt.Sprintf("*%s*\n%s\n%s\n%s",
|
||||
|
||||
202
reporter/util.go
202
reporter/util.go
@@ -5,20 +5,22 @@ import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/cti"
|
||||
"github.com/future-architect/vuls/logging"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
@@ -40,8 +42,8 @@ func OverwriteJSONFile(dir string, r models.ScanResult) error {
|
||||
|
||||
// LoadScanResults read JSON data
|
||||
func LoadScanResults(jsonDir string) (results models.ScanResults, err error) {
|
||||
var files []os.FileInfo
|
||||
if files, err = ioutil.ReadDir(jsonDir); err != nil {
|
||||
var files []fs.DirEntry
|
||||
if files, err = os.ReadDir(jsonDir); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", jsonDir, err)
|
||||
}
|
||||
for _, f := range files {
|
||||
@@ -68,7 +70,7 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
if data, err = ioutil.ReadFile(jsonFile); err != nil {
|
||||
if data, err = os.ReadFile(jsonFile); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", jsonFile, err)
|
||||
}
|
||||
result := &models.ScanResult{}
|
||||
@@ -78,24 +80,23 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// jsonDirPattern is file name pattern of JSON directory
|
||||
// 2016-11-16T10:43:28+09:00
|
||||
// 2016-11-16T10:43:28Z
|
||||
var jsonDirPattern = regexp.MustCompile(
|
||||
`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
|
||||
|
||||
// ListValidJSONDirs returns valid json directory as array
|
||||
// Returned array is sorted so that recent directories are at the head
|
||||
func ListValidJSONDirs(resultsDir string) (dirs []string, err error) {
|
||||
var dirInfo []os.FileInfo
|
||||
if dirInfo, err = ioutil.ReadDir(resultsDir); err != nil {
|
||||
err = xerrors.Errorf("Failed to read %s: %w", resultsDir, err)
|
||||
return
|
||||
dirInfo, err := os.ReadDir(resultsDir)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to read %s: %w", resultsDir, err)
|
||||
}
|
||||
for _, d := range dirInfo {
|
||||
if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
|
||||
jsonDir := filepath.Join(resultsDir, d.Name())
|
||||
dirs = append(dirs, jsonDir)
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, layout := range []string{"2006-01-02T15:04:05Z", "2006-01-02T15:04:05-07:00", "2006-01-02T15-04-05-0700"} {
|
||||
if _, err := time.Parse(layout, d.Name()); err == nil {
|
||||
dirs = append(dirs, filepath.Join(resultsDir, d.Name()))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(dirs, func(i, j int) bool {
|
||||
@@ -128,7 +129,7 @@ func JSONDir(resultsDir string, args []string) (path string, err error) {
|
||||
|
||||
// TODO remove Pipe flag
|
||||
if config.Conf.Pipe {
|
||||
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||
bytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("Failed to read stdin: %w", err)
|
||||
}
|
||||
@@ -255,20 +256,25 @@ No CVE-IDs are found in updatable packages.
|
||||
// v2max := vinfo.MaxCvss2Score().Value.Score
|
||||
// v3max := vinfo.MaxCvss3Score().Value.Score
|
||||
|
||||
// packname := vinfo.AffectedPackages.FormatTuiSummary()
|
||||
// packname += strings.Join(vinfo.CpeURIs, ", ")
|
||||
pkgNames := vinfo.AffectedPackages.Names()
|
||||
pkgNames = append(pkgNames, vinfo.CpeURIs...)
|
||||
pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...)
|
||||
pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...)
|
||||
pkgNames = append(pkgNames, vinfo.LibraryFixedIns.Names()...)
|
||||
pkgNames = append(pkgNames, vinfo.WindowsKBFixedIns...)
|
||||
packnames := strings.Join(pkgNames, ", ")
|
||||
|
||||
exploits := ""
|
||||
if 0 < len(vinfo.Exploits) || 0 < len(vinfo.Metasploits) {
|
||||
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 +285,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 +300,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 +381,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
|
||||
@@ -398,7 +406,7 @@ No CVE-IDs are found in updatable packages.
|
||||
}
|
||||
|
||||
for _, alert := range vuln.GitHubSecurityAlerts {
|
||||
data = append(data, []string{"GitHub", alert.PackageName})
|
||||
data = append(data, []string{"GitHub", alert.RepoURLPackageName()})
|
||||
}
|
||||
|
||||
for _, wp := range vuln.WpPackageFixStats {
|
||||
@@ -412,8 +420,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})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,35 +433,50 @@ No CVE-IDs are found in updatable packages.
|
||||
}
|
||||
}
|
||||
|
||||
if len(vuln.WindowsKBFixedIns) > 0 {
|
||||
data = append(data, []string{"Windows KB", fmt.Sprintf("FixedIn: %s", strings.Join(vuln.WindowsKBFixedIns, ", "))})
|
||||
}
|
||||
|
||||
for _, confidence := range vuln.Confidences {
|
||||
data = append(data, []string{"Confidence", confidence.String()})
|
||||
}
|
||||
|
||||
cweURLs, top10URLs := []string{}, []string{}
|
||||
cweTop25URLs, sansTop25URLs := []string{}, []string{}
|
||||
cweURLs, top10URLs, cweTop25URLs, sansTop25URLs := []string{}, map[string][]string{}, map[string][]string{}, map[string][]string{}
|
||||
for _, v := range vuln.CveContents.UniqCweIDs(r.Family) {
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := r.CweDict.Get(v.Value, r.Lang)
|
||||
if top10Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[OWASP Top%s] %s: %s (%s)",
|
||||
top10Rank, v.Value, name, v.Type)})
|
||||
top10URLs = append(top10URLs, top10URL)
|
||||
name, url, owasp, cwe25, sans := r.CweDict.Get(v.Value, r.Lang)
|
||||
|
||||
ds := [][]string{}
|
||||
for year, info := range owasp {
|
||||
ds = append(ds, []string{"CWE", fmt.Sprintf("[OWASP(%s) Top%s] %s: %s (%s)", year, info.Rank, v.Value, name, v.Type)})
|
||||
top10URLs[year] = append(top10URLs[year], info.URL)
|
||||
}
|
||||
if cweTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE Top%s] %s: %s (%s)",
|
||||
cweTop25Rank, v.Value, name, v.Type)})
|
||||
cweTop25URLs = append(cweTop25URLs, cweTop25URL)
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[1] < b[1]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
|
||||
ds = [][]string{}
|
||||
for year, info := range cwe25 {
|
||||
ds = append(ds, []string{"CWE", fmt.Sprintf("[CWE(%s) Top%s] %s: %s (%s)", year, info.Rank, v.Value, name, v.Type)})
|
||||
cweTop25URLs[year] = append(cweTop25URLs[year], info.URL)
|
||||
}
|
||||
if sansTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE/SANS Top%s] %s: %s (%s)",
|
||||
sansTop25Rank, v.Value, name, v.Type)})
|
||||
sansTop25URLs = append(sansTop25URLs, sansTop25URL)
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[1] < b[1]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
|
||||
ds = [][]string{}
|
||||
for year, info := range sans {
|
||||
ds = append(ds, []string{"CWE", fmt.Sprintf("[CWE/SANS(%s) Top%s] %s: %s (%s)", year, info.Rank, v.Value, name, v.Type)})
|
||||
sansTop25URLs[year] = append(sansTop25URLs[year], info.URL)
|
||||
}
|
||||
if top10Rank == "" && cweTop25Rank == "" && sansTop25Rank == "" {
|
||||
data = append(data, []string{"CWE", fmt.Sprintf("%s: %s (%s)",
|
||||
v.Value, name, v.Type)})
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[1] < b[1]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
|
||||
if len(owasp) == 0 && len(cwe25) == 0 && len(sans) == 0 {
|
||||
data = append(data, []string{"CWE", fmt.Sprintf("%s: %s (%s)", v.Value, name, v.Type)})
|
||||
}
|
||||
cweURLs = append(cweURLs, url)
|
||||
}
|
||||
@@ -462,18 +484,44 @@ 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})
|
||||
|
||||
for year, urls := range top10URLs {
|
||||
ds := [][]string{}
|
||||
for _, url := range urls {
|
||||
ds = append(ds, []string{fmt.Sprintf("OWASP(%s) Top10", year), url})
|
||||
}
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[0] < b[0]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
}
|
||||
if len(cweTop25URLs) != 0 {
|
||||
data = append(data, []string{"CWE Top25", cweTop25URLs[0]})
|
||||
|
||||
ds := [][]string{}
|
||||
for year, urls := range cweTop25URLs {
|
||||
ds = append(ds, []string{fmt.Sprintf("CWE(%s) Top25", year), urls[0]})
|
||||
}
|
||||
if len(sansTop25URLs) != 0 {
|
||||
data = append(data, []string{"SANS/CWE Top25", sansTop25URLs[0]})
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[0] < b[0]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
|
||||
ds = [][]string{}
|
||||
for year, urls := range sansTop25URLs {
|
||||
ds = append(ds, []string{fmt.Sprintf("SANS/CWE(%s) Top25", year), urls[0]})
|
||||
}
|
||||
slices.SortFunc(ds, func(a, b []string) bool {
|
||||
return a[0] < b[0]
|
||||
})
|
||||
data = append(data, ds...)
|
||||
|
||||
for _, alert := range vuln.AlertDict.CISA {
|
||||
data = append(data, []string{"CISA Alert", alert.URL})
|
||||
@@ -487,6 +535,22 @@ No CVE-IDs are found in updatable packages.
|
||||
data = append(data, []string{"US-CERT Alert", alert.URL})
|
||||
}
|
||||
|
||||
attacks := []string{}
|
||||
for _, techniqueID := range vuln.Ctis {
|
||||
if strings.HasPrefix(techniqueID, "CAPEC-") {
|
||||
continue
|
||||
}
|
||||
technique, ok := cti.TechniqueDict[techniqueID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
attacks = append(attacks, technique.Name)
|
||||
}
|
||||
slices.Sort(attacks)
|
||||
for _, attack := range attacks {
|
||||
data = append(data, []string{"MITER ATT&CK", attack})
|
||||
}
|
||||
|
||||
// for _, rr := range vuln.CveContents.References(r.Family) {
|
||||
// for _, ref := range rr.Value {
|
||||
// data = append(data, []string{ref.Source, ref.Link})
|
||||
@@ -614,7 +678,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 +698,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
|
||||
@@ -672,11 +736,7 @@ func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
}
|
||||
|
||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
cTypes := []models.CveContentType{
|
||||
models.Nvd,
|
||||
models.Jvn,
|
||||
models.NewCveContentType(current.Family),
|
||||
}
|
||||
cTypes := append([]models.CveContentType{models.Nvd, models.Jvn}, models.GetCveContentTypes(current.Family)...)
|
||||
|
||||
prevLastModifieds := map[models.CveContentType][]time.Time{}
|
||||
preVinfo, ok := previous.ScannedCves[cveID]
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
),
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user