Compare commits
	
		
			138 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7585f9d537 | ||
| 
						 | 
					76037cdf72 | ||
| 
						 | 
					98c5421edc | ||
| 
						 | 
					e63fc7e3f5 | ||
| 
						 | 
					6ed9cf3fb4 | ||
| 
						 | 
					9865eab2c0 | ||
| 
						 | 
					678e72a8b6 | ||
| 
						 | 
					ec41899089 | ||
| 
						 | 
					b2d913cc21 | ||
| 
						 | 
					bc86c24e6a | ||
| 
						 | 
					87a77dd95c | ||
| 
						 | 
					e8188f3432 | ||
| 
						 | 
					50506be546 | ||
| 
						 | 
					4ded028258 | ||
| 
						 | 
					6da8b3c4a1 | ||
| 
						 | 
					d5c92cbcb3 | ||
| 
						 | 
					ed5f98d6f0 | ||
| 
						 | 
					f854b8f908 | ||
| 
						 | 
					de7a6159d4 | ||
| 
						 | 
					6090a34037 | ||
| 
						 | 
					f566745479 | ||
| 
						 | 
					153234b623 | ||
| 
						 | 
					ac510d21ff | ||
| 
						 | 
					44fa2c5800 | ||
| 
						 | 
					d785fc2a54 | ||
| 
						 | 
					ea800e04bc | ||
| 
						 | 
					fe582ac635 | ||
| 
						 | 
					330edb3bce | ||
| 
						 | 
					212fec7115 | ||
| 
						 | 
					24d7021c47 | ||
| 
						 | 
					e3a01ff6a8 | ||
| 
						 | 
					81f2ba8a46 | ||
| 
						 | 
					9e9370b178 | ||
| 
						 | 
					ced6114a95 | ||
| 
						 | 
					3144faae5d | ||
| 
						 | 
					8960c67a82 | ||
| 
						 | 
					f8ca924434 | ||
| 
						 | 
					399a08775e | ||
| 
						 | 
					92f36ca558 | ||
| 
						 | 
					3dcc58205a | ||
| 
						 | 
					09779962cf | ||
| 
						 | 
					9cc78770a3 | ||
| 
						 | 
					f653ca9131 | ||
| 
						 | 
					6f9fd91849 | ||
| 
						 | 
					cb1aec4fc0 | ||
| 
						 | 
					7cebaf8a76 | ||
| 
						 | 
					241c943424 | ||
| 
						 | 
					d5d88d8cf0 | ||
| 
						 | 
					cf9d26068c | ||
| 
						 | 
					308a93dc72 | ||
| 
						 | 
					d6a7e65e4c | ||
| 
						 | 
					e0a5c5d3b8 | ||
| 
						 | 
					314f775243 | ||
| 
						 | 
					7a1644135a | ||
| 
						 | 
					5076326589 | ||
| 
						 | 
					ce56261b52 | ||
| 
						 | 
					baa0e897b2 | ||
| 
						 | 
					1d49c0e1ce | ||
| 
						 | 
					08755e446e | ||
| 
						 | 
					bb12d9dadb | ||
| 
						 | 
					fd1429fef0 | ||
| 
						 | 
					d3c421a4a8 | ||
| 
						 | 
					0c919da4b1 | ||
| 
						 | 
					9afbf1255f | ||
| 
						 | 
					50b105c4af | ||
| 
						 | 
					028508c1f7 | ||
| 
						 | 
					f0137a3695 | ||
| 
						 | 
					e6d3a1718c | ||
| 
						 | 
					86ba551e07 | ||
| 
						 | 
					26418be937 | ||
| 
						 | 
					092a19bdc1 | ||
| 
						 | 
					6d3398574c | ||
| 
						 | 
					b08969ad89 | ||
| 
						 | 
					0653656526 | ||
| 
						 | 
					7a5793c562 | ||
| 
						 | 
					562ff7807d | ||
| 
						 | 
					7971bdf7f7 | ||
| 
						 | 
					d926b7fd6d | ||
| 
						 | 
					c00404793a | ||
| 
						 | 
					a0e0ee6c1e | ||
| 
						 | 
					4ccbee705b | ||
| 
						 | 
					db43d55b2c | ||
| 
						 | 
					5a3a333eec | ||
| 
						 | 
					039edf1616 | ||
| 
						 | 
					47498bbf23 | ||
| 
						 | 
					cc28bf4ae2 | ||
| 
						 | 
					0e8736045e | ||
| 
						 | 
					19b581edef | ||
| 
						 | 
					295f6656d9 | ||
| 
						 | 
					1214d8c14d | ||
| 
						 | 
					b4cd96fc9a | ||
| 
						 | 
					3238a9b898 | ||
| 
						 | 
					c0f66320f6 | ||
| 
						 | 
					383220f384 | ||
| 
						 | 
					76a9c37e6b | ||
| 
						 | 
					e788e6a5ad | ||
| 
						 | 
					d00e912934 | ||
| 
						 | 
					8ebb663368 | ||
| 
						 | 
					445ffc4123 | ||
| 
						 | 
					6af49f4d55 | ||
| 
						 | 
					1de9e8c086 | ||
| 
						 | 
					59b0812adf | ||
| 
						 | 
					719785c1ed | ||
| 
						 | 
					8e5f627e59 | ||
| 
						 | 
					5ced3c72b8 | ||
| 
						 | 
					c002f0168c | ||
| 
						 | 
					00c690f516 | ||
| 
						 | 
					ab68ad5cc5 | ||
| 
						 | 
					5c84ebefab | ||
| 
						 | 
					eb2acaff22 | ||
| 
						 | 
					84d0655c52 | ||
| 
						 | 
					e137ebb9c2 | ||
| 
						 | 
					10d690d929 | ||
| 
						 | 
					14611d2fd9 | ||
| 
						 | 
					0665bfe15f | ||
| 
						 | 
					473096d35d | ||
| 
						 | 
					0eae26e261 | ||
| 
						 | 
					a32845f652 | ||
| 
						 | 
					15a0f7eadb | ||
| 
						 | 
					5a0a6abf11 | ||
| 
						 | 
					032b8d9572 | ||
| 
						 | 
					5798e3af83 | ||
| 
						 | 
					8e15b9ce1c | ||
| 
						 | 
					7a1f132c1f | ||
| 
						 | 
					a8483b2195 | ||
| 
						 | 
					83bbbd0cb0 | ||
| 
						 | 
					132432dce6 | ||
| 
						 | 
					e5eb8e42f5 | ||
| 
						 | 
					1095ebea24 | ||
| 
						 | 
					1541a602b2 | ||
| 
						 | 
					03a141c252 | ||
| 
						 | 
					5f2183fc8e | ||
| 
						 | 
					820831fa5d | ||
| 
						 | 
					6d2d767c52 | ||
| 
						 | 
					e0c3a728ae | ||
| 
						 | 
					ec92f7797f | ||
| 
						 | 
					0ba490c6df | ||
| 
						 | 
					cfd668e11d | 
							
								
								
									
										7
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
.dockerignore
 | 
			
		||||
Dockerfile
 | 
			
		||||
vendor/
 | 
			
		||||
cve.sqlite3*
 | 
			
		||||
oval.sqlite3*
 | 
			
		||||
setup/
 | 
			
		||||
img/
 | 
			
		||||
							
								
								
									
										46
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,26 @@
 | 
			
		||||
 | 
			
		||||
# Environment
 | 
			
		||||
# What did you do? (required. The issue will be **closed** when not provided.)
 | 
			
		||||
 | 
			
		||||
## Vuls
 | 
			
		||||
 | 
			
		||||
# What did you expect to happen?
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# What happened instead?
 | 
			
		||||
 | 
			
		||||
* Current Output
 | 
			
		||||
 | 
			
		||||
Please re-run the command using ```-debug``` and provide the output below.
 | 
			
		||||
 | 
			
		||||
# Steps to reproduce the behaviour
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Configuration (**MUST** fill this out):
 | 
			
		||||
 | 
			
		||||
* Go version (`go version`):
 | 
			
		||||
 | 
			
		||||
* Go environment (`go env`):
 | 
			
		||||
 | 
			
		||||
* Vuls environment:
 | 
			
		||||
 | 
			
		||||
Hash : ____
 | 
			
		||||
 | 
			
		||||
@@ -9,28 +28,11 @@ To check the commit hash of HEAD
 | 
			
		||||
$ vuls -v
 | 
			
		||||
 | 
			
		||||
or
 | 
			
		||||
 | 
			
		||||
$ cd $GOPATH/src/github.com/future-architect/vuls 
 | 
			
		||||
$ git rev-parse --short HEAD 
 | 
			
		||||
 | 
			
		||||
## OS
 | 
			
		||||
- Target Server: Write here
 | 
			
		||||
- Vuls Server: Write here
 | 
			
		||||
 | 
			
		||||
## Go
 | 
			
		||||
- Go version: here
 | 
			
		||||
 | 
			
		||||
# Current Output
 | 
			
		||||
 | 
			
		||||
Please re-run the command using ```-debug``` and provide the output below.
 | 
			
		||||
 | 
			
		||||
# Addition Details
 | 
			
		||||
 | 
			
		||||
Can you also please fill in each of the remaining sections.
 | 
			
		||||
 | 
			
		||||
## Expected Behavior
 | 
			
		||||
 | 
			
		||||
## Actual Behavior
 | 
			
		||||
 | 
			
		||||
## Steps to reproduce the behaviour
 | 
			
		||||
* config.toml:
 | 
			
		||||
 | 
			
		||||
* command:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@@ -1,14 +1,26 @@
 | 
			
		||||
## What did you implement:
 | 
			
		||||
 | 
			
		||||
Closes #XXXXX
 | 
			
		||||
If this Pull Request is work in progress, Add a prefix of “[WIP]” in the title.
 | 
			
		||||
 | 
			
		||||
## How did you implement it:
 | 
			
		||||
# What did you implement:
 | 
			
		||||
 | 
			
		||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. 
 | 
			
		||||
 | 
			
		||||
## How can we verify it:
 | 
			
		||||
Fixes # (issue)
 | 
			
		||||
 | 
			
		||||
## Type of change
 | 
			
		||||
 | 
			
		||||
## Todos:
 | 
			
		||||
Please delete options that are not relevant.
 | 
			
		||||
 | 
			
		||||
- [ ] Bug fix (non-breaking change which fixes an issue)
 | 
			
		||||
- [ ] New feature (non-breaking change which adds functionality)
 | 
			
		||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
 | 
			
		||||
- [ ] This change requires a documentation update
 | 
			
		||||
 | 
			
		||||
# How Has This Been Tested?
 | 
			
		||||
 | 
			
		||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
 | 
			
		||||
 | 
			
		||||
# Checklist:
 | 
			
		||||
You don't have to satisfy all of the following.
 | 
			
		||||
 | 
			
		||||
- [ ] Write tests
 | 
			
		||||
@@ -21,4 +33,8 @@ You don't have to satisfy all of the following.
 | 
			
		||||
- [ ] Update the messages below
 | 
			
		||||
 | 
			
		||||
***Is this ready for review?:*** NO  
 | 
			
		||||
***Is it a breaking change?:*** NO
 | 
			
		||||
 | 
			
		||||
# Reference
 | 
			
		||||
 | 
			
		||||
* https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
vuls
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.goreleaser.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
project_name: vuls
 | 
			
		||||
release:
 | 
			
		||||
  github:
 | 
			
		||||
    owner: future-architect
 | 
			
		||||
    name: vuls
 | 
			
		||||
builds:
 | 
			
		||||
- goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - amd64
 | 
			
		||||
  main: .
 | 
			
		||||
  ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
archive:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
 | 
			
		||||
    .Arm }}{{ end }}'
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
snapshot:
 | 
			
		||||
  name_template: SNAPSHOT-{{ .Commit }}
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - 1.8
 | 
			
		||||
  - "1.11.x"
 | 
			
		||||
 | 
			
		||||
after_success:
 | 
			
		||||
  - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
# Change Log
 | 
			
		||||
 | 
			
		||||
## v0.4.1 and later, see [GitHub release](https://github.com/future-architect/vuls/releases)
 | 
			
		||||
 | 
			
		||||
## [v0.4.0](https://github.com/future-architect/vuls/tree/v0.4.0) (2017-08-25)
 | 
			
		||||
[Full Changelog](https://github.com/future-architect/vuls/compare/v0.3.0...v0.4.0)
 | 
			
		||||
 | 
			
		||||
@@ -509,4 +511,4 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
FROM golang:alpine as builder
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
        git \
 | 
			
		||||
        make \
 | 
			
		||||
        gcc \
 | 
			
		||||
        musl-dev
 | 
			
		||||
 | 
			
		||||
ENV REPOSITORY github.com/future-architect/vuls
 | 
			
		||||
COPY . $GOPATH/src/$REPOSITORY
 | 
			
		||||
RUN cd $GOPATH/src/$REPOSITORY && make install
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FROM alpine:3.7
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
ENV LOGDIR /var/log/vuls
 | 
			
		||||
ENV WORKDIR /vuls
 | 
			
		||||
 | 
			
		||||
RUN apk add --no-cache \
 | 
			
		||||
        openssh-client \
 | 
			
		||||
        ca-certificates \
 | 
			
		||||
    && mkdir -p $WORKDIR $LOGDIR
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /go/bin/vuls /usr/local/bin/
 | 
			
		||||
 | 
			
		||||
VOLUME [$WORKDIR, $LOGDIR]
 | 
			
		||||
WORKDIR $WORKDIR
 | 
			
		||||
ENV PWD $WORKDIR
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["vuls"]
 | 
			
		||||
CMD ["--help"]
 | 
			
		||||
							
								
								
									
										34
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								GNUmakefile
									
									
									
									
									
								
							@@ -15,50 +15,52 @@
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./cache ./commands ./config ./models ./oval ./report ./scan ./util 
 | 
			
		||||
PKGS = $(shell go list ./...)
 | 
			
		||||
VERSION := $(shell git describe --tags --abbrev=0)
 | 
			
		||||
REVISION := $(shell git rev-parse --short HEAD)
 | 
			
		||||
LDFLAGS := -X 'main.version=$(VERSION)' \
 | 
			
		||||
	-X 'main.revision=$(REVISION)'
 | 
			
		||||
LDFLAGS := -X 'github.com/future-architect/vuls/config.Version=$(VERSION)' \
 | 
			
		||||
	-X 'github.com/future-architect/vuls/config.Revision=$(REVISION)'
 | 
			
		||||
 | 
			
		||||
all: dep build test
 | 
			
		||||
all: dep build
 | 
			
		||||
 | 
			
		||||
dep:
 | 
			
		||||
	go get -u github.com/golang/dep/...
 | 
			
		||||
	dep ensure
 | 
			
		||||
	dep ensure -v
 | 
			
		||||
 | 
			
		||||
depup:
 | 
			
		||||
	go get -u github.com/golang/dep/...
 | 
			
		||||
	dep ensure -update
 | 
			
		||||
	dep ensure -update -v
 | 
			
		||||
 | 
			
		||||
build: main.go dep
 | 
			
		||||
build: main.go dep pretest
 | 
			
		||||
	go build -ldflags "$(LDFLAGS)" -o vuls $<
 | 
			
		||||
 | 
			
		||||
install: main.go dep
 | 
			
		||||
install: main.go dep pretest
 | 
			
		||||
	go install -ldflags "$(LDFLAGS)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	@ go get -v github.com/golang/lint/golint
 | 
			
		||||
	$(foreach file,$(SRCS),golint $(file) || exit;)
 | 
			
		||||
	@ go get -v golang.org/x/lint/golint
 | 
			
		||||
	golint $(PKGS)
 | 
			
		||||
 | 
			
		||||
vet:
 | 
			
		||||
	#  @-go get -v golang.org/x/tools/cmd/vet
 | 
			
		||||
	echo $(PKGS) | xargs go vet || exit;
 | 
			
		||||
	go vet ./... || exit;
 | 
			
		||||
 | 
			
		||||
fmt:
 | 
			
		||||
	gofmt -w $(SRCS)
 | 
			
		||||
	gofmt -s -w $(SRCS)
 | 
			
		||||
 | 
			
		||||
mlint:
 | 
			
		||||
	$(foreach file,$(SRCS),gometalinter $(file) || exit;)
 | 
			
		||||
 | 
			
		||||
fmtcheck:
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -d $(file);)
 | 
			
		||||
	$(foreach file,$(SRCS),gofmt -s -d $(file);)
 | 
			
		||||
 | 
			
		||||
pretest: lint vet fmtcheck
 | 
			
		||||
 | 
			
		||||
test: pretest
 | 
			
		||||
	go install
 | 
			
		||||
test: 
 | 
			
		||||
	echo $(PKGS) | xargs go test -cover -v || exit;
 | 
			
		||||
 | 
			
		||||
unused :
 | 
			
		||||
unused:
 | 
			
		||||
	$(foreach pkg,$(PKGS),unused $(pkg);)
 | 
			
		||||
 | 
			
		||||
cov:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1010
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1010
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										85
									
								
								Gopkg.toml
									
									
									
									
									
								
							
							
						
						
									
										85
									
								
								Gopkg.toml
									
									
									
									
									
								
							@@ -1,7 +1,6 @@
 | 
			
		||||
 | 
			
		||||
# Gopkg.toml example
 | 
			
		||||
#
 | 
			
		||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
 | 
			
		||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
 | 
			
		||||
# for detailed Gopkg.toml documentation.
 | 
			
		||||
#
 | 
			
		||||
# required = ["github.com/user/thing/cmd/thing"]
 | 
			
		||||
@@ -17,74 +16,26 @@
 | 
			
		||||
#   source = "github.com/myfork/project2"
 | 
			
		||||
#
 | 
			
		||||
# [[override]]
 | 
			
		||||
#  name = "github.com/x/y"
 | 
			
		||||
#  version = "2.4.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/BurntSushi/toml"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/asaskevich/govalidator"
 | 
			
		||||
  version = "6.0.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/boltdb/bolt"
 | 
			
		||||
  version = "1.3.1"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/cenkalti/backoff"
 | 
			
		||||
  version = "1.0.0"
 | 
			
		||||
#   name = "github.com/x/y"
 | 
			
		||||
#   version = "2.4.0"
 | 
			
		||||
#
 | 
			
		||||
# [prune]
 | 
			
		||||
#   non-go = false
 | 
			
		||||
#   go-tests = true
 | 
			
		||||
#   unused-packages = true
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/knqyf263/gost"
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/gosuri/uitable"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/howeyc/gopass"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/jroimartin/gocui"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/k0kubun/pp"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-deb-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
  version = "0.1.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/parnurzeal/gorequest"
 | 
			
		||||
  version = "0.2.15"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/rifflock/lfshook"
 | 
			
		||||
  version = "1.7.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
  branch = "master"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/mozqnet/go-exploitdb"
 | 
			
		||||
  branch = "master"
 | 
			
		||||
 | 
			
		||||
[prune]
 | 
			
		||||
  go-tests = true
 | 
			
		||||
  unused-packages = true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							@@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    Vuls - Vulnerability Scanner
 | 
			
		||||
    Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
    Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
  If the program does terminal interaction, make it output a short
 | 
			
		||||
notice like this when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Vuls  Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
    Vuls  Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								NOTICE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								NOTICE
									
									
									
									
									
								
							@@ -1,2 +1,2 @@
 | 
			
		||||
Vuls Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Vuls Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										224
									
								
								README.fr.md
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								README.fr.md
									
									
									
									
									
								
							@@ -1,224 +0,0 @@
 | 
			
		||||
 | 
			
		||||
# Vuls: VULnerability Scanner
 | 
			
		||||
 | 
			
		||||
[](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
 | 
			
		||||
Scanneur de vulnérabilité Linux, sans agent, écrit en golang
 | 
			
		||||
 | 
			
		||||
Nous avons une équipe Slack. [Rejoignez notre Slack Team](http://goo.gl/forms/xm5KFo35tu)  
 | 
			
		||||
 | 
			
		||||
[README en English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
[README en Japonais](https://github.com/future-architect/vuls/blob/master/README.ja.md)  
 | 
			
		||||
 | 
			
		||||
[](https://asciinema.org/a/3y9zrf950agiko7klg8abvyck)
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Résumé
 | 
			
		||||
 | 
			
		||||
Effectuer des recherches de vulnérabilités et des mises à jour quotidiennes peut etre un fardeau pour un administrateur système.
 | 
			
		||||
Afin d'éviter des interruptions systèmes dans un environnement de production, il est fréquent pour un administrateur système de choisir de ne pas utiliser la fonction de mise à jour automatique proposée par le gestionnaire de paquets et d'effecter ces mises à jour manuellement.
 | 
			
		||||
Ce qui implique les problèmes suivants :
 | 
			
		||||
- L'administrateur système devra surveiller constamment toutes les nouvelles vulnérabilités dans NVD (National Vulnerability Database) etc.
 | 
			
		||||
- Il pourrait être impossible pour un administrateur système de surveiller tous les logiciels installés sur un serveur.
 | 
			
		||||
- Il est coûteux d'effectuer une analyse pour déterminer quels sont les serveurs affectés par de nouvelles vulnérabilités. La possibilité de négliger un serveur ou deux est bien présente.
 | 
			
		||||
 | 
			
		||||
Vuls est un outil crée pour palier aux problèmes listés ci-dessus. Voici ses caractéristiques.
 | 
			
		||||
- Informer les utilisateurs des vulnérabilités système.
 | 
			
		||||
- Informer les utilisateurs des systèmes concernés. 
 | 
			
		||||
- La détection de vulnérabilités est effectuée automatiquement pour éviter toute négligence.
 | 
			
		||||
- Les rapports sont générés régulièrement via CRON pour mieux gérer ces vulnérabilités.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Caractéristiques principales
 | 
			
		||||
 | 
			
		||||
- Recherche de vulnérabilités sur des serveurs Linux
 | 
			
		||||
    - Supporte Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbian
 | 
			
		||||
    - Cloud, auto-hébergement, Docker
 | 
			
		||||
- Scan d'intergiciels non inclus dans le gestionnaire de paquets de l'OS
 | 
			
		||||
    - Scan d'intergiciels, de libraries de language de programmation et framework pour des vulnérabilités
 | 
			
		||||
    - Supporte les logiciels inscrits au CPE
 | 
			
		||||
- Architecture sans agent
 | 
			
		||||
    - L'utilisateur doit seulement mettre en place VULS sur une seule machine qui se connectera aux autres via SSH
 | 
			
		||||
- Génération automatique des fichiers de configuration
 | 
			
		||||
    - Auto detection de serveurs via CIDR et génération de configuration
 | 
			
		||||
- Email et notification Slack possibles (supporte le Japonais) 
 | 
			
		||||
- Les résultats d'un scan sont accessibles dans un shell via TUI Viewer terminal.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Ce que Vuls ne fait pas
 | 
			
		||||
 | 
			
		||||
- Vuls ne met pas à jour les programmes affectés par les vulnérabilités découvertes.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
# Hello Vuls 
 | 
			
		||||
 | 
			
		||||
Ce tutoriel décrit la recherche de vulnérabilités sur une machine locale avec Vuls.
 | 
			
		||||
Voici les étapes à suivre. 
 | 
			
		||||
 | 
			
		||||
1. Démrarrage d'Amazon Linux
 | 
			
		||||
1. Autoriser les connexions SSH depuis localhost
 | 
			
		||||
1. Installation des prérequis
 | 
			
		||||
1. Déploiement de go-cve-dictionary
 | 
			
		||||
1. Deploiement de Vuls
 | 
			
		||||
1. Configuration
 | 
			
		||||
1. Préparation
 | 
			
		||||
1. Scan
 | 
			
		||||
1. TUI(Terminal-Based User Interface)
 | 
			
		||||
 | 
			
		||||
## Step1. Démrarrage d'Amazon Linux
 | 
			
		||||
 | 
			
		||||
- Nous utilisons dans cette exemple une vieille AMI (amzn-ami-hvm-2015.09.1.x86_64-gp2 - ami-383c1956)
 | 
			
		||||
- Taille de l'instance : t2.medium
 | 
			
		||||
    - La première fois, t2.medium et plus sont requis pour la récupération des CVE depuis NVD (2.3GB de mémoire utilisé)
 | 
			
		||||
    - Une fois la récupération initiale des données NVD terminée vous pouvez passer sur une instance t2.nano.
 | 
			
		||||
- Ajoutez la configuration suivante au cloud-init, afin d'éviter une mise à jour automatique lors du premier démarrage.
 | 
			
		||||
 | 
			
		||||
    - [Q: How do I disable the automatic installation of critical and important security updates on initial launch?](https://aws.amazon.com/amazon-linux-ami/faqs/?nc1=h_ls)
 | 
			
		||||
    ```
 | 
			
		||||
    #cloud-config
 | 
			
		||||
    repo_upgrade: none
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
## Step2. Paramètres SSH
 | 
			
		||||
 | 
			
		||||
Il est obligatoire que le serveur puisse se connecter à son propre serveur SSH
 | 
			
		||||
 | 
			
		||||
Générez une paire de clés SSH et ajoutez la clé publique dans le fichier authorized_keys
 | 
			
		||||
```bash
 | 
			
		||||
$ ssh-keygen -t rsa
 | 
			
		||||
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
 | 
			
		||||
$ chmod 600 ~/.ssh/authorized_keys
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step3. Installation des prérequis
 | 
			
		||||
 | 
			
		||||
Vuls requiert l'installation des paquets suivants : 
 | 
			
		||||
 | 
			
		||||
- sqlite
 | 
			
		||||
- git
 | 
			
		||||
- gcc
 | 
			
		||||
- go v1.7.1 or later
 | 
			
		||||
    - https://golang.org/doc/install
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ ssh ec2-user@52.100.100.100  -i ~/.ssh/private.pem
 | 
			
		||||
$ sudo yum -y install sqlite git gcc
 | 
			
		||||
$ wget https://storage.googleapis.com/golang/go1.7.1.linux-amd64.tar.gz
 | 
			
		||||
$ sudo tar -C /usr/local -xzf go1.7.1.linux-amd64.tar.gz
 | 
			
		||||
$ mkdir $HOME/go
 | 
			
		||||
```
 | 
			
		||||
Ajoutez les lignes suivantes dans /etc/profile.d/goenv.sh
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
export GOROOT=/usr/local/go
 | 
			
		||||
export GOPATH=$HOME/go
 | 
			
		||||
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Ajoutons ces nouvelles variables d’environnement au shell
 | 
			
		||||
```bash
 | 
			
		||||
$ source /etc/profile.d/goenv.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step4. Déploiement de [go-cve-dictionary](https://github.com/kotakanbe/go-cve-dictionary)
 | 
			
		||||
 | 
			
		||||
go get
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ sudo mkdir /var/log/vuls
 | 
			
		||||
$ sudo chown ec2-user /var/log/vuls
 | 
			
		||||
$ sudo chmod 700 /var/log/vuls
 | 
			
		||||
$ go get github.com/kotakanbe/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Démarrez go-cve-dictionary en mode serveur.
 | 
			
		||||
Lors de son premier démarrage go-cve-dictionary récupère la liste des vulnérabilités depuis NVD
 | 
			
		||||
Cette opération prend environ 10 minutes (sur AWS).  
 | 
			
		||||
 | 
			
		||||
## Step5. Déploiement de Vuls
 | 
			
		||||
 | 
			
		||||
Ouvrez un second terminal, connectez vous à l'instance ec2 via SSH
 | 
			
		||||
 | 
			
		||||
go get
 | 
			
		||||
```
 | 
			
		||||
$ go get github.com/future-architect/vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step6. Configuration
 | 
			
		||||
 | 
			
		||||
Créez un fichier de configuration (TOML format).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ cat config.toml
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.172-31-4-82]
 | 
			
		||||
host         = "172.31.4.82"
 | 
			
		||||
port        = "22"
 | 
			
		||||
user        = "ec2-user"
 | 
			
		||||
keyPath     = "/home/ec2-user/.ssh/id_rsa"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step7. Configuration des serveurs cibles vuls  
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls prepare
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step8. Scan
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls scan -cve-dictionary-dbpath=$PWD/cve.sqlite3
 | 
			
		||||
INFO[0000] Begin scanning (config: /home/ec2-user/config.toml)
 | 
			
		||||
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
172-31-4-82 (amazon 2015.09)
 | 
			
		||||
============================
 | 
			
		||||
CVE-2016-0494   10.0    Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle
 | 
			
		||||
                        Java SE 6u105, 7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to
 | 
			
		||||
                        affect confidentiality, integrity, and availability via unknown vectors related to
 | 
			
		||||
                        2D.
 | 
			
		||||
... snip ...
 | 
			
		||||
 | 
			
		||||
CVE-2016-0494
 | 
			
		||||
-------------
 | 
			
		||||
Score           10.0 (High)
 | 
			
		||||
Vector          (AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
Summary         Unspecified vulnerability in the Java SE and Java SE Embedded components in Oracle Java SE 6u105,
 | 
			
		||||
                7u91, and 8u66 and Java SE Embedded 8u65 allows remote attackers to affect confidentiality,
 | 
			
		||||
                integrity, and availability via unknown vectors related to 2D.
 | 
			
		||||
NVD             https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0494
 | 
			
		||||
MITRE           https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0494
 | 
			
		||||
CVE Details     http://www.cvedetails.com/cve/CVE-2016-0494
 | 
			
		||||
CVSS Calculator https://nvd.nist.gov/cvss/v2-calculator?name=CVE-2016-0494&vector=(AV:N/AC:L/Au:N/C:C/I:C/A:C)
 | 
			
		||||
RHEL-CVE        https://access.redhat.com/security/cve/CVE-2016-0494
 | 
			
		||||
ALAS-2016-643   https://alas.aws.amazon.com/ALAS-2016-643.html
 | 
			
		||||
Package/CPE     java-1.7.0-openjdk-1.7.0.91-2.6.2.2.63.amzn1 -> java-1.7.0-openjdk-1:1.7.0.95-2.6.4.0.65.amzn1
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step9. TUI
 | 
			
		||||
 | 
			
		||||
Les résultats de Vuls peuvent etre affichés dans un Shell via TUI (Terminal-Based User Interface).
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ vuls tui
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
For more information see [README in English](https://github.com/future-architect/vuls/blob/master/README.md)  
 | 
			
		||||
							
								
								
									
										1882
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
									
										1882
									
								
								README.ja.md
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -163,7 +163,7 @@ func (b Bolt) GetChangelog(servername, packName string) (changelog string, err e
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
			return fmt.Errorf("Failed to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		v := bkt.Get([]byte(packName))
 | 
			
		||||
		if v == nil {
 | 
			
		||||
@@ -181,11 +181,8 @@ func (b Bolt) PutChangelog(servername, packName, changelog string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
			return fmt.Errorf("Failed to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
		return bkt.Put([]byte(packName), []byte(changelog))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -33,15 +33,8 @@ import (
 | 
			
		||||
// ConfigtestCmd is Subcommand
 | 
			
		||||
type ConfigtestCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -54,7 +47,6 @@ func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
			[-deep]
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-log-dir=/path/to/log]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
@@ -63,6 +55,7 @@ func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
			[-containers-only]
 | 
			
		||||
			[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
			[-debug]
 | 
			
		||||
			[-vvv]
 | 
			
		||||
 | 
			
		||||
			[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
@@ -75,48 +68,40 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&p.timeoutSec, "timeout", 5*60, "Timeout(Sec)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.deep, "deep", false, "Config test for deep scan mode")
 | 
			
		||||
	f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshNative,
 | 
			
		||||
		"ssh-native-insecure",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false,
 | 
			
		||||
		"Use Native Go implementation of SSH. Default: Use the external command")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.containersOnly,
 | 
			
		||||
		"containers-only",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHConfig, "ssh-config", false,
 | 
			
		||||
		"Use SSH options specified in ssh_config preferentially")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ContainersOnly, "containers-only", false,
 | 
			
		||||
		"Test containers only. Default: Test both of hosts and containers")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Vvv, "vvv", false, "ssh -vvv")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := mkdirDotVuls(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to create .vuls: %s", err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
@@ -134,10 +119,6 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
		util.Log.Errorf("Please check README: https://github.com/future-architect/vuls#configuration")
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
@@ -174,12 +155,22 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking Scan Modes...")
 | 
			
		||||
	if err := scan.CheckScanModes(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Fix config.toml: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking dependencies...")
 | 
			
		||||
	scan.CheckDependencies(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking sudo settings...")
 | 
			
		||||
	scan.CheckIfSudoNoPasswd(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	scan.PrintSSHableServerNames()
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
	util.Log.Info("It can be scanned with fast scan mode even if warn or err messages are displayed due to lack of dependent packages or sudo settings in fast-root or deep scan mode")
 | 
			
		||||
 | 
			
		||||
	if scan.PrintSSHableServerNames() {
 | 
			
		||||
		return subcommands.ExitSuccess
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitFailure
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -57,6 +57,7 @@ func (p *DiscoverCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// validate
 | 
			
		||||
	if len(f.Args()) == 0 {
 | 
			
		||||
		logrus.Errorf("Usage: " + p.Usage())
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +66,6 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
			CIDR: cidr,
 | 
			
		||||
			PingOptions: []string{
 | 
			
		||||
				"-c1",
 | 
			
		||||
				"-t1",
 | 
			
		||||
			},
 | 
			
		||||
			NumOfConcurrency: 100,
 | 
			
		||||
		}
 | 
			
		||||
@@ -90,59 +90,133 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
// Output the template of config.toml
 | 
			
		||||
func printConfigToml(ips []string) (err error) {
 | 
			
		||||
	const tomlTemplate = `
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
#channel      = "${servername}"
 | 
			
		||||
iconEmoji    = ":ghost:"
 | 
			
		||||
authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[email]
 | 
			
		||||
smtpAddr      = "smtp.example.com"
 | 
			
		||||
smtpPort      = "587"
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@example.com"
 | 
			
		||||
to            = ["to@example.com"]
 | 
			
		||||
cc            = ["cc@example.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html
 | 
			
		||||
[cveDict]
 | 
			
		||||
type        = "sqlite3"
 | 
			
		||||
sqlite3Path = "/path/to/cve.sqlite3"
 | 
			
		||||
#url        = ""
 | 
			
		||||
 | 
			
		||||
[ovalDict]
 | 
			
		||||
type        = "sqlite3"
 | 
			
		||||
sqlite3Path = "/path/to/oval.sqlite3"
 | 
			
		||||
#url        = ""
 | 
			
		||||
 | 
			
		||||
[gost]
 | 
			
		||||
type        = "sqlite3"
 | 
			
		||||
sqlite3Path = "/path/to/gost.sqlite3"
 | 
			
		||||
#url        = ""
 | 
			
		||||
 | 
			
		||||
[exploit]
 | 
			
		||||
type        = "sqlite3"
 | 
			
		||||
sqlite3Path = "/path/to/go-exploitdb.sqlite3"
 | 
			
		||||
#url        = ""
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#slack-section
 | 
			
		||||
#[slack]
 | 
			
		||||
#hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
##legacyToken = "xoxp-11111111111-222222222222-3333333333"
 | 
			
		||||
#channel      = "#channel-name"
 | 
			
		||||
##channel     = "${servername}"
 | 
			
		||||
#iconEmoji    = ":ghost:"
 | 
			
		||||
#authUser     = "username"
 | 
			
		||||
#notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#email-section
 | 
			
		||||
#[email]
 | 
			
		||||
#smtpAddr      = "smtp.example.com"
 | 
			
		||||
#smtpPort      = "587"
 | 
			
		||||
#user          = "username"
 | 
			
		||||
#password      = "password"
 | 
			
		||||
#from          = "from@example.com"
 | 
			
		||||
#to            = ["to@example.com"]
 | 
			
		||||
#cc            = ["cc@example.com"]
 | 
			
		||||
#subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#http-section
 | 
			
		||||
#[http]
 | 
			
		||||
#url = "http://localhost:11234"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#syslog-section
 | 
			
		||||
#[syslog]
 | 
			
		||||
#protocol    = "tcp"
 | 
			
		||||
#host        = "localhost"
 | 
			
		||||
#port        = "514"
 | 
			
		||||
#tag         = "vuls"
 | 
			
		||||
#facility    = "local0"
 | 
			
		||||
#severity    = "alert"
 | 
			
		||||
#verbose     = false
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-report.html#example-put-results-in-s3-bucket
 | 
			
		||||
#[aws]
 | 
			
		||||
#profile                = "default"
 | 
			
		||||
#region                 = "ap-northeast-1"
 | 
			
		||||
#s3Bucket               = "vuls"
 | 
			
		||||
#s3ResultsDir           = "/path/to/result"
 | 
			
		||||
#s3ServerSideEncryption = "AES256"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-report.html#example-put-results-in-azure-blob-storage<Paste>
 | 
			
		||||
#[azure]
 | 
			
		||||
#accountName   = "default"
 | 
			
		||||
#accountKey    = "xxxxxxxxxxxxxx"
 | 
			
		||||
#containerName = "vuls"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#stride-section
 | 
			
		||||
#[stride]
 | 
			
		||||
#hookURL   = "xxxxxxxxxxxxxxx"
 | 
			
		||||
#authToken = "xxxxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#hipchat-section
 | 
			
		||||
#[hipchat]
 | 
			
		||||
#room      = "vuls"
 | 
			
		||||
#authToken = "xxxxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#chatwork-section
 | 
			
		||||
#[chatwork]
 | 
			
		||||
#room     = "xxxxxxxxxxx"
 | 
			
		||||
#apiToken = "xxxxxxxxxxxxxxxxxx"
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#default-section
 | 
			
		||||
[default]
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "username"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#port               = "22"
 | 
			
		||||
#user               = "username"
 | 
			
		||||
#keyPath            = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#scanMode           = ["fast", "fast-root", "deep", "offline"]
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-6271"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#containers = ["${running}"]
 | 
			
		||||
 | 
			
		||||
#owaspDCXMLPath     = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves         = ["CVE-2014-6271"]
 | 
			
		||||
#containerType      = "docker" #or "lxd" or "lxc" default: docker
 | 
			
		||||
#containersIncluded = ["${running}"]
 | 
			
		||||
#containersExcluded = ["container_name_a"]
 | 
			
		||||
 | 
			
		||||
# https://vuls.io/docs/en/usage-settings.html#servers-section
 | 
			
		||||
[servers]
 | 
			
		||||
{{- $names:=  .Names}}
 | 
			
		||||
{{range $i, $ip := .IPs}}
 | 
			
		||||
[servers.{{index $names $i}}]
 | 
			
		||||
host         = "{{$ip}}"
 | 
			
		||||
#port        = "22"
 | 
			
		||||
#user        = "root"
 | 
			
		||||
#keyPath     = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#cpeNames = [
 | 
			
		||||
#  "cpe:/a:rubyonrails:ruby_on_rails:4.2.1",
 | 
			
		||||
#]
 | 
			
		||||
#dependencyCheckXMLPath = "/tmp/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves = ["CVE-2014-0160"]
 | 
			
		||||
#optional = [
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#[servers.{{index $names $i}}.containers]
 | 
			
		||||
#type = "docker" #or "lxd" default: docker
 | 
			
		||||
#includes = ["${running}"]
 | 
			
		||||
#excludes = ["container_name_a", "4aa37a8b63b9"]
 | 
			
		||||
host                = "{{$ip}}"
 | 
			
		||||
#port               = "22"
 | 
			
		||||
#user               = "root"
 | 
			
		||||
#keyPath            = "/home/username/.ssh/id_rsa"
 | 
			
		||||
#scanMode           = ["fast", "fast-root", "deep", "offline"]
 | 
			
		||||
#type               = "pseudo"
 | 
			
		||||
#memo               = "DB Server"
 | 
			
		||||
#cpeNames           = [ "cpe:/a:rubyonrails:ruby_on_rails:4.2.1" ]
 | 
			
		||||
#owaspDCXMLPath     = "/path/to/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves         = ["CVE-2014-0160"]
 | 
			
		||||
#containerType      = "docker" #or "lxd" or "lxc" default: docker
 | 
			
		||||
#containersIncluded = ["${running}"]
 | 
			
		||||
#containersExcluded = ["container_name_a"]
 | 
			
		||||
 | 
			
		||||
#[servers.{{index $names $i}}.containers.container_name_a]
 | 
			
		||||
#cpeNames       = [ "cpe:/a:rubyonrails:ruby_on_rails:4.2.1" ]
 | 
			
		||||
#owaspDCXMLPath = "/path/to/dependency-check-report.xml"
 | 
			
		||||
#ignoreCves     = ["CVE-2014-0160"]
 | 
			
		||||
 | 
			
		||||
#[servers.{{index $names $i}}.optional]
 | 
			
		||||
#key = "value1"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -32,11 +32,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HistoryCmd is Subcommand of list scanned results
 | 
			
		||||
type HistoryCmd struct {
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
}
 | 
			
		||||
type HistoryCmd struct{}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*HistoryCmd) Name() string { return "history" }
 | 
			
		||||
@@ -56,19 +52,16 @@ func (*HistoryCmd) Usage() string {
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *HistoryCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
 | 
			
		||||
	dirs, err := report.ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,67 +20,29 @@ package commands
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	lang       string
 | 
			
		||||
	debug      bool
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	logDir     string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	httpProxy          string
 | 
			
		||||
 | 
			
		||||
	cveDBType string
 | 
			
		||||
	cveDBPath string
 | 
			
		||||
	cveDBURL  string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	toSlack     bool
 | 
			
		||||
	toEMail     bool
 | 
			
		||||
	toLocalFile bool
 | 
			
		||||
	toS3        bool
 | 
			
		||||
	toAzureBlob bool
 | 
			
		||||
 | 
			
		||||
	formatJSON        bool
 | 
			
		||||
	formatXML         bool
 | 
			
		||||
	formatOneEMail    bool
 | 
			
		||||
	formatOneLineText bool
 | 
			
		||||
	formatShortText   bool
 | 
			
		||||
	formatFullText    bool
 | 
			
		||||
 | 
			
		||||
	gzip bool
 | 
			
		||||
 | 
			
		||||
	awsProfile      string
 | 
			
		||||
	awsS3Bucket     string
 | 
			
		||||
	awsS3ResultsDir string
 | 
			
		||||
	awsRegion       string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
	azureContainer string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
 | 
			
		||||
	diff bool
 | 
			
		||||
	configPath  string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
	httpConf    c.HTTPConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -98,236 +60,163 @@ func (*ReportCmd) Usage() string {
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-http]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-stride]
 | 
			
		||||
		[-to-hipchat]
 | 
			
		||||
		[-to-chatwork]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-to-saas]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-xml]
 | 
			
		||||
		[-format-one-email]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-short-text]
 | 
			
		||||
		[-format-list]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-aws-s3-results-dir=/bucket/path/to/results]
 | 
			
		||||
		[-azure-account=account]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-uuid]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
		[-http="http://vuls-report-server"]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
	f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.RefreshCve, "refresh-cve", false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBType,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBPath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDBURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
		0,
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.diff,
 | 
			
		||||
		"diff",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Difference between previous result and current result "))
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.ignoreUnscoredCves,
 | 
			
		||||
		"ignore-unscored-cves",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatJSON,
 | 
			
		||||
		"format-json",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("JSON format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatXML,
 | 
			
		||||
		"format-xml",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("XML format"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneEMail,
 | 
			
		||||
		"format-one-email",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatXML, "format-xml", false, "XML format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatOneEMail, "format-one-email", false,
 | 
			
		||||
		"Send all the host report via only one EMail (Specify with -to-email)")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatOneLineText, "format-one-line-text", false,
 | 
			
		||||
		"One line summary in plain text")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatList, "format-list", false, "Display as list format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatFullText, "format-full-text", false,
 | 
			
		||||
		"Detail report in plain text")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatOneLineText,
 | 
			
		||||
		"format-one-line-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("One line summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatShortText,
 | 
			
		||||
		"format-short-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Summary in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.formatFullText,
 | 
			
		||||
		"format-full-text",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Detail report in plain text"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.gzip, "gzip", false, "gzip compression")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&p.toLocalFile,
 | 
			
		||||
		"to-localfile",
 | 
			
		||||
		false,
 | 
			
		||||
		fmt.Sprintf("Write report to localfile"))
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toS3,
 | 
			
		||||
		"to-s3",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToStride, "to-stride", false, "Send report via Stride")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToHipChat, "to-hipchat", false, "Send report via hipchat")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToChatWork, "to-chatwork", false, "Send report via chatwork")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToEmail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSyslog, "to-syslog", false, "Send report via Syslog")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToS3, "to-s3", false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
	f.StringVar(&p.awsS3ResultsDir, "aws-s3-results-dir", "", "/bucket/path/to/results")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob,
 | 
			
		||||
		"to-azure-blob",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.ToHTTP, "to-http", false, "Send report via HTTP POST")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToAzureBlob, "to-azure-blob", false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.StringVar(&p.azureAccount,
 | 
			
		||||
		"azure-account",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureKey,
 | 
			
		||||
		"azure-key",
 | 
			
		||||
		"",
 | 
			
		||||
		"Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified")
 | 
			
		||||
	f.StringVar(&p.azureContainer, "azure-container", "", "Azure storage container name")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSaas, "to-saas", false,
 | 
			
		||||
		"Upload report to Future Vuls(https://vuls.biz/) before report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.GZIP, "gzip", false, "gzip compression")
 | 
			
		||||
	f.BoolVar(&c.Conf.UUID, "uuid", false,
 | 
			
		||||
		"Auto generate of scan target servers and then write to config.toml and scan result")
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use args passed via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.httpConf.URL, "http", "", "-to-http http://vuls-report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use args passed via PIPE")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.RefreshCve = p.refreshCve
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.CveDBType = p.cveDBType
 | 
			
		||||
	c.Conf.CveDBPath = p.cveDBPath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDBURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	c.Conf.FormatXML = p.formatXML
 | 
			
		||||
	c.Conf.FormatJSON = p.formatJSON
 | 
			
		||||
	c.Conf.FormatOneEMail = p.formatOneEMail
 | 
			
		||||
	c.Conf.FormatOneLineText = p.formatOneLineText
 | 
			
		||||
	c.Conf.FormatShortText = p.formatShortText
 | 
			
		||||
	c.Conf.FormatFullText = p.formatFullText
 | 
			
		||||
 | 
			
		||||
	c.Conf.GZIP = p.gzip
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
	c.Conf.HTTP.Overwrite(p.httpConf)
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.diff {
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
@@ -342,96 +231,181 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toSlack {
 | 
			
		||||
	if c.Conf.ToSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toEMail {
 | 
			
		||||
	if c.Conf.ToStride {
 | 
			
		||||
		reports = append(reports, report.StrideWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToHipChat {
 | 
			
		||||
		reports = append(reports, report.HipChatWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToChatWork {
 | 
			
		||||
		reports = append(reports, report.ChatWorkWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToEmail {
 | 
			
		||||
		reports = append(reports, report.EMailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toLocalFile {
 | 
			
		||||
	if c.Conf.ToSyslog {
 | 
			
		||||
		reports = append(reports, report.SyslogWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToHTTP {
 | 
			
		||||
		reports = append(reports, report.HTTPRequestWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToLocalFile {
 | 
			
		||||
		reports = append(reports, report.LocalFileWriter{
 | 
			
		||||
			CurrentDir: dir,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toS3 {
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		c.Conf.S3ResultsDir = p.awsS3ResultsDir
 | 
			
		||||
	if c.Conf.ToS3 {
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s",
 | 
			
		||||
				c.Conf.AWS.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.toAzureBlob {
 | 
			
		||||
		c.Conf.AzureAccount = p.azureAccount
 | 
			
		||||
		if len(c.Conf.AzureAccount) == 0 {
 | 
			
		||||
			c.Conf.AzureAccount = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
	if c.Conf.ToAzureBlob {
 | 
			
		||||
		if len(c.Conf.Azure.AccountName) == 0 {
 | 
			
		||||
			c.Conf.Azure.AccountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureKey = p.azureKey
 | 
			
		||||
		if len(c.Conf.AzureKey) == 0 {
 | 
			
		||||
			c.Conf.AzureKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		if len(c.Conf.Azure.AccountKey) == 0 {
 | 
			
		||||
			c.Conf.Azure.AccountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if len(c.Conf.AzureContainer) == 0 {
 | 
			
		||||
		if len(c.Conf.Azure.ContainerName) == 0 {
 | 
			
		||||
			util.Log.Error("Azure storage container name is required with -azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a container beforehand: %s, err: %s", c.Conf.AzureContainer, err)
 | 
			
		||||
			util.Log.Errorf("Check if there is a container beforehand: %s, err: %s",
 | 
			
		||||
				c.Conf.Azure.ContainerName, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(p.formatJSON || p.formatOneLineText ||
 | 
			
		||||
		p.formatShortText || p.formatFullText || p.formatXML) {
 | 
			
		||||
		c.Conf.FormatShortText = true
 | 
			
		||||
	if c.Conf.ToSaas {
 | 
			
		||||
		if !c.Conf.UUID {
 | 
			
		||||
			util.Log.Errorf("If you use the -to-saas option, you need to enable the uuid option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.SaasWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(c.Conf.FormatJSON || c.Conf.FormatOneLineText ||
 | 
			
		||||
		c.Conf.FormatList || c.Conf.FormatFullText || c.Conf.FormatXML) {
 | 
			
		||||
		c.Conf.FormatList = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
		util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with -cvedb-path option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conf.CveDBURL != "" {
 | 
			
		||||
		util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBURL)
 | 
			
		||||
	} else {
 | 
			
		||||
		if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
			util.Log.Infof("cve-dictionary: %s", c.Conf.CveDBPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDBURL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with -ovaldb-path option")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
	var loaded models.ScanResults
 | 
			
		||||
	if loaded, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	for _, r := range loaded {
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			res = append(res, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Warnf("Ignored since errors occurred during scanning: %s",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range res {
 | 
			
		||||
		util.Log.Debugf("%s: %s",
 | 
			
		||||
			r.ServerInfo(),
 | 
			
		||||
			pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.UUID {
 | 
			
		||||
		// Ensure UUIDs of scan target servers in config.toml
 | 
			
		||||
		if err := report.EnsureUUIDs(p.configPath, res); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to ensure UUIDs: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !c.Conf.ToSaas {
 | 
			
		||||
		util.Log.Info("Validating db config...")
 | 
			
		||||
		if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.CveDict.URL != "" {
 | 
			
		||||
			if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
				util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
				util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
			err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
				util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.Gost.URL != "" {
 | 
			
		||||
			util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
			err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("gost HTTP server is not running. err: %s", err)
 | 
			
		||||
				util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.Exploit.URL != "" {
 | 
			
		||||
			err := exploit.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("exploit HTTP server is not running. err: %s", err)
 | 
			
		||||
				util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
			CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
			OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
			GostCnf:     c.Conf.Gost,
 | 
			
		||||
			ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
			DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
		})
 | 
			
		||||
		if locked {
 | 
			
		||||
			util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to init DB Clients: %s", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
		if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
@@ -440,5 +414,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								commands/scan.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -35,18 +35,8 @@ import (
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	debug          bool
 | 
			
		||||
	configPath     string
 | 
			
		||||
	resultsDir     string
 | 
			
		||||
	logDir         string
 | 
			
		||||
	cacheDBPath    string
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	skipBroken     bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	pipe           bool
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
	scanTimeoutSec int
 | 
			
		||||
}
 | 
			
		||||
@@ -61,12 +51,12 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-deep]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cachedb-path=/path/to/cache.db]
 | 
			
		||||
		[-ssh-native-insecure]
 | 
			
		||||
		[-ssh-config]
 | 
			
		||||
		[-containers-only]
 | 
			
		||||
		[-skip-broken]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
@@ -75,6 +65,7 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
		[-timeout-scan=7200]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-vvv]
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
@@ -82,93 +73,63 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cacheDBPath,
 | 
			
		||||
		"cachedb-path",
 | 
			
		||||
		defaultCacheDBPath,
 | 
			
		||||
	f.StringVar(&c.Conf.CacheDBPath, "cachedb-path", defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.sshNative,
 | 
			
		||||
		"ssh-native-insecure",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false,
 | 
			
		||||
		"Use Native Go implementation of SSH. Default: Use the external command")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.containersOnly,
 | 
			
		||||
		"containers-only",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHConfig, "ssh-config", false,
 | 
			
		||||
		"Use SSH options specified in ssh_config preferentially")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ContainersOnly, "containers-only", false,
 | 
			
		||||
		"Scan containers only. Default: Scan both of hosts and containers")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.skipBroken,
 | 
			
		||||
		"skip-broken",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.SkipBroken, "skip-broken", false,
 | 
			
		||||
		"[For CentOS] yum update changelog with --skip-broken option")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://proxy-url:port (default: empty)",
 | 
			
		||||
	)
 | 
			
		||||
	f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.askKeyPassword,
 | 
			
		||||
		"ask-key-password",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.deep,
 | 
			
		||||
		"deep",
 | 
			
		||||
		false,
 | 
			
		||||
		"Deep scan mode. Scan accuracy improves and scanned information becomes richer. Since analysis of changelog, issue commands requiring sudo, but it may be slower and high load on the target server")
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use stdin via PIPE")
 | 
			
		||||
	f.BoolVar(&c.Conf.Vvv, "vvv", false, "ssh -vvv")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(
 | 
			
		||||
		&p.timeoutSec,
 | 
			
		||||
		"timeout",
 | 
			
		||||
		5*60,
 | 
			
		||||
	f.IntVar(&p.timeoutSec, "timeout", 5*60,
 | 
			
		||||
		"Number of seconds for processing other than scan",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.IntVar(
 | 
			
		||||
		&p.scanTimeoutSec,
 | 
			
		||||
		"timeout-scan",
 | 
			
		||||
		120*60,
 | 
			
		||||
	f.IntVar(&p.scanTimeoutSec, "timeout-scan", 120*60,
 | 
			
		||||
		"Number of seconds for scanning vulnerabilities for all servers",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := mkdirDotVuls(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to create .vuls: %s", err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
@@ -190,7 +151,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
	util.Log.Info("Start scanning")
 | 
			
		||||
	util.Log.Infof("config: %s", p.configPath)
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
@@ -226,14 +186,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("%s", pp.Sprintf("%v", target))
 | 
			
		||||
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CacheDBPath = p.cacheDBPath
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
	c.Conf.SkipBroken = p.skipBroken
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnScan() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
@@ -245,6 +197,12 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking Scan Modes... ")
 | 
			
		||||
	if err := scan.CheckScanModes(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Fix config.toml: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										239
									
								
								commands/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								commands/server.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	// "github.com/future-architect/vuls/Server"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/server"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ServerCmd is subcommand for server
 | 
			
		||||
type ServerCmd struct {
 | 
			
		||||
	configPath  string
 | 
			
		||||
	listen      string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ServerCmd) Name() string { return "server" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ServerCmd) Synopsis() string { return "Server" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ServerCmd) Usage() string {
 | 
			
		||||
	return `Server:
 | 
			
		||||
	Server
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-listen=localhost:5515]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ServerCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means Servering CVSS Score 6.5 and over (default: 0 (means Server all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't Server the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't Server the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
 | 
			
		||||
	f.StringVar(&p.listen, "listen", "localhost:5515",
 | 
			
		||||
		"host:port (default: localhost:5515)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ServerCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating db config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.CveDict.URL != "" {
 | 
			
		||||
		if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
			util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Gost.URL != "" {
 | 
			
		||||
		util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
		err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("gost HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Exploit.URL != "" {
 | 
			
		||||
		err := exploit.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("exploit HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
		CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
		OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
		GostCnf:     c.Conf.Gost,
 | 
			
		||||
		ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
		DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
	})
 | 
			
		||||
	if locked {
 | 
			
		||||
		util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init DB Clients: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
	http.Handle("/vuls", server.VulsHandler{DBclient: *dbclient})
 | 
			
		||||
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		fmt.Fprintf(w, "ok")
 | 
			
		||||
	})
 | 
			
		||||
	util.Log.Infof("Listening on %s", p.listen)
 | 
			
		||||
	if err := http.ListenAndServe(p.listen, nil); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to start server: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										238
									
								
								commands/tui.go
									
									
									
									
									
								
							
							
						
						
									
										238
									
								
								commands/tui.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -24,32 +24,23 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	lang       string
 | 
			
		||||
	debugSQL   bool
 | 
			
		||||
	debug      bool
 | 
			
		||||
	configPath string
 | 
			
		||||
	logDir     string
 | 
			
		||||
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
	configPath  string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
@@ -62,19 +53,29 @@ func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabiliti
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
@@ -82,68 +83,61 @@ func (*TuiCmd) Usage() string {
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&p.debugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
	f.BoolVar(&p.debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&p.resultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.refreshCve,
 | 
			
		||||
		"refresh-cve",
 | 
			
		||||
		false,
 | 
			
		||||
	f.BoolVar(&c.Conf.RefreshCve, "refresh-cve", false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.example.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.example.com:1324 or mysql connection string")
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
		false,
 | 
			
		||||
		"Use stdin via PIPE")
 | 
			
		||||
		&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use stdin via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
@@ -151,37 +145,36 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	c.Conf.Debug = p.debug
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.LogDir = p.logDir
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	log := util.Log
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %s", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
 | 
			
		||||
	log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	dir, err := report.JSONDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
@@ -189,7 +182,66 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
	util.Log.Info("Validating db config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.CveDict.URL != "" {
 | 
			
		||||
		if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
			util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Gost.URL != "" {
 | 
			
		||||
		util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
		err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("gost HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Exploit.URL != "" {
 | 
			
		||||
		err := exploit.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("exploit HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
		CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
		OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
		GostCnf:     c.Conf.Gost,
 | 
			
		||||
		ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
		DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
	})
 | 
			
		||||
	if locked {
 | 
			
		||||
		util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init DB Clients: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -19,8 +19,11 @@ package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/howeyc/gopass"
 | 
			
		||||
	homedir "github.com/mitchellh/go-homedir"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getPasswd(prompt string) (string, error) {
 | 
			
		||||
@@ -31,8 +34,22 @@ func getPasswd(prompt string) (string, error) {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read password")
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(pass) {
 | 
			
		||||
			return string(pass[:]), nil
 | 
			
		||||
			return string(pass), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mkdirDotVuls() error {
 | 
			
		||||
	home, err := homedir.Dir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	dotVuls := filepath.Join(home, ".vuls")
 | 
			
		||||
	if _, err := os.Stat(dotVuls); os.IsNotExist(err) {
 | 
			
		||||
		if err := os.Mkdir(dotVuls, 0700); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										991
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										991
									
								
								config/config.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										103
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								config/config_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSyslogConfValidate(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		conf              SyslogConf
 | 
			
		||||
		expectedErrLength int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			conf:              SyslogConf{},
 | 
			
		||||
			expectedErrLength: 0,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			conf: SyslogConf{
 | 
			
		||||
				Protocol: "tcp",
 | 
			
		||||
				Port:     "5140",
 | 
			
		||||
			},
 | 
			
		||||
			expectedErrLength: 0,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			conf: SyslogConf{
 | 
			
		||||
				Protocol: "udp",
 | 
			
		||||
				Port:     "12345",
 | 
			
		||||
				Severity: "emerg",
 | 
			
		||||
				Facility: "user",
 | 
			
		||||
			},
 | 
			
		||||
			expectedErrLength: 0,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			conf: SyslogConf{
 | 
			
		||||
				Protocol: "foo",
 | 
			
		||||
				Port:     "514",
 | 
			
		||||
			},
 | 
			
		||||
			expectedErrLength: 1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			conf: SyslogConf{
 | 
			
		||||
				Protocol: "invalid",
 | 
			
		||||
				Port:     "-1",
 | 
			
		||||
			},
 | 
			
		||||
			expectedErrLength: 2,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			conf: SyslogConf{
 | 
			
		||||
				Protocol: "invalid",
 | 
			
		||||
				Port:     "invalid",
 | 
			
		||||
				Severity: "invalid",
 | 
			
		||||
				Facility: "invalid",
 | 
			
		||||
			},
 | 
			
		||||
			expectedErrLength: 4,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		Conf.ToSyslog = true
 | 
			
		||||
		errs := tt.conf.Validate()
 | 
			
		||||
		if len(errs) != tt.expectedErrLength {
 | 
			
		||||
			t.Errorf("test: %d, expected %d, actual %d", i, tt.expectedErrLength, len(errs))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMajorVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  Distro
 | 
			
		||||
		out int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: Distro{
 | 
			
		||||
				Family:  Amazon,
 | 
			
		||||
				Release: "2 (2017.12)",
 | 
			
		||||
			},
 | 
			
		||||
			out: 2,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Distro{
 | 
			
		||||
				Family:  Amazon,
 | 
			
		||||
				Release: "2017.12",
 | 
			
		||||
			},
 | 
			
		||||
			out: 1,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Distro{
 | 
			
		||||
				Family:  CentOS,
 | 
			
		||||
				Release: "7.10",
 | 
			
		||||
			},
 | 
			
		||||
			out: 7,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		ver, err := tt.in.MajorVersion()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("[%d] err occurred: %s", i, err)
 | 
			
		||||
		}
 | 
			
		||||
		if tt.out != ver {
 | 
			
		||||
			t.Errorf("[%d] expected %d, actual %d", i, tt.out, ver)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -23,7 +23,7 @@ import "fmt"
 | 
			
		||||
type JSONLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton JSON file specified by path arg.
 | 
			
		||||
// Load load the configuration JSON file specified by path arg.
 | 
			
		||||
func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) {
 | 
			
		||||
	return fmt.Errorf("Not implement yet")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,30 +20,38 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
type TOMLLoader struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load load the configuraiton TOML file specified by path arg.
 | 
			
		||||
// Load load the configuration TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	if Conf.Debug {
 | 
			
		||||
		log.SetLevel(log.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
		log.Error("Load config failed", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.EMail = conf.EMail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
	Conf.Stride = conf.Stride
 | 
			
		||||
	Conf.HipChat = conf.HipChat
 | 
			
		||||
	Conf.ChatWork = conf.ChatWork
 | 
			
		||||
	Conf.Saas = conf.Saas
 | 
			
		||||
	Conf.Syslog = conf.Syslog
 | 
			
		||||
	Conf.HTTP = conf.HTTP
 | 
			
		||||
	Conf.AWS = conf.AWS
 | 
			
		||||
	Conf.Azure = conf.Azure
 | 
			
		||||
 | 
			
		||||
	Conf.CveDict = conf.CveDict
 | 
			
		||||
	Conf.OvalDict = conf.OvalDict
 | 
			
		||||
	Conf.Gost = conf.Gost
 | 
			
		||||
	Conf.Exploit = conf.Exploit
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
	Conf.Default = d
 | 
			
		||||
@@ -54,53 +62,78 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for name, v := range conf.Servers {
 | 
			
		||||
	for serverName, v := range conf.Servers {
 | 
			
		||||
		if 0 < len(v.KeyPassword) {
 | 
			
		||||
			log.Warn("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE.")
 | 
			
		||||
			return fmt.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: name}
 | 
			
		||||
		s := ServerInfo{ServerName: serverName}
 | 
			
		||||
		if v.Type != ServerTypePseudo {
 | 
			
		||||
			s.Host = v.Host
 | 
			
		||||
			if len(s.Host) == 0 {
 | 
			
		||||
				return fmt.Errorf("%s is invalid. host is empty", serverName)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		s.Host = v.Host
 | 
			
		||||
		if len(s.Host) == 0 {
 | 
			
		||||
			return fmt.Errorf("%s is invalid. host is empty", name)
 | 
			
		||||
		}
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.Port != "":
 | 
			
		||||
				s.Port = v.Port
 | 
			
		||||
			case d.Port != "":
 | 
			
		||||
				s.Port = d.Port
 | 
			
		||||
			default:
 | 
			
		||||
				s.Port = "22"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.Port != "":
 | 
			
		||||
			s.Port = v.Port
 | 
			
		||||
		case d.Port != "":
 | 
			
		||||
			s.Port = d.Port
 | 
			
		||||
		default:
 | 
			
		||||
			s.Port = "22"
 | 
			
		||||
		}
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.User != "":
 | 
			
		||||
				s.User = v.User
 | 
			
		||||
			case d.User != "":
 | 
			
		||||
				s.User = d.User
 | 
			
		||||
			default:
 | 
			
		||||
				if s.Port != "local" {
 | 
			
		||||
					return fmt.Errorf("%s is invalid. User is empty", serverName)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case v.User != "":
 | 
			
		||||
			s.User = v.User
 | 
			
		||||
		case d.User != "":
 | 
			
		||||
			s.User = d.User
 | 
			
		||||
		default:
 | 
			
		||||
			if s.Port != "local" {
 | 
			
		||||
				return fmt.Errorf("%s is invalid. User is empty", name)
 | 
			
		||||
			s.KeyPath = v.KeyPath
 | 
			
		||||
			if len(s.KeyPath) == 0 {
 | 
			
		||||
				s.KeyPath = d.KeyPath
 | 
			
		||||
			}
 | 
			
		||||
			if s.KeyPath != "" {
 | 
			
		||||
				if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
					return fmt.Errorf(
 | 
			
		||||
						"%s is invalid. keypath: %s not exists", serverName, s.KeyPath)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			s.KeyPassword = v.KeyPassword
 | 
			
		||||
			if len(s.KeyPassword) == 0 {
 | 
			
		||||
				s.KeyPassword = d.KeyPassword
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.KeyPath = v.KeyPath
 | 
			
		||||
		if len(s.KeyPath) == 0 {
 | 
			
		||||
			s.KeyPath = d.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
		if s.KeyPath != "" {
 | 
			
		||||
			if _, err := os.Stat(s.KeyPath); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"%s is invalid. keypath: %s not exists", name, s.KeyPath)
 | 
			
		||||
		s.ScanMode = v.ScanMode
 | 
			
		||||
		if len(s.ScanMode) == 0 {
 | 
			
		||||
			s.ScanMode = d.ScanMode
 | 
			
		||||
			if len(s.ScanMode) == 0 {
 | 
			
		||||
				s.ScanMode = []string{"fast"}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//  s.KeyPassword = keyPass
 | 
			
		||||
		s.KeyPassword = v.KeyPassword
 | 
			
		||||
		if len(s.KeyPassword) == 0 {
 | 
			
		||||
			s.KeyPassword = d.KeyPassword
 | 
			
		||||
		for _, m := range s.ScanMode {
 | 
			
		||||
			switch m {
 | 
			
		||||
			case "fast":
 | 
			
		||||
				s.Mode.Set(Fast)
 | 
			
		||||
			case "fast-root":
 | 
			
		||||
				s.Mode.Set(FastRoot)
 | 
			
		||||
			case "deep":
 | 
			
		||||
				s.Mode.Set(Deep)
 | 
			
		||||
			case "offline":
 | 
			
		||||
				s.Mode.Set(Offline)
 | 
			
		||||
			default:
 | 
			
		||||
				return fmt.Errorf("scanMode: %s of %s is invalie. Specify -fast, -fast-root, -deep or offline", m, serverName)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err := s.Mode.validate(); err != nil {
 | 
			
		||||
			return fmt.Errorf("%s in %s", err, serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.CpeNames = v.CpeNames
 | 
			
		||||
@@ -108,26 +141,47 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.DependencyCheckXMLPath = v.DependencyCheckXMLPath
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) == 0 {
 | 
			
		||||
			s.DependencyCheckXMLPath = d.DependencyCheckXMLPath
 | 
			
		||||
		for i, n := range s.CpeNames {
 | 
			
		||||
			uri, err := toCpeURI(n)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to parse CPENames %s in %s: %s", n, serverName, err)
 | 
			
		||||
			}
 | 
			
		||||
			s.CpeNames[i] = uri
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load CPEs from OWASP Dependency Check XML
 | 
			
		||||
		if len(s.DependencyCheckXMLPath) != 0 {
 | 
			
		||||
			cpes, err := parser.Parse(s.DependencyCheckXMLPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to read OWASP Dependency Check XML: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			log.Debugf("Loaded from OWASP Dependency Check XML: %s",
 | 
			
		||||
				s.ServerName)
 | 
			
		||||
			s.CpeNames = append(s.CpeNames, cpes...)
 | 
			
		||||
		s.ContainersIncluded = v.ContainersIncluded
 | 
			
		||||
		if len(s.ContainersIncluded) == 0 {
 | 
			
		||||
			s.ContainersIncluded = d.ContainersIncluded
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ContainersExcluded = v.ContainersExcluded
 | 
			
		||||
		if len(s.ContainersExcluded) == 0 {
 | 
			
		||||
			s.ContainersExcluded = d.ContainersExcluded
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ContainerType = v.ContainerType
 | 
			
		||||
		if len(s.ContainerType) == 0 {
 | 
			
		||||
			s.ContainerType = d.ContainerType
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Containers = v.Containers
 | 
			
		||||
		if len(s.Containers.Includes) == 0 {
 | 
			
		||||
			s.Containers = d.Containers
 | 
			
		||||
		for contName, cont := range s.Containers {
 | 
			
		||||
			cont.IgnoreCves = append(cont.IgnoreCves, d.IgnoreCves...)
 | 
			
		||||
			s.Containers[contName] = cont
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(v.DependencyCheckXMLPath) != 0 || len(d.DependencyCheckXMLPath) != 0 {
 | 
			
		||||
			return fmt.Errorf("[DEPRECATED] dependencyCheckXMLPath IS DEPRECATED. USE owaspDCXMLPath INSTEAD: %s", serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.OwaspDCXMLPath = v.OwaspDCXMLPath
 | 
			
		||||
		if len(s.OwaspDCXMLPath) == 0 {
 | 
			
		||||
			s.OwaspDCXMLPath = d.OwaspDCXMLPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Memo = v.Memo
 | 
			
		||||
		if s.Memo == "" {
 | 
			
		||||
			s.Memo = d.Memo
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.IgnoreCves = v.IgnoreCves
 | 
			
		||||
@@ -144,19 +198,43 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Optional = v.Optional
 | 
			
		||||
		for _, dkv := range d.Optional {
 | 
			
		||||
		s.IgnorePkgsRegexp = v.IgnorePkgsRegexp
 | 
			
		||||
		for _, pkg := range d.IgnorePkgsRegexp {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, kv := range s.Optional {
 | 
			
		||||
				if dkv[0] == kv[0] {
 | 
			
		||||
			for _, p := range s.IgnorePkgsRegexp {
 | 
			
		||||
				if pkg == p {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.Optional = append(s.Optional, dkv)
 | 
			
		||||
				s.IgnorePkgsRegexp = append(s.IgnorePkgsRegexp, pkg)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, reg := range s.IgnorePkgsRegexp {
 | 
			
		||||
			_, err := regexp.Compile(reg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("Faild to parse %s in %s. err: %s", reg, serverName, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for contName, cont := range s.Containers {
 | 
			
		||||
			for _, reg := range cont.IgnorePkgsRegexp {
 | 
			
		||||
				_, err := regexp.Compile(reg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return fmt.Errorf("Faild to parse %s in %s@%s. err: %s",
 | 
			
		||||
						reg, contName, serverName, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		opt := map[string]interface{}{}
 | 
			
		||||
		for k, v := range d.Optional {
 | 
			
		||||
			opt[k] = v
 | 
			
		||||
		}
 | 
			
		||||
		for k, v := range v.Optional {
 | 
			
		||||
			opt[k] = v
 | 
			
		||||
		}
 | 
			
		||||
		s.Optional = opt
 | 
			
		||||
 | 
			
		||||
		s.Enablerepo = v.Enablerepo
 | 
			
		||||
		if len(s.Enablerepo) == 0 {
 | 
			
		||||
@@ -170,16 +248,36 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
				default:
 | 
			
		||||
					return fmt.Errorf(
 | 
			
		||||
						"For now, enablerepo have to be base or updates: %s, servername: %s",
 | 
			
		||||
						s.Enablerepo, name)
 | 
			
		||||
						s.Enablerepo, serverName)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.UUIDs = v.UUIDs
 | 
			
		||||
		s.Type = v.Type
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(Colors)]
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		servers[name] = s
 | 
			
		||||
		servers[serverName] = s
 | 
			
		||||
	}
 | 
			
		||||
	Conf.Servers = servers
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toCpeURI(cpename string) (string, error) {
 | 
			
		||||
	if strings.HasPrefix(cpename, "cpe:2.3:") {
 | 
			
		||||
		wfn, err := naming.UnbindFS(cpename)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return naming.BindToURI(wfn), nil
 | 
			
		||||
	} else if strings.HasPrefix(cpename, "cpe:/") {
 | 
			
		||||
		wfn, err := naming.UnbindURI(cpename)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		return naming.BindToURI(wfn), nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", fmt.Errorf("Unknow CPE format: %s", cpename)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								config/tomlloader_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								config/tomlloader_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestToCpeURI(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected string
 | 
			
		||||
		err      bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in:       "",
 | 
			
		||||
			expected: "",
 | 
			
		||||
			err:      true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:       "cpe:/a:microsoft:internet_explorer:10",
 | 
			
		||||
			expected: "cpe:/a:microsoft:internet_explorer:10",
 | 
			
		||||
			err:      false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:       "cpe:2.3:a:microsoft:internet_explorer:10:*:*:*:*:*:*:*",
 | 
			
		||||
			expected: "cpe:/a:microsoft:internet_explorer:10",
 | 
			
		||||
			err:      false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual, err := toCpeURI(tt.in)
 | 
			
		||||
		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 actual != tt.expected {
 | 
			
		||||
			t.Errorf("[%d] in: %s, actual: %s, expected: %s",
 | 
			
		||||
				i, tt.in, actual, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,8 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type analysis struct {
 | 
			
		||||
@@ -30,17 +32,19 @@ func appendIfMissing(slice []string, str string) []string {
 | 
			
		||||
	return append(slice, str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse parses XML and collect list of cpe
 | 
			
		||||
// Parse parses OWASP dependency check XML and collect list of cpe
 | 
			
		||||
func Parse(path string) ([]string, error) {
 | 
			
		||||
	file, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to open: %s", err)
 | 
			
		||||
		log.Warnf("OWASP Dependency Check XML is not found: %s", path)
 | 
			
		||||
		return []string{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := ioutil.ReadAll(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read: %s", err)
 | 
			
		||||
		log.Warnf("Failed to read OWASP Dependency Check XML: %s", path)
 | 
			
		||||
		return []string{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var anal analysis
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										65
									
								
								cwe/owasp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								cwe/owasp.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
package cwe
 | 
			
		||||
 | 
			
		||||
// OwaspTopTen2017 has CWE-ID in OWSP Top 10
 | 
			
		||||
var OwaspTopTen2017 = map[string]string{
 | 
			
		||||
	"77":  "1",
 | 
			
		||||
	"89":  "1",
 | 
			
		||||
	"564": "1",
 | 
			
		||||
	"917": "1",
 | 
			
		||||
 | 
			
		||||
	"287": "2",
 | 
			
		||||
	"384": "2",
 | 
			
		||||
 | 
			
		||||
	"220": "3",
 | 
			
		||||
	"310": "3",
 | 
			
		||||
	"312": "3",
 | 
			
		||||
	"319": "3",
 | 
			
		||||
	"326": "3",
 | 
			
		||||
	"359": "3",
 | 
			
		||||
 | 
			
		||||
	"611": "4",
 | 
			
		||||
 | 
			
		||||
	"22":  "5",
 | 
			
		||||
	"284": "5",
 | 
			
		||||
	"285": "5",
 | 
			
		||||
	"639": "5",
 | 
			
		||||
 | 
			
		||||
	"2":   "6",
 | 
			
		||||
	"16":  "6",
 | 
			
		||||
	"388": "6",
 | 
			
		||||
 | 
			
		||||
	"79": "7",
 | 
			
		||||
 | 
			
		||||
	"502": "8",
 | 
			
		||||
 | 
			
		||||
	"223": "10",
 | 
			
		||||
	"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",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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",
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										135
									
								
								exploit/exploit.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								exploit/exploit.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/mozqnet/go-exploitdb/db"
 | 
			
		||||
	exploitmodels "github.com/mozqnet/go-exploitdb/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithExploit fills exploit information that has in Exploit
 | 
			
		||||
func FillWithExploit(driver db.DB, r *models.ScanResult) (nExploitCve int, err error) {
 | 
			
		||||
	if cnf.Conf.Exploit.IsFetchViaHTTP() {
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
		for cveID := range r.ScannedCves {
 | 
			
		||||
			cveIDs = append(cveIDs, cveID)
 | 
			
		||||
		}
 | 
			
		||||
		prefix, _ := util.URLPathJoin(cnf.Conf.Exploit.URL, "cves")
 | 
			
		||||
		responses, err := getCvesViaHTTP(cveIDs, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			exps := []*exploitmodels.Exploit{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &exps); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			exploits := convertToModels(exps)
 | 
			
		||||
			v, ok := r.ScannedCves[res.request.cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				v.Exploits = exploits
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[res.request.cveID] = v
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
			es := driver.GetExploitByCveID(cveID)
 | 
			
		||||
			if len(es) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			exploits := convertToModels(es)
 | 
			
		||||
			vuln.Exploits = exploits
 | 
			
		||||
			r.ScannedCves[cveID] = vuln
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nExploitCve, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// convertToModels converts gost model to vuls model
 | 
			
		||||
func convertToModels(es []*exploitmodels.Exploit) (exploits []models.Exploit) {
 | 
			
		||||
	for _, e := range es {
 | 
			
		||||
		var documentURL, paperURL, shellURL *string
 | 
			
		||||
		if e.OffensiveSecurity != nil {
 | 
			
		||||
			os := e.OffensiveSecurity
 | 
			
		||||
			if os.Document != nil {
 | 
			
		||||
				documentURL = &os.Document.DocumentURL
 | 
			
		||||
			}
 | 
			
		||||
			if os.ShellCode != nil {
 | 
			
		||||
				shellURL = &os.ShellCode.ShellCodeURL
 | 
			
		||||
			}
 | 
			
		||||
			if os.Paper != nil {
 | 
			
		||||
				paperURL = &os.Paper.PaperURL
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		exploit := models.Exploit{
 | 
			
		||||
			ExploitType:  e.ExploitType,
 | 
			
		||||
			ID:           e.ExploitUniqueID,
 | 
			
		||||
			URL:          e.URL,
 | 
			
		||||
			Description:  e.Description,
 | 
			
		||||
			DocumentURL:  documentURL,
 | 
			
		||||
			ShellCodeURL: shellURL,
 | 
			
		||||
			PaperURL:     paperURL,
 | 
			
		||||
		}
 | 
			
		||||
		exploits = append(exploits, exploit)
 | 
			
		||||
	}
 | 
			
		||||
	return exploits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.Conf.Exploit.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.Conf.Exploit.URL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return fmt.Errorf("Failed to connect to exploit server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfExploitFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func CheckIfExploitFetched(driver db.DB, osFamily string) (fetched bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfExploitFresh checks if oval entries are fresh enough
 | 
			
		||||
func CheckIfExploitFresh(driver db.DB, osFamily string) (ok bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								exploit/exploit_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								exploit/exploit_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSetPackageStates(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								exploit/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								exploit/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	request request
 | 
			
		||||
	json    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
 | 
			
		||||
	responses []response, err error) {
 | 
			
		||||
	nReq := len(cveIDs)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				cveID: cveID,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.cveID,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(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, fmt.Errorf("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	osMajorVersion string
 | 
			
		||||
	packName       string
 | 
			
		||||
	isSrcPack      bool
 | 
			
		||||
	cveID          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, req request, resChan chan<- response, 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().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- fmt.Errorf("HRetry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		request: req,
 | 
			
		||||
		json:    body,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										182
									
								
								gost/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								gost/debian.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Debian is Gost client for Debian GNU/Linux
 | 
			
		||||
type Debian struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packCves struct {
 | 
			
		||||
	packName  string
 | 
			
		||||
	isSrcPack bool
 | 
			
		||||
	cves      []models.CveContent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithGost fills cve information that has in Gost
 | 
			
		||||
func (deb Debian) FillWithGost(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
	// 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,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packCvesList := []packCves{}
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.Gost.URL, "debian", major(r.Release), "pkgs")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, url)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			debCves := map[string]gostmodels.DebianCVE{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &debCves); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, debcve := range debCves {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&debcve))
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  res.request.packName,
 | 
			
		||||
				isSrcPack: res.request.isSrcPack,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(r.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  pack.Name,
 | 
			
		||||
				isSrcPack: false,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// SrcPack
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(r.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
			}
 | 
			
		||||
			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.DebianSecurityTracker] = cve
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cve.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(cve),
 | 
			
		||||
					Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
 | 
			
		||||
				}
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			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)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} 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.PackageStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					FixState:    "open",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[cve.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
 | 
			
		||||
	severity := ""
 | 
			
		||||
	for _, p := range cve.Package {
 | 
			
		||||
		for _, r := range p.Release {
 | 
			
		||||
			severity = r.Urgency
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		Type:          models.DebianSecurityTracker,
 | 
			
		||||
		CveID:         cve.CveID,
 | 
			
		||||
		Summary:       cve.Description,
 | 
			
		||||
		Cvss2Severity: severity,
 | 
			
		||||
		Cvss3Severity: severity,
 | 
			
		||||
		SourceLink:    "https://security-tracker.debian.org/tracker/" + cve.CveID,
 | 
			
		||||
		Optional: map[string]string{
 | 
			
		||||
			"attack range": cve.Scope,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								gost/gost.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								gost/gost.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	FillWithGost(db.DB, *models.ScanResult) (int, error)
 | 
			
		||||
 | 
			
		||||
	//TODO implement
 | 
			
		||||
	// CheckHTTPHealth() error
 | 
			
		||||
	// CheckIfGostFetched checks if Gost entries are fetched
 | 
			
		||||
	// CheckIfGostFetched(db.DB, string, string) (bool, error)
 | 
			
		||||
	// CheckIfGostFresh(db.DB, string, string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClient make Client by family
 | 
			
		||||
func NewClient(family string) Client {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case cnf.RedHat, cnf.CentOS:
 | 
			
		||||
		return RedHat{}
 | 
			
		||||
	case cnf.Debian:
 | 
			
		||||
		return Debian{}
 | 
			
		||||
	case cnf.Windows:
 | 
			
		||||
		return Microsoft{}
 | 
			
		||||
	default:
 | 
			
		||||
		return Pseudo{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	family string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.Conf.Gost.URL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return fmt.Errorf("Failed to connect to gost server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfGostFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfGostFetched(driver db.DB, osFamily string) (fetched bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfGostFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfGostFresh(driver db.DB, osFamily string) (ok bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pseudo is Gost client except for RedHat family and Debian
 | 
			
		||||
type Pseudo struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithGost fills cve information that has in Gost
 | 
			
		||||
func (pse Pseudo) FillWithGost(driver db.DB, r *models.ScanResult) (int, error) {
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func major(osVer string) (majorVersion string) {
 | 
			
		||||
	return strings.Split(osVer, ".")[0]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								gost/gost_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								gost/gost_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSetPackageStates(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pkgstats  []gostmodels.RedhatPackageState
 | 
			
		||||
		installed models.Packages
 | 
			
		||||
		release   string
 | 
			
		||||
		in        models.VulnInfo
 | 
			
		||||
		out       models.PackageStatuses
 | 
			
		||||
	}{
 | 
			
		||||
 | 
			
		||||
		//0 one
 | 
			
		||||
		{
 | 
			
		||||
			pkgstats: []gostmodels.RedhatPackageState{
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "Will not fix",
 | 
			
		||||
					PackageName: "bouncycastle",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			installed: models.Packages{
 | 
			
		||||
				"bouncycastle": models.Package{},
 | 
			
		||||
			},
 | 
			
		||||
			release: "7",
 | 
			
		||||
			in:      models.VulnInfo{},
 | 
			
		||||
			out: []models.PackageStatus{
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "bouncycastle",
 | 
			
		||||
					FixState:    "Will not fix",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		//1 two
 | 
			
		||||
		{
 | 
			
		||||
			pkgstats: []gostmodels.RedhatPackageState{
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "Will not fix",
 | 
			
		||||
					PackageName: "bouncycastle",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:7",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "Fix deferred",
 | 
			
		||||
					PackageName: "pack_a",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:7",
 | 
			
		||||
				},
 | 
			
		||||
				// ignore not-installed-package
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "Fix deferred",
 | 
			
		||||
					PackageName: "pack_b",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			installed: models.Packages{
 | 
			
		||||
				"bouncycastle": models.Package{},
 | 
			
		||||
				"pack_a":       models.Package{},
 | 
			
		||||
			},
 | 
			
		||||
			release: "7",
 | 
			
		||||
			in:      models.VulnInfo{},
 | 
			
		||||
			out: []models.PackageStatus{
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "bouncycastle",
 | 
			
		||||
					FixState:    "Will not fix",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "pack_a",
 | 
			
		||||
					FixState:    "Fix deferred",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		//2 ignore affected
 | 
			
		||||
		{
 | 
			
		||||
			pkgstats: []gostmodels.RedhatPackageState{
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "affected",
 | 
			
		||||
					PackageName: "bouncycastle",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			installed: models.Packages{
 | 
			
		||||
				"bouncycastle": models.Package{},
 | 
			
		||||
			},
 | 
			
		||||
			release: "7",
 | 
			
		||||
			in: models.VulnInfo{
 | 
			
		||||
				AffectedPackages: models.PackageStatuses{},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.PackageStatuses{},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		//3 look only the same os release.
 | 
			
		||||
		{
 | 
			
		||||
			pkgstats: []gostmodels.RedhatPackageState{
 | 
			
		||||
				{
 | 
			
		||||
					FixState:    "Will not fix",
 | 
			
		||||
					PackageName: "bouncycastle",
 | 
			
		||||
					Cpe:         "cpe:/o:redhat:enterprise_linux:6",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			installed: models.Packages{
 | 
			
		||||
				"bouncycastle": models.Package{},
 | 
			
		||||
			},
 | 
			
		||||
			release: "7",
 | 
			
		||||
			in: models.VulnInfo{
 | 
			
		||||
				AffectedPackages: models.PackageStatuses{},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.PackageStatuses{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := RedHat{}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		out := r.mergePackageStates(tt.in, tt.pkgstats, tt.installed, tt.release)
 | 
			
		||||
		if ok := reflect.DeepEqual(tt.out, out); !ok {
 | 
			
		||||
			t.Errorf("[%d]\nexpected: %v:%T\n  actual: %v:%T\n", i, tt.out, tt.out, out, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								gost/microsoft.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								gost/microsoft.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Microsoft is Gost client for windows
 | 
			
		||||
type Microsoft struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithGost fills cve information that has in Gost
 | 
			
		||||
func (ms Microsoft) FillWithGost(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	if driver == nil {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for cveID := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
	for cveID, msCve := range driver.GetMicrosoftMulti(cveIDs) {
 | 
			
		||||
		if _, ok := r.ScannedCves[cveID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		cveCont := ms.ConvertToModel(&msCve)
 | 
			
		||||
		v, _ := r.ScannedCves[cveID]
 | 
			
		||||
		if v.CveContents == nil {
 | 
			
		||||
			v.CveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		v.CveContents[models.Microsoft] = *cveCont
 | 
			
		||||
		r.ScannedCves[cveID] = v
 | 
			
		||||
	}
 | 
			
		||||
	return len(cveIDs), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveContent {
 | 
			
		||||
	v3score := 0.0
 | 
			
		||||
	var v3Vector string
 | 
			
		||||
	for _, scoreSet := range cve.ScoreSets {
 | 
			
		||||
		if v3score < scoreSet.BaseScore {
 | 
			
		||||
			v3score = scoreSet.BaseScore
 | 
			
		||||
			v3Vector = 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}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	option := map[string]string{}
 | 
			
		||||
	if 0 < len(cve.ExploitStatus) {
 | 
			
		||||
		option["exploit"] = cve.ExploitStatus
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(cve.Workaround) {
 | 
			
		||||
		option["workaround"] = cve.Workaround
 | 
			
		||||
	}
 | 
			
		||||
	var kbids []string
 | 
			
		||||
	for _, kbid := range cve.KBIDs {
 | 
			
		||||
		kbids = append(kbids, kbid.KBID)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(kbids) {
 | 
			
		||||
		option["kbids"] = strings.Join(kbids, ",")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		Type:          models.Microsoft,
 | 
			
		||||
		CveID:         cve.CveID,
 | 
			
		||||
		Title:         cve.Title,
 | 
			
		||||
		Summary:       cve.Description,
 | 
			
		||||
		Cvss3Score:    v3score,
 | 
			
		||||
		Cvss3Vector:   v3Vector,
 | 
			
		||||
		Cvss3Severity: v3Severity,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		CweIDs:        cwe,
 | 
			
		||||
		Mitigation:    cve.Mitigation,
 | 
			
		||||
		Published:     cve.PublishDate,
 | 
			
		||||
		LastModified:  cve.LastUpdateDate,
 | 
			
		||||
		SourceLink:    "https://portal.msrc.microsoft.com/ja-jp/security-guidance/advisory/" + cve.CveID,
 | 
			
		||||
		Optional:      option,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										289
									
								
								gost/redhat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								gost/redhat.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,289 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RedHat is Gost client for RedHat family linux
 | 
			
		||||
type RedHat struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithGost fills cve information that has in Gost
 | 
			
		||||
func (red RedHat) FillWithGost(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	if nCVEs, err = red.fillUnfixed(driver, r); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, red.fillFixed(driver, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
		if _, ok := vuln.CveContents[models.RedHatAPI]; ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		cveIDs = append(cveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(config.Conf.Gost.URL,
 | 
			
		||||
			"redhat", "cves")
 | 
			
		||||
		responses, err := getCvesViaHTTP(cveIDs, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			redCve := gostmodels.RedhatCVE{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &redCve); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if redCve.ID == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			cveCont := red.ConvertToModel(&redCve)
 | 
			
		||||
			v, ok := r.ScannedCves[res.request.cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cveCont.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
					Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[res.request.cveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for cveID, redCve := range driver.GetRedhatMulti(cveIDs) {
 | 
			
		||||
			if redCve.ID == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			cveCont := red.ConvertToModel(&redCve)
 | 
			
		||||
			v, ok := r.ScannedCves[cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cveCont.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
					Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[cveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) fillUnfixed(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(config.Conf.Gost.URL,
 | 
			
		||||
			"redhat", major(r.Release), "pkgs")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, 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
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				cveCont := red.ConvertToModel(&cve)
 | 
			
		||||
				v, ok := r.ScannedCves[cve.Name]
 | 
			
		||||
				if ok {
 | 
			
		||||
					if v.CveContents == nil {
 | 
			
		||||
						v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
					} else {
 | 
			
		||||
						v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					v = models.VulnInfo{
 | 
			
		||||
						CveID:       cveCont.CveID,
 | 
			
		||||
						CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
						Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
					}
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
				pkgStats := red.mergePackageStates(v,
 | 
			
		||||
					cve.PackageState, r.Packages, r.Release)
 | 
			
		||||
				if 0 < len(pkgStats) {
 | 
			
		||||
					v.AffectedPackages = pkgStats
 | 
			
		||||
					r.ScannedCves[cve.Name] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := map[string]gostmodels.RedhatCVE{}
 | 
			
		||||
			cves = driver.GetUnfixedCvesRedhat(major(r.Release), pack.Name)
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				cveCont := red.ConvertToModel(&cve)
 | 
			
		||||
				v, ok := r.ScannedCves[cve.Name]
 | 
			
		||||
				if ok {
 | 
			
		||||
					if v.CveContents == nil {
 | 
			
		||||
						v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
					} else {
 | 
			
		||||
						v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					v = models.VulnInfo{
 | 
			
		||||
						CveID:       cveCont.CveID,
 | 
			
		||||
						CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
						Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
					}
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				pkgStats := red.mergePackageStates(v,
 | 
			
		||||
					cve.PackageState, r.Packages, r.Release)
 | 
			
		||||
				if 0 < len(pkgStats) {
 | 
			
		||||
					v.AffectedPackages = pkgStats
 | 
			
		||||
					r.ScannedCves[cve.Name] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageStatuses) {
 | 
			
		||||
	pkgStats = v.AffectedPackages
 | 
			
		||||
	for _, pstate := range ps {
 | 
			
		||||
		if pstate.Cpe !=
 | 
			
		||||
			"cpe:/o:redhat:enterprise_linux:"+major(release) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !(pstate.FixState == "Will not fix" ||
 | 
			
		||||
			pstate.FixState == "Fix deferred") {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, ok := installed[pstate.PackageName]; !ok {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		notFixedYet := false
 | 
			
		||||
		switch pstate.FixState {
 | 
			
		||||
		case "Will not fix", "Fix deferred":
 | 
			
		||||
			notFixedYet = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pkgStats = pkgStats.Store(models.PackageStatus{
 | 
			
		||||
			Name:        pstate.PackageName,
 | 
			
		||||
			FixState:    pstate.FixState,
 | 
			
		||||
			NotFixedYet: notFixedYet,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) parseCwe(str string) (cwes []string) {
 | 
			
		||||
	if str != "" {
 | 
			
		||||
		s := strings.Replace(str, "(", "|", -1)
 | 
			
		||||
		s = strings.Replace(s, ")", "|", -1)
 | 
			
		||||
		s = strings.Replace(s, "->", "|", -1)
 | 
			
		||||
		for _, s := range strings.Split(s, "|") {
 | 
			
		||||
			if s != "" {
 | 
			
		||||
				cwes = append(cwes, s)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
 | 
			
		||||
	cwes := red.parseCwe(cve.Cwe)
 | 
			
		||||
 | 
			
		||||
	details := []string{}
 | 
			
		||||
	for _, detail := range cve.Details {
 | 
			
		||||
		details = append(details, detail.Detail)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v2score := 0.0
 | 
			
		||||
	if cve.Cvss.CvssBaseScore != "" {
 | 
			
		||||
		v2score, _ = strconv.ParseFloat(cve.Cvss.CvssBaseScore, 64)
 | 
			
		||||
	}
 | 
			
		||||
	v2severity := ""
 | 
			
		||||
	if v2score != 0 {
 | 
			
		||||
		v2severity = cve.ThreatSeverity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v3score := 0.0
 | 
			
		||||
	if cve.Cvss3.Cvss3BaseScore != "" {
 | 
			
		||||
		v3score, _ = strconv.ParseFloat(cve.Cvss3.Cvss3BaseScore, 64)
 | 
			
		||||
	}
 | 
			
		||||
	v3severity := ""
 | 
			
		||||
	if v3score != 0 {
 | 
			
		||||
		v3severity = cve.ThreatSeverity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	for _, r := range cve.References {
 | 
			
		||||
		refs = append(refs, models.Reference{Link: r.Reference})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		Type:          models.RedHatAPI,
 | 
			
		||||
		CveID:         cve.Name,
 | 
			
		||||
		Title:         cve.Bugzilla.Description,
 | 
			
		||||
		Summary:       strings.Join(details, "\n"),
 | 
			
		||||
		Cvss2Score:    v2score,
 | 
			
		||||
		Cvss2Vector:   cve.Cvss.CvssScoringVector,
 | 
			
		||||
		Cvss2Severity: v2severity,
 | 
			
		||||
		Cvss3Score:    v3score,
 | 
			
		||||
		Cvss3Vector:   cve.Cvss3.Cvss3ScoringVector,
 | 
			
		||||
		Cvss3Severity: v3severity,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		CweIDs:        cwes,
 | 
			
		||||
		Mitigation:    cve.Mitigation,
 | 
			
		||||
		Published:     cve.PublicDate,
 | 
			
		||||
		SourceLink:    "https://access.redhat.com/security/cve/" + cve.Name,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								gost/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								gost/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseCwe(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in:  "CWE-665->(CWE-200|CWE-89)",
 | 
			
		||||
			out: []string{"CWE-665", "CWE-200", "CWE-89"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:  "CWE-841->CWE-770->CWE-454",
 | 
			
		||||
			out: []string{"CWE-841", "CWE-770", "CWE-454"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:  "(CWE-122|CWE-125)",
 | 
			
		||||
			out: []string{"CWE-122", "CWE-125"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := RedHat{}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		out := r.parseCwe(tt.in)
 | 
			
		||||
		sort.Strings(out)
 | 
			
		||||
		sort.Strings(tt.out)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, out) {
 | 
			
		||||
			t.Errorf("[%d]expected: %s, actual: %s", i, tt.out, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										201
									
								
								gost/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								gost/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	request request
 | 
			
		||||
	json    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
 | 
			
		||||
	responses []response, err error) {
 | 
			
		||||
	nReq := len(cveIDs)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				cveID: cveID,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.cveID,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(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, fmt.Errorf("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	osMajorVersion string
 | 
			
		||||
	packName       string
 | 
			
		||||
	isSrcPack      bool
 | 
			
		||||
	cveID          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) (
 | 
			
		||||
	responses []response, err error) {
 | 
			
		||||
 | 
			
		||||
	nReq := len(r.Packages) + len(r.SrcPackages)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				osMajorVersion: major(r.Release),
 | 
			
		||||
				packName:       pack.Name,
 | 
			
		||||
				isSrcPack:      false,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				osMajorVersion: major(r.Release),
 | 
			
		||||
				packName:       pack.Name,
 | 
			
		||||
				isSrcPack:      true,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.packName,
 | 
			
		||||
					"unfixed-cves",
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(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, fmt.Errorf("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, req request, resChan chan<- response, 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().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- fmt.Errorf("HRetry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		request: req,
 | 
			
		||||
		json:    body,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -53,9 +53,11 @@
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
          <y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
SUSE: zypper
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -264,7 +266,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -298,7 +300,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n1" target="n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-123.36984126984123" ty="0.0">
 | 
			
		||||
@@ -306,11 +307,13 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="74.6640625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">CentOS
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="102.9296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">Alpine Linux
 | 
			
		||||
CentOS
 | 
			
		||||
RHEL
 | 
			
		||||
Ubuntu
 | 
			
		||||
Debian
 | 
			
		||||
Oracle Linux<y:LabelModel>
 | 
			
		||||
Oracle Linux
 | 
			
		||||
Suse<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -323,7 +326,6 @@ Oracle Linux<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n4" target="n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -364,7 +366,6 @@ Oracle Linux<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n3" target="n5">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -375,7 +376,6 @@ Oracle Linux<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n1" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
@@ -396,7 +396,6 @@ Oracle Linux<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n10" target="n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-125.78842258255952" ty="0.0">
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 78 KiB  | 
@@ -53,10 +53,12 @@
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
          <y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine Linux: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
FreeBSD: pkg
 | 
			
		||||
SUSE: zypper<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -235,7 +237,6 @@ Amazon / RHEL: yum changelog<y:LabelModel>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
@@ -315,13 +316,13 @@ Amazon / RHEL: yum changelog<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-66.95987036992159" y="-48.39843398912808">Debian
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-257.65322875976574" y="2.0000035108718635">Debian
 | 
			
		||||
Ubuntu
 | 
			
		||||
Raspbian<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.02215389573439544" segment="0"/>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.8652035780364729" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -379,13 +380,13 @@ Raspbian<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="200.87829463898197" y="4.000003510871693">Amazon
 | 
			
		||||
RHEL
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.022401276994204813" segment="0"/>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="6.999999999999886" distanceToCenter="false" position="right" ratio="0.8192728556300707" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -442,7 +443,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n11" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-24.34091537610618">
 | 
			
		||||
@@ -455,7 +455,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n8" target="n12">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -466,7 +465,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -477,7 +475,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n9" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
@@ -487,6 +484,30 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e15" source="n1" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="999.0" y="226.44247787610618"/>
 | 
			
		||||
            <y:Point x="999.0" y="570.8409153761062"/>
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.8203125" x="422.923942251054" y="13.867191010871807">Alpine Linux
 | 
			
		||||
SUSE<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.8856709076027529" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 90 KiB  | 
							
								
								
									
										12
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								main.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -25,15 +25,10 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var version = "0.4.0"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var revision string
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
@@ -44,13 +39,14 @@ func main() {
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ReportCmd{}, "report")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
	subcommands.Register(&commands.ServerCmd{}, "server")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", version, revision)
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,7 +18,6 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -61,12 +60,12 @@ func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents)
 | 
			
		||||
// SourceLinks returns link of source
 | 
			
		||||
func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v[JVN]; found && 0 < len(cont.SourceLink) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.SourceLink})
 | 
			
		||||
		if cont, found := v[Jvn]; found && 0 < len(cont.SourceLink) {
 | 
			
		||||
			values = append(values, CveContentStr{Jvn, cont.SourceLink})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order := CveContentTypes{Nvd, NvdXML, NewCveContentType(myFamily)}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found {
 | 
			
		||||
			values = append(values, CveContentStr{ctype, cont.SourceLink})
 | 
			
		||||
@@ -75,7 +74,7 @@ func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveCont
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  NVD,
 | 
			
		||||
			Type:  Nvd,
 | 
			
		||||
			Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
@@ -148,11 +147,14 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) {
 | 
			
		||||
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.CweID) {
 | 
			
		||||
			// RedHat's OVAL sometimes contains multiple CWE-IDs separated by spaces
 | 
			
		||||
			for _, cweID := range strings.Fields(cont.CweID) {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.CweIDs) {
 | 
			
		||||
			for _, cweID := range cont.CweIDs {
 | 
			
		||||
				for _, val := range values {
 | 
			
		||||
					if val.Value == cweID {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				values = append(values, CveContentStr{
 | 
			
		||||
					Type:  ctype,
 | 
			
		||||
					Value: cweID,
 | 
			
		||||
@@ -163,23 +165,38 @@ func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqCweIDs returns Uniq CweIDs
 | 
			
		||||
func (v CveContents) UniqCweIDs(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	uniq := map[string]CveContentStr{}
 | 
			
		||||
	for _, cwes := range v.CweIDs(myFamily) {
 | 
			
		||||
		uniq[cwes.Value] = cwes
 | 
			
		||||
	}
 | 
			
		||||
	for _, cwe := range uniq {
 | 
			
		||||
		values = append(values, cwe)
 | 
			
		||||
	}
 | 
			
		||||
	return values
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContent has abstraction of various vulnerability information
 | 
			
		||||
type CveContent struct {
 | 
			
		||||
	Type         CveContentType
 | 
			
		||||
	CveID        string
 | 
			
		||||
	Title        string
 | 
			
		||||
	Summary      string
 | 
			
		||||
	Severity     string
 | 
			
		||||
	Cvss2Score   float64
 | 
			
		||||
	Cvss2Vector  string
 | 
			
		||||
	Cvss3Score   float64
 | 
			
		||||
	Cvss3Vector  string
 | 
			
		||||
	SourceLink   string
 | 
			
		||||
	Cpes         []Cpe
 | 
			
		||||
	References   References
 | 
			
		||||
	CweID        string
 | 
			
		||||
	Published    time.Time
 | 
			
		||||
	LastModified time.Time
 | 
			
		||||
	Type          CveContentType    `json:"type"`
 | 
			
		||||
	CveID         string            `json:"cveID"`
 | 
			
		||||
	Title         string            `json:"title"`
 | 
			
		||||
	Summary       string            `json:"summary"`
 | 
			
		||||
	Cvss2Score    float64           `json:"cvss2Score"`
 | 
			
		||||
	Cvss2Vector   string            `json:"cvss2Vector"`
 | 
			
		||||
	Cvss2Severity string            `json:"cvss2Severity"`
 | 
			
		||||
	Cvss3Score    float64           `json:"cvss3Score"`
 | 
			
		||||
	Cvss3Vector   string            `json:"cvss3Vector"`
 | 
			
		||||
	Cvss3Severity string            `json:"cvss3Severity"`
 | 
			
		||||
	SourceLink    string            `json:"sourceLink"`
 | 
			
		||||
	Cpes          []Cpe             `json:"cpes,omitempty"`
 | 
			
		||||
	References    References        `json:"references,omitempty"`
 | 
			
		||||
	CweIDs        []string          `json:"cweIDs,omitempty"`
 | 
			
		||||
	Published     time.Time         `json:"published"`
 | 
			
		||||
	LastModified  time.Time         `json:"lastModified"`
 | 
			
		||||
	Mitigation    string            `json:"mitigation"` // RedHat API
 | 
			
		||||
	Optional      map[string]string `json:"optional,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Empty checks the content is empty
 | 
			
		||||
@@ -193,10 +210,12 @@ type CveContentType string
 | 
			
		||||
// NewCveContentType create CveContentType
 | 
			
		||||
func NewCveContentType(name string) CveContentType {
 | 
			
		||||
	switch name {
 | 
			
		||||
	case "nvdxml":
 | 
			
		||||
		return NvdXML
 | 
			
		||||
	case "nvd":
 | 
			
		||||
		return NVD
 | 
			
		||||
		return Nvd
 | 
			
		||||
	case "jvn":
 | 
			
		||||
		return JVN
 | 
			
		||||
		return Jvn
 | 
			
		||||
	case "redhat", "centos":
 | 
			
		||||
		return RedHat
 | 
			
		||||
	case "oracle":
 | 
			
		||||
@@ -205,21 +224,36 @@ func NewCveContentType(name string) CveContentType {
 | 
			
		||||
		return Ubuntu
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return Debian
 | 
			
		||||
	case "redhat_api":
 | 
			
		||||
		return RedHatAPI
 | 
			
		||||
	case "debian_security_tracker":
 | 
			
		||||
		return DebianSecurityTracker
 | 
			
		||||
	case "microsoft":
 | 
			
		||||
		return Microsoft
 | 
			
		||||
	default:
 | 
			
		||||
		return Unknown
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NVD is NVD
 | 
			
		||||
	NVD CveContentType = "nvd"
 | 
			
		||||
	// NvdXML is NvdXML
 | 
			
		||||
	NvdXML CveContentType = "nvdxml"
 | 
			
		||||
 | 
			
		||||
	// JVN is JVN
 | 
			
		||||
	JVN CveContentType = "jvn"
 | 
			
		||||
	// Nvd is Nvd
 | 
			
		||||
	Nvd CveContentType = "nvd"
 | 
			
		||||
 | 
			
		||||
	// Jvn is Jvn
 | 
			
		||||
	Jvn CveContentType = "jvn"
 | 
			
		||||
 | 
			
		||||
	// RedHat is RedHat
 | 
			
		||||
	RedHat CveContentType = "redhat"
 | 
			
		||||
 | 
			
		||||
	// RedHatAPI is RedHat
 | 
			
		||||
	RedHatAPI CveContentType = "redhat_api"
 | 
			
		||||
 | 
			
		||||
	// DebianSecurityTracker is Debian Secury tracker
 | 
			
		||||
	DebianSecurityTracker CveContentType = "debian_security_tracker"
 | 
			
		||||
 | 
			
		||||
	// Debian is Debian
 | 
			
		||||
	Debian CveContentType = "debian"
 | 
			
		||||
 | 
			
		||||
@@ -229,6 +263,12 @@ const (
 | 
			
		||||
	// Oracle is Oracle Linux
 | 
			
		||||
	Oracle CveContentType = "oracle"
 | 
			
		||||
 | 
			
		||||
	// SUSE is SUSE Linux
 | 
			
		||||
	SUSE CveContentType = "suse"
 | 
			
		||||
 | 
			
		||||
	// Microsoft is Microsoft
 | 
			
		||||
	Microsoft CveContentType = "microsoft"
 | 
			
		||||
 | 
			
		||||
	// Unknown is Unknown
 | 
			
		||||
	Unknown CveContentType = "unknown"
 | 
			
		||||
)
 | 
			
		||||
@@ -237,7 +277,16 @@ const (
 | 
			
		||||
type CveContentTypes []CveContentType
 | 
			
		||||
 | 
			
		||||
// AllCveContetTypes has all of CveContentTypes
 | 
			
		||||
var AllCveContetTypes = CveContentTypes{NVD, JVN, RedHat, Debian, Ubuntu}
 | 
			
		||||
var AllCveContetTypes = CveContentTypes{
 | 
			
		||||
	Nvd,
 | 
			
		||||
	NvdXML,
 | 
			
		||||
	Jvn,
 | 
			
		||||
	RedHat,
 | 
			
		||||
	Debian,
 | 
			
		||||
	Ubuntu,
 | 
			
		||||
	RedHatAPI,
 | 
			
		||||
	DebianSecurityTracker,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Except returns CveContentTypes except for given args
 | 
			
		||||
func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentTypes) {
 | 
			
		||||
@@ -258,7 +307,8 @@ func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentT
 | 
			
		||||
 | 
			
		||||
// Cpe is Common Platform Enumeration
 | 
			
		||||
type Cpe struct {
 | 
			
		||||
	CpeName string
 | 
			
		||||
	URI             string `json:"uri"`
 | 
			
		||||
	FormattedString string `json:"formattedString"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// References is a slice of Reference
 | 
			
		||||
@@ -266,7 +316,7 @@ type References []Reference
 | 
			
		||||
 | 
			
		||||
// Reference has a related link of the CVE
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	Source string
 | 
			
		||||
	Link   string
 | 
			
		||||
	RefID  string
 | 
			
		||||
	Source string `json:"source"`
 | 
			
		||||
	Link   string `json:"link"`
 | 
			
		||||
	RefID  string `json:"refID"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -60,27 +60,27 @@ func TestSourceLinks(t *testing.T) {
 | 
			
		||||
				lang:  "ja",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:       Jvn,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:       NvdXML,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Type:  Jvn,
 | 
			
		||||
					Value: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -95,23 +95,23 @@ func TestSourceLinks(t *testing.T) {
 | 
			
		||||
				lang:  "en",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:       Jvn,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:       NvdXML,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -129,16 +129,16 @@ func TestSourceLinks(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  Nvd,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.SourceLinks(tt.in.lang, "redhat", tt.in.cveID)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
			t.Errorf("\n[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -158,16 +158,16 @@ func TestVendorLink(t *testing.T) {
 | 
			
		||||
				vinfo: VulnInfo{
 | 
			
		||||
					CveID: "CVE-2017-6074",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:       JVN,
 | 
			
		||||
						Jvn: {
 | 
			
		||||
							Type:       Jvn,
 | 
			
		||||
							SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,4 +18,4 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// JSONVersion is JSON Version
 | 
			
		||||
const JSONVersion = 2
 | 
			
		||||
const JSONVersion = 4
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -38,6 +38,12 @@ func NewPackages(packs ...Package) Packages {
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps Packages) MergeNewVersion(as Packages) {
 | 
			
		||||
	for name, pack := range ps {
 | 
			
		||||
		pack.NewVersion = pack.Version
 | 
			
		||||
		pack.NewRelease = pack.Release
 | 
			
		||||
		ps[name] = pack
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		if pack, ok := ps[a.Name]; ok {
 | 
			
		||||
			pack.NewVersion = a.NewVersion
 | 
			
		||||
@@ -60,18 +66,7 @@ func (ps Packages) Merge(other Packages) Packages {
 | 
			
		||||
	return merged
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps Packages) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	nUpdatable := 0
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.NewVersion != "" {
 | 
			
		||||
			nUpdatable++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages", nUpdatable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindOne search a element by name-newver-newrel-arch
 | 
			
		||||
// FindOne search a element
 | 
			
		||||
func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
	for key, p := range ps {
 | 
			
		||||
		if f(p) {
 | 
			
		||||
@@ -81,16 +76,44 @@ func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
	return "", Package{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed packages.
 | 
			
		||||
// FindByFQPN search a package by Fully-Qualified-Package-Name
 | 
			
		||||
func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if nameVerRelArc == p.FQPN() {
 | 
			
		||||
			return &p, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, fmt.Errorf("Failed to find the package: %s", nameVerRelArc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed binary packages.
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Arch       string
 | 
			
		||||
	Repository string
 | 
			
		||||
	Changelog  Changelog
 | 
			
		||||
	Name             string               `json:"name"`
 | 
			
		||||
	Version          string               `json:"version"`
 | 
			
		||||
	Release          string               `json:"release"`
 | 
			
		||||
	NewVersion       string               `json:"newVersion"`
 | 
			
		||||
	NewRelease       string               `json:"newRelease"`
 | 
			
		||||
	Arch             string               `json:"arch"`
 | 
			
		||||
	Repository       string               `json:"repository"`
 | 
			
		||||
	Changelog        Changelog            `json:"changelog"`
 | 
			
		||||
	AffectedProcs    []AffectedProcess    `json:",omitempty"`
 | 
			
		||||
	NeedRestartProcs []NeedRestartProcess `json:",omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FQPN returns Fully-Qualified-Package-Name
 | 
			
		||||
// name-version-release.arch
 | 
			
		||||
func (p Package) FQPN() string {
 | 
			
		||||
	fqpn := p.Name
 | 
			
		||||
	if p.Version != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf("-%s", p.Version)
 | 
			
		||||
	}
 | 
			
		||||
	if p.Release != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf("-%s", p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	if p.Arch != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf(".%s", p.Arch)
 | 
			
		||||
	}
 | 
			
		||||
	return fqpn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVer returns package version-release
 | 
			
		||||
@@ -112,10 +135,16 @@ func (p Package) FormatNewVer() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVersionFromTo formats installed and new package version
 | 
			
		||||
func (p Package) FormatVersionFromTo(notFixedYet bool) string {
 | 
			
		||||
func (p Package) FormatVersionFromTo(notFixedYet bool, status string) string {
 | 
			
		||||
	to := p.FormatNewVer()
 | 
			
		||||
	if notFixedYet {
 | 
			
		||||
		to = "Not Fixed Yet"
 | 
			
		||||
		if status != "" {
 | 
			
		||||
			to = status
 | 
			
		||||
		} else {
 | 
			
		||||
			to = "Not Fixed Yet"
 | 
			
		||||
		}
 | 
			
		||||
	} else if p.NewVersion == "" {
 | 
			
		||||
		to = "Unknown"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s-%s -> %s", p.Name, p.FormatVer(), to)
 | 
			
		||||
}
 | 
			
		||||
@@ -139,7 +168,7 @@ func (p Package) FormatChangelog() string {
 | 
			
		||||
	case FailedToGetChangelog:
 | 
			
		||||
		clog = "No changelogs"
 | 
			
		||||
	case FailedToFindVersionInChangelog:
 | 
			
		||||
		clog = "Failed to parse changelogs. For detials, check yourself"
 | 
			
		||||
		clog = "Failed to parse changelogs. For details, check yourself"
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, packVer, delim.String(), clog)
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
@@ -148,6 +177,61 @@ func (p Package) FormatChangelog() string {
 | 
			
		||||
// Changelog has contents of changelog and how to get it.
 | 
			
		||||
// Method: models.detectionMethodStr
 | 
			
		||||
type Changelog struct {
 | 
			
		||||
	Contents string
 | 
			
		||||
	Method   DetectionMethod
 | 
			
		||||
	Contents string          `json:"contents"`
 | 
			
		||||
	Method   DetectionMethod `json:"method"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AffectedProcess keep a processes information affected by software update
 | 
			
		||||
type AffectedProcess struct {
 | 
			
		||||
	PID  string `json:"pid"`
 | 
			
		||||
	Name string `json:"name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NeedRestartProcess keep a processes information affected by software update
 | 
			
		||||
type NeedRestartProcess struct {
 | 
			
		||||
	PID         string `json:"pid"`
 | 
			
		||||
	Path        string `json:"path"`
 | 
			
		||||
	ServiceName string `json:"serviceName"`
 | 
			
		||||
	InitSystem  string `json:"initSystem"`
 | 
			
		||||
	HasInit     bool   `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SrcPackage has installed source package information.
 | 
			
		||||
// Debian based Linux has both of package and source information in dpkg.
 | 
			
		||||
// OVAL database often includes a source version (Not a binary version),
 | 
			
		||||
// so it is also needed to capture source version for OVAL version comparison.
 | 
			
		||||
// https://github.com/future-architect/vuls/issues/504
 | 
			
		||||
type SrcPackage struct {
 | 
			
		||||
	Name        string   `json:"name"`
 | 
			
		||||
	Version     string   `json:"version"`
 | 
			
		||||
	BinaryNames []string `json:"binaryNames"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddBinaryName add the name if not exists
 | 
			
		||||
func (s *SrcPackage) AddBinaryName(name string) {
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, n := range s.BinaryNames {
 | 
			
		||||
		if n == name {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		s.BinaryNames = append(s.BinaryNames, name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SrcPackages is Map of SrcPackage
 | 
			
		||||
// { "package-name": SrcPackage }
 | 
			
		||||
type SrcPackages map[string]SrcPackage
 | 
			
		||||
 | 
			
		||||
// FindByBinName finds by bin-package-name
 | 
			
		||||
func (s SrcPackages) FindByBinName(name string) (*SrcPackage, bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		for _, binName := range p.BinaryNames {
 | 
			
		||||
			if binName == name {
 | 
			
		||||
				return &p, true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -87,3 +87,107 @@ func TestMerge(t *testing.T) {
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddBinaryName(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       SrcPackage
 | 
			
		||||
		name     string
 | 
			
		||||
		expected SrcPackage
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{Name: "hoge"},
 | 
			
		||||
			"curl",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
			"curl",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl"},
 | 
			
		||||
			},
 | 
			
		||||
			"openssh",
 | 
			
		||||
			SrcPackage{
 | 
			
		||||
				Name:        "hoge",
 | 
			
		||||
				BinaryNames: []string{"curl", "openssh"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt.in.AddBinaryName(tt.name)
 | 
			
		||||
		if !reflect.DeepEqual(tt.in, tt.expected) {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", tt.in, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFindByBinName(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       SrcPackages
 | 
			
		||||
		name     string
 | 
			
		||||
		expected *SrcPackage
 | 
			
		||||
		ok       bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: map[string]SrcPackage{
 | 
			
		||||
				"packA": {
 | 
			
		||||
					Name:        "srcA",
 | 
			
		||||
					BinaryNames: []string{"binA"},
 | 
			
		||||
					Version:     "1.0.0",
 | 
			
		||||
				},
 | 
			
		||||
				"packB": {
 | 
			
		||||
					Name:        "srcB",
 | 
			
		||||
					BinaryNames: []string{"binB"},
 | 
			
		||||
					Version:     "2.0.0",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			name: "binA",
 | 
			
		||||
			expected: &SrcPackage{
 | 
			
		||||
				Name:        "srcA",
 | 
			
		||||
				BinaryNames: []string{"binA"},
 | 
			
		||||
				Version:     "1.0.0",
 | 
			
		||||
			},
 | 
			
		||||
			ok: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: map[string]SrcPackage{
 | 
			
		||||
				"packA": {
 | 
			
		||||
					Name:        "srcA",
 | 
			
		||||
					BinaryNames: []string{"binA"},
 | 
			
		||||
					Version:     "1.0.0",
 | 
			
		||||
				},
 | 
			
		||||
				"packB": {
 | 
			
		||||
					Name:        "srcB",
 | 
			
		||||
					BinaryNames: []string{"binB"},
 | 
			
		||||
					Version:     "2.0.0",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			name:     "nobin",
 | 
			
		||||
			expected: nil,
 | 
			
		||||
			ok:       false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		act, ok := tt.in.FindByBinName(tt.name)
 | 
			
		||||
		if ok != tt.ok {
 | 
			
		||||
			t.Errorf("[%d] expected %#v, actual %#v", i, tt.in, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
		if act != nil && !reflect.DeepEqual(*tt.expected, *act) {
 | 
			
		||||
			t.Errorf("[%d] expected %#v, actual %#v", i, tt.in, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -20,9 +20,13 @@ package models
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cwe"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanResults is a slide of ScanResult
 | 
			
		||||
@@ -30,36 +34,84 @@ type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
	ReportedAt  time.Time
 | 
			
		||||
	JSONVersion int
 | 
			
		||||
	Lang        string
 | 
			
		||||
	ServerUUID  string
 | 
			
		||||
	ServerName  string // TOML Section key
 | 
			
		||||
	Family      string
 | 
			
		||||
	Release     string
 | 
			
		||||
	Container   Container
 | 
			
		||||
	Platform    Platform
 | 
			
		||||
	JSONVersion      int                    `json:"jsonVersion"`
 | 
			
		||||
	Lang             string                 `json:"lang"`
 | 
			
		||||
	ServerUUID       string                 `json:"serverUUID"`
 | 
			
		||||
	ServerName       string                 `json:"serverName"` // TOML Section key
 | 
			
		||||
	Family           string                 `json:"family"`
 | 
			
		||||
	Release          string                 `json:"release"`
 | 
			
		||||
	Container        Container              `json:"container"`
 | 
			
		||||
	Platform         Platform               `json:"platform"`
 | 
			
		||||
	IPv4Addrs        []string               `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	IPv6Addrs        []string               `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	ScannedAt        time.Time              `json:"scannedAt"`
 | 
			
		||||
	ScanMode         string                 `json:"scanMode"`
 | 
			
		||||
	ScannedVersion   string                 `json:"scannedVersion"`
 | 
			
		||||
	ScannedRevision  string                 `json:"scannedRevision"`
 | 
			
		||||
	ScannedBy        string                 `json:"scannedBy"`
 | 
			
		||||
	ReportedAt       time.Time              `json:"reportedAt"`
 | 
			
		||||
	ReportedVersion  string                 `json:"reportedVersion"`
 | 
			
		||||
	ReportedRevision string                 `json:"reportedRevision"`
 | 
			
		||||
	ReportedBy       string                 `json:"reportedBy"`
 | 
			
		||||
	ScannedCves      VulnInfos              `json:"scannedCves"`
 | 
			
		||||
	RunningKernel    Kernel                 `json:"runningKernel"`
 | 
			
		||||
	Packages         Packages               `json:"packages"`
 | 
			
		||||
	CweDict          CweDict                `json:"cweDict"`
 | 
			
		||||
	Optional         map[string]interface{} `json:",omitempty"`
 | 
			
		||||
	SrcPackages      SrcPackages            `json:",omitempty"`
 | 
			
		||||
	Errors           []string               `json:"errors"`
 | 
			
		||||
	Config           struct {
 | 
			
		||||
		Scan   config.Config `json:"scan"`
 | 
			
		||||
		Report config.Config `json:"report"`
 | 
			
		||||
	} `json:"config"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// Scanned Vulns by SSH scan + CPE + OVAL
 | 
			
		||||
	ScannedCves VulnInfos
 | 
			
		||||
// CweDict is a dictionary for CWE
 | 
			
		||||
type CweDict map[string]CweDictEntry
 | 
			
		||||
 | 
			
		||||
	RunningKernel Kernel
 | 
			
		||||
	Packages      Packages
 | 
			
		||||
	Errors        []string
 | 
			
		||||
	Optional      [][]interface{}
 | 
			
		||||
 | 
			
		||||
	Config struct {
 | 
			
		||||
		Scan   config.Config
 | 
			
		||||
		Report config.Config
 | 
			
		||||
// Get the name, url, top10URL for the specified cweID, lang
 | 
			
		||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL string) {
 | 
			
		||||
	cweNum := strings.TrimPrefix(cweID, "CWE-")
 | 
			
		||||
	switch config.Conf.Lang {
 | 
			
		||||
	case "ja":
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
 | 
			
		||||
			top10Rank = dict.OwaspTopTen2017
 | 
			
		||||
			top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := cwe.CweDictJa[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
			url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
			
		||||
		} else {
 | 
			
		||||
			if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
				name = dict.Name
 | 
			
		||||
			}
 | 
			
		||||
			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]
 | 
			
		||||
		}
 | 
			
		||||
		url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
			
		||||
		if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kernel has the Release, version and whether need restart
 | 
			
		||||
type Kernel struct {
 | 
			
		||||
	Release        string
 | 
			
		||||
	Version        string
 | 
			
		||||
	RebootRequired bool
 | 
			
		||||
	Release        string `json:"release"`
 | 
			
		||||
	Version        string `json:"version"`
 | 
			
		||||
	RebootRequired bool   `json:"rebootRequired"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
@@ -76,25 +128,115 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	copiedScanResult := r
 | 
			
		||||
	copiedScanResult.ScannedCves = filtered
 | 
			
		||||
	return copiedScanResult
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnoreCves is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnoreCves(cveIDs []string) ScanResult {
 | 
			
		||||
func (r ScanResult) FilterIgnoreCves() ScanResult {
 | 
			
		||||
 | 
			
		||||
	ignoreCves := []string{}
 | 
			
		||||
	if len(r.Container.Name) == 0 {
 | 
			
		||||
		ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
 | 
			
		||||
	} else {
 | 
			
		||||
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
			if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
				ignoreCves = con.IgnoreCves
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
					r.Container.Name)
 | 
			
		||||
				return r
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
			return r
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		for _, c := range cveIDs {
 | 
			
		||||
		for _, c := range ignoreCves {
 | 
			
		||||
			if v.CveID == c {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	copiedScanResult := r
 | 
			
		||||
	copiedScanResult.ScannedCves = filtered
 | 
			
		||||
	return copiedScanResult
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterUnfixed is filter function.
 | 
			
		||||
func (r ScanResult) FilterUnfixed() ScanResult {
 | 
			
		||||
	if !config.Conf.IgnoreUnfixed {
 | 
			
		||||
		return r
 | 
			
		||||
	}
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		NotFixedAll := true
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			NotFixedAll = NotFixedAll && p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		return !NotFixedAll
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnorePkgs is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnorePkgs() ScanResult {
 | 
			
		||||
	ignorePkgsRegexps := []string{}
 | 
			
		||||
	if len(r.Container.Name) == 0 {
 | 
			
		||||
		ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
 | 
			
		||||
	} else {
 | 
			
		||||
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
			if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
				ignorePkgsRegexps = con.IgnorePkgsRegexp
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
					r.Container.Name)
 | 
			
		||||
				return r
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
			return r
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	regexps := []*regexp.Regexp{}
 | 
			
		||||
	for _, pkgRegexp := range ignorePkgsRegexps {
 | 
			
		||||
		re, err := regexp.Compile(pkgRegexp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Faild to parse %s, %s", pkgRegexp, err)
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			regexps = append(regexps, re)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(regexps) == 0 {
 | 
			
		||||
		return r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		if len(v.AffectedPackages) == 0 {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			match := false
 | 
			
		||||
			for _, re := range regexps {
 | 
			
		||||
				if re.MatchString(p.Name) {
 | 
			
		||||
					match = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !match {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extention
 | 
			
		||||
@@ -129,7 +271,7 @@ func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
// ServerInfoTui returns server information for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		line := fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
@@ -163,29 +305,106 @@ func (r ScanResult) FormatServerName() (name string) {
 | 
			
		||||
 | 
			
		||||
// FormatTextReportHeadedr returns header of text report
 | 
			
		||||
func (r ScanResult) FormatTextReportHeadedr() string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
	for i := 0; i < len(r.ServerInfo()); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
		r.ScannedCves.FormatFixedStatus(r.Packages),
 | 
			
		||||
		r.FormatUpdatablePacksSummary(),
 | 
			
		||||
		r.FormatExploitCveSummary(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (r ScanResult) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	if !r.isDisplayUpdatableNum() {
 | 
			
		||||
		return fmt.Sprintf("%d installed", len(r.Packages))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nUpdatable := 0
 | 
			
		||||
	for _, p := range r.Packages {
 | 
			
		||||
		if p.NewVersion == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if p.Version != p.NewVersion || p.Release != p.NewRelease {
 | 
			
		||||
			nUpdatable++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d installed, %d updatable",
 | 
			
		||||
		len(r.Packages),
 | 
			
		||||
		nUpdatable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatExploitCveSummary returns a summary of exploit cve
 | 
			
		||||
func (r ScanResult) FormatExploitCveSummary() string {
 | 
			
		||||
	nExploitCve := 0
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if 0 < len(vuln.Exploits) {
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d exploits", nExploitCve)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ScanResult) isDisplayUpdatableNum() bool {
 | 
			
		||||
	var mode config.ScanMode
 | 
			
		||||
	s, _ := config.Conf.Servers[r.ServerName]
 | 
			
		||||
	mode = s.Mode
 | 
			
		||||
 | 
			
		||||
	if mode.IsOffline() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if mode.IsFastRoot() || mode.IsDeep() {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if mode.IsFast() {
 | 
			
		||||
		switch r.Family {
 | 
			
		||||
		case config.RedHat,
 | 
			
		||||
			config.Oracle,
 | 
			
		||||
			config.Debian,
 | 
			
		||||
			config.Ubuntu,
 | 
			
		||||
			config.Raspbian:
 | 
			
		||||
			return false
 | 
			
		||||
		default:
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
func (r ScanResult) IsContainer() bool {
 | 
			
		||||
	return 0 < len(r.Container.ContainerID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsDeepScanMode checks if the scan mode is deep scan mode.
 | 
			
		||||
func (r ScanResult) IsDeepScanMode() bool {
 | 
			
		||||
	for _, s := range r.Config.Scan.Servers {
 | 
			
		||||
		for _, m := range s.ScanMode {
 | 
			
		||||
			if m == "deep" {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
	Type        string
 | 
			
		||||
	ContainerID string `json:"containerID"`
 | 
			
		||||
	Name        string `json:"name"`
 | 
			
		||||
	Image       string `json:"image"`
 | 
			
		||||
	Type        string `json:"type"`
 | 
			
		||||
	UUID        string `json:"uuid"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
	Name       string `json:"name"` // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string `json:"instanceID"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +43,7 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									Type:         NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Cvss2Score:   7.1,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
@@ -53,7 +54,7 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									Type:         NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
@@ -64,13 +65,13 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									Type:         NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         JVN,
 | 
			
		||||
									Type:         Jvn,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   7.2,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
@@ -86,7 +87,7 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								Type:         NvdXML,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Cvss2Score:   7.1,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
@@ -97,13 +98,13 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								Type:         NvdXML,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   6.9,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         JVN,
 | 
			
		||||
								Type:         Jvn,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   7.2,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
@@ -123,10 +124,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Ubuntu,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Severity:     "HIGH",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
									Type:          Ubuntu,
 | 
			
		||||
									CveID:         "CVE-2017-0001",
 | 
			
		||||
									Cvss2Severity: "HIGH",
 | 
			
		||||
									LastModified:  time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
@@ -134,10 +135,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         RedHat,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Severity:     "CRITICAL",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
									Type:          RedHat,
 | 
			
		||||
									CveID:         "CVE-2017-0002",
 | 
			
		||||
									Cvss2Severity: "CRITICAL",
 | 
			
		||||
									LastModified:  time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
@@ -145,10 +146,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Oracle,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Severity:     "IMPORTANT",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
									Type:          Oracle,
 | 
			
		||||
									CveID:         "CVE-2017-0003",
 | 
			
		||||
									Cvss2Severity: "IMPORTANT",
 | 
			
		||||
									LastModified:  time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
@@ -161,10 +162,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Ubuntu,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Severity:     "HIGH",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
								Type:          Ubuntu,
 | 
			
		||||
								CveID:         "CVE-2017-0001",
 | 
			
		||||
								Cvss2Severity: "HIGH",
 | 
			
		||||
								LastModified:  time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
@@ -172,10 +173,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         RedHat,
 | 
			
		||||
								CveID:        "CVE-2017-0002",
 | 
			
		||||
								Severity:     "CRITICAL",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
								Type:          RedHat,
 | 
			
		||||
								CveID:         "CVE-2017-0002",
 | 
			
		||||
								Cvss2Severity: "CRITICAL",
 | 
			
		||||
								LastModified:  time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
@@ -183,10 +184,10 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Oracle,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Severity:     "IMPORTANT",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
								Type:          Oracle,
 | 
			
		||||
								CveID:         "CVE-2017-0003",
 | 
			
		||||
								Cvss2Severity: "IMPORTANT",
 | 
			
		||||
								LastModified:  time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
@@ -205,7 +206,6 @@ func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		cves []string
 | 
			
		||||
@@ -219,6 +219,7 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
			in: in{
 | 
			
		||||
				cves: []string{"CVE-2017-0002"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
@@ -233,6 +234,7 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
@@ -245,7 +247,10 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.rs.FilterIgnoreCves(tt.in.cves)
 | 
			
		||||
		config.Conf.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			"name": {IgnoreCves: tt.in.cves},
 | 
			
		||||
		}
 | 
			
		||||
		actual := tt.in.rs.FilterIgnoreCves()
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
@@ -253,5 +258,481 @@ func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k := range actual.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnoreCveIDsContainer(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		cves []string
 | 
			
		||||
		rs   ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				cves: []string{"CVE-2017-0002"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					Container:  Container{Name: "dockerA"},
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				Container:  Container{Name: "dockerA"},
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		config.Conf.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			"name": {
 | 
			
		||||
				Containers: map[string]config.ContainerSetting{
 | 
			
		||||
					"dockerA": {
 | 
			
		||||
						IgnoreCves: tt.in.cves,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		actual := tt.in.rs.FilterIgnoreCves()
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k := range actual.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterUnfixed(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  ScanResult
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "a",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "b",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "c",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "d",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "b",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "c",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "d",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		config.Conf.IgnoreUnfixed = true
 | 
			
		||||
		actual := tt.in.FilterUnfixed()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out.ScannedCves, actual.ScannedCves) {
 | 
			
		||||
			o := pp.Sprintf("%v", tt.out.ScannedCves)
 | 
			
		||||
			a := pp.Sprintf("%v", actual.ScannedCves)
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, o, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnorePkgs(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		ignorePkgsRegexp []string
 | 
			
		||||
		rs               ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
								{Name: "vim"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{Name: "kernel"},
 | 
			
		||||
							{Name: "vim"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel", "^vim", "^bind"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
								{Name: "vim"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName:  "name",
 | 
			
		||||
				ScannedCves: VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		config.Conf.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			"name": {IgnorePkgsRegexp: tt.in.ignorePkgsRegexp},
 | 
			
		||||
		}
 | 
			
		||||
		actual := tt.in.rs.FilterIgnorePkgs()
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k := range actual.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnorePkgsContainer(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		ignorePkgsRegexp []string
 | 
			
		||||
		rs               ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					Container:  Container{Name: "dockerA"},
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				Container:  Container{Name: "dockerA"},
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					Container:  Container{Name: "dockerA"},
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
								{Name: "vim"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName: "name",
 | 
			
		||||
				Container:  Container{Name: "dockerA"},
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						AffectedPackages: PackageStatuses{
 | 
			
		||||
							{Name: "kernel"},
 | 
			
		||||
							{Name: "vim"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				ignorePkgsRegexp: []string{"^kernel", "^vim", "^bind"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ServerName: "name",
 | 
			
		||||
					Container:  Container{Name: "dockerA"},
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							AffectedPackages: PackageStatuses{
 | 
			
		||||
								{Name: "kernel"},
 | 
			
		||||
								{Name: "vim"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ServerName:  "name",
 | 
			
		||||
				Container:   Container{Name: "dockerA"},
 | 
			
		||||
				ScannedCves: VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		config.Conf.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			"name": {
 | 
			
		||||
				Containers: map[string]config.ContainerSetting{
 | 
			
		||||
					"dockerA": {
 | 
			
		||||
						IgnorePkgsRegexp: tt.in.ignorePkgsRegexp,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		actual := tt.in.rs.FilterIgnorePkgs()
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k := range actual.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsDisplayUpdatableNum(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		mode     []byte
 | 
			
		||||
		family   string
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Offline},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.FastRoot},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Deep},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.RedHat,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Oracle,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Debian,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Ubuntu,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Raspbian,
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.CentOS,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Amazon,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.FreeBSD,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.OpenSUSE,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			mode:     []byte{config.Fast},
 | 
			
		||||
			family:   config.Alpine,
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		mode := config.ScanMode{}
 | 
			
		||||
		for _, m := range tt.mode {
 | 
			
		||||
			mode.Set(m)
 | 
			
		||||
		}
 | 
			
		||||
		config.Conf.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			"name": {Mode: mode},
 | 
			
		||||
		}
 | 
			
		||||
		r := ScanResult{
 | 
			
		||||
			ServerName: "name",
 | 
			
		||||
			Family:     tt.family,
 | 
			
		||||
		}
 | 
			
		||||
		act := r.isDisplayUpdatableNum()
 | 
			
		||||
		if tt.expected != act {
 | 
			
		||||
			t.Errorf("[%d] expected %#v, actual %#v", i, tt.expected, act)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										142
									
								
								models/utils.go
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								models/utils.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,17 +18,22 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	cvedict "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConvertNvdToModel convert NVD to CveContent
 | 
			
		||||
func ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
 | 
			
		||||
// ConvertNvdXMLToModel convert NVD to CveContent
 | 
			
		||||
func ConvertNvdXMLToModel(cveID string, nvd *cvedict.NvdXML) *CveContent {
 | 
			
		||||
	if nvd == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range nvd.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
		cpes = append(cpes, Cpe{
 | 
			
		||||
			FormattedString: c.FormattedString,
 | 
			
		||||
			URI:             c.URI,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []Reference
 | 
			
		||||
@@ -39,42 +44,21 @@ func ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	validVec := true
 | 
			
		||||
	for _, v := range []string{
 | 
			
		||||
		nvd.AccessVector,
 | 
			
		||||
		nvd.AccessComplexity,
 | 
			
		||||
		nvd.Authentication,
 | 
			
		||||
		nvd.ConfidentialityImpact,
 | 
			
		||||
		nvd.IntegrityImpact,
 | 
			
		||||
		nvd.AvailabilityImpact,
 | 
			
		||||
	} {
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			validVec = false
 | 
			
		||||
		}
 | 
			
		||||
	cweIDs := []string{}
 | 
			
		||||
	for _, cid := range nvd.Cwes {
 | 
			
		||||
		cweIDs = append(cweIDs, cid.CweID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := ""
 | 
			
		||||
	if validVec {
 | 
			
		||||
		vector = fmt.Sprintf("AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s",
 | 
			
		||||
			string(nvd.AccessVector[0]),
 | 
			
		||||
			string(nvd.AccessComplexity[0]),
 | 
			
		||||
			string(nvd.Authentication[0]),
 | 
			
		||||
			string(nvd.ConfidentialityImpact[0]),
 | 
			
		||||
			string(nvd.IntegrityImpact[0]),
 | 
			
		||||
			string(nvd.AvailabilityImpact[0]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO CVSSv3
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         NVD,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Summary:      nvd.Summary,
 | 
			
		||||
		Cvss2Score:   nvd.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		Severity:     "", // severity is not contained in NVD
 | 
			
		||||
		SourceLink:   "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		CweID:        nvd.CweID,
 | 
			
		||||
		Type:          Nvd,
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		Summary:       nvd.Summary,
 | 
			
		||||
		Cvss2Score:    nvd.Cvss2.BaseScore,
 | 
			
		||||
		Cvss2Vector:   nvd.Cvss2.VectorString,
 | 
			
		||||
		Cvss2Severity: nvd.Cvss2.Severity,
 | 
			
		||||
		SourceLink:    "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		// Cpes:          cpes,
 | 
			
		||||
		CweIDs:       cweIDs,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    nvd.PublishedDate,
 | 
			
		||||
		LastModified: nvd.LastModifiedDate,
 | 
			
		||||
@@ -82,10 +66,16 @@ func ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertJvnToModel convert JVN to CveContent
 | 
			
		||||
func ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent {
 | 
			
		||||
func ConvertJvnToModel(cveID string, jvn *cvedict.Jvn) *CveContent {
 | 
			
		||||
	if jvn == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range jvn.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
		cpes = append(cpes, Cpe{
 | 
			
		||||
			FormattedString: c.FormattedString,
 | 
			
		||||
			URI:             c.URI,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refs := []Reference{}
 | 
			
		||||
@@ -96,19 +86,71 @@ func ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := strings.TrimSuffix(strings.TrimPrefix(jvn.Vector, "("), ")")
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         JVN,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Title:        jvn.Title,
 | 
			
		||||
		Summary:      jvn.Summary,
 | 
			
		||||
		Severity:     jvn.Severity,
 | 
			
		||||
		Cvss2Score:   jvn.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		SourceLink:   jvn.JvnLink,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		Type:          Jvn,
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		Title:         jvn.Title,
 | 
			
		||||
		Summary:       jvn.Summary,
 | 
			
		||||
		Cvss2Score:    jvn.Cvss2.BaseScore,
 | 
			
		||||
		Cvss2Vector:   jvn.Cvss2.VectorString,
 | 
			
		||||
		Cvss2Severity: jvn.Cvss2.Severity,
 | 
			
		||||
		Cvss3Score:    jvn.Cvss3.BaseScore,
 | 
			
		||||
		Cvss3Vector:   jvn.Cvss3.VectorString,
 | 
			
		||||
		Cvss3Severity: jvn.Cvss3.BaseSeverity,
 | 
			
		||||
		SourceLink:    jvn.JvnLink,
 | 
			
		||||
		// Cpes:          cpes,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    jvn.PublishedDate,
 | 
			
		||||
		LastModified: jvn.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertNvdJSONToModel convert NVD to CveContent
 | 
			
		||||
func ConvertNvdJSONToModel(cveID string, nvd *cvedict.NvdJSON) *CveContent {
 | 
			
		||||
	if nvd == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range nvd.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{
 | 
			
		||||
			FormattedString: c.FormattedString,
 | 
			
		||||
			URI:             c.URI,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []Reference
 | 
			
		||||
	for _, r := range nvd.References {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			Link:   r.Link,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cweIDs := []string{}
 | 
			
		||||
	for _, cid := range nvd.Cwes {
 | 
			
		||||
		cweIDs = append(cweIDs, cid.CweID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	desc := []string{}
 | 
			
		||||
	for _, d := range nvd.Descriptions {
 | 
			
		||||
		desc = append(desc, d.Value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:          Nvd,
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		Summary:       strings.Join(desc, "\n"),
 | 
			
		||||
		Cvss2Score:    nvd.Cvss2.BaseScore,
 | 
			
		||||
		Cvss2Vector:   nvd.Cvss2.VectorString,
 | 
			
		||||
		Cvss2Severity: nvd.Cvss2.Severity,
 | 
			
		||||
		Cvss3Score:    nvd.Cvss3.BaseScore,
 | 
			
		||||
		Cvss3Vector:   nvd.Cvss3.VectorString,
 | 
			
		||||
		Cvss3Severity: nvd.Cvss3.BaseSeverity,
 | 
			
		||||
		SourceLink:    "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		// Cpes:          cpes,
 | 
			
		||||
		CweIDs:       cweIDs,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    nvd.PublishedDate,
 | 
			
		||||
		LastModified: nvd.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -25,6 +25,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	exploitmodels "github.com/mozqnet/go-exploitdb/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VulnInfos has a map of VulnInfo
 | 
			
		||||
@@ -104,43 +105,86 @@ func (v VulnInfos) FormatCveSummary() string {
 | 
			
		||||
		m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatFixedStatus summarize the number of cves are fixed.
 | 
			
		||||
func (v VulnInfos) FormatFixedStatus(packs Packages) string {
 | 
			
		||||
	total, fixed := 0, 0
 | 
			
		||||
	for _, vInfo := range v {
 | 
			
		||||
		if len(vInfo.CpeURIs) != 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		total++
 | 
			
		||||
		if vInfo.PatchStatus(packs) == "Fixed" {
 | 
			
		||||
			fixed++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d/%d Fixed", fixed, total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatuses is a list of PackageStatus
 | 
			
		||||
type PackageStatuses []PackageStatus
 | 
			
		||||
 | 
			
		||||
// FormatTuiSummary format packname to show TUI summary
 | 
			
		||||
func (ps PackageStatuses) FormatTuiSummary() string {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		names = append(names, p.Name)
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(names, ", ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Store insert given pkg if missing, update pkg if exists
 | 
			
		||||
func (ps PackageStatuses) Store(pkg PackageStatus) PackageStatuses {
 | 
			
		||||
	for i, p := range ps {
 | 
			
		||||
		if p.Name == pkg.Name {
 | 
			
		||||
			ps[i] = pkg
 | 
			
		||||
			return ps
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ps = append(ps, pkg)
 | 
			
		||||
	return ps
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sort by Name
 | 
			
		||||
func (p PackageStatuses) Sort() {
 | 
			
		||||
	sort.Slice(p, func(i, j int) bool {
 | 
			
		||||
		return p[i].Name < p[j].Name
 | 
			
		||||
func (ps PackageStatuses) Sort() {
 | 
			
		||||
	sort.Slice(ps, func(i, j int) bool {
 | 
			
		||||
		return ps[i].Name < ps[j].Name
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatus has name and other status abount the package
 | 
			
		||||
type PackageStatus struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	NotFixedYet bool
 | 
			
		||||
	Name        string `json:"name"`
 | 
			
		||||
	NotFixedYet bool   `json:"notFixedYet"`
 | 
			
		||||
	FixState    string `json:"fixState"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnInfo has a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Confidence       Confidence
 | 
			
		||||
	AffectedPackages PackageStatuses
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	CveContents      CveContents
 | 
			
		||||
	CveID            string           `json:"cveID"`
 | 
			
		||||
	Confidences      Confidences      `json:"confidences"`
 | 
			
		||||
	AffectedPackages PackageStatuses  `json:"affectedPackages"`
 | 
			
		||||
	DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeURIs          []string         `json:"cpeURIs,omitempty"`          // CpeURIs related to this CVE defined in config.toml
 | 
			
		||||
	CveContents      CveContents      `json:"cveContents"`
 | 
			
		||||
	Exploits         []Exploit        `json:"exploits"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Titles returns tilte (TUI)
 | 
			
		||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Title) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.Title})
 | 
			
		||||
		if cont, found := v.CveContents[Jvn]; found && 0 < len(cont.Title) {
 | 
			
		||||
			values = append(values, CveContentStr{Jvn, cont.Title})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	// RedHat API has one line title.
 | 
			
		||||
	if cont, found := v.CveContents[RedHatAPI]; found && 0 < len(cont.Title) {
 | 
			
		||||
		values = append(values, CveContentStr{RedHatAPI, cont.Title})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{Nvd, NvdXML, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		// Only JVN has meaningful title. so return first 100 char of summary
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
@@ -171,16 +215,16 @@ func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
// Summaries returns summaries
 | 
			
		||||
func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Summary) {
 | 
			
		||||
		if cont, found := v.CveContents[Jvn]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := cont.Title
 | 
			
		||||
			summary += "\n" + strings.Replace(
 | 
			
		||||
				strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{JVN, summary})
 | 
			
		||||
			values = append(values, CveContentStr{Jvn, summary})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	order := CveContentTypes{Nvd, NvdXML, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
			
		||||
@@ -208,32 +252,75 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2Scores returns CVSS V2 Scores
 | 
			
		||||
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
// Mitigations returns mitigations
 | 
			
		||||
func (v VulnInfo) Mitigations(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{RedHatAPI}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Mitigation) {
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.Mitigation,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  Unknown,
 | 
			
		||||
			Value: "-",
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2Scores returns CVSS V2 Scores
 | 
			
		||||
func (v VulnInfo) Cvss2Scores(myFamily string) (values []CveContentCvss) {
 | 
			
		||||
	order := []CveContentType{Nvd, NvdXML, RedHat, Jvn}
 | 
			
		||||
	if myFamily != config.RedHat && myFamily != config.CentOS {
 | 
			
		||||
		order = append(order, NewCveContentType(myFamily))
 | 
			
		||||
	}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found {
 | 
			
		||||
			if cont.Cvss2Score == 0 && cont.Cvss2Severity == "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
					Severity: strings.ToUpper(cont.Cvss2Severity),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range values {
 | 
			
		||||
		if v.Type == RedHat {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Set the CVSS v2 score of vuln that exists only in gost.
 | 
			
		||||
	// Unfixed vulnerabilities detected by gost are not in OVAL, because
 | 
			
		||||
	// OVAL data has only vulnerabilities for already fixed.
 | 
			
		||||
	if cont, found := v.CveContents[RedHatAPI]; found {
 | 
			
		||||
		values = append(values, CveContentCvss{
 | 
			
		||||
			Type: RedHatAPI,
 | 
			
		||||
			Value: Cvss{
 | 
			
		||||
				Type:     CVSS2,
 | 
			
		||||
				Score:    cont.Cvss2Score,
 | 
			
		||||
				Vector:   cont.Cvss2Vector,
 | 
			
		||||
				Severity: strings.ToUpper(cont.Cvss2Severity),
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		if adv.Severity != "" {
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: "Vendor",
 | 
			
		||||
				Type: "Advisory",
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                severityToV2ScoreRoughly(adv.Severity),
 | 
			
		||||
@@ -245,35 +332,75 @@ func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// An OVAL entry in Ubuntu and Debian has only severity (CVSS score isn't included).
 | 
			
		||||
	// Show severity and dummy score calculated roughly.
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(order...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found &&
 | 
			
		||||
			cont.Cvss2Score == 0 &&
 | 
			
		||||
			cont.Cvss3Score == 0 &&
 | 
			
		||||
			cont.Cvss2Severity != "" {
 | 
			
		||||
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: cont.Type,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                severityToV2ScoreRoughly(cont.Cvss2Severity),
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Vector:               "-",
 | 
			
		||||
					Severity:             strings.ToUpper(cont.Cvss2Severity),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss3Scores returns CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	order := []CveContentType{Nvd, RedHat, Jvn}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss3Score {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
					Severity: strings.ToUpper(cont.Cvss3Severity),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range values {
 | 
			
		||||
		if v.Type == RedHat {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set the CVSS v3 score of vuln that exists only in gost.
 | 
			
		||||
	// Unfixed vulnerabilities detected by gost are not in OVAL, because
 | 
			
		||||
	// OVAL data has only vulnerabilities for already fixed.
 | 
			
		||||
	if cont, found := v.CveContents[RedHatAPI]; found {
 | 
			
		||||
		values = append(values, CveContentCvss{
 | 
			
		||||
			Type: RedHatAPI,
 | 
			
		||||
			Value: Cvss{
 | 
			
		||||
				Type:     CVSS3,
 | 
			
		||||
				Score:    cont.Cvss3Score,
 | 
			
		||||
				Vector:   cont.Cvss3Vector,
 | 
			
		||||
				Severity: strings.ToUpper(cont.Cvss3Severity),
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvss3Score returns Max CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	order := []CveContentType{Nvd, RedHat, RedHatAPI, Jvn}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
@@ -282,14 +409,13 @@ func (v VulnInfo) MaxCvss3Score() CveContentCvss {
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
					Severity: strings.ToUpper(cont.Cvss3Severity),
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss3Score
 | 
			
		||||
@@ -316,7 +442,7 @@ func (v VulnInfo) MaxCvssScore() CveContentCvss {
 | 
			
		||||
 | 
			
		||||
// MaxCvss2Score returns Max CVSS V2 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
	order := []CveContentType{Nvd, NvdXML, RedHat, RedHatAPI, Jvn}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
@@ -325,17 +451,13 @@ func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
			}
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
					Severity: strings.ToUpper(cont.Cvss2Severity),
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss2Score
 | 
			
		||||
@@ -350,8 +472,8 @@ func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	// Only Ubuntu, RedHat and Oracle have severity data in OVAL.
 | 
			
		||||
	order = []CveContentType{Ubuntu, RedHat, Oracle}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Severity) {
 | 
			
		||||
			score := severityToV2ScoreRoughly(cont.Severity)
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Cvss2Severity) {
 | 
			
		||||
			score := severityToV2ScoreRoughly(cont.Cvss2Severity)
 | 
			
		||||
			if max < score {
 | 
			
		||||
				value = CveContentCvss{
 | 
			
		||||
					Type: ctype,
 | 
			
		||||
@@ -360,7 +482,7 @@ func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
						Score:                score,
 | 
			
		||||
						CalculatedBySeverity: true,
 | 
			
		||||
						Vector:               cont.Cvss2Vector,
 | 
			
		||||
						Severity:             cont.Severity,
 | 
			
		||||
						Severity:             strings.ToUpper(cont.Cvss2Severity),
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -389,10 +511,55 @@ func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss has CveContentType and Cvss2
 | 
			
		||||
// AttackVector returns attack vector string
 | 
			
		||||
func (v VulnInfo) AttackVector() string {
 | 
			
		||||
	for _, cnt := range v.CveContents {
 | 
			
		||||
		if strings.HasPrefix(cnt.Cvss2Vector, "AV:N") ||
 | 
			
		||||
			strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:N") {
 | 
			
		||||
			return "Network"
 | 
			
		||||
		} else if strings.HasPrefix(cnt.Cvss2Vector, "AV:A") ||
 | 
			
		||||
			strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:A") {
 | 
			
		||||
			return "Adjacent"
 | 
			
		||||
		} else if strings.HasPrefix(cnt.Cvss2Vector, "AV:L") ||
 | 
			
		||||
			strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:L") {
 | 
			
		||||
			return "Local"
 | 
			
		||||
		} else if strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:P") {
 | 
			
		||||
			return "Physical"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if cont, found := v.CveContents[DebianSecurityTracker]; found {
 | 
			
		||||
		if attackRange, found := cont.Optional["attack range"]; found {
 | 
			
		||||
			return attackRange
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PatchStatus returns attack vector string
 | 
			
		||||
func (v VulnInfo) PatchStatus(packs Packages) string {
 | 
			
		||||
	// Vuls don't know patch status of the CPE
 | 
			
		||||
	if len(v.CpeURIs) != 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range v.AffectedPackages {
 | 
			
		||||
		if p.NotFixedYet {
 | 
			
		||||
			return "Unfixed"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// fast, offline mode doesn't have new version
 | 
			
		||||
		if pack, ok := packs[p.Name]; ok {
 | 
			
		||||
			if pack.NewVersion == "" {
 | 
			
		||||
				return "Unknown"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "Fixed"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss has CVSS information
 | 
			
		||||
type CveContentCvss struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value Cvss
 | 
			
		||||
	Type  CveContentType `json:"type"`
 | 
			
		||||
	Value Cvss           `json:"value"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvssType Represent the type of CVSS
 | 
			
		||||
@@ -408,20 +575,23 @@ const (
 | 
			
		||||
 | 
			
		||||
// Cvss has CVSS Score
 | 
			
		||||
type Cvss struct {
 | 
			
		||||
	Type                 CvssType
 | 
			
		||||
	Score                float64
 | 
			
		||||
	CalculatedBySeverity bool
 | 
			
		||||
	Vector               string
 | 
			
		||||
	Severity             string
 | 
			
		||||
	Type                 CvssType `json:"type"`
 | 
			
		||||
	Score                float64  `json:"score"`
 | 
			
		||||
	CalculatedBySeverity bool     `json:"calculatedBySeverity"`
 | 
			
		||||
	Vector               string   `json:"vector"`
 | 
			
		||||
	Severity             string   `json:"severity"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
func (c Cvss) Format() string {
 | 
			
		||||
	if c.Score == 0 || c.Vector == "" {
 | 
			
		||||
		return c.Severity
 | 
			
		||||
	}
 | 
			
		||||
	switch c.Type {
 | 
			
		||||
	case CVSS2:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector)
 | 
			
		||||
		return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
 | 
			
		||||
	case CVSS3:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
		return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
@@ -461,49 +631,13 @@ func severityToV2ScoreRoughly(severity string) float64 {
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss3 has CveContentType and Cvss3
 | 
			
		||||
//  type CveContentCvss3 struct {
 | 
			
		||||
//      Type  CveContentType
 | 
			
		||||
//      Value Cvss3
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Cvss3 has CVSS v3 Score, Vector and  Severity
 | 
			
		||||
//  type Cvss3 struct {
 | 
			
		||||
//      Score    float64
 | 
			
		||||
//      Vector   string
 | 
			
		||||
//      Severity string
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
//  func (c Cvss3) Format() string {
 | 
			
		||||
//      return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
//  func cvss3ScoreToSeverity(score float64) string {
 | 
			
		||||
//      if 9.0 <= score {
 | 
			
		||||
//          return "CRITICAL"
 | 
			
		||||
//      } else if 7.0 <= score {
 | 
			
		||||
//          return "HIGH"
 | 
			
		||||
//      } else if 4.0 <= score {
 | 
			
		||||
//          return "MEDIUM"
 | 
			
		||||
//      }
 | 
			
		||||
//      return "LOW"
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// FormatMaxCvssScore returns Max CVSS Score
 | 
			
		||||
func (v VulnInfo) FormatMaxCvssScore() string {
 | 
			
		||||
	v2Max := v.MaxCvss2Score()
 | 
			
		||||
	v3Max := v.MaxCvss3Score()
 | 
			
		||||
	if v2Max.Value.Score <= v3Max.Value.Score {
 | 
			
		||||
		return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
			v3Max.Value.Score,
 | 
			
		||||
			strings.ToUpper(v3Max.Value.Severity),
 | 
			
		||||
			v3Max.Type)
 | 
			
		||||
	}
 | 
			
		||||
	max := v.MaxCvssScore()
 | 
			
		||||
	return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
		v2Max.Value.Score,
 | 
			
		||||
		strings.ToUpper(v2Max.Value.Severity),
 | 
			
		||||
		v2Max.Type)
 | 
			
		||||
		max.Value.Score,
 | 
			
		||||
		strings.ToUpper(max.Value.Severity),
 | 
			
		||||
		max.Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2CalcURL returns CVSS v2 caluclator's URL
 | 
			
		||||
@@ -546,6 +680,8 @@ func (v VulnInfo) VendorLinks(family string) map[string]string {
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID
 | 
			
		||||
	case config.SUSEEnterpriseServer:
 | 
			
		||||
		links["SUSE-CVE"] = "https://www.suse.com/security/cve/" + v.CveID
 | 
			
		||||
	case config.FreeBSD:
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID)
 | 
			
		||||
@@ -556,37 +692,13 @@ func (v VulnInfo) VendorLinks(family string) map[string]string {
 | 
			
		||||
	return links
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilToEmpty set nil slice or map fields to empty to avoid null in JSON
 | 
			
		||||
func (v *VulnInfo) NilToEmpty() *VulnInfo {
 | 
			
		||||
	if v.CpeNames == nil {
 | 
			
		||||
		v.CpeNames = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.DistroAdvisories == nil {
 | 
			
		||||
		v.DistroAdvisories = []DistroAdvisory{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.AffectedPackages == nil {
 | 
			
		||||
		v.AffectedPackages = PackageStatuses{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.CveContents == nil {
 | 
			
		||||
		v.CveContents = NewCveContents()
 | 
			
		||||
	}
 | 
			
		||||
	for key := range v.CveContents {
 | 
			
		||||
		if v.CveContents[key].Cpes == nil {
 | 
			
		||||
			cont := v.CveContents[key]
 | 
			
		||||
			cont.Cpes = []Cpe{}
 | 
			
		||||
			v.CveContents[key] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	AdvisoryID  string
 | 
			
		||||
	Severity    string
 | 
			
		||||
	Issued      time.Time
 | 
			
		||||
	Updated     time.Time
 | 
			
		||||
	Description string
 | 
			
		||||
	AdvisoryID  string    `json:"advisoryID"`
 | 
			
		||||
	Severity    string    `json:"severity"`
 | 
			
		||||
	Issued      time.Time `json:"issued"`
 | 
			
		||||
	Updated     time.Time `json:"updated"`
 | 
			
		||||
	Description string    `json:"description"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format the distro advisory information
 | 
			
		||||
@@ -603,11 +715,45 @@ func (p DistroAdvisory) Format() string {
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Exploit :
 | 
			
		||||
type Exploit struct {
 | 
			
		||||
	ExploitType  exploitmodels.ExploitType `json:"exploitType"`
 | 
			
		||||
	ID           string                    `json:"id"`
 | 
			
		||||
	URL          string                    `json:"url"`
 | 
			
		||||
	Description  string                    `json:"description"`
 | 
			
		||||
	DocumentURL  *string                   `json:"documentURL,omitempty"`
 | 
			
		||||
	PaperURL     *string                   `json:"paperURL,omitempty"`
 | 
			
		||||
	ShellCodeURL *string                   `json:"shellCodeURL,omitempty"`
 | 
			
		||||
	BinaryURL    *string                   `json:"binaryURL,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidences is a list of Confidence
 | 
			
		||||
type Confidences []Confidence
 | 
			
		||||
 | 
			
		||||
// AppendIfMissing appends confidence to the list if missiong
 | 
			
		||||
func (cs *Confidences) AppendIfMissing(confidence Confidence) {
 | 
			
		||||
	for _, c := range *cs {
 | 
			
		||||
		if c.DetectionMethod == confidence.DetectionMethod {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	*cs = append(*cs, confidence)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SortByConfident sorts Confidences
 | 
			
		||||
func (cs Confidences) SortByConfident() Confidences {
 | 
			
		||||
	sort.Slice(cs, func(i, j int) bool {
 | 
			
		||||
		return cs[i].SortOrder < cs[j].SortOrder
 | 
			
		||||
	})
 | 
			
		||||
	return cs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
// Score: 0 - 100
 | 
			
		||||
type Confidence struct {
 | 
			
		||||
	Score           int
 | 
			
		||||
	DetectionMethod DetectionMethod
 | 
			
		||||
	Score           int             `json:"score"`
 | 
			
		||||
	DetectionMethod DetectionMethod `json:"detectionMethod"`
 | 
			
		||||
	SortOrder       int             `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Confidence) String() string {
 | 
			
		||||
@@ -632,6 +778,12 @@ const (
 | 
			
		||||
	// OvalMatchStr is a String representation of OvalMatch
 | 
			
		||||
	OvalMatchStr = "OvalMatch"
 | 
			
		||||
 | 
			
		||||
	// RedHatAPIStr is a String representation of RedHatAPIMatch
 | 
			
		||||
	RedHatAPIStr = "RedHatAPIMatch"
 | 
			
		||||
 | 
			
		||||
	// DebianSecurityTrackerMatchStr is a String representation of DebianSecurityTrackerMatch
 | 
			
		||||
	DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
 | 
			
		||||
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
			
		||||
 | 
			
		||||
@@ -647,20 +799,26 @@ const (
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	CpeNameMatch = Confidence{100, CpeNameMatchStr}
 | 
			
		||||
	CpeNameMatch = Confidence{100, CpeNameMatchStr, 1}
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
 | 
			
		||||
	YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr, 2}
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
 | 
			
		||||
	PkgAuditMatch = Confidence{100, PkgAuditMatchStr, 2}
 | 
			
		||||
 | 
			
		||||
	// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	OvalMatch = Confidence{100, OvalMatchStr}
 | 
			
		||||
	OvalMatch = Confidence{100, OvalMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
	// RedHatAPIMatch ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	RedHatAPIMatch = Confidence{100, RedHatAPIStr, 0}
 | 
			
		||||
 | 
			
		||||
	// DebianSecurityTrackerMatch ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
 | 
			
		||||
	ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3}
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
 | 
			
		||||
	ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr, 4}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -36,16 +36,16 @@ func TestTitles(t *testing.T) {
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
						Jvn: {
 | 
			
		||||
							Type:  Jvn,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:    NvdXML,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
@@ -54,11 +54,11 @@ func TestTitles(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Type:  Jvn,
 | 
			
		||||
					Value: "Title1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -73,16 +73,16 @@ func TestTitles(t *testing.T) {
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
						Jvn: {
 | 
			
		||||
							Type:  Jvn,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:    NvdXML,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
@@ -91,7 +91,7 @@ func TestTitles(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -137,8 +137,8 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
						Jvn: {
 | 
			
		||||
							Type:    Jvn,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
@@ -146,8 +146,8 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:    NvdXML,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
@@ -156,11 +156,11 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Type:  Jvn,
 | 
			
		||||
					Value: "Title JVN\nSummary JVN",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -175,8 +175,8 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
						Jvn: {
 | 
			
		||||
							Type:    Jvn,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
@@ -184,8 +184,8 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:    NvdXML,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
@@ -194,7 +194,7 @@ func TestSummaries(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Type:  NvdXML,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -235,8 +235,8 @@ func TestCountGroupBySeverity(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -248,8 +248,8 @@ func TestCountGroupBySeverity(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 2.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
@@ -257,8 +257,8 @@ func TestCountGroupBySeverity(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0004": {
 | 
			
		||||
					CveID: "CVE-2017-0004",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 5.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
@@ -296,8 +296,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -309,8 +309,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -324,8 +324,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -337,8 +337,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -355,8 +355,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -388,8 +388,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
						NvdXML: {
 | 
			
		||||
							Type:       NvdXML,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
@@ -407,8 +407,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							Cvss2Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
@@ -416,8 +416,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							Cvss2Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
@@ -427,8 +427,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							Cvss2Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
@@ -436,8 +436,8 @@ func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							Cvss2Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
@@ -460,29 +460,29 @@ func TestCvss2Scores(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:          Jvn,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.2,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.0,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:          NvdXML,
 | 
			
		||||
						Cvss2Score:    8.1,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentCvss{
 | 
			
		||||
				{
 | 
			
		||||
					Type: NVD,
 | 
			
		||||
					Type: NvdXML,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.1,
 | 
			
		||||
@@ -500,7 +500,7 @@ func TestCvss2Scores(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type: JVN,
 | 
			
		||||
					Type: Jvn,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.2,
 | 
			
		||||
@@ -517,9 +517,9 @@ func TestCvss2Scores(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.Cvss2Scores()
 | 
			
		||||
		actual := tt.in.Cvss2Scores("redhat")
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
			t.Errorf("[%d]\nexpected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -532,20 +532,20 @@ func TestMaxCvss2Scores(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:          Jvn,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.2,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.0,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:        NvdXML,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
@@ -553,7 +553,7 @@ func TestMaxCvss2Scores(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: JVN,
 | 
			
		||||
				Type: Jvn,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    8.2,
 | 
			
		||||
@@ -567,8 +567,8 @@ func TestMaxCvss2Scores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
						Type:          Ubuntu,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -613,16 +613,16 @@ func TestCvss3Scores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss3Severity: "HIGH",
 | 
			
		||||
						Cvss3Score:    8.0,
 | 
			
		||||
						Cvss3Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss3Score:  8.1,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:          NvdXML,
 | 
			
		||||
						Cvss2Score:    8.1,
 | 
			
		||||
						Cvss2Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -661,10 +661,10 @@ func TestMaxCvss3Scores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss3Severity: "HIGH",
 | 
			
		||||
						Cvss3Score:    8.0,
 | 
			
		||||
						Cvss3Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -708,8 +708,8 @@ func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:       NvdXML,
 | 
			
		||||
						Cvss3Score: 7.0,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
@@ -748,8 +748,8 @@ func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
						Type:          Ubuntu,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -768,17 +768,18 @@ func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
						Type:          Ubuntu,
 | 
			
		||||
						Cvss2Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 7.0,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:          NvdXML,
 | 
			
		||||
						Cvss2Score:    7.0,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Type: NvdXML,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    7.0,
 | 
			
		||||
@@ -810,12 +811,13 @@ func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
						Type:          Ubuntu,
 | 
			
		||||
						Cvss2Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 4.0,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:          NvdXML,
 | 
			
		||||
						Cvss2Score:    4.0,
 | 
			
		||||
						Cvss2Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				DistroAdvisories: []DistroAdvisory{
 | 
			
		||||
@@ -825,7 +827,7 @@ func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Type: NvdXML,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    4,
 | 
			
		||||
@@ -861,18 +863,18 @@ func TestFormatMaxCvssScore(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:          Jvn,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss3Score: 8.0,
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss3Score:    8.0,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:       NvdXML,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
@@ -883,19 +885,20 @@ func TestFormatMaxCvssScore(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					Jvn: {
 | 
			
		||||
						Type:          Jvn,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.0,
 | 
			
		||||
						Cvss3Score: 9.9,
 | 
			
		||||
						Type:          RedHat,
 | 
			
		||||
						Cvss2Severity: "HIGH",
 | 
			
		||||
						Cvss2Score:    8.0,
 | 
			
		||||
						Cvss3Severity: "HIGH",
 | 
			
		||||
						Cvss3Score:    9.9,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
					NvdXML: {
 | 
			
		||||
						Type:       NvdXML,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
@@ -934,3 +937,100 @@ func TestSortPackageStatues(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestStorePackageStatueses(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		pkgstats PackageStatuses
 | 
			
		||||
		in       PackageStatus
 | 
			
		||||
		out      PackageStatuses
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			pkgstats: PackageStatuses{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
			},
 | 
			
		||||
			in: PackageStatus{
 | 
			
		||||
				Name: "c",
 | 
			
		||||
			},
 | 
			
		||||
			out: PackageStatuses{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
				{Name: "c"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		out := tt.pkgstats.Store(tt.in)
 | 
			
		||||
		if ok := reflect.DeepEqual(tt.out, out); !ok {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAppendIfMissing(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  Confidences
 | 
			
		||||
		arg Confidence
 | 
			
		||||
		out Confidences
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
			arg: CpeNameMatch,
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
			arg: ChangelogExactMatch,
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				ChangelogExactMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt.in.AppendIfMissing(tt.arg)
 | 
			
		||||
		if !reflect.DeepEqual(tt.in, tt.out) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSortByConfiden(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  Confidences
 | 
			
		||||
		out Confidences
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
			},
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		act := tt.in.SortByConfident()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, act) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, act)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								oval/alpine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								oval/alpine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Alpine is the struct of Alpine Linux
 | 
			
		||||
type Alpine struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine creates OVAL client for SUSE
 | 
			
		||||
func NewAlpine() Alpine {
 | 
			
		||||
	return Alpine{
 | 
			
		||||
		Base{
 | 
			
		||||
			family: config.Alpine,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Alpine) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(relatedDefs.entries), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o Alpine) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	cveID := defPacks.def.Advisory.Cves[0].CveID
 | 
			
		||||
	vinfo, ok := r.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", cveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       cveID,
 | 
			
		||||
			Confidences: []models.Confidence{models.OvalMatch},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses()
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[cveID] = vinfo
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +38,7 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Debian.CveID,
 | 
			
		||||
			Confidence:  models.OvalMatch,
 | 
			
		||||
			Confidences: []models.Confidence{models.OvalMatch},
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -51,18 +52,28 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
			vinfo.Confidence = models.OvalMatch
 | 
			
		||||
		}
 | 
			
		||||
		vinfo.Confidences.AppendIfMissing(models.OvalMatch)
 | 
			
		||||
		cveContents[ctype] = ovalContent
 | 
			
		||||
		vinfo.CveContents = cveContents
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
	for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = true
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = pack.NotFixedYet
 | 
			
		||||
	}
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
 | 
			
		||||
 | 
			
		||||
	// update notFixedYet of SrcPackage
 | 
			
		||||
	for binName := range defPacks.actuallyAffectedPackNames {
 | 
			
		||||
		if srcPack, ok := r.SrcPackages.FindByBinName(binName); ok {
 | 
			
		||||
			for _, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == srcPack.Name {
 | 
			
		||||
					defPacks.actuallyAffectedPackNames[binName] = p.NotFixedYet
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses()
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[defPacks.def.Debian.CveID] = vinfo
 | 
			
		||||
}
 | 
			
		||||
@@ -78,11 +89,11 @@ func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveConten
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID:      def.Debian.CveID,
 | 
			
		||||
		Title:      def.Title,
 | 
			
		||||
		Summary:    def.Description,
 | 
			
		||||
		Severity:   def.Advisory.Severity,
 | 
			
		||||
		References: refs,
 | 
			
		||||
		CveID:         def.Debian.CveID,
 | 
			
		||||
		Title:         def.Title,
 | 
			
		||||
		Summary:       def.Description,
 | 
			
		||||
		Cvss2Severity: def.Advisory.Severity,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -103,36 +114,42 @@ func NewDebian() Debian {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
func (o Debian) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
 | 
			
		||||
	//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
 | 
			
		||||
	// 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,
 | 
			
		||||
			Name:       "linux",
 | 
			
		||||
			Version:    r.RunningKernel.Version,
 | 
			
		||||
			NewVersion: newVer,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(r.Packages, "linux")
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		// Remove linux added above to search for oval
 | 
			
		||||
		// Remove "linux" added above for oval search
 | 
			
		||||
		// linux is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if _, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = true
 | 
			
		||||
		if notFixedYet, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = notFixedYet
 | 
			
		||||
			delete(defPacks.actuallyAffectedPackNames, "linux")
 | 
			
		||||
			for i, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == "linux" {
 | 
			
		||||
@@ -141,6 +158,7 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -150,7 +168,7 @@ func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
			vuln.CveContents[models.Debian] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return len(relatedDefs.entries), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu is the interface for Debian OVAL
 | 
			
		||||
@@ -170,7 +188,7 @@ func NewUbuntu() Ubuntu {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	ovalKernelImageNames := []string{
 | 
			
		||||
		"linux-aws",
 | 
			
		||||
		"linux-azure",
 | 
			
		||||
@@ -225,13 +243,13 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -240,7 +258,6 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
 | 
			
		||||
		// Remove "linux" added above to search for oval
 | 
			
		||||
		// "linux" is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if _, ok := defPacks.actuallyAffectedPackNames["linux"]; !found && ok {
 | 
			
		||||
@@ -253,6 +270,7 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -262,5 +280,5 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
			vuln.CveContents[models.Ubuntu] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return len(relatedDefs.entries), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -36,7 +36,10 @@ func TestPackNamesOfUpdateDebian(t *testing.T) {
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "packA"}},
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packC"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -55,7 +58,8 @@ func TestPackNamesOfUpdateDebian(t *testing.T) {
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: true},
 | 
			
		||||
							{Name: "packC"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										64
									
								
								oval/oval.go
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								oval/oval.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,25 +21,23 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	CheckHTTPHealth() error
 | 
			
		||||
	FillWithOval(r *models.ScanResult) error
 | 
			
		||||
	FillWithOval(db.DB, *models.ScanResult) (int, error)
 | 
			
		||||
 | 
			
		||||
	// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
	CheckIfOvalFetched(string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFetched(db.DB, string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(db.DB, string, string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
@@ -49,11 +47,11 @@ type Base struct {
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
	if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", config.Conf.OvalDBURL)
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.Conf.OvalDict.URL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
@@ -67,20 +65,9 @@ func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		count, err := ovaldb.CountDefs(osFamily, release)
 | 
			
		||||
func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		count, err := driver.CountDefs(osFamily, release)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, fmt.Errorf("Failed to count OVAL defs: %s, %s, %v",
 | 
			
		||||
				osFamily, release, err)
 | 
			
		||||
@@ -88,7 +75,7 @@ func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err er
 | 
			
		||||
		return 0 < count, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "count", osFamily, release)
 | 
			
		||||
	url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "count", osFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
@@ -103,23 +90,12 @@ func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err er
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool, err error) {
 | 
			
		||||
	var lastModified time.Time
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		lastModified = ovaldb.GetLastModified(osFamily, release)
 | 
			
		||||
	if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		lastModified = driver.GetLastModified(osFamily, release)
 | 
			
		||||
	} else {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "lastmodified", osFamily, release)
 | 
			
		||||
		url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "lastmodified", osFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
@@ -132,19 +108,13 @@ func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	major := strings.Split(release, ".")[0]
 | 
			
		||||
	since := time.Now()
 | 
			
		||||
	since = since.AddDate(0, 0, -3)
 | 
			
		||||
	if lastModified.Before(since) {
 | 
			
		||||
		util.Log.Warnf("OVAL for %s %s is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. How to update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage",
 | 
			
		||||
			osFamily, major, lastModified)
 | 
			
		||||
			osFamily, release, lastModified)
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("OVAL is fresh: %s %s ", osFamily, major)
 | 
			
		||||
	util.Log.Infof("OVAL is fresh: %s %s ", osFamily, release)
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b Base) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of OvalDBType is sqlite3
 | 
			
		||||
	return config.Conf.OvalDBURL != "" && config.Conf.OvalDBType == "sqlite3"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										122
									
								
								oval/redhat.go
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								oval/redhat.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -25,6 +25,7 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -34,21 +35,20 @@ type RedHatBase struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
func (o RedHatBase) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(
 | 
			
		||||
			o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
		nCVEs += o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
@@ -65,10 +65,42 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
var kernelRelatedPackNames = map[string]bool{
 | 
			
		||||
	"kernel":                  true,
 | 
			
		||||
	"kernel-aarch64":          true,
 | 
			
		||||
	"kernel-abi-whitelists":   true,
 | 
			
		||||
	"kernel-bootwrapper":      true,
 | 
			
		||||
	"kernel-debug":            true,
 | 
			
		||||
	"kernel-debug-devel":      true,
 | 
			
		||||
	"kernel-devel":            true,
 | 
			
		||||
	"kernel-doc":              true,
 | 
			
		||||
	"kernel-headers":          true,
 | 
			
		||||
	"kernel-kdump":            true,
 | 
			
		||||
	"kernel-kdump-devel":      true,
 | 
			
		||||
	"kernel-rt":               true,
 | 
			
		||||
	"kernel-rt-debug":         true,
 | 
			
		||||
	"kernel-rt-debug-devel":   true,
 | 
			
		||||
	"kernel-rt-debug-kvm":     true,
 | 
			
		||||
	"kernel-rt-devel":         true,
 | 
			
		||||
	"kernel-rt-doc":           true,
 | 
			
		||||
	"kernel-rt-kvm":           true,
 | 
			
		||||
	"kernel-rt-trace":         true,
 | 
			
		||||
	"kernel-rt-trace-devel":   true,
 | 
			
		||||
	"kernel-rt-trace-kvm":     true,
 | 
			
		||||
	"kernel-rt-virt":          true,
 | 
			
		||||
	"kernel-rt-virt-devel":    true,
 | 
			
		||||
	"kernel-tools":            true,
 | 
			
		||||
	"kernel-tools-libs":       true,
 | 
			
		||||
	"kernel-tools-libs-devel": true,
 | 
			
		||||
	"perf":                    true,
 | 
			
		||||
	"python-perf":             true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int) {
 | 
			
		||||
	ctype := models.NewCveContentType(o.family)
 | 
			
		||||
	for _, cve := range defPacks.def.Advisory.Cves {
 | 
			
		||||
		ovalContent := *o.convertToModel(cve.CveID, &defPacks.def)
 | 
			
		||||
@@ -77,33 +109,43 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
			util.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
 | 
			
		||||
			vinfo = models.VulnInfo{
 | 
			
		||||
				CveID:       cve.CveID,
 | 
			
		||||
				Confidence:  models.OvalMatch,
 | 
			
		||||
				Confidences: models.Confidences{models.OvalMatch},
 | 
			
		||||
				CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
			}
 | 
			
		||||
			nCVEs++
 | 
			
		||||
		} else {
 | 
			
		||||
			cveContents := vinfo.CveContents
 | 
			
		||||
			if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
				util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
 | 
			
		||||
			if v, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
				if v.LastModified.After(ovalContent.LastModified) {
 | 
			
		||||
					util.Log.Debugf("%s, OvalID: %d ignroed: ",
 | 
			
		||||
						cve.CveID, defPacks.def.ID)
 | 
			
		||||
					continue
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("%s also detected by OVAL", cve.CveID)
 | 
			
		||||
				cveContents = models.CveContents{}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
				vinfo.Confidence = models.OvalMatch
 | 
			
		||||
			}
 | 
			
		||||
			vinfo.Confidences.AppendIfMissing(models.OvalMatch)
 | 
			
		||||
			cveContents[ctype] = ovalContent
 | 
			
		||||
			vinfo.CveContents = cveContents
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
		for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[pack.Name] = true
 | 
			
		||||
			if nfy, ok := defPacks.actuallyAffectedPackNames[pack.Name]; !ok {
 | 
			
		||||
				defPacks.actuallyAffectedPackNames[pack.Name] = pack.NotFixedYet
 | 
			
		||||
			} else if nfy {
 | 
			
		||||
				defPacks.actuallyAffectedPackNames[pack.Name] = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
 | 
			
		||||
		vinfo.AffectedPackages = defPacks.toPackStatuses()
 | 
			
		||||
		vinfo.AffectedPackages.Sort()
 | 
			
		||||
		r.ScannedCves[cve.CveID] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
@@ -128,20 +170,32 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo
 | 
			
		||||
			severity = cve.Impact
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sev2, sev3 := "", ""
 | 
			
		||||
		if score2 != 0 {
 | 
			
		||||
			sev2 = severity
 | 
			
		||||
		}
 | 
			
		||||
		if score3 != 0 {
 | 
			
		||||
			sev3 = severity
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// CWE-ID in RedHat OVAL may have multiple cweIDs separated by space
 | 
			
		||||
		cwes := strings.Fields(cve.Cwe)
 | 
			
		||||
 | 
			
		||||
		return &models.CveContent{
 | 
			
		||||
			Type:         models.NewCveContentType(o.family),
 | 
			
		||||
			CveID:        cve.CveID,
 | 
			
		||||
			Title:        def.Title,
 | 
			
		||||
			Summary:      def.Description,
 | 
			
		||||
			Severity:     severity,
 | 
			
		||||
			Cvss2Score:   score2,
 | 
			
		||||
			Cvss2Vector:  vec2,
 | 
			
		||||
			Cvss3Score:   score3,
 | 
			
		||||
			Cvss3Vector:  vec3,
 | 
			
		||||
			References:   refs,
 | 
			
		||||
			CweID:        cve.Cwe,
 | 
			
		||||
			Published:    def.Advisory.Issued,
 | 
			
		||||
			LastModified: def.Advisory.Updated,
 | 
			
		||||
			Type:          models.NewCveContentType(o.family),
 | 
			
		||||
			CveID:         cve.CveID,
 | 
			
		||||
			Title:         def.Title,
 | 
			
		||||
			Summary:       def.Description,
 | 
			
		||||
			Cvss2Score:    score2,
 | 
			
		||||
			Cvss2Vector:   vec2,
 | 
			
		||||
			Cvss2Severity: sev2,
 | 
			
		||||
			Cvss3Score:    score3,
 | 
			
		||||
			Cvss3Vector:   vec3,
 | 
			
		||||
			Cvss3Severity: sev3,
 | 
			
		||||
			References:    refs,
 | 
			
		||||
			CweIDs:        cwes,
 | 
			
		||||
			Published:     def.Advisory.Issued,
 | 
			
		||||
			LastModified:  def.Advisory.Updated,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -156,7 +210,7 @@ func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:len(ss)], "/")
 | 
			
		||||
		return score, strings.Join(ss[1:], "/")
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
@@ -170,7 +224,7 @@ func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:len(ss)], "/")
 | 
			
		||||
		return score, fmt.Sprintf("CVSS:3.0/%s", ss[1])
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
@@ -207,7 +261,7 @@ func NewCentOS() CentOS {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Oracle is the interface for CentOS OVAL
 | 
			
		||||
// Oracle is the interface for Oracle OVAL
 | 
			
		||||
type Oracle struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -72,7 +72,7 @@ func TestParseCvss3(t *testing.T) {
 | 
			
		||||
			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: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
				vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
@@ -102,7 +102,10 @@ func TestPackNamesOfUpdate(t *testing.T) {
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "packA"}},
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: false},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -125,7 +128,7 @@ func TestPackNamesOfUpdate(t *testing.T) {
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB"},
 | 
			
		||||
							{Name: "packB", NotFixedYet: true},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								oval/suse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								oval/suse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SUSE is the struct of SUSE Linux
 | 
			
		||||
type SUSE struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSUSE creates OVAL client for SUSE
 | 
			
		||||
func NewSUSE() SUSE {
 | 
			
		||||
	// TODO implement other family
 | 
			
		||||
	return SUSE{
 | 
			
		||||
		Base{
 | 
			
		||||
			family: config.SUSEEnterpriseServer,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o SUSE) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.SUSE]; ok {
 | 
			
		||||
			cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.SUSE] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return len(relatedDefs.entries), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o SUSE) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ovalContent := *o.convertToModel(&defPacks.def)
 | 
			
		||||
	ovalContent.Type = models.NewCveContentType(o.family)
 | 
			
		||||
	vinfo, ok := r.ScannedCves[defPacks.def.Title]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Title)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Title,
 | 
			
		||||
			Confidences: models.Confidences{models.OvalMatch},
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cveContents := vinfo.CveContents
 | 
			
		||||
		ctype := models.NewCveContentType(o.family)
 | 
			
		||||
		if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
			util.Log.Debugf("%s OVAL will be overwritten", defPacks.def.Title)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("%s is also detected by OVAL", defPacks.def.Title)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		vinfo.Confidences.AppendIfMissing(models.OvalMatch)
 | 
			
		||||
		cveContents[ctype] = ovalContent
 | 
			
		||||
		vinfo.CveContents = cveContents
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
	for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = pack.NotFixedYet
 | 
			
		||||
	}
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses()
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[defPacks.def.Title] = vinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	for _, r := range def.References {
 | 
			
		||||
		refs = append(refs, models.Reference{
 | 
			
		||||
			Link:   r.RefURL,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
			RefID:  r.RefID,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID:      def.Title,
 | 
			
		||||
		Title:      def.Title,
 | 
			
		||||
		Summary:    def.Description,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										319
									
								
								oval/util.go
									
									
									
									
									
								
							
							
						
						
									
										319
									
								
								oval/util.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -21,6 +21,8 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
@@ -30,7 +32,6 @@ import (
 | 
			
		||||
	debver "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	rpmver "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
@@ -40,114 +41,61 @@ type ovalResult struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type defPacks struct {
 | 
			
		||||
	def                       ovalmodels.Definition
 | 
			
		||||
	def ovalmodels.Definition
 | 
			
		||||
 | 
			
		||||
	// BinaryPackageName : NotFixedYet
 | 
			
		||||
	actuallyAffectedPackNames map[string]bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e defPacks) toPackStatuses(family string, packs models.Packages) (ps models.PackageStatuses) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.Ubuntu:
 | 
			
		||||
		packNotFixedYet := map[string]bool{}
 | 
			
		||||
		for _, p := range e.def.AffectedPacks {
 | 
			
		||||
			packNotFixedYet[p.Name] = p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		for k := range e.actuallyAffectedPackNames {
 | 
			
		||||
			ps = append(ps, models.PackageStatus{
 | 
			
		||||
				Name:        k,
 | 
			
		||||
				NotFixedYet: packNotFixedYet[k],
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case config.CentOS, config.Debian:
 | 
			
		||||
		// There are many packages that has been fixed in RedHat, but not been fixed in CentOS
 | 
			
		||||
		for name := range e.actuallyAffectedPackNames {
 | 
			
		||||
			pack, ok := packs[name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				util.Log.Warnf("Faild to find in Package list: %s", name)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ovalPackVer := ""
 | 
			
		||||
			for _, p := range e.def.AffectedPacks {
 | 
			
		||||
				if p.Name == name {
 | 
			
		||||
					ovalPackVer = p.Version
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if ovalPackVer == "" {
 | 
			
		||||
				util.Log.Warnf("Faild to find in Oval Package list: %s", name)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if pack.NewVersion == "" {
 | 
			
		||||
				// compare version: installed vs oval
 | 
			
		||||
				vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
 | 
			
		||||
				verb := rpmver.NewVersion(ovalPackVer)
 | 
			
		||||
				notFixedYet := false
 | 
			
		||||
				if vera.LessThan(verb) {
 | 
			
		||||
					notFixedYet = true
 | 
			
		||||
				}
 | 
			
		||||
				ps = append(ps, models.PackageStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					NotFixedYet: notFixedYet,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				// compare version: newVer vs oval
 | 
			
		||||
				packNewVer := fmt.Sprintf("%s-%s", pack.NewVersion, pack.NewRelease)
 | 
			
		||||
				vera := rpmver.NewVersion(packNewVer)
 | 
			
		||||
				verb := rpmver.NewVersion(ovalPackVer)
 | 
			
		||||
				notFixedYet := false
 | 
			
		||||
				if vera.LessThan(verb) {
 | 
			
		||||
					notFixedYet = true
 | 
			
		||||
				}
 | 
			
		||||
				ps = append(ps, models.PackageStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					NotFixedYet: notFixedYet,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		for k := range e.actuallyAffectedPackNames {
 | 
			
		||||
			ps = append(ps, models.PackageStatus{
 | 
			
		||||
				Name: k,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
func (e defPacks) toPackStatuses() (ps models.PackageStatuses) {
 | 
			
		||||
	for name, notFixedYet := range e.actuallyAffectedPackNames {
 | 
			
		||||
		ps = append(ps, models.PackageStatus{
 | 
			
		||||
			Name:        name,
 | 
			
		||||
			NotFixedYet: notFixedYet,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string) (upserted bool) {
 | 
			
		||||
	for i, entry := range e.entries {
 | 
			
		||||
		if entry.def.DefinitionID == def.DefinitionID {
 | 
			
		||||
			e.entries[i].actuallyAffectedPackNames[packName] = true
 | 
			
		||||
			return true
 | 
			
		||||
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string, notFixedYet bool) (upserted bool) {
 | 
			
		||||
	// alpine's entry is empty since Alpine secdb is not OVAL format
 | 
			
		||||
	if def.DefinitionID != "" {
 | 
			
		||||
		for i, entry := range e.entries {
 | 
			
		||||
			if entry.def.DefinitionID == def.DefinitionID {
 | 
			
		||||
				e.entries[i].actuallyAffectedPackNames[packName] = notFixedYet
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	e.entries = append(e.entries, defPacks{
 | 
			
		||||
		def: def,
 | 
			
		||||
		actuallyAffectedPackNames: map[string]bool{packName: true},
 | 
			
		||||
		def:                       def,
 | 
			
		||||
		actuallyAffectedPackNames: map[string]bool{packName: notFixedYet},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	pack models.Package
 | 
			
		||||
	packName          string
 | 
			
		||||
	versionRelease    string
 | 
			
		||||
	NewVersionRelease string
 | 
			
		||||
	binaryPackNames   []string
 | 
			
		||||
	isSrcPack         bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	pack *models.Package
 | 
			
		||||
	defs []ovalmodels.Definition
 | 
			
		||||
	request request
 | 
			
		||||
	defs    []ovalmodels.Definition
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
 | 
			
		||||
func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
	relatedDefs ovalResult, err error) {
 | 
			
		||||
 | 
			
		||||
	reqChan := make(chan request, len(r.Packages))
 | 
			
		||||
	resChan := make(chan response, len(r.Packages))
 | 
			
		||||
	errChan := make(chan error, len(r.Packages))
 | 
			
		||||
	nReq := len(r.Packages) + len(r.SrcPackages)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
@@ -155,29 +103,40 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				pack: pack,
 | 
			
		||||
				packName:          pack.Name,
 | 
			
		||||
				versionRelease:    pack.FormatVer(),
 | 
			
		||||
				NewVersionRelease: pack.FormatVer(),
 | 
			
		||||
				isSrcPack:         false,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				packName:        pack.Name,
 | 
			
		||||
				binaryPackNames: pack.BinaryNames,
 | 
			
		||||
				versionRelease:  pack.Version,
 | 
			
		||||
				isSrcPack:       true,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range r.Packages {
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					config.Conf.OvalDBURL,
 | 
			
		||||
					config.Conf.OvalDict.URL,
 | 
			
		||||
					"packs",
 | 
			
		||||
					r.Family,
 | 
			
		||||
					r.Release,
 | 
			
		||||
					req.pack.Name,
 | 
			
		||||
					req.packName,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, &req.pack, resChan, errChan)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -185,26 +144,21 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for range r.Packages {
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			for _, def := range res.defs {
 | 
			
		||||
				for _, p := range def.AffectedPacks {
 | 
			
		||||
					if res.pack.Name != p.Name {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				affected, notFixedYet := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel)
 | 
			
		||||
				if !affected {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
					if p.NotFixedYet {
 | 
			
		||||
						relatedDefs.upsert(def, p.Name)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if less, err := lessThan(r.Family, *res.pack, p); err != nil {
 | 
			
		||||
						util.Log.Debugf("Failed to parse versions: %s", err)
 | 
			
		||||
						util.Log.Debugf("%#v\n%#v", *res.pack, p)
 | 
			
		||||
					} else if less {
 | 
			
		||||
						relatedDefs.upsert(def, p.Name)
 | 
			
		||||
				if res.request.isSrcPack {
 | 
			
		||||
					for _, n := range res.request.binaryPackNames {
 | 
			
		||||
						relatedDefs.upsert(def, n, false)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					relatedDefs.upsert(def, res.request.packName, notFixedYet)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
@@ -219,7 +173,7 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, pack *models.Package, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
func httpGet(url string, req request, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
@@ -257,64 +211,123 @@ func httpGet(url string, pack *models.Package, resChan chan<- response, errChan
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		pack: pack,
 | 
			
		||||
		defs: defs,
 | 
			
		||||
		request: req,
 | 
			
		||||
		defs:    defs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDefsByPackNameFromOvalDB(family, osRelease string,
 | 
			
		||||
	installedPacks models.Packages) (relatedDefs ovalResult, err error) {
 | 
			
		||||
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	path := config.Conf.OvalDBURL
 | 
			
		||||
	if config.Conf.OvalDBType == "sqlite3" {
 | 
			
		||||
		path = config.Conf.OvalDBPath
 | 
			
		||||
func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDefs ovalResult, err error) {
 | 
			
		||||
	requests := []request{}
 | 
			
		||||
	for _, pack := range r.Packages {
 | 
			
		||||
		requests = append(requests, request{
 | 
			
		||||
			packName:          pack.Name,
 | 
			
		||||
			versionRelease:    pack.FormatVer(),
 | 
			
		||||
			NewVersionRelease: pack.FormatNewVer(),
 | 
			
		||||
			isSrcPack:         false,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Open oval-dictionary db (%s): %s", config.Conf.OvalDBType, path)
 | 
			
		||||
 | 
			
		||||
	var ovaldb db.DB
 | 
			
		||||
	if ovaldb, err = db.NewDB(
 | 
			
		||||
		family,
 | 
			
		||||
		config.Conf.OvalDBType,
 | 
			
		||||
		path,
 | 
			
		||||
		config.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	for _, pack := range r.SrcPackages {
 | 
			
		||||
		requests = append(requests, request{
 | 
			
		||||
			packName:        pack.Name,
 | 
			
		||||
			binaryPackNames: pack.BinaryNames,
 | 
			
		||||
			versionRelease:  pack.Version,
 | 
			
		||||
			isSrcPack:       true,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	defer ovaldb.CloseDB()
 | 
			
		||||
	for _, installedPack := range installedPacks {
 | 
			
		||||
		definitions, err := ovaldb.GetByPackName(osRelease, installedPack.Name)
 | 
			
		||||
 | 
			
		||||
	for _, req := range requests {
 | 
			
		||||
		definitions, err := driver.GetByPackName(r.Release, req.packName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", family, err)
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package: %#v, err: %s", r.Family, req, err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, def := range definitions {
 | 
			
		||||
			for _, ovalPack := range def.AffectedPacks {
 | 
			
		||||
				if installedPack.Name != ovalPack.Name {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			affected, notFixedYet := isOvalDefAffected(def, req, r.Family, r.RunningKernel)
 | 
			
		||||
			if !affected {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
				if ovalPack.NotFixedYet {
 | 
			
		||||
					relatedDefs.upsert(def, installedPack.Name)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				less, err := lessThan(family, installedPack, ovalPack)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					util.Log.Debugf("Failed to parse versions: %s", err)
 | 
			
		||||
					util.Log.Debugf("%#v\n%#v", installedPack, ovalPack)
 | 
			
		||||
				} else if less {
 | 
			
		||||
					relatedDefs.upsert(def, installedPack.Name)
 | 
			
		||||
			if req.isSrcPack {
 | 
			
		||||
				for _, n := range req.binaryPackNames {
 | 
			
		||||
					relatedDefs.upsert(def, n, false)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				relatedDefs.upsert(def, req.packName, notFixedYet)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bool, error) {
 | 
			
		||||
func major(version string) string {
 | 
			
		||||
	ss := strings.SplitN(version, ":", 2)
 | 
			
		||||
	ver := ""
 | 
			
		||||
	if len(ss) == 1 {
 | 
			
		||||
		ver = ss[0]
 | 
			
		||||
	} else {
 | 
			
		||||
		ver = ss[1]
 | 
			
		||||
	}
 | 
			
		||||
	return ver[0:strings.Index(ver, ".")]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel) (affected, notFixedYet bool) {
 | 
			
		||||
	for _, ovalPack := range def.AffectedPacks {
 | 
			
		||||
		if req.packName != ovalPack.Name {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if running.Release != "" {
 | 
			
		||||
			switch family {
 | 
			
		||||
			case config.RedHat, config.CentOS:
 | 
			
		||||
				// For kernel related packages, ignore OVAL information with different major versions
 | 
			
		||||
				if _, ok := kernelRelatedPackNames[ovalPack.Name]; ok {
 | 
			
		||||
					if major(ovalPack.Version) != major(running.Release) {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ovalPack.NotFixedYet {
 | 
			
		||||
			return true, true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		less, err := lessThan(family, req.versionRelease, ovalPack)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Debugf("Failed to parse versions: %s, Ver: %#v, OVAL: %#v, DefID: %s",
 | 
			
		||||
				err, req.versionRelease, ovalPack, def.DefinitionID)
 | 
			
		||||
			return false, false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if less {
 | 
			
		||||
			if req.isSrcPack {
 | 
			
		||||
				// Unable to judge whether fixed or not fixed of src package(Ubuntu, Debian)
 | 
			
		||||
				return true, false
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// `offline` or `fast` scan mode can't get a updatable version.
 | 
			
		||||
			// In these mode, the blow field was set empty.
 | 
			
		||||
			// Vuls can not judge fixed or unfixed.
 | 
			
		||||
			if req.NewVersionRelease == "" {
 | 
			
		||||
				return true, false
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// compare version: newVer vs oval
 | 
			
		||||
			less, err := lessThan(family, req.NewVersionRelease, ovalPack)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Debugf("Failed to parse versions: %s, NewVer: %#v, OVAL: %#v, DefID: %s",
 | 
			
		||||
					err, req.NewVersionRelease, ovalPack, def.DefinitionID)
 | 
			
		||||
				return false, false
 | 
			
		||||
			}
 | 
			
		||||
			return true, less
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lessThan(family, versionRelease string, packB ovalmodels.Package) (bool, error) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.Debian, config.Ubuntu:
 | 
			
		||||
		vera, err := debver.NewVersion(packA.Version)
 | 
			
		||||
		vera, err := debver.NewVersion(versionRelease)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -323,10 +336,18 @@ func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bo
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	case config.RedHat, config.CentOS, config.Oracle:
 | 
			
		||||
		vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", packA.Version, packA.Release))
 | 
			
		||||
	case config.Oracle, config.SUSEEnterpriseServer, config.Alpine:
 | 
			
		||||
		vera := rpmver.NewVersion(versionRelease)
 | 
			
		||||
		verb := rpmver.NewVersion(packB.Version)
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	case config.RedHat, config.CentOS: // TODO: Suport config.Scientific
 | 
			
		||||
		rea := regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.centos)?`)
 | 
			
		||||
		reb := regexp.MustCompile(`\.el(\d+)(?:_\d+)?`)
 | 
			
		||||
		vera := rpmver.NewVersion(rea.ReplaceAllString(versionRelease, ".el$1"))
 | 
			
		||||
		verb := rpmver.NewVersion(reb.ReplaceAllString(packB.Version, ".el$1"))
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	default:
 | 
			
		||||
		util.Log.Errorf("Not implemented yet: %s", family)
 | 
			
		||||
	}
 | 
			
		||||
	return false, fmt.Errorf("Package version comparison not supported: %s", family)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1015
									
								
								oval/util_test.go
									
									
									
									
									
								
							
							
						
						
									
										1015
									
								
								oval/util_test.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -67,9 +67,9 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
		if c.Conf.FormatList {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			b := []byte(formatShortPlainText(r))
 | 
			
		||||
			b := []byte(formatList(r))
 | 
			
		||||
			if err := createBlockBlob(cli, k, b); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@@ -111,19 +111,19 @@ func CheckIfAzureContainerExists() error {
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, con := range r.Containers {
 | 
			
		||||
		if con.Name == c.Conf.AzureContainer {
 | 
			
		||||
		if con.Name == c.Conf.Azure.ContainerName {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return fmt.Errorf("Container not found. Container: %s", c.Conf.AzureContainer)
 | 
			
		||||
		return fmt.Errorf("Container not found. Container: %s", c.Conf.Azure.ContainerName)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getBlobClient() (storage.BlobStorageClient, error) {
 | 
			
		||||
	api, err := storage.NewBasicClient(c.Conf.AzureAccount, c.Conf.AzureKey)
 | 
			
		||||
	api, err := storage.NewBasicClient(c.Conf.Azure.AccountName, c.Conf.Azure.AccountKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return storage.BlobStorageClient{}, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -136,14 +136,14 @@ func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
		k += ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ref := cli.GetContainerReference(c.Conf.AzureContainer)
 | 
			
		||||
	ref := cli.GetContainerReference(c.Conf.Azure.ContainerName)
 | 
			
		||||
	blob := ref.GetBlobReference(k)
 | 
			
		||||
	if err := blob.CreateBlockBlobFromReader(bytes.NewReader(b), nil); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, k, err)
 | 
			
		||||
			c.Conf.Azure.ContainerName, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										73
									
								
								report/chatwork.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								report/chatwork.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ChatWorkWriter send report to ChatWork
 | 
			
		||||
type ChatWorkWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w ChatWorkWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.ChatWork
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		serverInfo := fmt.Sprintf("%s", r.ServerInfo())
 | 
			
		||||
		if err = chatWorkpostMessage(conf.Room, conf.APIToken, serverInfo); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, vinfo := range r.ScannedCves {
 | 
			
		||||
			maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
			severity := strings.ToUpper(maxCvss.Value.Severity)
 | 
			
		||||
			if severity == "" {
 | 
			
		||||
				severity = "?"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			message := fmt.Sprintf(`%s[info][title]"https://nvd.nist.gov/vuln/detail/%s" %s %s[/title]%s[/info]`,
 | 
			
		||||
				serverInfo,
 | 
			
		||||
				vinfo.CveID,
 | 
			
		||||
				strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64),
 | 
			
		||||
				severity,
 | 
			
		||||
				vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value)
 | 
			
		||||
 | 
			
		||||
			if err = chatWorkpostMessage(conf.Room, conf.APIToken, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func chatWorkpostMessage(room, token, message string) error {
 | 
			
		||||
	uri := fmt.Sprintf("https://api.chatwork.com/v2/rooms/%s/messages=%s", room, token)
 | 
			
		||||
 | 
			
		||||
	payload := url.Values{
 | 
			
		||||
		"body": {message},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs, err := http.NewRequest("POST", uri, strings.NewReader(payload.Encode()))
 | 
			
		||||
 | 
			
		||||
	reqs.Header.Add("X-ChatWorkToken", token)
 | 
			
		||||
	reqs.Header.Add("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Do(reqs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								report/chatwork_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								report/chatwork_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package report
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -28,10 +28,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE disctionary service.
 | 
			
		||||
@@ -43,12 +41,12 @@ type cvedictClient struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() error {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 | 
			
		||||
	if !config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDict.Type)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -70,12 +68,25 @@ type response struct {
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveDetails []cve.CveDetail, err error) {
 | 
			
		||||
	if !config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			cveDetail, err := driver.Get(cveID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("Failed to fetch CVE. err: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			if len(cveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
					CveID: cveID,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, *cveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
@@ -112,64 +123,25 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails []*cve.Cve
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if len(res.CveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
					CveID: res.Key,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, &res.CveDetail)
 | 
			
		||||
				cveDetails = append(cveDetails, res.CveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return []*cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
			return nil, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
		return nil,
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Opening DB (%s).", driver.Name())
 | 
			
		||||
	if err := driver.OpenDB(
 | 
			
		||||
		cveconfig.Conf.DBType,
 | 
			
		||||
		cveconfig.Conf.DBPath,
 | 
			
		||||
		cveconfig.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail := driver.Get(cveID)
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
@@ -204,36 +176,22 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of CveDBType is sqlite3
 | 
			
		||||
	if config.Conf.CveDBURL != "" && config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]*cve.CveDetail, error) {
 | 
			
		||||
	if api.isFetchViaHTTP() {
 | 
			
		||||
		api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(driver cvedb.DB, cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	if config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
		url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []*cve.CveDetail{}, err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		query := map[string]string{"name": cpeName}
 | 
			
		||||
		util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
		return api.httpPost(cpeName, url, query)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
	return driver.GetByCpeURI(cpeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]*cve.CveDetail, error) {
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
@@ -254,41 +212,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]*
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return nil, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails := []*cve.CveDetail{}
 | 
			
		||||
	cveDetails := []cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
		return nil,
 | 
			
		||||
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBPath
 | 
			
		||||
	} else {
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Opening DB (%s).", driver.Name())
 | 
			
		||||
	if err = driver.OpenDB(
 | 
			
		||||
		cveconfig.Conf.DBType,
 | 
			
		||||
		cveconfig.Conf.DBPath,
 | 
			
		||||
		cveconfig.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return driver.GetByCpeName(cpeName), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										188
									
								
								report/db_client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								report/db_client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	gostdb "github.com/knqyf263/gost/db"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	ovaldb "github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	exploitdb "github.com/mozqnet/go-exploitdb/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DBClient is a dictionarie's db client for reporting
 | 
			
		||||
type DBClient struct {
 | 
			
		||||
	CveDB     cvedb.DB
 | 
			
		||||
	OvalDB    ovaldb.DB
 | 
			
		||||
	GostDB    gostdb.DB
 | 
			
		||||
	ExploitDB exploitdb.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DBClientConf has a configuration of Vulnerability DBs
 | 
			
		||||
type DBClientConf struct {
 | 
			
		||||
	CveDictCnf  config.GoCveDictConf
 | 
			
		||||
	OvalDictCnf config.GovalDictConf
 | 
			
		||||
	GostCnf     config.GostConf
 | 
			
		||||
	ExploitCnf  config.ExploitConf
 | 
			
		||||
	DebugSQL    bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDBClient returns db clients
 | 
			
		||||
func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) {
 | 
			
		||||
	cveDriver, locked, err := NewCveDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, true, fmt.Errorf("CveDB is locked: %s",
 | 
			
		||||
			cnf.OvalDictCnf.SQLite3Path)
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return nil, locked, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ovaldb, locked, err := NewOvalDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, true, fmt.Errorf("OvalDB is locked: %s",
 | 
			
		||||
			cnf.OvalDictCnf.SQLite3Path)
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		util.Log.Warnf("Unable to use OvalDB: %s, err: %s",
 | 
			
		||||
			cnf.OvalDictCnf.SQLite3Path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gostdb, locked, err := NewGostDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, true, fmt.Errorf("gostDB is locked: %s",
 | 
			
		||||
			cnf.GostCnf.SQLite3Path)
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		util.Log.Warnf("Unable to use gostDB: %s, err: %s",
 | 
			
		||||
			cnf.GostCnf.SQLite3Path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exploitdb, locked, err := NewExploitDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, true, fmt.Errorf("exploitDB is locked: %s",
 | 
			
		||||
			cnf.ExploitCnf.SQLite3Path)
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		util.Log.Warnf("Unable to use exploitDB: %s, err: %s",
 | 
			
		||||
			cnf.ExploitCnf.SQLite3Path, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &DBClient{
 | 
			
		||||
		CveDB:     cveDriver,
 | 
			
		||||
		OvalDB:    ovaldb,
 | 
			
		||||
		GostDB:    gostdb,
 | 
			
		||||
		ExploitDB: exploitdb,
 | 
			
		||||
	}, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCveDB returns cve db client
 | 
			
		||||
func NewCveDB(cnf DBClientConf) (driver cvedb.DB, locked bool, err error) {
 | 
			
		||||
	if config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", cnf.CveDictCnf.Type)
 | 
			
		||||
	path := cnf.CveDictCnf.URL
 | 
			
		||||
	if cnf.CveDictCnf.Type == "sqlite3" {
 | 
			
		||||
		path = cnf.CveDictCnf.SQLite3Path
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Open cve-dictionary db (%s): %s", cnf.CveDictCnf.Type, path)
 | 
			
		||||
	driver, locked, err = cvedb.NewDB(cnf.CveDictCnf.Type, path, cnf.DebugSQL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to init CVE DB. err: %s, path: %s", err, path)
 | 
			
		||||
		return nil, locked, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewOvalDB returns oval db client
 | 
			
		||||
func NewOvalDB(cnf DBClientConf) (driver ovaldb.DB, locked bool, err error) {
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.OvalDictCnf.URL
 | 
			
		||||
	if cnf.OvalDictCnf.Type == "sqlite3" {
 | 
			
		||||
		path = cnf.OvalDictCnf.SQLite3Path
 | 
			
		||||
 | 
			
		||||
		if _, err := os.Stat(path); os.IsNotExist(err) {
 | 
			
		||||
			util.Log.Warnf("--ovaldb-path=%s is not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage", path)
 | 
			
		||||
			return nil, false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Open oval-dictionary db (%s): %s", cnf.OvalDictCnf.Type, path)
 | 
			
		||||
	driver, locked, err = ovaldb.NewDB("", cnf.OvalDictCnf.Type, path, cnf.DebugSQL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to new OVAL DB. err: %s", err)
 | 
			
		||||
		if locked {
 | 
			
		||||
			return nil, true, err
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewGostDB returns db client for Gost
 | 
			
		||||
func NewGostDB(cnf DBClientConf) (driver gostdb.DB, locked bool, err error) {
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.GostCnf.URL
 | 
			
		||||
	if cnf.GostCnf.Type == "sqlite3" {
 | 
			
		||||
		path = cnf.GostCnf.SQLite3Path
 | 
			
		||||
 | 
			
		||||
		if _, err := os.Stat(path); os.IsNotExist(err) {
 | 
			
		||||
			util.Log.Warnf("--gostdb-path=%s is not found. If the scan target server is Debian, RHEL or CentOS, it's recommended to use gost to improve scanning accuracy. To use gost database, see https://github.com/knqyf263/gost#fetch-redhat", path)
 | 
			
		||||
			return nil, false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Open gost db (%s): %s", cnf.GostCnf.Type, path)
 | 
			
		||||
	if driver, locked, err = gostdb.NewDB(cnf.GostCnf.Type, path, cnf.DebugSQL); err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			util.Log.Errorf("gostDB is locked: %s", err)
 | 
			
		||||
			return nil, true, err
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewExploitDB returns db client for Exploit
 | 
			
		||||
func NewExploitDB(cnf DBClientConf) (driver exploitdb.DB, locked bool, err error) {
 | 
			
		||||
	if config.Conf.Exploit.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.ExploitCnf.URL
 | 
			
		||||
	if cnf.ExploitCnf.Type == "sqlite3" {
 | 
			
		||||
		path = cnf.ExploitCnf.SQLite3Path
 | 
			
		||||
 | 
			
		||||
		if _, err := os.Stat(path); os.IsNotExist(err) {
 | 
			
		||||
			util.Log.Warnf("--exploitdb-path=%s is not found. It's recommended to use exploit to improve scanning accuracy. To use exploit db database, see https://github.com/mozqnet/go-exploitdb", path)
 | 
			
		||||
			return nil, false, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Open exploit db (%s): %s", cnf.ExploitCnf.Type, path)
 | 
			
		||||
	if driver, locked, err = exploitdb.NewDB(cnf.ExploitCnf.Type, path, cnf.DebugSQL); err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			util.Log.Errorf("exploitDB is locked: %s", err)
 | 
			
		||||
			return nil, true, err
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloseDB close dbs
 | 
			
		||||
func (d DBClient) CloseDB() {
 | 
			
		||||
	if d.CveDB != nil {
 | 
			
		||||
		if err := d.CveDB.CloseDB(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to close DB: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if d.OvalDB != nil {
 | 
			
		||||
		if err := d.OvalDB.CloseDB(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to close DB: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								report/hipchat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								report/hipchat.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HipChatWriter send report to HipChat
 | 
			
		||||
type HipChatWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w HipChatWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.HipChat
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		serverInfo := fmt.Sprintf("%s", r.ServerInfo())
 | 
			
		||||
		if err = postMessage(conf.Room, conf.AuthToken, serverInfo); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, vinfo := range r.ScannedCves {
 | 
			
		||||
			maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
			severity := strings.ToUpper(maxCvss.Value.Severity)
 | 
			
		||||
			if severity == "" {
 | 
			
		||||
				severity = "?"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			message := fmt.Sprintf(`<a href="https://nvd.nist.gov/vuln/detail\%s"> %s </a> <br/>%s (%s)<br/>%s`,
 | 
			
		||||
				vinfo.CveID,
 | 
			
		||||
				vinfo.CveID,
 | 
			
		||||
				strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64),
 | 
			
		||||
				severity,
 | 
			
		||||
				vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value,
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			if err = postMessage(conf.Room, conf.AuthToken, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func postMessage(room, token, message string) error {
 | 
			
		||||
	uri := fmt.Sprintf("https://api.hipchat.com/v2/room/%s/notification?auth_token=%s", room, token)
 | 
			
		||||
 | 
			
		||||
	payload := url.Values{
 | 
			
		||||
		"color":          {"purple"},
 | 
			
		||||
		"message_format": {"html"},
 | 
			
		||||
		"message":        {message},
 | 
			
		||||
	}
 | 
			
		||||
	reqs, err := http.NewRequest("POST", uri, strings.NewReader(payload.Encode()))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	reqs.Header.Add("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
 | 
			
		||||
	resp, err := client.Do(reqs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								report/hipchat_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								report/hipchat_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package report
 | 
			
		||||
							
								
								
									
										62
									
								
								report/http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								report/http.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTTPRequestWriter writes results to HTTP request
 | 
			
		||||
type HTTPRequestWriter struct{}
 | 
			
		||||
 | 
			
		||||
// Write sends results as HTTP response
 | 
			
		||||
func (w HTTPRequestWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		b := new(bytes.Buffer)
 | 
			
		||||
		json.NewEncoder(b).Encode(r)
 | 
			
		||||
		_, err = http.Post(c.Conf.HTTP.URL, "application/json; charset=utf-8", b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTTPResponseWriter writes results to HTTP response
 | 
			
		||||
type HTTPResponseWriter struct {
 | 
			
		||||
	Writer http.ResponseWriter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write sends results as HTTP response
 | 
			
		||||
func (w HTTPResponseWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	res, err := json.Marshal(rs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "Failed to marshal scah results")
 | 
			
		||||
	}
 | 
			
		||||
	w.Writer.Header().Set("Content-Type", "application/json")
 | 
			
		||||
	_, err = w.Writer.Write(res)
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -72,7 +72,7 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
		if c.Conf.FormatList {
 | 
			
		||||
			var p string
 | 
			
		||||
			if c.Conf.Diff {
 | 
			
		||||
				p = path + "_short_diff.txt"
 | 
			
		||||
@@ -81,7 +81,7 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := writeFile(
 | 
			
		||||
				p, []byte(formatShortPlainText(r)), 0600); err != nil {
 | 
			
		||||
				p, []byte(formatList(r)), 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf(
 | 
			
		||||
					"Failed to write text files. path: %s, err: %s", p, err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -129,13 +129,7 @@ func writeFile(path string, data []byte, perm os.FileMode) error {
 | 
			
		||||
		if data, err = gz(data); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		path = path + ".gz"
 | 
			
		||||
		path += ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ioutil.WriteFile(
 | 
			
		||||
		path, []byte(data), perm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return ioutil.WriteFile(path, []byte(data), perm)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										549
									
								
								report/report.go
									
									
									
									
									
								
							
							
						
						
									
										549
									
								
								report/report.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,14 +18,30 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
	"github.com/future-architect/vuls/cwe"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/hashicorp/uuid"
 | 
			
		||||
	gostdb "github.com/knqyf263/gost/db"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	ovaldb "github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	exploitdb "github.com/mozqnet/go-exploitdb/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -34,16 +50,50 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillCveInfos fills CVE Detailed Information
 | 
			
		||||
func FillCveInfos(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
 | 
			
		||||
	var filled []models.ScanResult
 | 
			
		||||
func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
 | 
			
		||||
	var filledResults []models.ScanResult
 | 
			
		||||
	reportedAt := time.Now()
 | 
			
		||||
	hostname, _ := os.Hostname()
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if c.Conf.RefreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if err := FillCveInfo(&r); err != nil {
 | 
			
		||||
			r.ScannedCves = models.VulnInfos{}
 | 
			
		||||
			cpeURIs := []string{}
 | 
			
		||||
			if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
				cpeURIs = c.Conf.Servers[r.ServerName].CpeNames
 | 
			
		||||
				owaspDCXMLPath := c.Conf.Servers[r.ServerName].OwaspDCXMLPath
 | 
			
		||||
				if owaspDCXMLPath != "" {
 | 
			
		||||
					cpes, err := parser.Parse(owaspDCXMLPath)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return nil, fmt.Errorf("Failed to read OWASP Dependency Check XML: %s, %s, %s",
 | 
			
		||||
							r.ServerName, owaspDCXMLPath, err)
 | 
			
		||||
					}
 | 
			
		||||
					cpeURIs = append(cpeURIs, cpes...)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if s, ok := c.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
					if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
						cpeURIs = con.Cpes
 | 
			
		||||
						owaspDCXMLPath := con.OwaspDCXMLPath
 | 
			
		||||
						if owaspDCXMLPath != "" {
 | 
			
		||||
							cpes, err := parser.Parse(owaspDCXMLPath)
 | 
			
		||||
							if err != nil {
 | 
			
		||||
								return nil, fmt.Errorf("Failed to read OWASP Dependency Check XML: %s, %s, %s",
 | 
			
		||||
									r.ServerInfo(), owaspDCXMLPath, err)
 | 
			
		||||
							}
 | 
			
		||||
							cpeURIs = append(cpeURIs, cpes...)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := FillCveInfo(dbclient, &r, cpeURIs); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			r.Lang = c.Conf.Lang
 | 
			
		||||
			r.ReportedAt = reportedAt
 | 
			
		||||
			r.ReportedVersion = c.Version
 | 
			
		||||
			r.ReportedRevision = c.Revision
 | 
			
		||||
			r.ReportedBy = hostname
 | 
			
		||||
			r.Config.Report = c.Conf
 | 
			
		||||
			r.Config.Report.Servers = map[string]c.ServerInfo{
 | 
			
		||||
				r.ServerName: c.Conf.Servers[r.ServerName],
 | 
			
		||||
@@ -51,84 +101,122 @@ func FillCveInfos(rs []models.ScanResult, dir string) ([]models.ScanResult, erro
 | 
			
		||||
			if err := overwriteJSONFile(dir, r); err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
			filledResults = append(filledResults, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("No need to refresh")
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
			filledResults = append(filledResults, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		previous, err := loadPrevious(filled)
 | 
			
		||||
		prevs, err := loadPrevious(filledResults)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diff, err := diff(filled, previous)
 | 
			
		||||
		diff, err := diff(filledResults, prevs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		filled = []models.ScanResult{}
 | 
			
		||||
		filledResults = []models.ScanResult{}
 | 
			
		||||
		for _, r := range diff {
 | 
			
		||||
			if err := fillCveDetail(&r); err != nil {
 | 
			
		||||
			if err := fillCveDetail(dbclient.CveDB, &r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
			filledResults = append(filledResults, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := []models.ScanResult{}
 | 
			
		||||
	for _, r := range filled {
 | 
			
		||||
	for _, r := range filledResults {
 | 
			
		||||
		r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
 | 
			
		||||
		r = r.FilterIgnoreCves(c.Conf.Servers[r.ServerName].IgnoreCves)
 | 
			
		||||
		r = r.FilterIgnoreCves()
 | 
			
		||||
		r = r.FilterUnfixed()
 | 
			
		||||
		r = r.FilterIgnorePkgs()
 | 
			
		||||
		if c.Conf.IgnoreUnscoredCves {
 | 
			
		||||
			r.ScannedCves = r.ScannedCves.FindScoredVulns()
 | 
			
		||||
		}
 | 
			
		||||
		filtered = append(filtered, r)
 | 
			
		||||
	}
 | 
			
		||||
	return filtered, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveInfo fill scanResult with cve info.
 | 
			
		||||
func FillCveInfo(r *models.ScanResult) error {
 | 
			
		||||
func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string) error {
 | 
			
		||||
	util.Log.Debugf("need to refresh")
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with OVAL")
 | 
			
		||||
	if err := FillWithOval(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill OVAL information: %s", err)
 | 
			
		||||
	nCVEs, err := FillWithOval(dbclient.OvalDB, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill with OVAL: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("%s: %d CVEs are detected with OVAL",
 | 
			
		||||
		r.FormatServerName(), nCVEs)
 | 
			
		||||
 | 
			
		||||
	for i, v := range r.ScannedCves {
 | 
			
		||||
		for j, p := range v.AffectedPackages {
 | 
			
		||||
			if p.NotFixedYet && p.FixState == "" {
 | 
			
		||||
				p.FixState = "Not fixed yet"
 | 
			
		||||
				r.ScannedCves[i].AffectedPackages[j] = p
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nCVEs, err = fillVulnByCpeURIs(dbclient.CveDB, r, cpeURIs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to detect vulns of %s: %s", cpeURIs, err)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs)
 | 
			
		||||
 | 
			
		||||
	nCVEs, err = FillWithGost(dbclient.GostDB, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill with gost: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("%s: %d unfixed CVEs are detected with gost",
 | 
			
		||||
		r.FormatServerName(), nCVEs)
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with CVE-DB")
 | 
			
		||||
	if err := fillWithCveDB(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
	if err := fillCveDetail(dbclient.CveDB, r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill with CVE: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID := range r.ScannedCves {
 | 
			
		||||
		vinfo := r.ScannedCves[cveID]
 | 
			
		||||
		r.ScannedCves[cveID] = *vinfo.NilToEmpty()
 | 
			
		||||
	util.Log.Infof("Fill exploit information with Exploit-DB")
 | 
			
		||||
	nExploitCve, err := FillWithExploit(dbclient.ExploitDB, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill with exploit: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("%s: %d exploits are detected",
 | 
			
		||||
		r.FormatServerName(), nExploitCve)
 | 
			
		||||
 | 
			
		||||
	fillCweDict(r)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fillCveDetail fetches NVD, JVN from CVE Database, and then set to fields.
 | 
			
		||||
func fillCveDetail(r *models.ScanResult) error {
 | 
			
		||||
// fillCveDetail fetches NVD, JVN from CVE Database
 | 
			
		||||
func fillCveDetail(driver cvedb.DB, r *models.ScanResult) error {
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	ds, err := CveClient.FetchCveDetails(driver, cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		nvd := models.ConvertNvdToModel(d.CveID, d.Nvd)
 | 
			
		||||
		nvd := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON)
 | 
			
		||||
		if nvd == nil {
 | 
			
		||||
			nvd = models.ConvertNvdXMLToModel(d.CveID, d.NvdXML)
 | 
			
		||||
		}
 | 
			
		||||
		jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
 | 
			
		||||
 | 
			
		||||
		for cveID, vinfo := range r.ScannedCves {
 | 
			
		||||
			if vinfo.CveID == d.CveID {
 | 
			
		||||
				if vinfo.CveContents == nil {
 | 
			
		||||
					vinfo.CveContents = models.CveContents{}
 | 
			
		||||
				}
 | 
			
		||||
				for _, con := range []models.CveContent{*nvd, *jvn} {
 | 
			
		||||
					if !con.Empty() {
 | 
			
		||||
						vinfo.CveContents[con.Type] = con
 | 
			
		||||
				for _, con := range []*models.CveContent{nvd, jvn} {
 | 
			
		||||
					if con != nil && !con.Empty() {
 | 
			
		||||
						vinfo.CveContents[con.Type] = *con
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				r.ScannedCves[cveID] = vinfo
 | 
			
		||||
@@ -139,23 +227,11 @@ func fillCveDetail(r *models.ScanResult) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillWithCveDB(r *models.ScanResult) error {
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	if err := fillVulnByCpeNames(sInfo.CpeNames, r.ScannedCves); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := fillCveDetail(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval fetches OVAL database, and then set to fields.
 | 
			
		||||
func FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
// FillWithOval fetches OVAL database
 | 
			
		||||
func FillWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var ovalClient oval.Client
 | 
			
		||||
	var ovalFamily string
 | 
			
		||||
 | 
			
		||||
	// TODO
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case c.Debian:
 | 
			
		||||
		ovalClient = oval.NewDebian()
 | 
			
		||||
@@ -173,55 +249,386 @@ func FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	case c.Oracle:
 | 
			
		||||
		ovalClient = oval.NewOracle()
 | 
			
		||||
		ovalFamily = c.Oracle
 | 
			
		||||
	case c.Amazon, c.Raspbian, c.FreeBSD:
 | 
			
		||||
		return nil
 | 
			
		||||
	case c.SUSEEnterpriseServer:
 | 
			
		||||
		// TODO other suse family
 | 
			
		||||
		ovalClient = oval.NewSUSE()
 | 
			
		||||
		ovalFamily = c.SUSEEnterpriseServer
 | 
			
		||||
	case c.Alpine:
 | 
			
		||||
		ovalClient = oval.NewAlpine()
 | 
			
		||||
		ovalFamily = c.Alpine
 | 
			
		||||
	case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows:
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	case c.ServerTypePseudo:
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("OVAL for %s is not implemented yet", r.Family)
 | 
			
		||||
		if r.Family == "" {
 | 
			
		||||
			return 0, fmt.Errorf("Probably an error occurred during scanning. Check the error message")
 | 
			
		||||
		}
 | 
			
		||||
		return 0, fmt.Errorf("OVAL for %s is not implemented yet", r.Family)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ok, err := ovalClient.CheckIfOvalFetched(ovalFamily, r.Release)
 | 
			
		||||
	if !c.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		if err = driver.NewOvalDB(ovalFamily); err != nil {
 | 
			
		||||
			return 0, fmt.Errorf("Failed to New Oval DB. err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Check whether oval fetched: %s %s", ovalFamily, r.Release)
 | 
			
		||||
	ok, err := ovalClient.CheckIfOvalFetched(driver, ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		major := strings.Split(r.Release, ".")[0]
 | 
			
		||||
		util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, major)
 | 
			
		||||
		return nil
 | 
			
		||||
		util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, r.Release)
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = ovalClient.CheckIfOvalFresh(ovalFamily, r.Release)
 | 
			
		||||
	_, err = ovalClient.CheckIfOvalFresh(driver, ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ovalClient.FillWithOval(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return ovalClient.FillWithOval(driver, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillVulnByCpeNames(cpeNames []string, scannedVulns models.VulnInfos) error {
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
// FillWithGost fills CVEs with gost dataabase
 | 
			
		||||
// https://github.com/knqyf263/gost
 | 
			
		||||
func FillWithGost(driver gostdb.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	gostClient := gost.NewClient(r.Family)
 | 
			
		||||
	// TODO chekc if fetched
 | 
			
		||||
	// TODO chekc if fresh enough
 | 
			
		||||
	return gostClient.FillWithGost(driver, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithExploit fills Exploits with exploit dataabase
 | 
			
		||||
// https://github.com/mozqnet/go-exploitdb
 | 
			
		||||
func FillWithExploit(driver exploitdb.DB, r *models.ScanResult) (nExploitCve int, err error) {
 | 
			
		||||
	// TODO chekc if fetched
 | 
			
		||||
	// TODO chekc if fresh enough
 | 
			
		||||
	return exploit.FillWithExploit(driver, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillVulnByCpeURIs(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) (nCVEs int, err error) {
 | 
			
		||||
	for _, name := range cpeURIs {
 | 
			
		||||
		details, err := CveClient.FetchCveDetailsByCpeName(driver, name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := scannedVulns[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
			if val, ok := r.ScannedCves[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeURIs
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				val.Confidence = models.CpeNameMatch
 | 
			
		||||
				scannedVulns[detail.CveID] = val
 | 
			
		||||
				val.CpeURIs = names
 | 
			
		||||
				val.Confidences.AppendIfMissing(models.CpeNameMatch)
 | 
			
		||||
				r.ScannedCves[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:      detail.CveID,
 | 
			
		||||
					CpeNames:   []string{name},
 | 
			
		||||
					Confidence: models.CpeNameMatch,
 | 
			
		||||
					CveID:       detail.CveID,
 | 
			
		||||
					CpeURIs:     []string{name},
 | 
			
		||||
					Confidences: models.Confidences{models.CpeNameMatch},
 | 
			
		||||
				}
 | 
			
		||||
				scannedVulns[detail.CveID] = v
 | 
			
		||||
				r.ScannedCves[detail.CveID] = v
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillCweDict(r *models.ScanResult) {
 | 
			
		||||
	uniqCweIDMap := map[string]bool{}
 | 
			
		||||
	for _, vinfo := range r.ScannedCves {
 | 
			
		||||
		for _, cont := range vinfo.CveContents {
 | 
			
		||||
			for _, id := range cont.CweIDs {
 | 
			
		||||
				if strings.HasPrefix(id, "CWE-") {
 | 
			
		||||
					id = strings.TrimPrefix(id, "CWE-")
 | 
			
		||||
					uniqCweIDMap[id] = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO check the format of CWEID, clean CWEID
 | 
			
		||||
	// JVN, NVD XML, JSON, OVALs
 | 
			
		||||
 | 
			
		||||
	dict := map[string]models.CweDictEntry{}
 | 
			
		||||
	for id := range uniqCweIDMap {
 | 
			
		||||
		entry := models.CweDictEntry{}
 | 
			
		||||
		if e, ok := cwe.CweDictEn[id]; ok {
 | 
			
		||||
			if rank, ok := cwe.OwaspTopTen2017[id]; ok {
 | 
			
		||||
				entry.OwaspTopTen2017 = rank
 | 
			
		||||
			}
 | 
			
		||||
			entry.En = &e
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("CWE-ID %s is not found in English CWE Dict", id)
 | 
			
		||||
			entry.En = &cwe.Cwe{CweID: id}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.Lang == "ja" {
 | 
			
		||||
			if e, ok := cwe.CweDictJa[id]; ok {
 | 
			
		||||
				if rank, ok := cwe.OwaspTopTen2017[id]; ok {
 | 
			
		||||
					entry.OwaspTopTen2017 = rank
 | 
			
		||||
				}
 | 
			
		||||
				entry.Ja = &e
 | 
			
		||||
			} else {
 | 
			
		||||
				util.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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}"
 | 
			
		||||
 | 
			
		||||
// EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet.
 | 
			
		||||
// And then set the generated UUID to config.toml and scan results.
 | 
			
		||||
func EnsureUUIDs(configPath string, results models.ScanResults) error {
 | 
			
		||||
	// Sort Host->Container
 | 
			
		||||
	sort.Slice(results, func(i, j int) bool {
 | 
			
		||||
		if results[i].ServerName == results[j].ServerName {
 | 
			
		||||
			return results[i].Container.ContainerID < results[j].Container.ContainerID
 | 
			
		||||
		}
 | 
			
		||||
		return results[i].ServerName < results[j].ServerName
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for i, r := range results {
 | 
			
		||||
		server := c.Conf.Servers[r.ServerName]
 | 
			
		||||
		if server.UUIDs == nil {
 | 
			
		||||
			server.UUIDs = map[string]string{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		name := ""
 | 
			
		||||
		if r.IsContainer() {
 | 
			
		||||
			name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
 | 
			
		||||
			// Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated,
 | 
			
		||||
			// so check it. Otherwise create a UUID of the Container Host and set it.
 | 
			
		||||
			serverUUID := ""
 | 
			
		||||
			if id, ok := server.UUIDs[r.ServerName]; !ok {
 | 
			
		||||
				serverUUID = uuid.GenerateUUID()
 | 
			
		||||
			} else {
 | 
			
		||||
				matched, err := regexp.MatchString(reUUID, id)
 | 
			
		||||
				if !matched || err != nil {
 | 
			
		||||
					serverUUID = uuid.GenerateUUID()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if serverUUID != "" {
 | 
			
		||||
				server.UUIDs[r.ServerName] = serverUUID
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			name = r.ServerName
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if id, ok := server.UUIDs[name]; ok {
 | 
			
		||||
			matched, err := regexp.MatchString(reUUID, id)
 | 
			
		||||
			if !matched || err != nil {
 | 
			
		||||
				util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				if r.IsContainer() {
 | 
			
		||||
					results[i].Container.UUID = id
 | 
			
		||||
					results[i].ServerUUID = server.UUIDs[r.ServerName]
 | 
			
		||||
				} else {
 | 
			
		||||
					results[i].ServerUUID = id
 | 
			
		||||
				}
 | 
			
		||||
				// continue if the UUID has already assigned and valid
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Generate a new UUID and set to config and scan result
 | 
			
		||||
		id := uuid.GenerateUUID()
 | 
			
		||||
		server.UUIDs[name] = id
 | 
			
		||||
		server = cleanForTOMLEncoding(server, c.Conf.Default)
 | 
			
		||||
		c.Conf.Servers[r.ServerName] = server
 | 
			
		||||
 | 
			
		||||
		if r.IsContainer() {
 | 
			
		||||
			results[i].Container.UUID = id
 | 
			
		||||
			results[i].ServerUUID = server.UUIDs[r.ServerName]
 | 
			
		||||
		} else {
 | 
			
		||||
			results[i].ServerUUID = id
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for name, server := range c.Conf.Servers {
 | 
			
		||||
		server = cleanForTOMLEncoding(server, c.Conf.Default)
 | 
			
		||||
		c.Conf.Servers[name] = server
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	email := &c.Conf.EMail
 | 
			
		||||
	if email.SMTPAddr == "" {
 | 
			
		||||
		email = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	slack := &c.Conf.Slack
 | 
			
		||||
	if slack.HookURL == "" {
 | 
			
		||||
		slack = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDict := &c.Conf.CveDict
 | 
			
		||||
	ovalDict := &c.Conf.OvalDict
 | 
			
		||||
	gost := &c.Conf.Gost
 | 
			
		||||
	exploit := &c.Conf.Exploit
 | 
			
		||||
	http := &c.Conf.HTTP
 | 
			
		||||
	if http.URL == "" {
 | 
			
		||||
		http = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	syslog := &c.Conf.Syslog
 | 
			
		||||
	if syslog.Host == "" {
 | 
			
		||||
		syslog = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	aws := &c.Conf.AWS
 | 
			
		||||
	if aws.S3Bucket == "" {
 | 
			
		||||
		aws = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	azure := &c.Conf.Azure
 | 
			
		||||
	if azure.AccountName == "" {
 | 
			
		||||
		azure = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stride := &c.Conf.Stride
 | 
			
		||||
	if stride.HookURL == "" {
 | 
			
		||||
		stride = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hipChat := &c.Conf.HipChat
 | 
			
		||||
	if hipChat.AuthToken == "" {
 | 
			
		||||
		hipChat = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	chatWork := &c.Conf.ChatWork
 | 
			
		||||
	if chatWork.APIToken == "" {
 | 
			
		||||
		chatWork = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	saas := &c.Conf.Saas
 | 
			
		||||
	if saas.GroupID == 0 {
 | 
			
		||||
		saas = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c := struct {
 | 
			
		||||
		CveDict  *c.GoCveDictConf `toml:"cveDict"`
 | 
			
		||||
		OvalDict *c.GovalDictConf `toml:"ovalDict"`
 | 
			
		||||
		Gost     *c.GostConf      `toml:"gost"`
 | 
			
		||||
		Exploit  *c.ExploitConf   `toml:"exploit"`
 | 
			
		||||
		Slack    *c.SlackConf     `toml:"slack"`
 | 
			
		||||
		Email    *c.SMTPConf      `toml:"email"`
 | 
			
		||||
		HTTP     *c.HTTPConf      `toml:"http"`
 | 
			
		||||
		Syslog   *c.SyslogConf    `toml:"syslog"`
 | 
			
		||||
		AWS      *c.AWS           `toml:"aws"`
 | 
			
		||||
		Azure    *c.Azure         `toml:"azure"`
 | 
			
		||||
		Stride   *c.StrideConf    `toml:"stride"`
 | 
			
		||||
		HipChat  *c.HipChatConf   `toml:"hipChat"`
 | 
			
		||||
		ChatWork *c.ChatWorkConf  `toml:"chatWork"`
 | 
			
		||||
		Saas     *c.SaasConf      `toml:"saas"`
 | 
			
		||||
 | 
			
		||||
		Default c.ServerInfo            `toml:"default"`
 | 
			
		||||
		Servers map[string]c.ServerInfo `toml:"servers"`
 | 
			
		||||
	}{
 | 
			
		||||
		CveDict:  cveDict,
 | 
			
		||||
		OvalDict: ovalDict,
 | 
			
		||||
		Gost:     gost,
 | 
			
		||||
		Exploit:  exploit,
 | 
			
		||||
		Slack:    slack,
 | 
			
		||||
		Email:    email,
 | 
			
		||||
		HTTP:     http,
 | 
			
		||||
		Syslog:   syslog,
 | 
			
		||||
		AWS:      aws,
 | 
			
		||||
		Azure:    azure,
 | 
			
		||||
		Stride:   stride,
 | 
			
		||||
		HipChat:  hipChat,
 | 
			
		||||
		ChatWork: chatWork,
 | 
			
		||||
		Saas:     saas,
 | 
			
		||||
 | 
			
		||||
		Default: c.Conf.Default,
 | 
			
		||||
		Servers: c.Conf.Servers,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// rename the current config.toml to config.toml.bak
 | 
			
		||||
	info, err := os.Lstat(configPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to lstat %s: %s", configPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	realPath := configPath
 | 
			
		||||
	if info.Mode()&os.ModeSymlink == os.ModeSymlink {
 | 
			
		||||
		if realPath, err = os.Readlink(configPath); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Read link %s: %s", configPath, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := os.Rename(realPath, realPath+".bak"); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to rename %s: %s", configPath, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	if err := toml.NewEncoder(&buf).Encode(c); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to encode to toml: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	str := strings.Replace(buf.String(), "\n  [", "\n\n  [", -1)
 | 
			
		||||
	str = fmt.Sprintf("%s\n\n%s",
 | 
			
		||||
		"# See REAME for details: https://vuls.io/docs/en/usage-settings.html",
 | 
			
		||||
		str)
 | 
			
		||||
 | 
			
		||||
	return ioutil.WriteFile(realPath, []byte(str), 0600)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo {
 | 
			
		||||
	if reflect.DeepEqual(server.Optional, def.Optional) {
 | 
			
		||||
		server.Optional = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.User == server.User {
 | 
			
		||||
		server.User = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.Host == server.Host {
 | 
			
		||||
		server.Host = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.Port == server.Port {
 | 
			
		||||
		server.Port = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.KeyPath == server.KeyPath {
 | 
			
		||||
		server.KeyPath = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if reflect.DeepEqual(server.ScanMode, def.ScanMode) {
 | 
			
		||||
		server.ScanMode = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.Type == server.Type {
 | 
			
		||||
		server.Type = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if reflect.DeepEqual(server.CpeNames, def.CpeNames) {
 | 
			
		||||
		server.CpeNames = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if def.OwaspDCXMLPath == server.OwaspDCXMLPath {
 | 
			
		||||
		server.OwaspDCXMLPath = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) {
 | 
			
		||||
		server.IgnoreCves = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) {
 | 
			
		||||
		server.Enablerepo = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k, v := range def.Optional {
 | 
			
		||||
		if vv, ok := server.Optional[k]; ok && v == vv {
 | 
			
		||||
			delete(server.Optional, k)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return server
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								report/s3.go
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								report/s3.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -41,10 +41,10 @@ type S3Writer struct{}
 | 
			
		||||
 | 
			
		||||
func getS3() *s3.S3 {
 | 
			
		||||
	Config := &aws.Config{
 | 
			
		||||
		Region: aws.String(c.Conf.AwsRegion),
 | 
			
		||||
		Region: aws.String(c.Conf.AWS.Region),
 | 
			
		||||
		Credentials: credentials.NewChainCredentials([]credentials.Provider{
 | 
			
		||||
			&credentials.EnvProvider{},
 | 
			
		||||
			&credentials.SharedCredentialsProvider{Filename: "", Profile: c.Conf.AwsProfile},
 | 
			
		||||
			&credentials.SharedCredentialsProvider{Filename: "", Profile: c.Conf.AWS.Profile},
 | 
			
		||||
			&ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())},
 | 
			
		||||
		}),
 | 
			
		||||
	}
 | 
			
		||||
@@ -82,9 +82,9 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.FormatShortText {
 | 
			
		||||
		if c.Conf.FormatList {
 | 
			
		||||
			k := key + "_short.txt"
 | 
			
		||||
			text := formatShortPlainText(r)
 | 
			
		||||
			text := formatList(r)
 | 
			
		||||
			if err := putObject(svc, k, []byte(text)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@@ -120,20 +120,20 @@ func CheckIfBucketExists() error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to list buckets. err: %s, profile: %s, region: %s",
 | 
			
		||||
			err, c.Conf.AwsProfile, c.Conf.AwsRegion)
 | 
			
		||||
			err, c.Conf.AWS.Profile, c.Conf.AWS.Region)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	for _, bucket := range result.Buckets {
 | 
			
		||||
		if *bucket.Name == c.Conf.S3Bucket {
 | 
			
		||||
		if *bucket.Name == c.Conf.AWS.S3Bucket {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"Failed to find the buckets. profile: %s, region: %s, bukdet: %s",
 | 
			
		||||
			c.Conf.AwsProfile, c.Conf.AwsRegion, c.Conf.S3Bucket)
 | 
			
		||||
			"Failed to find the buckets. profile: %s, region: %s, bucket: %s",
 | 
			
		||||
			c.Conf.AWS.Profile, c.Conf.AWS.Region, c.Conf.AWS.S3Bucket)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -144,16 +144,22 @@ func putObject(svc *s3.S3, k string, b []byte) error {
 | 
			
		||||
		if b, err = gz(b); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
		k += ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: aws.String(c.Conf.S3Bucket),
 | 
			
		||||
		Key:    aws.String(path.Join(c.Conf.S3ResultsDir, k)),
 | 
			
		||||
	putObjectInput := &s3.PutObjectInput{
 | 
			
		||||
		Bucket: aws.String(c.Conf.AWS.S3Bucket),
 | 
			
		||||
		Key:    aws.String(path.Join(c.Conf.AWS.S3ResultsDir, k)),
 | 
			
		||||
		Body:   bytes.NewReader(b),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.AWS.S3ServerSideEncryption != "" {
 | 
			
		||||
		putObjectInput.ServerSideEncryption = aws.String(c.Conf.AWS.S3ServerSideEncryption)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := svc.PutObject(putObjectInput); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.S3Bucket, k, err)
 | 
			
		||||
			c.Conf.AWS.S3Bucket, k, err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										153
									
								
								report/saas.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								report/saas.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/credentials"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws/session"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/s3"
 | 
			
		||||
	"github.com/aws/aws-sdk-go/service/sts"
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SaasWriter writes results to SaaS
 | 
			
		||||
type SaasWriter struct{}
 | 
			
		||||
 | 
			
		||||
// TempCredential : TempCredential
 | 
			
		||||
type TempCredential struct {
 | 
			
		||||
	Credential   *sts.Credentials `json:"Credential"`
 | 
			
		||||
	S3Bucket     string           `json:"S3Bucket"`
 | 
			
		||||
	S3ResultsDir string           `json:"S3ResultsDir"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type payload struct {
 | 
			
		||||
	GroupID int    `json:"GroupID"`
 | 
			
		||||
	Token   string `json:"Token"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadSaas : UploadSaas
 | 
			
		||||
func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	// dir string, configPath string, config *c.Config
 | 
			
		||||
	if len(rs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	payload := payload{
 | 
			
		||||
		GroupID: c.Conf.Saas.GroupID,
 | 
			
		||||
		Token:   c.Conf.Saas.Token,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var body []byte
 | 
			
		||||
	if body, err = json.Marshal(payload); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var req *http.Request
 | 
			
		||||
	if req, err = http.NewRequest("POST", c.Conf.Saas.URL, bytes.NewBuffer(body)); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	req.Header.Set("Accept", "application/json")
 | 
			
		||||
 | 
			
		||||
	proxy := c.Conf.HTTPProxy
 | 
			
		||||
	var client http.Client
 | 
			
		||||
	if proxy != "" {
 | 
			
		||||
		proxyURL, _ := url.Parse(proxy)
 | 
			
		||||
		client = http.Client{
 | 
			
		||||
			Transport: &http.Transport{
 | 
			
		||||
				Proxy: http.ProxyURL(proxyURL),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		client = http.Client{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	if resp, err = client.Do(req); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode != 200 {
 | 
			
		||||
		return fmt.Errorf("Failed to get Credential. Request JSON : %s,", string(body))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var t []byte
 | 
			
		||||
	if t, err = ioutil.ReadAll(resp.Body); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tempCredential TempCredential
 | 
			
		||||
	if err = json.Unmarshal(t, &tempCredential); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to unmarshal saas credential file. err : %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	credential := credentials.NewStaticCredentialsFromCreds(credentials.Value{
 | 
			
		||||
		AccessKeyID:     *tempCredential.Credential.AccessKeyId,
 | 
			
		||||
		SecretAccessKey: *tempCredential.Credential.SecretAccessKey,
 | 
			
		||||
		SessionToken:    *tempCredential.Credential.SessionToken,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	var sess *session.Session
 | 
			
		||||
	if sess, err = session.NewSession(&aws.Config{
 | 
			
		||||
		Credentials: credential,
 | 
			
		||||
		Region:      aws.String("ap-northeast-1"),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to new aws session. err : %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc := s3.New(sess)
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		s3Key := renameKeyNameUTC(r.ScannedAt, r.ServerUUID, r.Container)
 | 
			
		||||
		var b []byte
 | 
			
		||||
		if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		util.Log.Infof("Uploading...: ServerName: %s, ", r.ServerName)
 | 
			
		||||
		putObjectInput := &s3.PutObjectInput{
 | 
			
		||||
			Bucket: aws.String(tempCredential.S3Bucket),
 | 
			
		||||
			Key:    aws.String(path.Join(tempCredential.S3ResultsDir, s3Key)),
 | 
			
		||||
			Body:   bytes.NewReader(b),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := svc.PutObject(putObjectInput); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
				tempCredential.S3Bucket, s3Key, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string {
 | 
			
		||||
	timestr := scannedAt.UTC().Format(time.RFC3339)
 | 
			
		||||
	if len(container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s/%s.json", timestr, uuid)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						
									
										195
									
								
								report/slack.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -27,6 +27,7 @@ import (
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/nlopes/slack"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
@@ -36,31 +37,23 @@ type field struct {
 | 
			
		||||
	Value string `json:"value"`
 | 
			
		||||
	Short bool   `json:"short"`
 | 
			
		||||
}
 | 
			
		||||
type attachment struct {
 | 
			
		||||
	Title     string   `json:"title"`
 | 
			
		||||
	TitleLink string   `json:"title_link"`
 | 
			
		||||
	Fallback  string   `json:"fallback"`
 | 
			
		||||
	Text      string   `json:"text"`
 | 
			
		||||
	Pretext   string   `json:"pretext"`
 | 
			
		||||
	Color     string   `json:"color"`
 | 
			
		||||
	Fields    []*field `json:"fields"`
 | 
			
		||||
	MrkdwnIn  []string `json:"mrkdwn_in"`
 | 
			
		||||
	Footer    string   `json:"footer"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type message struct {
 | 
			
		||||
	Text        string        `json:"text"`
 | 
			
		||||
	Username    string        `json:"username"`
 | 
			
		||||
	IconEmoji   string        `json:"icon_emoji"`
 | 
			
		||||
	Channel     string        `json:"channel"`
 | 
			
		||||
	Attachments []*attachment `json:"attachments"`
 | 
			
		||||
	Text            string             `json:"text"`
 | 
			
		||||
	Username        string             `json:"username"`
 | 
			
		||||
	IconEmoji       string             `json:"icon_emoji"`
 | 
			
		||||
	Channel         string             `json:"channel"`
 | 
			
		||||
	ThreadTimeStamp string             `json:"thread_ts"`
 | 
			
		||||
	Attachments     []slack.Attachment `json:"attachments"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SlackWriter send report to slack
 | 
			
		||||
type SlackWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
func (w SlackWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.Slack
 | 
			
		||||
	channel := conf.Channel
 | 
			
		||||
	token := conf.LegacyToken
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if channel == "${servername}" {
 | 
			
		||||
@@ -68,17 +61,13 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if 0 < len(r.Errors) {
 | 
			
		||||
			serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
			notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
			txt := fmt.Sprintf("%s\n%s\nError: %s",
 | 
			
		||||
				notifyUsers, serverInfo, r.Errors)
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:      txt,
 | 
			
		||||
				Text:      msgText(r),
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
				IconEmoji: conf.IconEmoji,
 | 
			
		||||
				Channel:   channel,
 | 
			
		||||
			}
 | 
			
		||||
			if err := send(msg); err != nil {
 | 
			
		||||
			if err = send(msg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
@@ -88,7 +77,7 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
		// Split into chunks with 100 elements
 | 
			
		||||
		// https://api.slack.com/methods/chat.postMessage
 | 
			
		||||
		maxAttachments := 100
 | 
			
		||||
		m := map[int][]*attachment{}
 | 
			
		||||
		m := map[int][]slack.Attachment{}
 | 
			
		||||
		for i, a := range toSlackAttachments(r) {
 | 
			
		||||
			m[i/maxAttachments] = append(m[i/maxAttachments], a)
 | 
			
		||||
		}
 | 
			
		||||
@@ -98,21 +87,49 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
		}
 | 
			
		||||
		sort.Ints(chunkKeys)
 | 
			
		||||
 | 
			
		||||
		for i, k := range chunkKeys {
 | 
			
		||||
			txt := ""
 | 
			
		||||
			if i == 0 {
 | 
			
		||||
				txt = msgText(r)
 | 
			
		||||
		// Send slack by API
 | 
			
		||||
		if 0 < len(token) {
 | 
			
		||||
			api := slack.New(token)
 | 
			
		||||
			ParentMsg := slack.PostMessageParameters{
 | 
			
		||||
				//			Text:      msgText(r),
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
				IconEmoji: conf.IconEmoji,
 | 
			
		||||
			}
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:        txt,
 | 
			
		||||
				Username:    conf.AuthUser,
 | 
			
		||||
				IconEmoji:   conf.IconEmoji,
 | 
			
		||||
				Channel:     channel,
 | 
			
		||||
				Attachments: m[k],
 | 
			
		||||
			}
 | 
			
		||||
			if err := send(msg); err != nil {
 | 
			
		||||
 | 
			
		||||
			var ts string
 | 
			
		||||
			if _, ts, err = api.PostMessage(channel, msgText(r), ParentMsg); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, k := range chunkKeys {
 | 
			
		||||
				params := slack.PostMessageParameters{
 | 
			
		||||
					//		Text:            msgText(r),
 | 
			
		||||
					Username:        conf.AuthUser,
 | 
			
		||||
					IconEmoji:       conf.IconEmoji,
 | 
			
		||||
					Attachments:     m[k],
 | 
			
		||||
					ThreadTimestamp: ts,
 | 
			
		||||
				}
 | 
			
		||||
				if _, _, err = api.PostMessage(channel, msgText(r), params); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			for i, k := range chunkKeys {
 | 
			
		||||
				txt := ""
 | 
			
		||||
				if i == 0 {
 | 
			
		||||
					txt = msgText(r)
 | 
			
		||||
				}
 | 
			
		||||
				msg := message{
 | 
			
		||||
					Text:        txt,
 | 
			
		||||
					Username:    conf.AuthUser,
 | 
			
		||||
					IconEmoji:   conf.IconEmoji,
 | 
			
		||||
					Channel:     channel,
 | 
			
		||||
					Attachments: m[k],
 | 
			
		||||
				}
 | 
			
		||||
				if err = send(msg); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -158,20 +175,26 @@ func msgText(r models.ScanResult) string {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s",
 | 
			
		||||
 | 
			
		||||
	if 0 < len(r.Errors) {
 | 
			
		||||
		return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\nError: %s",
 | 
			
		||||
			notifyUsers,
 | 
			
		||||
			serverInfo,
 | 
			
		||||
			r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
			r.ScannedCves.FormatFixedStatus(r.Packages),
 | 
			
		||||
			r.FormatUpdatablePacksSummary(),
 | 
			
		||||
			r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s",
 | 
			
		||||
		notifyUsers,
 | 
			
		||||
		serverInfo,
 | 
			
		||||
		r.ScannedCves.FormatCveSummary())
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
		r.ScannedCves.FormatFixedStatus(r.Packages),
 | 
			
		||||
		r.FormatUpdatablePacksSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vinfos = r.ScannedCves.FindScoredVulns().ToSortedSlice()
 | 
			
		||||
	} else {
 | 
			
		||||
		vinfos = r.ScannedCves.ToSortedSlice()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) {
 | 
			
		||||
	vinfos := r.ScannedCves.ToSortedSlice()
 | 
			
		||||
	for _, vinfo := range vinfos {
 | 
			
		||||
		curent := []string{}
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
@@ -182,7 +205,7 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
				curent = append(curent, affected.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, n := range vinfo.CpeNames {
 | 
			
		||||
		for _, n := range vinfo.CpeURIs {
 | 
			
		||||
			curent = append(curent, n)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -198,16 +221,16 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
				new = append(new, "?")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for range vinfo.CpeNames {
 | 
			
		||||
		for range vinfo.CpeURIs {
 | 
			
		||||
			new = append(new, "?")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := attachment{
 | 
			
		||||
			Title:     vinfo.CveID,
 | 
			
		||||
			TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
 | 
			
		||||
			Text:      attachmentText(vinfo, r.Family),
 | 
			
		||||
			MrkdwnIn:  []string{"text", "pretext"},
 | 
			
		||||
			Fields: []*field{
 | 
			
		||||
		a := slack.Attachment{
 | 
			
		||||
			Title:      vinfo.CveID,
 | 
			
		||||
			TitleLink:  "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
 | 
			
		||||
			Text:       attachmentText(vinfo, r.Family, r.CweDict, r.Packages),
 | 
			
		||||
			MarkdownIn: []string{"text", "pretext"},
 | 
			
		||||
			Fields: []slack.AttachmentField{
 | 
			
		||||
				{
 | 
			
		||||
					// Title: "Current Package/CPE",
 | 
			
		||||
					Title: "Installed",
 | 
			
		||||
@@ -220,15 +243,15 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Color: color(vinfo.MaxCvssScore().Value.Score),
 | 
			
		||||
			Color: cvssColor(vinfo.MaxCvssScore().Value.Score),
 | 
			
		||||
		}
 | 
			
		||||
		attaches = append(attaches, &a)
 | 
			
		||||
		attaches = append(attaches, a)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://api.slack.com/docs/attachments
 | 
			
		||||
func color(cvssScore float64) string {
 | 
			
		||||
func cvssColor(cvssScore float64) string {
 | 
			
		||||
	switch {
 | 
			
		||||
	case 7 <= cvssScore:
 | 
			
		||||
		return "danger"
 | 
			
		||||
@@ -241,10 +264,15 @@ func color(cvssScore float64) string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]models.CweDictEntry, packs models.Packages) string {
 | 
			
		||||
	maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
	vectors := []string{}
 | 
			
		||||
	for _, cvss := range vinfo.Cvss2Scores() {
 | 
			
		||||
 | 
			
		||||
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(osFamily)...)
 | 
			
		||||
	for _, cvss := range scores {
 | 
			
		||||
		if cvss.Value.Severity == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		calcURL := ""
 | 
			
		||||
		switch cvss.Value.Type {
 | 
			
		||||
		case models.CVSS2:
 | 
			
		||||
@@ -258,9 +286,10 @@ func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if cont, ok := vinfo.CveContents[cvss.Type]; ok {
 | 
			
		||||
			v := fmt.Sprintf("<%s|%s> (<%s|%s>)",
 | 
			
		||||
			v := fmt.Sprintf("<%s|%s> %s (<%s|%s>)",
 | 
			
		||||
				calcURL,
 | 
			
		||||
				cvss.Value.Format(),
 | 
			
		||||
				fmt.Sprintf("%3.1f/%s", cvss.Value.Score, cvss.Value.Vector),
 | 
			
		||||
				cvss.Value.Severity,
 | 
			
		||||
				cont.SourceLink,
 | 
			
		||||
				cvss.Type)
 | 
			
		||||
			vectors = append(vectors, v)
 | 
			
		||||
@@ -273,9 +302,10 @@ func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
						v, k))
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				v := fmt.Sprintf("<%s|%s> (%s)",
 | 
			
		||||
				v := fmt.Sprintf("<%s|%s> %s (%s)",
 | 
			
		||||
					calcURL,
 | 
			
		||||
					cvss.Value.Format(),
 | 
			
		||||
					fmt.Sprintf("%3.1f/%s", cvss.Value.Score, cvss.Value.Vector),
 | 
			
		||||
					cvss.Value.Severity,
 | 
			
		||||
					strings.Join(links, ", "))
 | 
			
		||||
				vectors = append(vectors, v)
 | 
			
		||||
			}
 | 
			
		||||
@@ -287,27 +317,42 @@ func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
		severity = "?"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("*%4.1f (%s)* %s\n%s\n```%s```",
 | 
			
		||||
	nwvec := vinfo.AttackVector()
 | 
			
		||||
	if nwvec == "Network" || nwvec == "remote" {
 | 
			
		||||
		nwvec = fmt.Sprintf("*%s*", nwvec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mitigation := ""
 | 
			
		||||
	if vinfo.Mitigations(osFamily)[0].Type != models.Unknown {
 | 
			
		||||
		mitigation = fmt.Sprintf("\nMitigation:\n```%s```\n",
 | 
			
		||||
			vinfo.Mitigations(osFamily)[0].Value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("*%4.1f (%s)* %s %s\n%s\n```\n%s\n```%s\n%s\n",
 | 
			
		||||
		maxCvss.Value.Score,
 | 
			
		||||
		severity,
 | 
			
		||||
		cweIDs(vinfo, osFamily),
 | 
			
		||||
		nwvec,
 | 
			
		||||
		vinfo.PatchStatus(packs),
 | 
			
		||||
		strings.Join(vectors, "\n"),
 | 
			
		||||
		vinfo.Summaries(config.Conf.Lang, osFamily)[0].Value,
 | 
			
		||||
		mitigation,
 | 
			
		||||
		cweIDs(vinfo, osFamily, cweDict),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweIDs(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
func cweIDs(vinfo models.VulnInfo, osFamily string, cweDict models.CweDict) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
	for _, cwe := range vinfo.CveContents.CweIDs(osFamily) {
 | 
			
		||||
		if config.Conf.Lang == "ja" {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweJvnURL(cwe.Value), cwe.Value))
 | 
			
		||||
		} else {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweURL(cwe.Value), cwe.Value))
 | 
			
		||||
	for _, c := range vinfo.CveContents.UniqCweIDs(osFamily) {
 | 
			
		||||
		name, url, top10Rank, top10URL := cweDict.Get(c.Value, osFamily)
 | 
			
		||||
		line := ""
 | 
			
		||||
		if top10Rank != "" {
 | 
			
		||||
			line = fmt.Sprintf("<%s|[OWASP Top %s]>",
 | 
			
		||||
				top10URL, top10Rank)
 | 
			
		||||
		}
 | 
			
		||||
		links = append(links, fmt.Sprintf("%s <%s|%s>: %s",
 | 
			
		||||
			line, url, c.Value, name))
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(links, " / ")
 | 
			
		||||
	return strings.Join(links, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// See testcase
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -44,9 +44,9 @@ func (w StdoutWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
		fmt.Print("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.FormatShortText {
 | 
			
		||||
	if c.Conf.FormatList {
 | 
			
		||||
		for _, r := range rs {
 | 
			
		||||
			fmt.Println(formatShortPlainText(r))
 | 
			
		||||
			fmt.Println(formatList(r))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								report/stride.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								report/stride.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// StrideWriter send report to Stride
 | 
			
		||||
type StrideWriter struct{}
 | 
			
		||||
type strideSender struct{}
 | 
			
		||||
 | 
			
		||||
func (w StrideWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.Stride
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		w := strideSender{}
 | 
			
		||||
 | 
			
		||||
		serverInfo := fmt.Sprintf("%s", r.ServerInfo())
 | 
			
		||||
		message := fmt.Sprintf(`{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":" %s  "}]}]}}`,
 | 
			
		||||
			serverInfo,
 | 
			
		||||
		)
 | 
			
		||||
		if err = w.sendMessage(conf.HookURL, conf.AuthToken, message); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, vinfo := range r.ScannedCves {
 | 
			
		||||
			maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
			severity := strings.ToUpper(maxCvss.Value.Severity)
 | 
			
		||||
			if severity == "" {
 | 
			
		||||
				severity = "?"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			message = fmt.Sprintf(`{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":" %s ","marks": [ { "type": "link", "attrs": { "href": "https://nvd.nist.gov/vuln/detail/%s", "title": "cve" } } ]}]}]}}`,
 | 
			
		||||
				vinfo.CveID,
 | 
			
		||||
				vinfo.CveID,
 | 
			
		||||
			)
 | 
			
		||||
			if err = w.sendMessage(conf.HookURL, conf.AuthToken, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			message = fmt.Sprintf(`{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":" %s (%s) "}]}]}}`,
 | 
			
		||||
				strconv.FormatFloat(maxCvss.Value.Score, 'f', 1, 64),
 | 
			
		||||
				severity,
 | 
			
		||||
			)
 | 
			
		||||
			if err = w.sendMessage(conf.HookURL, conf.AuthToken, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			message = fmt.Sprintf(`{"body":{"version":1,"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":" %s "}]}]}}`,
 | 
			
		||||
				vinfo.Summaries(config.Conf.Lang, r.Family)[0].Value,
 | 
			
		||||
			)
 | 
			
		||||
			if err = w.sendMessage(conf.HookURL, conf.AuthToken, message); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w strideSender) sendMessage(uri, token, jsonStr string) error {
 | 
			
		||||
	reqs, err := http.NewRequest("POST", uri, bytes.NewBuffer([]byte(jsonStr)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	reqs.Header.Add("Content-Type", "application/json")
 | 
			
		||||
	reqs.Header.Add("Authorization", "Bearer "+token)
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	resp, err := client.Do(reqs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								report/stride_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								report/stride_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package report
 | 
			
		||||
							
								
								
									
										111
									
								
								report/syslog.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								report/syslog.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2018  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	syslog "github.com/RackSec/srslog"
 | 
			
		||||
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SyslogWriter send report to syslog
 | 
			
		||||
type SyslogWriter struct{}
 | 
			
		||||
 | 
			
		||||
func (w SyslogWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf.Syslog
 | 
			
		||||
	facility, _ := conf.GetFacility()
 | 
			
		||||
	severity, _ := conf.GetSeverity()
 | 
			
		||||
	raddr := fmt.Sprintf("%s:%s", conf.Host, conf.Port)
 | 
			
		||||
 | 
			
		||||
	sysLog, err := syslog.Dial(conf.Protocol, raddr, severity|facility, conf.Tag)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "Failed to initialize syslog client")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		messages := w.encodeSyslog(r)
 | 
			
		||||
		for _, m := range messages {
 | 
			
		||||
			if _, err = fmt.Fprintf(sysLog, m); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w SyslogWriter) encodeSyslog(result models.ScanResult) (messages []string) {
 | 
			
		||||
	ipv4Addrs := strings.Join(result.IPv4Addrs, ",")
 | 
			
		||||
	ipv6Addrs := strings.Join(result.IPv6Addrs, ",")
 | 
			
		||||
 | 
			
		||||
	var commonKvPairs []string
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`scanned_at="%s"`, result.ScannedAt))
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`server_name="%s"`, result.ServerName))
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`os_family="%s"`, result.Family))
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`os_release="%s"`, result.Release))
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`ipv4_addr="%s"`, ipv4Addrs))
 | 
			
		||||
	commonKvPairs = append(commonKvPairs, fmt.Sprintf(`ipv6_addr="%s"`, ipv6Addrs))
 | 
			
		||||
 | 
			
		||||
	for cveID, vinfo := range result.ScannedCves {
 | 
			
		||||
		kvPairs := commonKvPairs
 | 
			
		||||
 | 
			
		||||
		var pkgNames []string
 | 
			
		||||
		for _, pkg := range vinfo.AffectedPackages {
 | 
			
		||||
			pkgNames = append(pkgNames, pkg.Name)
 | 
			
		||||
		}
 | 
			
		||||
		pkgs := strings.Join(pkgNames, ",")
 | 
			
		||||
		kvPairs = append(kvPairs, fmt.Sprintf(`packages="%s"`, pkgs))
 | 
			
		||||
 | 
			
		||||
		kvPairs = append(kvPairs, fmt.Sprintf(`cve_id="%s"`, cveID))
 | 
			
		||||
		for _, cvss := range vinfo.Cvss2Scores(result.Family) {
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`cvss_score_%s_v2="%.2f"`, cvss.Type, cvss.Value.Score))
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`cvss_vector_%s_v2="%s"`, cvss.Type, cvss.Value.Vector))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cvss := range vinfo.Cvss3Scores() {
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`cvss_score_%s_v3="%.2f"`, cvss.Type, cvss.Value.Score))
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`cvss_vector_%s_v3="%s"`, cvss.Type, cvss.Value.Vector))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if content, ok := vinfo.CveContents[models.NvdXML]; ok {
 | 
			
		||||
			cwes := strings.Join(content.CweIDs, ",")
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`cwe_ids="%s"`, cwes))
 | 
			
		||||
			if config.Conf.Syslog.Verbose {
 | 
			
		||||
				kvPairs = append(kvPairs, fmt.Sprintf(`source_link="%s"`, content.SourceLink))
 | 
			
		||||
				kvPairs = append(kvPairs, fmt.Sprintf(`summary="%s"`, content.Summary))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if content, ok := vinfo.CveContents[models.RedHat]; ok {
 | 
			
		||||
			kvPairs = append(kvPairs, fmt.Sprintf(`title="%s"`, content.Title))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// message: key1="value1" key2="value2"...
 | 
			
		||||
		messages = append(messages, strings.Join(kvPairs, " "))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(messages) == 0 {
 | 
			
		||||
		commonKvPairs = append(commonKvPairs, `message="No CVE-IDs are found"`)
 | 
			
		||||
		messages = append(messages, strings.Join(commonKvPairs, " "))
 | 
			
		||||
	}
 | 
			
		||||
	return messages
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								report/syslog_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								report/syslog_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSyslogWriterEncodeSyslog(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		result           models.ScanResult
 | 
			
		||||
		expectedMessages []string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			result: models.ScanResult{
 | 
			
		||||
				ScannedAt:  time.Date(2018, 6, 13, 16, 10, 0, 0, time.UTC),
 | 
			
		||||
				ServerName: "teste01",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				IPv4Addrs:  []string{"192.168.0.1", "10.0.2.15"},
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							models.PackageStatus{Name: "pkg1"},
 | 
			
		||||
							models.PackageStatus{Name: "pkg2"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0002": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							models.PackageStatus{Name: "pkg3"},
 | 
			
		||||
							models.PackageStatus{Name: "pkg4"},
 | 
			
		||||
						},
 | 
			
		||||
						CveContents: models.CveContents{
 | 
			
		||||
							models.NvdXML: models.CveContent{
 | 
			
		||||
								Cvss2Score:    5.0,
 | 
			
		||||
								Cvss2Vector:   "AV:L/AC:L/Au:N/C:N/I:N/A:C",
 | 
			
		||||
								Cvss2Severity: "MEDIUM",
 | 
			
		||||
								CweIDs:        []string{"CWE-20"},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedMessages: []string{
 | 
			
		||||
				`scanned_at="2018-06-13 16:10:00 +0000 UTC" server_name="teste01" os_family="ubuntu" os_release="16.04" ipv4_addr="192.168.0.1,10.0.2.15" ipv6_addr="" packages="pkg1,pkg2" cve_id="CVE-2017-0001"`,
 | 
			
		||||
				`scanned_at="2018-06-13 16:10:00 +0000 UTC" server_name="teste01" os_family="ubuntu" os_release="16.04" ipv4_addr="192.168.0.1,10.0.2.15" ipv6_addr="" packages="pkg3,pkg4" cve_id="CVE-2017-0002" cvss_score_nvdxml_v2="5.00" cvss_vector_nvdxml_v2="AV:L/AC:L/Au:N/C:N/I:N/A:C" cwe_ids="CWE-20"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			result: models.ScanResult{
 | 
			
		||||
				ScannedAt:  time.Date(2018, 6, 13, 17, 10, 0, 0, time.UTC),
 | 
			
		||||
				ServerName: "teste02",
 | 
			
		||||
				Family:     "centos",
 | 
			
		||||
				Release:    "6",
 | 
			
		||||
				IPv6Addrs:  []string{"2001:0DB8::1"},
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2017-0003": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							models.PackageStatus{Name: "pkg5"},
 | 
			
		||||
						},
 | 
			
		||||
						CveContents: models.CveContents{
 | 
			
		||||
							models.RedHat: models.CveContent{
 | 
			
		||||
								Cvss3Score:  5.0,
 | 
			
		||||
								Cvss3Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C",
 | 
			
		||||
								CweIDs:      []string{"CWE-284"},
 | 
			
		||||
								Title:       "RHSA-2017:0001: pkg5 security update (Important)",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedMessages: []string{
 | 
			
		||||
				`scanned_at="2018-06-13 17:10:00 +0000 UTC" server_name="teste02" os_family="centos" os_release="6" ipv4_addr="" ipv6_addr="2001:0DB8::1" packages="pkg5" cve_id="CVE-2017-0003" cvss_score_redhat_v3="5.00" cvss_vector_redhat_v3="AV:L/AC:L/Au:N/C:N/I:N/A:C" title="RHSA-2017:0001: pkg5 security update (Important)"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			result: models.ScanResult{
 | 
			
		||||
				ScannedAt:   time.Date(2018, 6, 13, 12, 10, 0, 0, time.UTC),
 | 
			
		||||
				ServerName:  "teste03",
 | 
			
		||||
				Family:      "centos",
 | 
			
		||||
				Release:     "7",
 | 
			
		||||
				IPv6Addrs:   []string{"2001:0DB8::1"},
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
			expectedMessages: []string{
 | 
			
		||||
				`scanned_at="2018-06-13 12:10:00 +0000 UTC" server_name="teste03" os_family="centos" os_release="7" ipv4_addr="" ipv6_addr="2001:0DB8::1" message="No CVE-IDs are found"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		messages := SyslogWriter{}.encodeSyslog(tt.result)
 | 
			
		||||
		if len(messages) != len(tt.expectedMessages) {
 | 
			
		||||
			t.Fatalf("test: %d, Message Length: expected %d, actual: %d",
 | 
			
		||||
				i, len(tt.expectedMessages), len(messages))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sort.Slice(messages, func(i, j int) bool {
 | 
			
		||||
			return messages[i] < messages[j]
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		for j, m := range messages {
 | 
			
		||||
			e := tt.expectedMessages[j]
 | 
			
		||||
			if e != m {
 | 
			
		||||
				t.Errorf("test: %d, Messsage %d: \nexpected %s \nactual   %s", i, j, e, m)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										295
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								report/tui.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -32,7 +32,6 @@ import (
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/jroimartin/gocui"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanResults models.ScanResults
 | 
			
		||||
@@ -45,19 +44,23 @@ var currentChangelogLimitY int
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui(results models.ScanResults) subcommands.ExitStatus {
 | 
			
		||||
	scanResults = results
 | 
			
		||||
	sort.Slice(scanResults, func(i, j int) bool {
 | 
			
		||||
		if scanResults[i].ServerName == scanResults[j].ServerName {
 | 
			
		||||
			return scanResults[i].Container.Name < scanResults[j].Container.Name
 | 
			
		||||
		}
 | 
			
		||||
		return scanResults[i].ServerName < scanResults[j].ServerName
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
	g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	// g.SetManagerFunc(layout)
 | 
			
		||||
	g.SetManagerFunc(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		util.Log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	g.SelBgColor = gocui.ColorGreen
 | 
			
		||||
@@ -66,7 +69,7 @@ func RunTui(results models.ScanResults) subcommands.ExitStatus {
 | 
			
		||||
 | 
			
		||||
	if err := g.MainLoop(); err != nil {
 | 
			
		||||
		g.Close()
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		util.Log.Errorf("%s", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
@@ -180,19 +183,19 @@ func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		err = g.SetCurrentView("changelog")
 | 
			
		||||
		_, err = g.SetCurrentView("changelog")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -201,19 +204,19 @@ func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
	default:
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -396,7 +399,7 @@ func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -404,7 +407,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -414,7 +417,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -422,7 +425,7 @@ func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -462,10 +465,7 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return setChangelogLayout(g)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
@@ -473,10 +473,7 @@ func redrawDetail(g *gocui.Gui) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return setDetailLayout(g)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func redrawChangelog(g *gocui.Gui) error {
 | 
			
		||||
@@ -484,10 +481,7 @@ func redrawChangelog(g *gocui.Gui) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return setChangelogLayout(g)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
@@ -505,7 +499,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -528,7 +522,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -539,10 +533,8 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("msg"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	_, err := g.SetCurrentView("summary")
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func quit(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
@@ -559,11 +551,7 @@ func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setDetailLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return setChangelogLayout(g)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func debug(g *gocui.Gui, str string) error {
 | 
			
		||||
@@ -595,7 +583,7 @@ func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanResults[0]
 | 
			
		||||
		vinfos = scanResults[0].ScannedCves.ToSortedSlice()
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
		if _, err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -609,7 +597,7 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := summaryLines()
 | 
			
		||||
		lines := summaryLines(currentScanResult)
 | 
			
		||||
		fmt.Fprintf(v, lines)
 | 
			
		||||
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
@@ -619,37 +607,42 @@ func setSummaryLayout(g *gocui.Gui) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func summaryLines() string {
 | 
			
		||||
func summaryLines(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = 1000
 | 
			
		||||
	stable.Wrap = false
 | 
			
		||||
 | 
			
		||||
	if len(currentScanResult.Errors) != 0 {
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return "Error: Scan with --debug to view the details"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(currentScanResult.ScannedCves) < 10 {
 | 
			
		||||
	if len(r.ScannedCves) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(currentScanResult.ScannedCves) < 100 {
 | 
			
		||||
	} else if len(r.ScannedCves) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, vinfo := range vinfos {
 | 
			
		||||
		summary := vinfo.Titles(
 | 
			
		||||
			config.Conf.Lang, currentScanResult.Family)[0].Value
 | 
			
		||||
		cvssScore := fmt.Sprintf("| %4.1f",
 | 
			
		||||
			vinfo.MaxCvssScore().Value.Score)
 | 
			
		||||
	for i, vinfo := range r.ScannedCves.ToSortedSlice() {
 | 
			
		||||
		max := vinfo.MaxCvssScore().Value.Score
 | 
			
		||||
		cvssScore := "|     "
 | 
			
		||||
		if 0 < max {
 | 
			
		||||
			cvssScore = fmt.Sprintf("| %4.1f", max)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packname := vinfo.AffectedPackages.FormatTuiSummary()
 | 
			
		||||
		packname += strings.Join(vinfo.CpeURIs, ", ")
 | 
			
		||||
 | 
			
		||||
		var cols []string
 | 
			
		||||
		cols = []string{
 | 
			
		||||
			fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			cvssScore,
 | 
			
		||||
			fmt.Sprintf("| %3d |", vinfo.Confidence.Score),
 | 
			
		||||
			summary,
 | 
			
		||||
			cvssScore + " |",
 | 
			
		||||
			fmt.Sprintf("%8s |", vinfo.AttackVector()),
 | 
			
		||||
			fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)),
 | 
			
		||||
			packname,
 | 
			
		||||
		}
 | 
			
		||||
		icols := make([]interface{}, len(cols))
 | 
			
		||||
		for j := range cols {
 | 
			
		||||
@@ -689,16 +682,12 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
 | 
			
		||||
	summaryView, err := g.View("summary")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentVinfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	maxX, maxY := g.Size()
 | 
			
		||||
	if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -707,17 +696,74 @@ func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := []string{}
 | 
			
		||||
		lines := []string{
 | 
			
		||||
			"Affected Packages, Processes",
 | 
			
		||||
			"============================",
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_, cy := summaryView.Cursor()
 | 
			
		||||
		_, oy := summaryView.Origin()
 | 
			
		||||
		currentVinfo = cy + oy
 | 
			
		||||
		vinfo := vinfos[currentVinfo]
 | 
			
		||||
		vinfo.AffectedPackages.Sort()
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			// packages detected by OVAL may not be actually installed
 | 
			
		||||
			if pack, ok := currentScanResult.Packages[affected.Name]; ok {
 | 
			
		||||
				var line string
 | 
			
		||||
				if pack.Repository != "" {
 | 
			
		||||
					line = fmt.Sprintf("* %s (%s)",
 | 
			
		||||
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
 | 
			
		||||
						pack.Repository)
 | 
			
		||||
				} else {
 | 
			
		||||
					line = fmt.Sprintf("* %s",
 | 
			
		||||
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
				lines = append(lines, line)
 | 
			
		||||
 | 
			
		||||
				if len(pack.AffectedProcs) != 0 {
 | 
			
		||||
					for _, p := range pack.AffectedProcs {
 | 
			
		||||
						lines = append(lines, fmt.Sprintf("  * PID: %s %s", p.PID, p.Name))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					// lines = append(lines, fmt.Sprintf("  * No affected process"))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		sort.Strings(vinfo.CpeURIs)
 | 
			
		||||
		for _, uri := range vinfo.CpeURIs {
 | 
			
		||||
			lines = append(lines, "* "+uri)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, adv := range vinfo.DistroAdvisories {
 | 
			
		||||
			lines = append(lines, "\n",
 | 
			
		||||
				"Advisories",
 | 
			
		||||
				"==========",
 | 
			
		||||
			)
 | 
			
		||||
			lines = append(lines, adv.Format())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			pack := currentScanResult.Packages[affected.Name]
 | 
			
		||||
			for _, p := range currentScanResult.Packages {
 | 
			
		||||
				if pack.Name == p.Name {
 | 
			
		||||
					lines = append(lines, p.FormatChangelog(), "\n")
 | 
			
		||||
		if len(vinfo.Exploits) != 0 {
 | 
			
		||||
			lines = append(lines, "\n",
 | 
			
		||||
				"Exploit Codes",
 | 
			
		||||
				"=============",
 | 
			
		||||
			)
 | 
			
		||||
			for _, exploit := range vinfo.Exploits {
 | 
			
		||||
				lines = append(lines, fmt.Sprintf("* [%s](%s)", exploit.Description, exploit.URL))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if currentScanResult.IsDeepScanMode() {
 | 
			
		||||
			lines = append(lines, "\n",
 | 
			
		||||
				"ChangeLogs",
 | 
			
		||||
				"==========",
 | 
			
		||||
			)
 | 
			
		||||
			for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
				pack := currentScanResult.Packages[affected.Name]
 | 
			
		||||
				for _, p := range currentScanResult.Packages {
 | 
			
		||||
					if pack.Name == p.Name {
 | 
			
		||||
						lines = append(lines, p.FormatChangelog(), "\n")
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -734,13 +780,15 @@ func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Cvsses           string
 | 
			
		||||
	Exploits         []models.Exploit
 | 
			
		||||
	Summary          string
 | 
			
		||||
	Confidence       models.Confidence
 | 
			
		||||
	Cwes             []models.CveContentStr
 | 
			
		||||
	Mitigation       string
 | 
			
		||||
	Confidences      models.Confidences
 | 
			
		||||
	Cwes             []models.CweDictEntry
 | 
			
		||||
	Links            []string
 | 
			
		||||
	References       []models.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	CpeURIs          []string
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
	LastModifiedDate time.Time
 | 
			
		||||
}
 | 
			
		||||
@@ -761,20 +809,6 @@ func detailLines() (string, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfo := vinfos[currentVinfo]
 | 
			
		||||
 | 
			
		||||
	packsVer := []string{}
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
		// packages detected by OVAL may not be actually installed
 | 
			
		||||
		if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
			packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(vinfo.CpeNames)
 | 
			
		||||
	for _, name := range vinfo.CpeNames {
 | 
			
		||||
		packsVer = append(packsVer, name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	links := []string{vinfo.CveContents.SourceLinks(
 | 
			
		||||
		config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
 | 
			
		||||
		vinfo.Cvss2CalcURL(),
 | 
			
		||||
@@ -786,35 +820,57 @@ func detailLines() (string, error) {
 | 
			
		||||
	refs := []models.Reference{}
 | 
			
		||||
	for _, rr := range vinfo.CveContents.References(r.Family) {
 | 
			
		||||
		for _, ref := range rr.Value {
 | 
			
		||||
			if ref.Source == "" {
 | 
			
		||||
				ref.Source = "-"
 | 
			
		||||
			}
 | 
			
		||||
			refs = append(refs, ref)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := vinfo.Summaries(r.Lang, r.Family)[0]
 | 
			
		||||
	mitigation := vinfo.Mitigations(r.Family)[0]
 | 
			
		||||
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
 | 
			
		||||
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(r.Family)...)
 | 
			
		||||
	var cols []interface{}
 | 
			
		||||
	for _, score := range scores {
 | 
			
		||||
		if score.Value.Score == 0 && score.Value.Severity == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		scoreStr := "-"
 | 
			
		||||
		if 0 < score.Value.Score {
 | 
			
		||||
			scoreStr = fmt.Sprintf("%3.1f", score.Value.Score)
 | 
			
		||||
		}
 | 
			
		||||
		scoreVec := fmt.Sprintf("%s/%s", scoreStr, score.Value.Vector)
 | 
			
		||||
		cols = []interface{}{
 | 
			
		||||
			scoreVec,
 | 
			
		||||
			score.Value.Severity,
 | 
			
		||||
			score.Value.Format(),
 | 
			
		||||
			score.Type,
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uniqCweIDs := vinfo.CveContents.UniqCweIDs(r.Family)
 | 
			
		||||
	cwes := []models.CweDictEntry{}
 | 
			
		||||
	for _, cweID := range uniqCweIDs {
 | 
			
		||||
		if strings.HasPrefix(cweID.Value, "CWE-") {
 | 
			
		||||
			if dict, ok := r.CweDict[strings.TrimPrefix(cweID.Value, "CWE-")]; ok {
 | 
			
		||||
				cwes = append(cwes, dict)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := dataForTmpl{
 | 
			
		||||
		CveID:      vinfo.CveID,
 | 
			
		||||
		Cvsses:     fmt.Sprintf("%s\n", table),
 | 
			
		||||
		Summary:    fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
 | 
			
		||||
		Confidence: vinfo.Confidence,
 | 
			
		||||
		Cwes:       vinfo.CveContents.CweIDs(r.Family),
 | 
			
		||||
		Links:      util.Distinct(links),
 | 
			
		||||
		Packages:   packsVer,
 | 
			
		||||
		References: refs,
 | 
			
		||||
		CveID:       vinfo.CveID,
 | 
			
		||||
		Cvsses:      fmt.Sprintf("%s\n", table),
 | 
			
		||||
		Summary:     fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
 | 
			
		||||
		Mitigation:  fmt.Sprintf("%s (%s)", mitigation.Value, mitigation.Type),
 | 
			
		||||
		Confidences: vinfo.Confidences,
 | 
			
		||||
		Cwes:        cwes,
 | 
			
		||||
		Links:       util.Distinct(links),
 | 
			
		||||
		References:  refs,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil) // create empty buffer
 | 
			
		||||
@@ -827,47 +883,42 @@ func detailLines() (string, error) {
 | 
			
		||||
 | 
			
		||||
const mdTemplate = `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
==============
 | 
			
		||||
================
 | 
			
		||||
 | 
			
		||||
CVSS Scores
 | 
			
		||||
--------------
 | 
			
		||||
-----------
 | 
			
		||||
{{.Cvsses }}
 | 
			
		||||
 | 
			
		||||
Summary
 | 
			
		||||
--------------
 | 
			
		||||
-----------
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
Mitigation
 | 
			
		||||
-----------
 | 
			
		||||
 {{.Mitigation }}
 | 
			
		||||
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
-----------
 | 
			
		||||
{{range $link := .Links -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
CWE
 | 
			
		||||
--------------
 | 
			
		||||
-----------
 | 
			
		||||
{{range .Cwes -}}
 | 
			
		||||
* {{.Value}} ({{.Type}})
 | 
			
		||||
* {{.En.CweID}} [{{.En.Name}}](https://cwe.mitre.org/data/definitions/{{.En.CweID}}.html)
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range $name := .CpeNames -}}
 | 
			
		||||
{{range $name := .CpeURIs -}}
 | 
			
		||||
* {{$name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
Confidence
 | 
			
		||||
--------------
 | 
			
		||||
 {{.Confidence }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-----------
 | 
			
		||||
{{range $confidence := .Confidences -}}
 | 
			
		||||
* {{$confidence.DetectionMethod}}
 | 
			
		||||
{{end}}
 | 
			
		||||
References
 | 
			
		||||
--------------
 | 
			
		||||
-----------
 | 
			
		||||
{{range .References -}}
 | 
			
		||||
* [{{.Source}}]( {{.Link}} )
 | 
			
		||||
* [{{.Source}}]({{.Link}})
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										356
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						
									
										356
									
								
								report/util.go
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
@@ -18,11 +18,13 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -32,9 +34,10 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/olekukonko/tablewriter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const maxColWidth = 80
 | 
			
		||||
const maxColWidth = 100
 | 
			
		||||
 | 
			
		||||
func formatScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
@@ -46,7 +49,8 @@ func formatScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				fmt.Sprintf("%s%s", r.Family, r.Release),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
				r.FormatUpdatablePacksSummary(),
 | 
			
		||||
				r.FormatExploitCveSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
@@ -71,7 +75,9 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
				r.ScannedCves.FormatFixedStatus(r.Packages),
 | 
			
		||||
				r.FormatUpdatablePacksSummary(),
 | 
			
		||||
				r.FormatExploitCveSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
@@ -85,7 +91,7 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
	return fmt.Sprintf("%s\n", table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatShortPlainText(r models.ScanResult) string {
 | 
			
		||||
func formatList(r models.ScanResult) string {
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
@@ -93,73 +99,56 @@ func formatShortPlainText(r models.ScanResult) string {
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns := r.ScannedCves
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vulns = vulns.FindScoredVulns()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(vulns) == 0 {
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	 `, header, r.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	for _, vuln := range vulns.ToSortedSlice() {
 | 
			
		||||
		summaries := vuln.Summaries(config.Conf.Lang, r.Family)
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
	data := [][]string{}
 | 
			
		||||
	for _, vinfo := range r.ScannedCves.ToSortedSlice() {
 | 
			
		||||
		max := vinfo.MaxCvssScore().Value.Score
 | 
			
		||||
		// v2max := vinfo.MaxCvss2Score().Value.Score
 | 
			
		||||
		// v3max := vinfo.MaxCvss3Score().Value.Score
 | 
			
		||||
 | 
			
		||||
		vlinks := []string{}
 | 
			
		||||
		for name, url := range vuln.VendorLinks(r.Family) {
 | 
			
		||||
			vlinks = append(vlinks, fmt.Sprintf("%s (%s)", url, name))
 | 
			
		||||
		}
 | 
			
		||||
		// packname := vinfo.AffectedPackages.FormatTuiSummary()
 | 
			
		||||
		// packname += strings.Join(vinfo.CpeURIs, ", ")
 | 
			
		||||
 | 
			
		||||
		cvsses := ""
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		cvsses += vuln.Cvss2CalcURL() + "\n"
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			cvsses += vuln.Cvss3CalcURL() + "\n"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		maxCvss := vuln.FormatMaxCvssScore()
 | 
			
		||||
		rightCol := fmt.Sprintf(`%s
 | 
			
		||||
%s
 | 
			
		||||
---
 | 
			
		||||
%s
 | 
			
		||||
%s
 | 
			
		||||
%sConfidence: %v`,
 | 
			
		||||
			maxCvss,
 | 
			
		||||
			summaries[0].Value,
 | 
			
		||||
			links[0].Value,
 | 
			
		||||
			strings.Join(vlinks, "\n"),
 | 
			
		||||
			cvsses,
 | 
			
		||||
			//  packsVer,
 | 
			
		||||
			vuln.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		leftCol := fmt.Sprintf("%s", vuln.CveID)
 | 
			
		||||
		scols := []string{leftCol, rightCol}
 | 
			
		||||
		cols := make([]interface{}, len(scols))
 | 
			
		||||
		for i := range cols {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
		}
 | 
			
		||||
		stable.AddRow(cols...)
 | 
			
		||||
		stable.AddRow("")
 | 
			
		||||
		data = append(data, []string{
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			fmt.Sprintf("%4.1f", max),
 | 
			
		||||
			// fmt.Sprintf("%4.1f", v2max),
 | 
			
		||||
			// fmt.Sprintf("%4.1f", v3max),
 | 
			
		||||
			fmt.Sprintf("%8s", vinfo.AttackVector()),
 | 
			
		||||
			fmt.Sprintf("%7s", vinfo.PatchStatus(r.Packages)),
 | 
			
		||||
			// packname,
 | 
			
		||||
			fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID),
 | 
			
		||||
			fmt.Sprintf("%t", 0 < len(vinfo.Exploits)),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n", header, stable)
 | 
			
		||||
 | 
			
		||||
	b := bytes.Buffer{}
 | 
			
		||||
	table := tablewriter.NewWriter(&b)
 | 
			
		||||
	table.SetHeader([]string{
 | 
			
		||||
		"CVE-ID",
 | 
			
		||||
		"CVSS",
 | 
			
		||||
		// "v3",
 | 
			
		||||
		// "v2",
 | 
			
		||||
		"Attack",
 | 
			
		||||
		"Fixed",
 | 
			
		||||
		// "Pkg",
 | 
			
		||||
		"NVD",
 | 
			
		||||
		"Exploit",
 | 
			
		||||
	})
 | 
			
		||||
	table.SetBorder(true)
 | 
			
		||||
	table.AppendBulk(data)
 | 
			
		||||
	table.Render()
 | 
			
		||||
	return fmt.Sprintf("%s\n%s", header, b.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatFullPlainText(r models.ScanResult) string {
 | 
			
		||||
func formatFullPlainText(r models.ScanResult) (lines string) {
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
@@ -167,72 +156,131 @@ func formatFullPlainText(r models.ScanResult) string {
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vulns := r.ScannedCves
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vulns = vulns.FindScoredVulns()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(vulns) == 0 {
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	 `, header, r.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, vuln := range vulns.ToSortedSlice() {
 | 
			
		||||
		table.AddRow(vuln.CveID)
 | 
			
		||||
		table.AddRow("----------------")
 | 
			
		||||
		table.AddRow("Max Score", vuln.FormatMaxCvssScore())
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
	lines = header + "\n"
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves.ToSortedSlice() {
 | 
			
		||||
		data := [][]string{}
 | 
			
		||||
		data = append(data, []string{"Max Score", vuln.FormatMaxCvssScore()})
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss2Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv2 Calc", vuln.Cvss2CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv3 Calc", vuln.Cvss3CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Summary", vuln.Summaries(
 | 
			
		||||
			config.Conf.Lang, r.Family)[0].Value)
 | 
			
		||||
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
		table.AddRow("Source", links[0].Value)
 | 
			
		||||
 | 
			
		||||
		vlinks := vuln.VendorLinks(r.Family)
 | 
			
		||||
		for name, url := range vlinks {
 | 
			
		||||
			table.AddRow(name, url)
 | 
			
		||||
			if cvssstr := cvss.Value.Format(); cvssstr != "" {
 | 
			
		||||
				data = append(data, []string{string(cvss.Type), cvssstr})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range vuln.CveContents.CweIDs(r.Family) {
 | 
			
		||||
			table.AddRow(fmt.Sprintf("%s (%s)", v.Value, v.Type), cweURL(v.Value))
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores(r.Family) {
 | 
			
		||||
			if cvssstr := cvss.Value.Format(); cvssstr != "" {
 | 
			
		||||
				data = append(data, []string{string(cvss.Type), cvssstr})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data = append(data, []string{"Summary", vuln.Summaries(
 | 
			
		||||
			config.Conf.Lang, r.Family)[0].Value})
 | 
			
		||||
 | 
			
		||||
		mitigation := vuln.Mitigations(r.Family)[0]
 | 
			
		||||
		if mitigation.Type != models.Unknown {
 | 
			
		||||
			data = append(data, []string{"Mitigation", mitigation.Value})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cweURLs, top10URLs := []string{}, []string{}
 | 
			
		||||
		for _, v := range vuln.CveContents.UniqCweIDs(r.Family) {
 | 
			
		||||
			name, url, top10Rank, top10URL := 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)
 | 
			
		||||
			} else {
 | 
			
		||||
				data = append(data, []string{"CWE", fmt.Sprintf("%s: %s (%s)",
 | 
			
		||||
					v.Value, name, v.Type)})
 | 
			
		||||
			}
 | 
			
		||||
			cweURLs = append(cweURLs, url)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packsVer := []string{}
 | 
			
		||||
		vuln.AffectedPackages.Sort()
 | 
			
		||||
		for _, affected := range vuln.AffectedPackages {
 | 
			
		||||
			if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
				var line string
 | 
			
		||||
				if pack.Repository != "" {
 | 
			
		||||
					line = fmt.Sprintf("%s (%s)",
 | 
			
		||||
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
 | 
			
		||||
						pack.Repository)
 | 
			
		||||
				} else {
 | 
			
		||||
					line = fmt.Sprintf("%s",
 | 
			
		||||
						pack.FormatVersionFromTo(affected.NotFixedYet, affected.FixState),
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
				data = append(data, []string{"Affected Pkg", line})
 | 
			
		||||
 | 
			
		||||
				if len(pack.AffectedProcs) != 0 {
 | 
			
		||||
					for _, p := range pack.AffectedProcs {
 | 
			
		||||
						data = append(data, []string{"",
 | 
			
		||||
							fmt.Sprintf("  - PID: %s %s", p.PID, p.Name)})
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		sort.Strings(vuln.CpeNames)
 | 
			
		||||
		for _, name := range vuln.CpeNames {
 | 
			
		||||
			packsVer = append(packsVer, name)
 | 
			
		||||
		sort.Strings(vuln.CpeURIs)
 | 
			
		||||
		for _, name := range vuln.CpeURIs {
 | 
			
		||||
			data = append(data, []string{"CPE", name})
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Package/CPE", strings.Join(packsVer, "\n"))
 | 
			
		||||
		table.AddRow("Confidence", vuln.Confidence)
 | 
			
		||||
 | 
			
		||||
		table.AddRow("\n")
 | 
			
		||||
		for _, confidence := range vuln.Confidences {
 | 
			
		||||
			data = append(data, []string{"Confidence", confidence.String()})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
		data = append(data, []string{"Source", links[0].Value})
 | 
			
		||||
 | 
			
		||||
		if 0 < len(vuln.Cvss2Scores(r.Family)) {
 | 
			
		||||
			data = append(data, []string{"CVSSv2 Calc", vuln.Cvss2CalcURL()})
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			data = append(data, []string{"CVSSv3 Calc", vuln.Cvss3CalcURL()})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vlinks := vuln.VendorLinks(r.Family)
 | 
			
		||||
		for name, url := range vlinks {
 | 
			
		||||
			data = append(data, []string{name, url})
 | 
			
		||||
		}
 | 
			
		||||
		for _, url := range cweURLs {
 | 
			
		||||
			data = append(data, []string{"CWE", url})
 | 
			
		||||
		}
 | 
			
		||||
		for _, exploit := range vuln.Exploits {
 | 
			
		||||
			data = append(data, []string{string(exploit.ExploitType), exploit.URL})
 | 
			
		||||
		}
 | 
			
		||||
		for _, url := range top10URLs {
 | 
			
		||||
			data = append(data, []string{"OWASP Top10", url})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// for _, rr := range vuln.CveContents.References(r.Family) {
 | 
			
		||||
		// for _, ref := range rr.Value {
 | 
			
		||||
		// data = append(data, []string{ref.Source, ref.Link})
 | 
			
		||||
		// }
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		b := bytes.Buffer{}
 | 
			
		||||
		table := tablewriter.NewWriter(&b)
 | 
			
		||||
		table.SetColWidth(80)
 | 
			
		||||
		table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
 | 
			
		||||
		table.SetHeader([]string{
 | 
			
		||||
			vuln.CveID,
 | 
			
		||||
			"",
 | 
			
		||||
		})
 | 
			
		||||
		table.SetBorder(true)
 | 
			
		||||
		table.AppendBulk(data)
 | 
			
		||||
		table.Render()
 | 
			
		||||
		lines += b.String() + "\n"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n%s", header, table)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweURL(cweID string) string {
 | 
			
		||||
@@ -283,27 +331,35 @@ func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPrevious(current models.ScanResults) (previous models.ScanResults, err error) {
 | 
			
		||||
func loadPrevious(currs models.ScanResults) (prevs models.ScanResults, err error) {
 | 
			
		||||
	dirs, err := ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range current {
 | 
			
		||||
	for _, result := range currs {
 | 
			
		||||
		filename := result.ServerName + ".json"
 | 
			
		||||
		if result.Container.Name != "" {
 | 
			
		||||
			filename = fmt.Sprintf("%s@%s.json", result.Container.Name, result.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			var r *models.ScanResult
 | 
			
		||||
			path := filepath.Join(dir, result.ServerName+".json")
 | 
			
		||||
			if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
			path := filepath.Join(dir, filename)
 | 
			
		||||
			r, err := loadOneServerScanResult(path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("%s", err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				previous = append(previous, *r)
 | 
			
		||||
				util.Log.Infof("Privious json found: %s", path)
 | 
			
		||||
				prevs = append(prevs, *r)
 | 
			
		||||
				util.Log.Infof("Previous json found: %s", path)
 | 
			
		||||
				break
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Infof("Previous json is different family.Release: %s, pre: %s.%s cur: %s.%s",
 | 
			
		||||
					path, r.Family, r.Release, result.Family, result.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return previous, nil
 | 
			
		||||
	return prevs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults, err error) {
 | 
			
		||||
@@ -311,7 +367,7 @@ func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults,
 | 
			
		||||
		found := false
 | 
			
		||||
		var previous models.ScanResult
 | 
			
		||||
		for _, r := range preResults {
 | 
			
		||||
			if current.ServerName == r.ServerName {
 | 
			
		||||
			if current.ServerName == r.ServerName && current.Container.Name == r.Container.Name {
 | 
			
		||||
				found = true
 | 
			
		||||
				previous = r
 | 
			
		||||
				break
 | 
			
		||||
@@ -347,8 +403,20 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(v.CveID, previous, current) {
 | 
			
		||||
				updated[v.CveID] = v
 | 
			
		||||
				util.Log.Debugf("updated: %s", v.CveID)
 | 
			
		||||
 | 
			
		||||
				// TODO commented out beause  a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at
 | 
			
		||||
				// if these OVAL defs have different affected packages, this logic detects as updated.
 | 
			
		||||
				// This logic will be uncommented after integration with ghost https://github.com/knqyf263/gost
 | 
			
		||||
				// } else if isCveFixed(v, previous) {
 | 
			
		||||
				// updated[v.CveID] = v
 | 
			
		||||
				// util.Log.Debugf("fixed: %s", v.CveID)
 | 
			
		||||
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("same: %s", v.CveID)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("new: %s", v.CveID)
 | 
			
		||||
			new[v.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -359,36 +427,54 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	return updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveFixed(current models.VulnInfo, previous models.ScanResult) bool {
 | 
			
		||||
	preVinfo, _ := previous.ScannedCves[current.CveID]
 | 
			
		||||
	pre := map[string]bool{}
 | 
			
		||||
	for _, h := range preVinfo.AffectedPackages {
 | 
			
		||||
		pre[h.Name] = h.NotFixedYet
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cur := map[string]bool{}
 | 
			
		||||
	for _, h := range current.AffectedPackages {
 | 
			
		||||
		cur[h.Name] = h.NotFixedYet
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return !reflect.DeepEqual(pre, cur)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
 | 
			
		||||
	cTypes := []models.CveContentType{
 | 
			
		||||
		models.NVD,
 | 
			
		||||
		models.JVN,
 | 
			
		||||
		models.NvdXML,
 | 
			
		||||
		models.Jvn,
 | 
			
		||||
		models.NewCveContentType(current.Family),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prevLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range previous.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				prevLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
	preVinfo, ok := previous.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if content, ok := preVinfo.CveContents[cType]; ok {
 | 
			
		||||
			prevLastModified[cType] = content.LastModified
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	curLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range current.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				curLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	curVinfo, ok := current.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if equal := prevLastModified[cType].Equal(curLastModified[cType]); !equal {
 | 
			
		||||
		if content, ok := curVinfo.CveContents[cType]; ok {
 | 
			
		||||
			curLastModified[cType] = content.LastModified
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, t := range cTypes {
 | 
			
		||||
		if !curLastModified[t].Equal(prevLastModified[t]) {
 | 
			
		||||
			util.Log.Debugf("%s LastModified not equal: \n%s\n%s",
 | 
			
		||||
				cveID, curLastModified[t], prevLastModified[t])
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,23 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	code := m.Run()
 | 
			
		||||
	os.Exit(code)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
	f := "2006-01-02"
 | 
			
		||||
	old, _ := time.Parse(f, "2015-12-15")
 | 
			
		||||
@@ -33,7 +42,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
@@ -47,7 +56,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
@@ -68,7 +77,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.Jvn,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
@@ -82,7 +91,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.Jvn,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
@@ -104,7 +113,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: new,
 | 
			
		||||
								},
 | 
			
		||||
@@ -119,7 +128,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
@@ -141,7 +150,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
							CveID: "CVE-2017-0004",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
@@ -185,18 +194,18 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
					Optional: map[string]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
@@ -210,18 +219,18 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
					Optional: map[string]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
@@ -232,7 +241,7 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
				Packages:    models.Packages{},
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
				Errors:      []string{},
 | 
			
		||||
				Optional:    [][]interface{}{},
 | 
			
		||||
				Optional:    map[string]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
@@ -247,7 +256,7 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
							CpeURIs:          []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
@@ -285,7 +294,7 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeNames:         []string{},
 | 
			
		||||
						CpeURIs:          []string{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
@@ -325,3 +334,104 @@ func TestDiff(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsCveFixed(t *testing.T) {
 | 
			
		||||
	type In struct {
 | 
			
		||||
		v    models.VulnInfo
 | 
			
		||||
		prev models.ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       In
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				v: models.VulnInfo{
 | 
			
		||||
					CveID: "CVE-2016-6662",
 | 
			
		||||
					AffectedPackages: models.PackageStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "mysql-libs",
 | 
			
		||||
							NotFixedYet: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					CveContents: models.NewCveContents(
 | 
			
		||||
						models.CveContent{
 | 
			
		||||
							Type:         models.NvdXML,
 | 
			
		||||
							CveID:        "CVE-2016-6662",
 | 
			
		||||
							LastModified: time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID: "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{
 | 
			
		||||
								{
 | 
			
		||||
									Name:        "mysql-libs",
 | 
			
		||||
									NotFixedYet: true,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2016-6662",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				v: models.VulnInfo{
 | 
			
		||||
					CveID: "CVE-2016-6662",
 | 
			
		||||
					AffectedPackages: models.PackageStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "mysql-libs",
 | 
			
		||||
							NotFixedYet: true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					CveContents: models.NewCveContents(
 | 
			
		||||
						models.CveContent{
 | 
			
		||||
							Type:         models.NvdXML,
 | 
			
		||||
							CveID:        "CVE-2016-6662",
 | 
			
		||||
							LastModified: time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID: "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{
 | 
			
		||||
								{
 | 
			
		||||
									Name:        "mysql-libs",
 | 
			
		||||
									NotFixedYet: true,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NvdXML,
 | 
			
		||||
									CveID:        "CVE-2016-6662",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := isCveFixed(tt.in.v, tt.in.prev)
 | 
			
		||||
		if actual != tt.expected {
 | 
			
		||||
			t.Errorf("[%d] actual: %t, expected: %t", i, actual, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										201
									
								
								scan/alpine.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								scan/alpine.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,201 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Corporation , Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
type alpine struct {
 | 
			
		||||
	base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine is constructor
 | 
			
		||||
func newAlpine(c config.ServerInfo) *alpine {
 | 
			
		||||
	d := &alpine{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alpine
 | 
			
		||||
// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/alpine.rb
 | 
			
		||||
func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) {
 | 
			
		||||
	os = newAlpine(c)
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "ls /etc/alpine-release", noSudo); !r.isSuccess() {
 | 
			
		||||
		return false, os
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "cat /etc/alpine-release", noSudo); r.isSuccess() {
 | 
			
		||||
		os.setDistro(config.Alpine, strings.TrimSpace(r.Stdout))
 | 
			
		||||
		return true, os
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, os
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkScanMode() error {
 | 
			
		||||
	if o.getServerInfo().Mode.IsOffline() {
 | 
			
		||||
		return fmt.Errorf("Remove offline scan mode, Alpine needs internet connection")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkDeps() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) checkIfSudoNoPasswd() error {
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) apkUpdate() error {
 | 
			
		||||
	r := o.exec("apk update", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) preCure() error {
 | 
			
		||||
	o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
 | 
			
		||||
	if err := o.detectIPAddr(); err != nil {
 | 
			
		||||
		o.log.Debugf("Failed to detect IP addresses: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	// Ignore this error as it just failed to detect the IP addresses
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) postScan() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) detectIPAddr() (err error) {
 | 
			
		||||
	o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanPackages() error {
 | 
			
		||||
	if err := o.apkUpdate(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updatable, err := o.scanUpdatablePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed.MergeNewVersion(updatable)
 | 
			
		||||
	o.Packages = installed
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk info -v")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkInfo(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
 | 
			
		||||
	installedPackages, err := o.parseApkInfo(stdout)
 | 
			
		||||
	return installedPackages, nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkInfo(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		ss := strings.Split(line, "-")
 | 
			
		||||
		if len(ss) < 3 {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to parse apk info -v: %s", line)
 | 
			
		||||
		}
 | 
			
		||||
		name := strings.Join(ss[:len(ss)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:    name,
 | 
			
		||||
			Version: strings.Join(ss[len(ss)-2:], "-"),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) scanUpdatablePackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apk version")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.parseApkVersion(r.Stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *alpine) parseApkVersion(stdout string) (models.Packages, error) {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		if !strings.Contains(line, "<") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Split(line, "<")
 | 
			
		||||
		namever := strings.TrimSpace(ss[0])
 | 
			
		||||
		tt := strings.Split(namever, "-")
 | 
			
		||||
		name := strings.Join(tt[:len(tt)-2], "-")
 | 
			
		||||
		packs[name] = models.Package{
 | 
			
		||||
			Name:       name,
 | 
			
		||||
			NewVersion: strings.TrimSpace(ss[1]),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return packs, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								scan/alpine_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								scan/alpine_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseApkInfo(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `musl-1.1.16-r14
 | 
			
		||||
busybox-1.26.2-r7
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"musl": {
 | 
			
		||||
					Name:    "musl",
 | 
			
		||||
					Version: "1.1.16-r14",
 | 
			
		||||
				},
 | 
			
		||||
				"busybox": {
 | 
			
		||||
					Name:    "busybox",
 | 
			
		||||
					Version: "1.26.2-r7",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkInfo(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseApkVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in    string
 | 
			
		||||
		packs models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: `Installed:                                Available:
 | 
			
		||||
libcrypto1.0-1.0.1q-r0                  < 1.0.2m-r0
 | 
			
		||||
libssl1.0-1.0.1q-r0                     < 1.0.2m-r0
 | 
			
		||||
nrpe-2.14-r2                            < 2.15-r5
 | 
			
		||||
`,
 | 
			
		||||
			packs: models.Packages{
 | 
			
		||||
				"libcrypto1.0": {
 | 
			
		||||
					Name:       "libcrypto1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"libssl1.0": {
 | 
			
		||||
					Name:       "libssl1.0",
 | 
			
		||||
					NewVersion: "1.0.2m-r0",
 | 
			
		||||
				},
 | 
			
		||||
				"nrpe": {
 | 
			
		||||
					Name:       "nrpe",
 | 
			
		||||
					NewVersion: "2.15-r5",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d := newAlpine(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		pkgs, _ := d.parseApkVersion(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(tt.packs, pkgs) {
 | 
			
		||||
			t.Errorf("[%d] expected %v, actual %v", i, tt.packs, pkgs)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user